Alexander Timin | 5a99b5c | 2021-05-11 22:48:07 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2021 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "src/trace_processor/util/proto_to_args_parser.h" |
| 18 | |
| 19 | #include "protos/perfetto/common/descriptor.pbzero.h" |
| 20 | #include "src/trace_processor/util/descriptors.h" |
| 21 | #include "src/trace_processor/util/status_macros.h" |
| 22 | |
| 23 | namespace perfetto { |
| 24 | namespace trace_processor { |
| 25 | namespace util { |
| 26 | |
| 27 | namespace { |
| 28 | |
| 29 | // ScopedStringAppender will add |append| to |dest| when constructed and |
| 30 | // erases the appended suffix from |dest| when it goes out of scope. Thus |
| 31 | // |dest| must be valid for the entire lifetime of ScopedStringAppender. |
| 32 | // |
| 33 | // This is useful as we descend into a proto since the column names just |
| 34 | // appended with ".field_name" as we go lower. |
| 35 | // |
| 36 | // I.E. message1.message2.field_name1 is a column, but we'll then need to |
| 37 | // append message1.message2.field_name2 afterwards so we only need to append |
| 38 | // "field_name1" within some scope. |
| 39 | class ScopedStringAppender { |
| 40 | public: |
| 41 | ScopedStringAppender(const std::string& append, std::string* dest) |
| 42 | : old_size_(dest->size()), dest_(dest) { |
| 43 | if (dest->empty()) { |
| 44 | dest_->reserve(append.size()); |
| 45 | } else { |
| 46 | dest_->reserve(old_size_ + 1 + append.size()); |
| 47 | dest_->append("."); |
| 48 | } |
| 49 | dest_->append(append); |
| 50 | } |
| 51 | ~ScopedStringAppender() { dest_->erase(old_size_); } |
| 52 | |
| 53 | private: |
| 54 | size_t old_size_; |
| 55 | std::string* dest_; |
| 56 | }; |
| 57 | |
| 58 | } // namespace |
| 59 | |
Alexander Timin | 99653b1 | 2021-05-11 22:50:00 +0000 | [diff] [blame] | 60 | ProtoToArgsParser::Key::Key() = default; |
| 61 | ProtoToArgsParser::Key::Key(const std::string& k) : flat_key(k), key(k) {} |
| 62 | ProtoToArgsParser::Key::Key(const std::string& fk, const std::string& k) |
| 63 | : flat_key(fk), key(k) {} |
| 64 | ProtoToArgsParser::Key::~Key() = default; |
| 65 | |
Alexander Timin | 97d8785 | 2021-05-17 18:01:33 +0000 | [diff] [blame^] | 66 | ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext(Key& key) |
| 67 | : key_(key), |
| 68 | old_flat_key_length_(key.flat_key.length()), |
| 69 | old_key_length_(key.key.length()) {} |
| 70 | |
| 71 | ProtoToArgsParser::ScopedNestedKeyContext::ScopedNestedKeyContext( |
| 72 | ProtoToArgsParser::ScopedNestedKeyContext&& other) |
| 73 | : key_(other.key_), |
| 74 | old_flat_key_length_(other.old_flat_key_length_), |
| 75 | old_key_length_(other.old_key_length_) { |
| 76 | other.old_flat_key_length_ = base::nullopt; |
| 77 | other.old_key_length_ = base::nullopt; |
| 78 | } |
| 79 | |
| 80 | ProtoToArgsParser::ScopedNestedKeyContext::~ScopedNestedKeyContext() { |
| 81 | Reset(); |
| 82 | } |
| 83 | |
| 84 | void ProtoToArgsParser::ScopedNestedKeyContext::Reset() { |
| 85 | if (old_flat_key_length_) |
| 86 | key_.flat_key.resize(old_flat_key_length_.value()); |
| 87 | if (old_key_length_) |
| 88 | key_.key.resize(old_key_length_.value()); |
| 89 | old_flat_key_length_ = base::nullopt; |
| 90 | old_key_length_ = base::nullopt; |
| 91 | } |
| 92 | |
Alexander Timin | 5a99b5c | 2021-05-11 22:48:07 +0000 | [diff] [blame] | 93 | ProtoToArgsParser::Delegate::~Delegate() = default; |
| 94 | |
| 95 | ProtoToArgsParser::ProtoToArgsParser(const DescriptorPool& pool) : pool_(pool) { |
| 96 | constexpr int kDefaultSize = 64; |
| 97 | key_prefix_.key.reserve(kDefaultSize); |
| 98 | key_prefix_.flat_key.reserve(kDefaultSize); |
| 99 | } |
| 100 | |
| 101 | base::Status ProtoToArgsParser::ParseMessage( |
| 102 | const protozero::ConstBytes& cb, |
| 103 | const std::string& type, |
| 104 | const std::vector<uint16_t>* allowed_fields, |
| 105 | Delegate& delegate) { |
| 106 | auto idx = pool_.FindDescriptorIdx(type); |
| 107 | if (!idx) { |
| 108 | return base::Status("Failed to find proto descriptor"); |
| 109 | } |
| 110 | |
| 111 | auto& descriptor = pool_.descriptors()[*idx]; |
| 112 | |
| 113 | std::unordered_map<size_t, int> repeated_field_index; |
| 114 | |
| 115 | protozero::ProtoDecoder decoder(cb); |
| 116 | for (protozero::Field f = decoder.ReadField(); f.valid(); |
| 117 | f = decoder.ReadField()) { |
| 118 | auto field = descriptor.FindFieldByTag(f.id()); |
| 119 | if (!field) { |
| 120 | // Unknown field, possibly an unknown extension. |
| 121 | continue; |
| 122 | } |
| 123 | |
| 124 | // If allowlist is not provided, reflect all fields. Otherwise, check if the |
| 125 | // current field either an extension or is in allowlist. |
| 126 | bool is_allowed = field->is_extension() || !allowed_fields || |
| 127 | std::find(allowed_fields->begin(), allowed_fields->end(), |
| 128 | f.id()) != allowed_fields->end(); |
| 129 | |
| 130 | if (!is_allowed) { |
| 131 | // Field is neither an extension, nor is allowed to be |
| 132 | // reflected. |
| 133 | continue; |
| 134 | } |
| 135 | RETURN_IF_ERROR( |
| 136 | ParseField(*field, repeated_field_index[f.id()], f, delegate)); |
| 137 | if (field->is_repeated()) { |
| 138 | repeated_field_index[f.id()]++; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | return base::OkStatus(); |
| 143 | } |
| 144 | |
| 145 | base::Status ProtoToArgsParser::ParseField( |
| 146 | const FieldDescriptor& field_descriptor, |
| 147 | int repeated_field_number, |
| 148 | protozero::Field field, |
| 149 | Delegate& delegate) { |
| 150 | std::string prefix_part = field_descriptor.name(); |
| 151 | if (field_descriptor.is_repeated()) { |
| 152 | std::string number = std::to_string(repeated_field_number); |
| 153 | prefix_part.reserve(prefix_part.length() + number.length() + 2); |
| 154 | prefix_part.append("["); |
| 155 | prefix_part.append(number); |
| 156 | prefix_part.append("]"); |
| 157 | } |
| 158 | |
| 159 | // In the args table we build up message1.message2.field1 as the column |
| 160 | // name. This will append the ".field1" suffix to |key_prefix| and then |
| 161 | // remove it when it goes out of scope. |
| 162 | ScopedStringAppender scoped_prefix(prefix_part, &key_prefix_.key); |
| 163 | ScopedStringAppender scoped_flat_key_prefix(field_descriptor.name(), |
| 164 | &key_prefix_.flat_key); |
| 165 | |
| 166 | // If we have an override parser then use that instead and move onto the |
| 167 | // next loop. |
| 168 | if (base::Optional<base::Status> status = |
| 169 | MaybeApplyOverride(field, delegate)) { |
| 170 | return *status; |
| 171 | } |
| 172 | |
| 173 | // If this is not a message we can just immediately add the column name and |
| 174 | // get the value out of |field|. However if it is a message we need to |
| 175 | // recurse into it. |
| 176 | if (field_descriptor.type() == |
| 177 | protos::pbzero::FieldDescriptorProto::TYPE_MESSAGE) { |
| 178 | return ParseMessage(field.as_bytes(), field_descriptor.resolved_type_name(), |
| 179 | nullptr, delegate); |
| 180 | } |
| 181 | |
| 182 | return ParseSimpleField(field_descriptor, field, delegate); |
| 183 | } |
| 184 | |
| 185 | void ProtoToArgsParser::AddParsingOverride(std::string field, |
| 186 | ParsingOverride func) { |
| 187 | overrides_[std::move(field)] = std::move(func); |
| 188 | } |
| 189 | |
| 190 | base::Optional<base::Status> ProtoToArgsParser::MaybeApplyOverride( |
| 191 | const protozero::Field& field, |
| 192 | Delegate& delegate) { |
| 193 | auto it = overrides_.find(key_prefix_.flat_key); |
| 194 | if (it == overrides_.end()) |
| 195 | return base::nullopt; |
| 196 | return it->second(field, delegate); |
| 197 | } |
| 198 | |
| 199 | base::Status ProtoToArgsParser::ParseSimpleField( |
| 200 | const FieldDescriptor& descriptor, |
| 201 | const protozero::Field& field, |
| 202 | Delegate& delegate) { |
| 203 | using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto; |
| 204 | switch (descriptor.type()) { |
| 205 | case FieldDescriptorProto::TYPE_INT32: |
| 206 | case FieldDescriptorProto::TYPE_SFIXED32: |
| 207 | case FieldDescriptorProto::TYPE_FIXED32: |
| 208 | delegate.AddInteger(key_prefix_, field.as_int32()); |
| 209 | return base::OkStatus(); |
| 210 | case FieldDescriptorProto::TYPE_SINT32: |
| 211 | delegate.AddInteger(key_prefix_, field.as_sint32()); |
| 212 | return base::OkStatus(); |
| 213 | case FieldDescriptorProto::TYPE_INT64: |
| 214 | case FieldDescriptorProto::TYPE_SFIXED64: |
| 215 | case FieldDescriptorProto::TYPE_FIXED64: |
| 216 | delegate.AddInteger(key_prefix_, field.as_int64()); |
| 217 | return base::OkStatus(); |
| 218 | case FieldDescriptorProto::TYPE_SINT64: |
| 219 | delegate.AddInteger(key_prefix_, field.as_sint64()); |
| 220 | return base::OkStatus(); |
| 221 | case FieldDescriptorProto::TYPE_UINT32: |
| 222 | delegate.AddUnsignedInteger(key_prefix_, field.as_uint32()); |
| 223 | return base::OkStatus(); |
| 224 | case FieldDescriptorProto::TYPE_UINT64: |
| 225 | delegate.AddUnsignedInteger(key_prefix_, field.as_uint64()); |
| 226 | return base::OkStatus(); |
| 227 | case FieldDescriptorProto::TYPE_BOOL: |
| 228 | delegate.AddBoolean(key_prefix_, field.as_bool()); |
| 229 | return base::OkStatus(); |
| 230 | case FieldDescriptorProto::TYPE_DOUBLE: |
| 231 | delegate.AddDouble(key_prefix_, field.as_double()); |
| 232 | return base::OkStatus(); |
| 233 | case FieldDescriptorProto::TYPE_FLOAT: |
| 234 | delegate.AddDouble(key_prefix_, static_cast<double>(field.as_float())); |
| 235 | return base::OkStatus(); |
| 236 | case FieldDescriptorProto::TYPE_STRING: |
| 237 | delegate.AddString(key_prefix_, field.as_string()); |
| 238 | return base::OkStatus(); |
| 239 | case FieldDescriptorProto::TYPE_ENUM: { |
| 240 | auto opt_enum_descriptor_idx = |
| 241 | pool_.FindDescriptorIdx(descriptor.resolved_type_name()); |
| 242 | if (!opt_enum_descriptor_idx) { |
| 243 | delegate.AddInteger(key_prefix_, field.as_int32()); |
| 244 | return base::OkStatus(); |
| 245 | } |
| 246 | auto opt_enum_string = |
| 247 | pool_.descriptors()[*opt_enum_descriptor_idx].FindEnumString( |
| 248 | field.as_int32()); |
| 249 | if (!opt_enum_string) { |
| 250 | // Fall back to the integer representation of the field. |
| 251 | delegate.AddInteger(key_prefix_, field.as_int32()); |
| 252 | return base::OkStatus(); |
| 253 | } |
| 254 | delegate.AddString(key_prefix_, |
| 255 | protozero::ConstChars{opt_enum_string->data(), |
| 256 | opt_enum_string->size()}); |
| 257 | return base::OkStatus(); |
| 258 | } |
| 259 | default: |
| 260 | return base::ErrStatus( |
| 261 | "Tried to write value of type field %s (in proto type " |
| 262 | "%s) which has type enum %d", |
| 263 | descriptor.name().c_str(), descriptor.resolved_type_name().c_str(), |
| 264 | descriptor.type()); |
| 265 | } |
| 266 | } |
| 267 | |
Alexander Timin | 97d8785 | 2021-05-17 18:01:33 +0000 | [diff] [blame^] | 268 | ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterArray( |
| 269 | size_t index) { |
| 270 | auto context = ScopedNestedKeyContext(key_prefix_); |
| 271 | key_prefix_.key += "[" + std::to_string(index) + "]"; |
| 272 | return context; |
| 273 | } |
| 274 | |
| 275 | ProtoToArgsParser::ScopedNestedKeyContext ProtoToArgsParser::EnterDictionary( |
| 276 | const std::string& name) { |
| 277 | auto context = ScopedNestedKeyContext(key_prefix_); |
| 278 | if (!key_prefix_.key.empty()) |
| 279 | key_prefix_.key += '.'; |
| 280 | key_prefix_.key += name; |
| 281 | if (!key_prefix_.flat_key.empty()) |
| 282 | key_prefix_.flat_key += '.'; |
| 283 | key_prefix_.flat_key += name; |
| 284 | return context; |
| 285 | } |
| 286 | |
Alexander Timin | 5a99b5c | 2021-05-11 22:48:07 +0000 | [diff] [blame] | 287 | } // namespace util |
| 288 | } // namespace trace_processor |
| 289 | } // namespace perfetto |