| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/trace_processor/importers/proto/track_event_parser.h" |
| |
| #include <iostream> |
| #include <optional> |
| #include <string> |
| |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/string_writer.h" |
| #include "perfetto/trace_processor/status.h" |
| #include "src/trace_processor/importers/common/args_tracker.h" |
| #include "src/trace_processor/importers/common/args_translation_table.h" |
| #include "src/trace_processor/importers/common/event_tracker.h" |
| #include "src/trace_processor/importers/common/flow_tracker.h" |
| #include "src/trace_processor/importers/common/process_tracker.h" |
| #include "src/trace_processor/importers/common/track_tracker.h" |
| #include "src/trace_processor/importers/json/json_utils.h" |
| #include "src/trace_processor/importers/proto/packet_analyzer.h" |
| #include "src/trace_processor/importers/proto/packet_sequence_state.h" |
| #include "src/trace_processor/importers/proto/profile_packet_utils.h" |
| #include "src/trace_processor/importers/proto/track_event_tracker.h" |
| #include "src/trace_processor/util/debug_annotation_parser.h" |
| #include "src/trace_processor/util/proto_to_args_parser.h" |
| #include "src/trace_processor/util/status_macros.h" |
| |
| #include "protos/perfetto/trace/extension_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h" |
| #include "protos/perfetto/trace/track_event/chrome_active_processes.pbzero.h" |
| #include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h" |
| #include "protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h" |
| #include "protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.h" |
| #include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/track_event/debug_annotation.pbzero.h" |
| #include "protos/perfetto/trace/track_event/log_message.pbzero.h" |
| #include "protos/perfetto/trace/track_event/process_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/track_event/source_location.pbzero.h" |
| #include "protos/perfetto/trace/track_event/task_execution.pbzero.h" |
| #include "protos/perfetto/trace/track_event/thread_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h" |
| #include "protos/perfetto/trace/track_event/track_event.pbzero.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| namespace { |
| using BoundInserter = ArgsTracker::BoundInserter; |
| using protos::pbzero::TrackEvent; |
| using LegacyEvent = TrackEvent::LegacyEvent; |
| using protozero::ConstBytes; |
| |
| // Slices which have been opened but haven't been closed yet will be marked |
| // with these placeholder values. |
| constexpr int64_t kPendingThreadDuration = -1; |
| constexpr int64_t kPendingThreadInstructionDelta = -1; |
| |
| class TrackEventArgsParser : public util::ProtoToArgsParser::Delegate { |
| public: |
| TrackEventArgsParser(int64_t packet_timestamp, |
| BoundInserter& inserter, |
| TraceStorage& storage, |
| PacketSequenceStateGeneration& sequence_state) |
| : packet_timestamp_(packet_timestamp), |
| inserter_(inserter), |
| storage_(storage), |
| sequence_state_(sequence_state) {} |
| |
| ~TrackEventArgsParser() override; |
| |
| using Key = util::ProtoToArgsParser::Key; |
| |
| void AddInteger(const Key& key, int64_t value) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::Integer(value)); |
| } |
| void AddUnsignedInteger(const Key& key, uint64_t value) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::UnsignedInteger(value)); |
| } |
| void AddString(const Key& key, const protozero::ConstChars& value) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::String(storage_.InternString(value))); |
| } |
| void AddString(const Key& key, const std::string& value) final { |
| inserter_.AddArg( |
| storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::String(storage_.InternString(base::StringView(value)))); |
| } |
| void AddDouble(const Key& key, double value) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::Real(value)); |
| } |
| void AddPointer(const Key& key, const void* value) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::Pointer(reinterpret_cast<uintptr_t>(value))); |
| } |
| void AddBoolean(const Key& key, bool value) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::Boolean(value)); |
| } |
| bool AddJson(const Key& key, const protozero::ConstChars& value) final { |
| auto json_value = json::ParseJsonString(value); |
| if (!json_value) |
| return false; |
| return json::AddJsonValueToArgs(*json_value, base::StringView(key.flat_key), |
| base::StringView(key.key), &storage_, |
| &inserter_); |
| } |
| void AddNull(const Key& key) final { |
| inserter_.AddArg(storage_.InternString(base::StringView(key.flat_key)), |
| storage_.InternString(base::StringView(key.key)), |
| Variadic::Null()); |
| } |
| |
| size_t GetArrayEntryIndex(const std::string& array_key) final { |
| return inserter_.GetNextArrayEntryIndex( |
| storage_.InternString(base::StringView(array_key))); |
| } |
| |
| size_t IncrementArrayEntryIndex(const std::string& array_key) final { |
| return inserter_.IncrementArrayEntryIndex( |
| storage_.InternString(base::StringView(array_key))); |
| } |
| |
| InternedMessageView* GetInternedMessageView(uint32_t field_id, |
| uint64_t iid) final { |
| return sequence_state_.GetInternedMessageView(field_id, iid); |
| } |
| |
| int64_t packet_timestamp() final { return packet_timestamp_; } |
| |
| PacketSequenceStateGeneration* seq_state() final { return &sequence_state_; } |
| |
| private: |
| int64_t packet_timestamp_; |
| BoundInserter& inserter_; |
| TraceStorage& storage_; |
| PacketSequenceStateGeneration& sequence_state_; |
| }; |
| |
| TrackEventArgsParser::~TrackEventArgsParser() = default; |
| |
| // Paths on Windows use backslash rather than slash as a separator. |
| // Normalise the paths by replacing backslashes with slashes to make it |
| // easier to write cross-platform scripts. |
| std::string NormalizePathSeparators(const protozero::ConstChars& path) { |
| std::string result(path.data, path.size); |
| for (char& c : result) { |
| if (c == '\\') |
| c = '/'; |
| } |
| return result; |
| } |
| |
| std::optional<base::Status> MaybeParseUnsymbolizedSourceLocation( |
| std::string prefix, |
| const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| auto* decoder = delegate.GetInternedMessage( |
| protos::pbzero::InternedData::kUnsymbolizedSourceLocations, |
| field.as_uint64()); |
| if (!decoder) { |
| // Lookup failed fall back on default behaviour which will just put |
| // the iid into the args table. |
| return std::nullopt; |
| } |
| // Interned mapping_id loses it's meaning when the sequence ends. So we need |
| // to get an id from stack_profile_mapping table. |
| ProfilePacketInternLookup intern_lookup(delegate.seq_state()); |
| auto mapping_id = |
| delegate.seq_state() |
| ->state() |
| ->sequence_stack_profile_tracker() |
| .FindOrInsertMapping(decoder->mapping_id(), &intern_lookup); |
| if (!mapping_id) { |
| return std::nullopt; |
| } |
| delegate.AddUnsignedInteger( |
| util::ProtoToArgsParser::Key(prefix + ".mapping_id"), mapping_id->value); |
| delegate.AddUnsignedInteger(util::ProtoToArgsParser::Key(prefix + ".rel_pc"), |
| decoder->rel_pc()); |
| return base::OkStatus(); |
| } |
| |
| std::optional<base::Status> MaybeParseSourceLocation( |
| std::string prefix, |
| const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| auto* decoder = delegate.GetInternedMessage( |
| protos::pbzero::InternedData::kSourceLocations, field.as_uint64()); |
| if (!decoder) { |
| // Lookup failed fall back on default behaviour which will just put |
| // the source_location_iid into the args table. |
| return std::nullopt; |
| } |
| |
| delegate.AddString(util::ProtoToArgsParser::Key(prefix + ".file_name"), |
| NormalizePathSeparators(decoder->file_name())); |
| delegate.AddString(util::ProtoToArgsParser::Key(prefix + ".function_name"), |
| decoder->function_name()); |
| if (decoder->has_line_number()) { |
| delegate.AddInteger(util::ProtoToArgsParser::Key(prefix + ".line_number"), |
| decoder->line_number()); |
| } |
| |
| return base::OkStatus(); |
| } |
| |
| } // namespace |
| |
| class TrackEventParser::EventImporter { |
| public: |
| EventImporter(TrackEventParser* parser, |
| int64_t ts, |
| const TrackEventData* event_data, |
| ConstBytes blob, |
| uint32_t packet_sequence_id) |
| : context_(parser->context_), |
| track_event_tracker_(parser->track_event_tracker_), |
| storage_(context_->storage.get()), |
| parser_(parser), |
| args_translation_table_(context_->args_translation_table.get()), |
| ts_(ts), |
| event_data_(event_data), |
| sequence_state_(event_data->trace_packet_data.sequence_state.get()), |
| blob_(std::move(blob)), |
| event_(blob_), |
| legacy_event_(event_.legacy_event()), |
| defaults_(event_data->trace_packet_data.sequence_state |
| ->GetTrackEventDefaults()), |
| thread_timestamp_(event_data->thread_timestamp), |
| thread_instruction_count_(event_data->thread_instruction_count), |
| packet_sequence_id_(packet_sequence_id) {} |
| |
| util::Status Import() { |
| // TODO(eseckler): This legacy event field will eventually be replaced by |
| // fields in TrackEvent itself. |
| if (PERFETTO_UNLIKELY(!event_.type() && !legacy_event_.has_phase())) |
| return util::ErrStatus("TrackEvent without type or phase"); |
| |
| category_id_ = ParseTrackEventCategory(); |
| name_id_ = ParseTrackEventName(); |
| |
| if (context_->content_analyzer) { |
| PacketAnalyzer::SampleAnnotation annotation; |
| annotation.push_back({parser_->event_category_key_id_, category_id_}); |
| annotation.push_back({parser_->event_name_key_id_, name_id_}); |
| PacketAnalyzer::Get(context_)->ProcessPacket( |
| event_data_->trace_packet_data.packet, annotation); |
| } |
| |
| RETURN_IF_ERROR(ParseTrackAssociation()); |
| |
| // Counter-type events don't support arguments (those are on the |
| // CounterDescriptor instead). All they have is a |{double_,}counter_value|. |
| if (event_.type() == TrackEvent::TYPE_COUNTER) { |
| ParseCounterEvent(); |
| return util::OkStatus(); |
| } |
| |
| // If we have legacy thread time / instruction count fields, also parse them |
| // into the counters tables. |
| ParseLegacyThreadTimeAndInstructionsAsCounters(); |
| |
| // Parse extra counter values before parsing the actual event. This way, we |
| // can update the slice's thread time / instruction count fields based on |
| // these counter values and also parse them as slice attributes / arguments. |
| ParseExtraCounterValues(); |
| |
| // TODO(eseckler): Replace phase with type and remove handling of |
| // legacy_event_.phase() once it is no longer used by producers. |
| char phase = static_cast<char>(ParsePhaseOrType()); |
| |
| switch (phase) { |
| case 'B': // TRACE_EVENT_PHASE_BEGIN. |
| return ParseThreadBeginEvent(); |
| case 'E': // TRACE_EVENT_PHASE_END. |
| return ParseThreadEndEvent(); |
| case 'X': // TRACE_EVENT_PHASE_COMPLETE. |
| return ParseThreadCompleteEvent(); |
| case 's': // TRACE_EVENT_PHASE_FLOW_BEGIN. |
| case 't': // TRACE_EVENT_PHASE_FLOW_STEP. |
| case 'f': // TRACE_EVENT_PHASE_FLOW_END. |
| return ParseFlowEventV1(phase); |
| case 'i': |
| case 'I': // TRACE_EVENT_PHASE_INSTANT. |
| case 'R': // TRACE_EVENT_PHASE_MARK. |
| return ParseThreadInstantEvent(phase); |
| case 'b': // TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN |
| case 'S': |
| return ParseAsyncBeginEvent(phase); |
| case 'e': // TRACE_EVENT_PHASE_NESTABLE_ASYNC_END |
| case 'F': |
| return ParseAsyncEndEvent(); |
| case 'n': // TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT |
| return ParseAsyncInstantEvent(); |
| case 'T': |
| case 'p': |
| return ParseAsyncStepEvent(phase); |
| case 'M': // TRACE_EVENT_PHASE_METADATA (process and thread names). |
| return ParseMetadataEvent(); |
| default: |
| // Other events are proxied via the raw table for JSON export. |
| return ParseLegacyEventAsRawEvent(); |
| } |
| } |
| |
| private: |
| StringId ParseTrackEventCategory() { |
| StringId category_id = kNullStringId; |
| |
| std::vector<uint64_t> category_iids; |
| for (auto it = event_.category_iids(); it; ++it) { |
| category_iids.push_back(*it); |
| } |
| std::vector<protozero::ConstChars> category_strings; |
| for (auto it = event_.categories(); it; ++it) { |
| category_strings.push_back(*it); |
| } |
| |
| // If there's a single category, we can avoid building a concatenated |
| // string. |
| if (PERFETTO_LIKELY(category_iids.size() == 1 && |
| category_strings.empty())) { |
| auto* decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kEventCategoriesFieldNumber, |
| protos::pbzero::EventCategory>(category_iids[0]); |
| if (decoder) { |
| category_id = storage_->InternString(decoder->name()); |
| } else { |
| char buffer[32]; |
| base::StringWriter writer(buffer, sizeof(buffer)); |
| writer.AppendLiteral("unknown("); |
| writer.AppendUnsignedInt(category_iids[0]); |
| writer.AppendChar(')'); |
| category_id = storage_->InternString(writer.GetStringView()); |
| } |
| } else if (category_iids.empty() && category_strings.size() == 1) { |
| category_id = storage_->InternString(category_strings[0]); |
| } else if (category_iids.size() + category_strings.size() > 1) { |
| // We concatenate the category strings together since we currently only |
| // support a single "cat" column. |
| // TODO(eseckler): Support multi-category events in the table schema. |
| std::string categories; |
| for (uint64_t iid : category_iids) { |
| auto* decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kEventCategoriesFieldNumber, |
| protos::pbzero::EventCategory>(iid); |
| if (!decoder) |
| continue; |
| base::StringView name = decoder->name(); |
| if (!categories.empty()) |
| categories.append(","); |
| categories.append(name.data(), name.size()); |
| } |
| for (const protozero::ConstChars& cat : category_strings) { |
| if (!categories.empty()) |
| categories.append(","); |
| categories.append(cat.data, cat.size); |
| } |
| if (!categories.empty()) |
| category_id = storage_->InternString(base::StringView(categories)); |
| } |
| |
| return category_id; |
| } |
| |
| StringId ParseTrackEventName() { |
| uint64_t name_iid = event_.name_iid(); |
| if (!name_iid) |
| name_iid = legacy_event_.name_iid(); |
| |
| if (PERFETTO_LIKELY(name_iid)) { |
| auto* decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kEventNamesFieldNumber, |
| protos::pbzero::EventName>(name_iid); |
| if (decoder) |
| return storage_->InternString(decoder->name()); |
| } else if (event_.has_name()) { |
| return storage_->InternString(event_.name()); |
| } |
| |
| return kNullStringId; |
| } |
| |
| util::Status ParseTrackAssociation() { |
| TrackTracker* track_tracker = context_->track_tracker.get(); |
| ProcessTracker* procs = context_->process_tracker.get(); |
| |
| // Consider track_uuid from the packet and TrackEventDefaults, fall back to |
| // the default descriptor track (uuid 0). |
| track_uuid_ = event_.has_track_uuid() |
| ? event_.track_uuid() |
| : (defaults_ && defaults_->has_track_uuid() |
| ? defaults_->track_uuid() |
| : 0u); |
| |
| // Determine track from track_uuid specified in either TrackEvent or |
| // TrackEventDefaults. If a non-default track is not set, we either: |
| // a) fall back to the track specified by the sequence's (or event's) pid |
| // + tid (only in case of legacy tracks/events, i.e. events that don't |
| // specify an explicit track uuid or use legacy event phases instead of |
| // TrackEvent types), or |
| // b) a default track. |
| if (track_uuid_) { |
| std::optional<TrackId> opt_track_id = |
| track_event_tracker_->GetDescriptorTrack(track_uuid_, name_id_, |
| packet_sequence_id_); |
| if (!opt_track_id) { |
| track_event_tracker_->ReserveDescriptorChildTrack(track_uuid_, |
| /*parent_uuid=*/0, |
| name_id_); |
| opt_track_id = track_event_tracker_->GetDescriptorTrack( |
| track_uuid_, name_id_, packet_sequence_id_); |
| } |
| track_id_ = *opt_track_id; |
| |
| auto thread_track_row = |
| storage_->thread_track_table().id().IndexOf(track_id_); |
| if (thread_track_row) { |
| utid_ = storage_->thread_track_table().utid()[*thread_track_row]; |
| upid_ = storage_->thread_table().upid()[*utid_]; |
| } else { |
| auto process_track_row = |
| storage_->process_track_table().id().IndexOf(track_id_); |
| if (process_track_row) { |
| upid_ = storage_->process_track_table().upid()[*process_track_row]; |
| if (sequence_state_->state()->pid_and_tid_valid()) { |
| uint32_t pid = |
| static_cast<uint32_t>(sequence_state_->state()->pid()); |
| uint32_t tid = |
| static_cast<uint32_t>(sequence_state_->state()->tid()); |
| UniqueTid utid_candidate = procs->UpdateThread(tid, pid); |
| if (storage_->thread_table().upid()[utid_candidate] == upid_) |
| legacy_passthrough_utid_ = utid_candidate; |
| } |
| } else { |
| auto* tracks = context_->storage->mutable_track_table(); |
| auto track_index = tracks->id().IndexOf(track_id_); |
| if (track_index) { |
| const StringPool::Id& id = tracks->name()[*track_index]; |
| if (id.is_null()) |
| tracks->mutable_name()->Set(*track_index, name_id_); |
| } |
| |
| if (sequence_state_->state()->pid_and_tid_valid()) { |
| uint32_t pid = |
| static_cast<uint32_t>(sequence_state_->state()->pid()); |
| uint32_t tid = |
| static_cast<uint32_t>(sequence_state_->state()->tid()); |
| legacy_passthrough_utid_ = procs->UpdateThread(tid, pid); |
| } |
| } |
| } |
| } else { |
| bool pid_tid_state_valid = sequence_state_->state()->pid_and_tid_valid(); |
| |
| // We have a 0-value |track_uuid|. Nevertheless, we should only fall back |
| // if we have either no |track_uuid| specified at all or |track_uuid| was |
| // set explicitly to 0 (e.g. to override a default track_uuid) and we have |
| // a legacy phase. Events with real phases should use |track_uuid| to |
| // specify a different track (or use the pid/tid_override fields). |
| bool fallback_to_legacy_pid_tid_tracks = |
| (!event_.has_track_uuid() || !event_.has_type()) && |
| pid_tid_state_valid; |
| |
| // Always allow fallback if we have a process override. |
| fallback_to_legacy_pid_tid_tracks |= legacy_event_.has_pid_override(); |
| |
| // A thread override requires a valid pid. |
| fallback_to_legacy_pid_tid_tracks |= |
| legacy_event_.has_tid_override() && pid_tid_state_valid; |
| |
| if (fallback_to_legacy_pid_tid_tracks) { |
| uint32_t pid = static_cast<uint32_t>(sequence_state_->state()->pid()); |
| uint32_t tid = static_cast<uint32_t>(sequence_state_->state()->tid()); |
| if (legacy_event_.has_pid_override()) { |
| pid = static_cast<uint32_t>(legacy_event_.pid_override()); |
| tid = static_cast<uint32_t>(-1); |
| } |
| if (legacy_event_.has_tid_override()) |
| tid = static_cast<uint32_t>(legacy_event_.tid_override()); |
| |
| utid_ = procs->UpdateThread(tid, pid); |
| upid_ = storage_->thread_table().upid()[*utid_]; |
| track_id_ = track_tracker->InternThreadTrack(*utid_); |
| } else { |
| track_id_ = track_event_tracker_->GetOrCreateDefaultDescriptorTrack(); |
| } |
| } |
| |
| if (!legacy_event_.has_phase()) |
| return util::OkStatus(); |
| |
| // Legacy phases may imply a different track than the one specified by |
| // the fallback (or default track uuid) above. |
| switch (legacy_event_.phase()) { |
| case 'b': |
| case 'e': |
| case 'n': |
| case 'S': |
| case 'T': |
| case 'p': |
| case 'F': { |
| // Intern tracks for legacy async events based on legacy event ids. |
| int64_t source_id = 0; |
| bool source_id_is_process_scoped = false; |
| if (legacy_event_.has_unscoped_id()) { |
| source_id = static_cast<int64_t>(legacy_event_.unscoped_id()); |
| } else if (legacy_event_.has_global_id()) { |
| source_id = static_cast<int64_t>(legacy_event_.global_id()); |
| } else if (legacy_event_.has_local_id()) { |
| if (!upid_) { |
| return util::ErrStatus( |
| "TrackEvent with local_id without process association"); |
| } |
| |
| source_id = static_cast<int64_t>(legacy_event_.local_id()); |
| source_id_is_process_scoped = true; |
| } else { |
| return util::ErrStatus("Async LegacyEvent without ID"); |
| } |
| |
| // Catapult treats nestable async events of different categories with |
| // the same ID as separate tracks. We replicate the same behavior |
| // here. For legacy async events, it uses different tracks based on |
| // event names. |
| const bool legacy_async = |
| legacy_event_.phase() == 'S' || legacy_event_.phase() == 'T' || |
| legacy_event_.phase() == 'p' || legacy_event_.phase() == 'F'; |
| StringId id_scope = legacy_async ? name_id_ : category_id_; |
| if (legacy_event_.has_id_scope()) { |
| std::string concat = storage_->GetString(category_id_).ToStdString() + |
| ":" + legacy_event_.id_scope().ToStdString(); |
| id_scope = storage_->InternString(base::StringView(concat)); |
| } |
| |
| track_id_ = context_->track_tracker->InternLegacyChromeAsyncTrack( |
| name_id_, upid_.value_or(0), source_id, source_id_is_process_scoped, |
| id_scope); |
| legacy_passthrough_utid_ = utid_; |
| break; |
| } |
| case 'i': |
| case 'I': { |
| // Intern tracks for global or process-scoped legacy instant events. |
| switch (legacy_event_.instant_event_scope()) { |
| case LegacyEvent::SCOPE_UNSPECIFIED: |
| case LegacyEvent::SCOPE_THREAD: |
| // Thread-scoped legacy instant events already have the right |
| // track based on the tid/pid of the sequence. |
| if (!utid_) { |
| return util::ErrStatus( |
| "Thread-scoped instant event without thread association"); |
| } |
| break; |
| case LegacyEvent::SCOPE_GLOBAL: |
| track_id_ = context_->track_tracker |
| ->GetOrCreateLegacyChromeGlobalInstantTrack(); |
| legacy_passthrough_utid_ = utid_; |
| utid_ = std::nullopt; |
| break; |
| case LegacyEvent::SCOPE_PROCESS: |
| if (!upid_) { |
| return util::ErrStatus( |
| "Process-scoped instant event without process association"); |
| } |
| |
| track_id_ = |
| context_->track_tracker->InternLegacyChromeProcessInstantTrack( |
| *upid_); |
| legacy_passthrough_utid_ = utid_; |
| utid_ = std::nullopt; |
| break; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return util::OkStatus(); |
| } |
| |
| int32_t ParsePhaseOrType() { |
| if (legacy_event_.has_phase()) |
| return legacy_event_.phase(); |
| |
| switch (event_.type()) { |
| case TrackEvent::TYPE_SLICE_BEGIN: |
| return utid_ ? 'B' : 'b'; |
| case TrackEvent::TYPE_SLICE_END: |
| return utid_ ? 'E' : 'e'; |
| case TrackEvent::TYPE_INSTANT: |
| return utid_ ? 'i' : 'n'; |
| default: |
| PERFETTO_ELOG("unexpected event type %d", event_.type()); |
| return 0; |
| } |
| } |
| |
| void ParseCounterEvent() { |
| // Tokenizer ensures that TYPE_COUNTER events are associated with counter |
| // tracks and have values. |
| PERFETTO_DCHECK(storage_->counter_track_table().id().IndexOf(track_id_)); |
| PERFETTO_DCHECK(event_.has_counter_value() || |
| event_.has_double_counter_value()); |
| |
| context_->event_tracker->PushCounter( |
| ts_, static_cast<double>(event_data_->counter_value), track_id_); |
| } |
| |
| void ParseLegacyThreadTimeAndInstructionsAsCounters() { |
| if (!utid_) |
| return; |
| // When these fields are set, we don't expect TrackDescriptor-based counters |
| // for thread time or instruction count for this thread in the trace, so we |
| // intern separate counter tracks based on name + utid. Note that we cannot |
| // import the counter values from the end of a complete event, because the |
| // EventTracker expects counters to be pushed in order of their timestamps. |
| // One more reason to switch to split begin/end events. |
| if (thread_timestamp_) { |
| TrackId track_id = context_->track_tracker->InternThreadCounterTrack( |
| parser_->counter_name_thread_time_id_, *utid_); |
| context_->event_tracker->PushCounter( |
| ts_, static_cast<double>(*thread_timestamp_), track_id); |
| } |
| if (thread_instruction_count_) { |
| TrackId track_id = context_->track_tracker->InternThreadCounterTrack( |
| parser_->counter_name_thread_instruction_count_id_, *utid_); |
| context_->event_tracker->PushCounter( |
| ts_, static_cast<double>(*thread_instruction_count_), track_id); |
| } |
| } |
| |
| void ParseExtraCounterValues() { |
| if (!event_.has_extra_counter_values() && |
| !event_.has_extra_double_counter_values()) { |
| return; |
| } |
| |
| // Add integer extra counter values. |
| size_t index = 0; |
| protozero::RepeatedFieldIterator<uint64_t> track_uuid_it; |
| if (event_.has_extra_counter_track_uuids()) { |
| track_uuid_it = event_.extra_counter_track_uuids(); |
| } else if (defaults_ && defaults_->has_extra_counter_track_uuids()) { |
| track_uuid_it = defaults_->extra_counter_track_uuids(); |
| } |
| for (auto value_it = event_.extra_counter_values(); value_it; |
| ++value_it, ++track_uuid_it, ++index) { |
| AddExtraCounterValue(track_uuid_it, index); |
| } |
| |
| // Add double extra counter values. |
| track_uuid_it = protozero::RepeatedFieldIterator<uint64_t>(); |
| if (event_.has_extra_double_counter_track_uuids()) { |
| track_uuid_it = event_.extra_double_counter_track_uuids(); |
| } else if (defaults_ && defaults_->has_extra_double_counter_track_uuids()) { |
| track_uuid_it = defaults_->extra_double_counter_track_uuids(); |
| } |
| for (auto value_it = event_.extra_double_counter_values(); value_it; |
| ++value_it, ++track_uuid_it, ++index) { |
| AddExtraCounterValue(track_uuid_it, index); |
| } |
| } |
| |
| void AddExtraCounterValue( |
| protozero::RepeatedFieldIterator<uint64_t> track_uuid_it, |
| size_t index) { |
| // Tokenizer ensures that there aren't more values than uuids, that we |
| // don't have more values than kMaxNumExtraCounters and that the |
| // track_uuids are for valid counter tracks. |
| PERFETTO_DCHECK(track_uuid_it); |
| PERFETTO_DCHECK(index < TrackEventData::kMaxNumExtraCounters); |
| |
| std::optional<TrackId> track_id = track_event_tracker_->GetDescriptorTrack( |
| *track_uuid_it, kNullStringId, packet_sequence_id_); |
| std::optional<uint32_t> counter_row = |
| storage_->counter_track_table().id().IndexOf(*track_id); |
| |
| double value = event_data_->extra_counter_values[index]; |
| context_->event_tracker->PushCounter(ts_, value, *track_id); |
| |
| // Also import thread_time and thread_instruction_count counters into |
| // slice columns to simplify JSON export. |
| StringId counter_name = |
| storage_->counter_track_table().name()[*counter_row]; |
| if (counter_name == parser_->counter_name_thread_time_id_) { |
| thread_timestamp_ = static_cast<int64_t>(value); |
| } else if (counter_name == |
| parser_->counter_name_thread_instruction_count_id_) { |
| thread_instruction_count_ = static_cast<int64_t>(value); |
| } |
| } |
| |
| util::Status ParseThreadBeginEvent() { |
| if (!utid_) { |
| return util::ErrStatus( |
| "TrackEvent with phase B without thread association"); |
| } |
| |
| auto* thread_slices = storage_->mutable_slice_table(); |
| auto opt_slice_id = context_->slice_tracker->BeginTyped( |
| thread_slices, MakeThreadSliceRow(), |
| [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); }); |
| |
| if (opt_slice_id.has_value()) { |
| MaybeParseFlowEvents(opt_slice_id.value()); |
| } |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseThreadEndEvent() { |
| if (!utid_) { |
| return util::ErrStatus( |
| "TrackEvent with phase E without thread association"); |
| } |
| auto opt_slice_id = context_->slice_tracker->End( |
| ts_, track_id_, category_id_, name_id_, |
| [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); }); |
| if (!opt_slice_id) |
| return base::OkStatus(); |
| |
| MaybeParseFlowEvents(*opt_slice_id); |
| auto* thread_slices = storage_->mutable_slice_table(); |
| auto opt_thread_slice_ref = thread_slices->FindById(*opt_slice_id); |
| if (!opt_thread_slice_ref) { |
| // This means that the end event did not match a corresponding track event |
| // begin packet so we likely closed the wrong slice. There's not much we |
| // can do about this beyond flag it as a stat. |
| context_->storage->IncrementStats(stats::track_event_thread_invalid_end); |
| return base::OkStatus(); |
| } |
| |
| tables::SliceTable::RowReference slice_ref = *opt_thread_slice_ref; |
| std::optional<int64_t> tts = slice_ref.thread_ts(); |
| if (tts) { |
| PERFETTO_DCHECK(thread_timestamp_); |
| slice_ref.set_thread_dur(*thread_timestamp_ - *tts); |
| } |
| std::optional<int64_t> tic = slice_ref.thread_instruction_count(); |
| if (tic) { |
| PERFETTO_DCHECK(event_data_->thread_instruction_count); |
| slice_ref.set_thread_instruction_delta( |
| *event_data_->thread_instruction_count - *tic); |
| } |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseThreadCompleteEvent() { |
| if (!utid_) { |
| return util::ErrStatus( |
| "TrackEvent with phase X without thread association"); |
| } |
| |
| auto duration_ns = legacy_event_.duration_us() * 1000; |
| if (duration_ns < 0) |
| return util::ErrStatus("TrackEvent with phase X with negative duration"); |
| |
| auto* thread_slices = storage_->mutable_slice_table(); |
| tables::SliceTable::Row row = MakeThreadSliceRow(); |
| row.dur = duration_ns; |
| if (legacy_event_.has_thread_duration_us()) { |
| row.thread_dur = legacy_event_.thread_duration_us() * 1000; |
| } |
| if (legacy_event_.has_thread_instruction_delta()) { |
| row.thread_instruction_delta = legacy_event_.thread_instruction_delta(); |
| } |
| auto opt_slice_id = context_->slice_tracker->ScopedTyped( |
| thread_slices, std::move(row), |
| [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); }); |
| |
| if (opt_slice_id.has_value()) { |
| MaybeParseFlowEvents(opt_slice_id.value()); |
| } |
| return util::OkStatus(); |
| } |
| |
| std::optional<uint64_t> GetLegacyEventId() { |
| if (legacy_event_.has_unscoped_id()) |
| return legacy_event_.unscoped_id(); |
| // TODO(andrewbb): Catapult doesn't support global_id and local_id on flow |
| // events. We could add support in trace processor (e.g. because there seem |
| // to be some callsites supplying local_id in chromium), but we would have |
| // to consider the process ID for local IDs and use a separate ID scope for |
| // global_id and unscoped_id. |
| return std::nullopt; |
| } |
| |
| util::Status ParseFlowEventV1(char phase) { |
| auto opt_source_id = GetLegacyEventId(); |
| if (!opt_source_id) { |
| storage_->IncrementStats(stats::flow_invalid_id); |
| return util::ErrStatus("Invalid id for flow event v1"); |
| } |
| FlowId flow_id = context_->flow_tracker->GetFlowIdForV1Event( |
| opt_source_id.value(), category_id_, name_id_); |
| switch (phase) { |
| case 's': |
| context_->flow_tracker->Begin(track_id_, flow_id); |
| break; |
| case 't': |
| context_->flow_tracker->Step(track_id_, flow_id); |
| break; |
| case 'f': |
| context_->flow_tracker->End(track_id_, flow_id, |
| legacy_event_.bind_to_enclosing(), |
| /* close_flow = */ false); |
| break; |
| } |
| return util::OkStatus(); |
| } |
| |
| void MaybeParseTrackEventFlows(SliceId slice_id) { |
| if (event_.has_flow_ids_old() || event_.has_flow_ids()) { |
| auto it = |
| event_.has_flow_ids() ? event_.flow_ids() : event_.flow_ids_old(); |
| for (; it; ++it) { |
| FlowId flow_id = *it; |
| if (!context_->flow_tracker->IsActive(flow_id)) { |
| context_->flow_tracker->Begin(slice_id, flow_id); |
| continue; |
| } |
| context_->flow_tracker->Step(slice_id, flow_id); |
| } |
| } |
| if (event_.has_terminating_flow_ids_old() || |
| event_.has_terminating_flow_ids()) { |
| auto it = event_.has_terminating_flow_ids() |
| ? event_.terminating_flow_ids() |
| : event_.terminating_flow_ids_old(); |
| for (; it; ++it) { |
| FlowId flow_id = *it; |
| if (!context_->flow_tracker->IsActive(flow_id)) { |
| // If we should terminate a flow, do not begin a new one if it's not |
| // active already. |
| continue; |
| } |
| context_->flow_tracker->End(slice_id, flow_id, |
| /* close_flow = */ true); |
| } |
| } |
| } |
| |
| void MaybeParseFlowEventV2(SliceId slice_id) { |
| if (!legacy_event_.has_bind_id()) { |
| return; |
| } |
| if (!legacy_event_.has_flow_direction()) { |
| storage_->IncrementStats(stats::flow_without_direction); |
| return; |
| } |
| |
| auto bind_id = legacy_event_.bind_id(); |
| switch (legacy_event_.flow_direction()) { |
| case LegacyEvent::FLOW_OUT: |
| context_->flow_tracker->Begin(slice_id, bind_id); |
| break; |
| case LegacyEvent::FLOW_INOUT: |
| context_->flow_tracker->Step(slice_id, bind_id); |
| break; |
| case LegacyEvent::FLOW_IN: |
| context_->flow_tracker->End(slice_id, bind_id, |
| /* close_flow = */ false); |
| break; |
| default: |
| storage_->IncrementStats(stats::flow_without_direction); |
| } |
| } |
| |
| void MaybeParseFlowEvents(SliceId slice_id) { |
| MaybeParseFlowEventV2(slice_id); |
| MaybeParseTrackEventFlows(slice_id); |
| } |
| |
| util::Status ParseThreadInstantEvent(char phase) { |
| // Handle instant events as slices with zero duration, so that they end |
| // up nested underneath their parent slices. |
| int64_t duration_ns = 0; |
| int64_t tidelta = 0; |
| std::optional<tables::SliceTable::Id> opt_slice_id; |
| auto args_inserter = [this, phase](BoundInserter* inserter) { |
| ParseTrackEventArgs(inserter); |
| // For legacy MARK event, add phase for JSON exporter. |
| if (phase == 'R') { |
| std::string phase_string(1, static_cast<char>(phase)); |
| StringId phase_id = storage_->InternString(phase_string.c_str()); |
| inserter->AddArg(parser_->legacy_event_phase_key_id_, |
| Variadic::String(phase_id)); |
| } |
| }; |
| if (utid_) { |
| auto* thread_slices = storage_->mutable_slice_table(); |
| tables::SliceTable::Row row = MakeThreadSliceRow(); |
| row.dur = duration_ns; |
| if (thread_timestamp_) { |
| row.thread_dur = duration_ns; |
| } |
| if (thread_instruction_count_) { |
| row.thread_instruction_delta = tidelta; |
| } |
| opt_slice_id = context_->slice_tracker->ScopedTyped( |
| thread_slices, row, std::move(args_inserter)); |
| } else { |
| opt_slice_id = context_->slice_tracker->Scoped( |
| ts_, track_id_, category_id_, name_id_, duration_ns, |
| std::move(args_inserter)); |
| } |
| if (!opt_slice_id.has_value()) { |
| return util::OkStatus(); |
| } |
| MaybeParseFlowEvents(opt_slice_id.value()); |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseAsyncBeginEvent(char phase) { |
| auto args_inserter = [this, phase](BoundInserter* inserter) { |
| ParseTrackEventArgs(inserter); |
| |
| if (phase == 'b') |
| return; |
| PERFETTO_DCHECK(phase == 'S'); |
| // For legacy ASYNC_BEGIN, add phase for JSON exporter. |
| std::string phase_string(1, static_cast<char>(phase)); |
| StringId phase_id = storage_->InternString(phase_string.c_str()); |
| inserter->AddArg(parser_->legacy_event_phase_key_id_, |
| Variadic::String(phase_id)); |
| }; |
| auto opt_slice_id = context_->slice_tracker->Begin( |
| ts_, track_id_, category_id_, name_id_, args_inserter); |
| if (!opt_slice_id.has_value()) { |
| return util::OkStatus(); |
| } |
| MaybeParseFlowEvents(opt_slice_id.value()); |
| // For the time being, we only create vtrack slice rows if we need to |
| // store thread timestamps/counters. |
| if (legacy_event_.use_async_tts()) { |
| auto* vtrack_slices = storage_->mutable_virtual_track_slices(); |
| PERFETTO_DCHECK(!vtrack_slices->slice_count() || |
| vtrack_slices->slice_ids().back() < opt_slice_id.value()); |
| int64_t tts = thread_timestamp_.value_or(0); |
| int64_t tic = thread_instruction_count_.value_or(0); |
| vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts, |
| kPendingThreadDuration, tic, |
| kPendingThreadInstructionDelta); |
| } |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseAsyncEndEvent() { |
| auto opt_slice_id = context_->slice_tracker->End( |
| ts_, track_id_, category_id_, name_id_, |
| [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); }); |
| if (!opt_slice_id) |
| return base::OkStatus(); |
| |
| MaybeParseFlowEvents(*opt_slice_id); |
| if (legacy_event_.use_async_tts()) { |
| auto* vtrack_slices = storage_->mutable_virtual_track_slices(); |
| int64_t tts = event_data_->thread_timestamp.value_or(0); |
| int64_t tic = event_data_->thread_instruction_count.value_or(0); |
| vtrack_slices->UpdateThreadDeltasForSliceId(*opt_slice_id, tts, tic); |
| } |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseAsyncStepEvent(char phase) { |
| // Parse step events as instant events. Reconstructing the begin/end times |
| // of the child slice would be too complicated, see b/178540838. For JSON |
| // export, we still record the original step's phase in an arg. |
| int64_t duration_ns = 0; |
| context_->slice_tracker->Scoped( |
| ts_, track_id_, category_id_, name_id_, duration_ns, |
| [this, phase](BoundInserter* inserter) { |
| ParseTrackEventArgs(inserter); |
| |
| PERFETTO_DCHECK(phase == 'T' || phase == 'p'); |
| std::string phase_string(1, static_cast<char>(phase)); |
| StringId phase_id = storage_->InternString(phase_string.c_str()); |
| inserter->AddArg(parser_->legacy_event_phase_key_id_, |
| Variadic::String(phase_id)); |
| }); |
| // Step events don't support thread timestamps, so no need to add a row to |
| // virtual_track_slices. |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseAsyncInstantEvent() { |
| // Handle instant events as slices with zero duration, so that they end |
| // up nested underneath their parent slices. |
| int64_t duration_ns = 0; |
| int64_t tidelta = 0; |
| auto opt_slice_id = context_->slice_tracker->Scoped( |
| ts_, track_id_, category_id_, name_id_, duration_ns, |
| [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); }); |
| if (!opt_slice_id.has_value()) { |
| return util::OkStatus(); |
| } |
| MaybeParseFlowEvents(opt_slice_id.value()); |
| if (legacy_event_.use_async_tts()) { |
| auto* vtrack_slices = storage_->mutable_virtual_track_slices(); |
| PERFETTO_DCHECK(!vtrack_slices->slice_count() || |
| vtrack_slices->slice_ids().back() < opt_slice_id.value()); |
| int64_t tts = thread_timestamp_.value_or(0); |
| int64_t tic = thread_instruction_count_.value_or(0); |
| vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts, |
| duration_ns, tic, tidelta); |
| } |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseMetadataEvent() { |
| ProcessTracker* procs = context_->process_tracker.get(); |
| |
| if (name_id_ == kNullStringId) |
| return util::ErrStatus("Metadata event without name"); |
| |
| // Parse process and thread names from correspondingly named events. |
| NullTermStringView event_name = storage_->GetString(name_id_); |
| PERFETTO_DCHECK(event_name.data()); |
| if (strcmp(event_name.c_str(), "thread_name") == 0) { |
| if (!utid_) { |
| return util::ErrStatus( |
| "thread_name metadata event without thread association"); |
| } |
| |
| auto it = event_.debug_annotations(); |
| if (!it) { |
| return util::ErrStatus( |
| "thread_name metadata event without debug annotations"); |
| } |
| protos::pbzero::DebugAnnotation::Decoder annotation(*it); |
| auto thread_name = annotation.string_value(); |
| if (!thread_name.size) |
| return util::OkStatus(); |
| auto thread_name_id = storage_->InternString(thread_name); |
| procs->UpdateThreadNameByUtid( |
| *utid_, thread_name_id, |
| ThreadNamePriority::kTrackDescriptorThreadType); |
| return util::OkStatus(); |
| } |
| if (strcmp(event_name.c_str(), "process_name") == 0) { |
| if (!upid_) { |
| return util::ErrStatus( |
| "process_name metadata event without process association"); |
| } |
| |
| auto it = event_.debug_annotations(); |
| if (!it) { |
| return util::ErrStatus( |
| "process_name metadata event without debug annotations"); |
| } |
| protos::pbzero::DebugAnnotation::Decoder annotation(*it); |
| auto process_name = annotation.string_value(); |
| if (!process_name.size) |
| return util::OkStatus(); |
| auto process_name_id = |
| storage_->InternString(base::StringView(process_name)); |
| // Don't override system-provided names. |
| procs->SetProcessNameIfUnset(*upid_, process_name_id); |
| return util::OkStatus(); |
| } |
| // Other metadata events are proxied via the raw table for JSON export. |
| ParseLegacyEventAsRawEvent(); |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseLegacyEventAsRawEvent() { |
| if (!utid_) |
| return util::ErrStatus("raw legacy event without thread association"); |
| |
| RawId id = storage_->mutable_raw_table() |
| ->Insert({ts_, parser_->raw_legacy_event_id_, 0, *utid_}) |
| .id; |
| |
| auto inserter = context_->args_tracker->AddArgsTo(id); |
| inserter |
| .AddArg(parser_->legacy_event_category_key_id_, |
| Variadic::String(category_id_)) |
| .AddArg(parser_->legacy_event_name_key_id_, Variadic::String(name_id_)); |
| |
| std::string phase_string(1, static_cast<char>(legacy_event_.phase())); |
| StringId phase_id = storage_->InternString(phase_string.c_str()); |
| inserter.AddArg(parser_->legacy_event_phase_key_id_, |
| Variadic::String(phase_id)); |
| |
| if (legacy_event_.has_duration_us()) { |
| inserter.AddArg(parser_->legacy_event_duration_ns_key_id_, |
| Variadic::Integer(legacy_event_.duration_us() * 1000)); |
| } |
| |
| if (thread_timestamp_) { |
| inserter.AddArg(parser_->legacy_event_thread_timestamp_ns_key_id_, |
| Variadic::Integer(*thread_timestamp_)); |
| if (legacy_event_.has_thread_duration_us()) { |
| inserter.AddArg( |
| parser_->legacy_event_thread_duration_ns_key_id_, |
| Variadic::Integer(legacy_event_.thread_duration_us() * 1000)); |
| } |
| } |
| |
| if (thread_instruction_count_) { |
| inserter.AddArg(parser_->legacy_event_thread_instruction_count_key_id_, |
| Variadic::Integer(*thread_instruction_count_)); |
| if (legacy_event_.has_thread_instruction_delta()) { |
| inserter.AddArg( |
| parser_->legacy_event_thread_instruction_delta_key_id_, |
| Variadic::Integer(legacy_event_.thread_instruction_delta())); |
| } |
| } |
| |
| if (legacy_event_.use_async_tts()) { |
| inserter.AddArg(parser_->legacy_event_use_async_tts_key_id_, |
| Variadic::Boolean(true)); |
| } |
| |
| bool has_id = false; |
| if (legacy_event_.has_unscoped_id()) { |
| // Unscoped ids are either global or local depending on the phase. Pass |
| // them through as unscoped IDs to JSON export to preserve this behavior. |
| inserter.AddArg(parser_->legacy_event_unscoped_id_key_id_, |
| Variadic::UnsignedInteger(legacy_event_.unscoped_id())); |
| has_id = true; |
| } else if (legacy_event_.has_global_id()) { |
| inserter.AddArg(parser_->legacy_event_global_id_key_id_, |
| Variadic::UnsignedInteger(legacy_event_.global_id())); |
| has_id = true; |
| } else if (legacy_event_.has_local_id()) { |
| inserter.AddArg(parser_->legacy_event_local_id_key_id_, |
| Variadic::UnsignedInteger(legacy_event_.local_id())); |
| has_id = true; |
| } |
| |
| if (has_id && legacy_event_.has_id_scope() && |
| legacy_event_.id_scope().size) { |
| inserter.AddArg( |
| parser_->legacy_event_id_scope_key_id_, |
| Variadic::String(storage_->InternString(legacy_event_.id_scope()))); |
| } |
| |
| // No need to parse legacy_event.instant_event_scope() because we import |
| // instant events into the slice table. |
| |
| ParseTrackEventArgs(&inserter); |
| return util::OkStatus(); |
| } |
| |
| void ParseTrackEventArgs(BoundInserter* inserter) { |
| auto log_errors = [this](util::Status status) { |
| if (status.ok()) |
| return; |
| // Log error but continue parsing the other args. |
| storage_->IncrementStats(stats::track_event_parser_errors); |
| PERFETTO_DLOG("ParseTrackEventArgs error: %s", status.c_message()); |
| }; |
| |
| if (event_.has_source_location_iid()) { |
| log_errors(AddSourceLocationArgs(event_.source_location_iid(), inserter)); |
| } |
| |
| if (event_.has_task_execution()) { |
| log_errors(ParseTaskExecutionArgs(event_.task_execution(), inserter)); |
| } |
| if (event_.has_log_message()) { |
| log_errors(ParseLogMessage(event_.log_message(), inserter)); |
| } |
| if (event_.has_chrome_histogram_sample()) { |
| log_errors( |
| ParseHistogramName(event_.chrome_histogram_sample(), inserter)); |
| } |
| if (event_.has_chrome_active_processes()) { |
| protos::pbzero::ChromeActiveProcesses::Decoder message( |
| event_.chrome_active_processes()); |
| for (auto it = message.pid(); it; ++it) { |
| parser_->AddActiveProcess(ts_, *it); |
| } |
| } |
| |
| TrackEventArgsParser args_writer(ts_, *inserter, *storage_, |
| *sequence_state_); |
| int unknown_extensions = 0; |
| log_errors(parser_->args_parser_.ParseMessage( |
| blob_, ".perfetto.protos.TrackEvent", &parser_->reflect_fields_, |
| args_writer, &unknown_extensions)); |
| if (unknown_extensions > 0) { |
| context_->storage->IncrementStats(stats::unknown_extension_fields, |
| unknown_extensions); |
| } |
| |
| { |
| auto key = parser_->args_parser_.EnterDictionary("debug"); |
| util::DebugAnnotationParser parser(parser_->args_parser_); |
| for (auto it = event_.debug_annotations(); it; ++it) { |
| log_errors(parser.Parse(*it, args_writer)); |
| } |
| } |
| |
| if (legacy_passthrough_utid_) { |
| inserter->AddArg(parser_->legacy_event_passthrough_utid_id_, |
| Variadic::UnsignedInteger(*legacy_passthrough_utid_), |
| ArgsTracker::UpdatePolicy::kSkipIfExists); |
| } |
| } |
| |
| util::Status ParseTaskExecutionArgs(ConstBytes task_execution, |
| BoundInserter* inserter) { |
| protos::pbzero::TaskExecution::Decoder task(task_execution); |
| uint64_t iid = task.posted_from_iid(); |
| if (!iid) |
| return util::ErrStatus("TaskExecution with invalid posted_from_iid"); |
| |
| auto* decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kSourceLocationsFieldNumber, |
| protos::pbzero::SourceLocation>(iid); |
| if (!decoder) |
| return util::ErrStatus("TaskExecution with invalid posted_from_iid"); |
| |
| StringId file_name_id = kNullStringId; |
| StringId function_name_id = kNullStringId; |
| uint32_t line_number = 0; |
| |
| std::string file_name = NormalizePathSeparators(decoder->file_name()); |
| file_name_id = storage_->InternString(base::StringView(file_name)); |
| function_name_id = storage_->InternString(decoder->function_name()); |
| line_number = decoder->line_number(); |
| |
| inserter->AddArg(parser_->task_file_name_args_key_id_, |
| Variadic::String(file_name_id)); |
| inserter->AddArg(parser_->task_function_name_args_key_id_, |
| Variadic::String(function_name_id)); |
| inserter->AddArg(parser_->task_line_number_args_key_id_, |
| Variadic::UnsignedInteger(line_number)); |
| return util::OkStatus(); |
| } |
| |
| util::Status AddSourceLocationArgs(uint64_t iid, BoundInserter* inserter) { |
| if (!iid) |
| return util::ErrStatus("SourceLocation with invalid iid"); |
| |
| auto* decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kSourceLocationsFieldNumber, |
| protos::pbzero::SourceLocation>(iid); |
| if (!decoder) |
| return util::ErrStatus("SourceLocation with invalid iid"); |
| |
| StringId file_name_id = kNullStringId; |
| StringId function_name_id = kNullStringId; |
| uint32_t line_number = 0; |
| |
| std::string file_name = NormalizePathSeparators(decoder->file_name()); |
| file_name_id = storage_->InternString(base::StringView(file_name)); |
| function_name_id = storage_->InternString(decoder->function_name()); |
| line_number = decoder->line_number(); |
| |
| inserter->AddArg(parser_->source_location_file_name_key_id_, |
| Variadic::String(file_name_id)); |
| inserter->AddArg(parser_->source_location_function_name_key_id_, |
| Variadic::String(function_name_id)); |
| inserter->AddArg(parser_->source_location_line_number_key_id_, |
| Variadic::UnsignedInteger(line_number)); |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseLogMessage(ConstBytes blob, BoundInserter* inserter) { |
| if (!utid_) |
| return util::ErrStatus("LogMessage without thread association"); |
| |
| protos::pbzero::LogMessage::Decoder message(blob); |
| |
| auto* body_decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kLogMessageBodyFieldNumber, |
| protos::pbzero::LogMessageBody>(message.body_iid()); |
| if (!body_decoder) |
| return util::ErrStatus("LogMessage with invalid body_iid"); |
| |
| const StringId log_message_id = |
| storage_->InternString(body_decoder->body()); |
| inserter->AddArg(parser_->log_message_body_key_id_, |
| Variadic::String(log_message_id)); |
| |
| StringId source_location_id = kNullStringId; |
| if (message.has_source_location_iid()) { |
| auto* source_location_decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kSourceLocationsFieldNumber, |
| protos::pbzero::SourceLocation>(message.source_location_iid()); |
| if (!source_location_decoder) |
| return util::ErrStatus("LogMessage with invalid source_location_iid"); |
| const std::string source_location = |
| source_location_decoder->file_name().ToStdString() + ":" + |
| std::to_string(source_location_decoder->line_number()); |
| source_location_id = |
| storage_->InternString(base::StringView(source_location)); |
| |
| inserter->AddArg(parser_->log_message_source_location_file_name_key_id_, |
| Variadic::String(storage_->InternString( |
| source_location_decoder->file_name()))); |
| inserter->AddArg( |
| parser_->log_message_source_location_function_name_key_id_, |
| Variadic::String(storage_->InternString( |
| source_location_decoder->function_name()))); |
| inserter->AddArg( |
| parser_->log_message_source_location_line_number_key_id_, |
| Variadic::Integer(source_location_decoder->line_number())); |
| } |
| |
| storage_->mutable_android_log_table()->Insert( |
| {ts_, *utid_, |
| /*priority*/ 0, |
| /*tag_id*/ source_location_id, log_message_id}); |
| |
| return util::OkStatus(); |
| } |
| |
| util::Status ParseHistogramName(ConstBytes blob, BoundInserter* inserter) { |
| protos::pbzero::ChromeHistogramSample::Decoder sample(blob); |
| if (!sample.has_name_iid()) |
| return util::OkStatus(); |
| |
| if (sample.has_name()) { |
| return util::ErrStatus( |
| "name is already set for ChromeHistogramSample: only one of name and " |
| "name_iid can be set."); |
| } |
| |
| auto* decoder = sequence_state_->LookupInternedMessage< |
| protos::pbzero::InternedData::kHistogramNamesFieldNumber, |
| protos::pbzero::HistogramName>(sample.name_iid()); |
| if (!decoder) |
| return util::ErrStatus("HistogramName with invalid name_iid"); |
| |
| inserter->AddArg(parser_->histogram_name_key_id_, |
| Variadic::String(storage_->InternString(decoder->name()))); |
| return util::OkStatus(); |
| } |
| |
| tables::SliceTable::Row MakeThreadSliceRow() { |
| tables::SliceTable::Row row; |
| row.ts = ts_; |
| row.track_id = track_id_; |
| row.category = category_id_; |
| row.name = name_id_; |
| row.thread_ts = thread_timestamp_; |
| row.thread_dur = std::nullopt; |
| row.thread_instruction_count = thread_instruction_count_; |
| row.thread_instruction_delta = std::nullopt; |
| return row; |
| } |
| |
| TraceProcessorContext* context_; |
| TrackEventTracker* track_event_tracker_; |
| TraceStorage* storage_; |
| TrackEventParser* parser_; |
| ArgsTranslationTable* args_translation_table_; |
| int64_t ts_; |
| const TrackEventData* event_data_; |
| PacketSequenceStateGeneration* sequence_state_; |
| ConstBytes blob_; |
| TrackEvent::Decoder event_; |
| LegacyEvent::Decoder legacy_event_; |
| protos::pbzero::TrackEventDefaults::Decoder* defaults_; |
| |
| // Importing state. |
| StringId category_id_; |
| StringId name_id_; |
| uint64_t track_uuid_; |
| TrackId track_id_; |
| std::optional<UniqueTid> utid_; |
| std::optional<UniqueTid> upid_; |
| std::optional<int64_t> thread_timestamp_; |
| std::optional<int64_t> thread_instruction_count_; |
| // All events in legacy JSON require a thread ID, but for some types of |
| // events (e.g. async events or process/global-scoped instants), we don't |
| // store it in the slice/track model. To pass the utid through to the json |
| // export, we store it in an arg. |
| std::optional<UniqueTid> legacy_passthrough_utid_; |
| |
| uint32_t packet_sequence_id_; |
| }; |
| |
| TrackEventParser::TrackEventParser(TraceProcessorContext* context, |
| TrackEventTracker* track_event_tracker) |
| : args_parser_(*context->descriptor_pool_.get()), |
| context_(context), |
| track_event_tracker_(track_event_tracker), |
| counter_name_thread_time_id_( |
| context->storage->InternString("thread_time")), |
| counter_name_thread_instruction_count_id_( |
| context->storage->InternString("thread_instruction_count")), |
| task_file_name_args_key_id_( |
| context->storage->InternString("task.posted_from.file_name")), |
| task_function_name_args_key_id_( |
| context->storage->InternString("task.posted_from.function_name")), |
| task_line_number_args_key_id_( |
| context->storage->InternString("task.posted_from.line_number")), |
| log_message_body_key_id_( |
| context->storage->InternString("track_event.log_message")), |
| log_message_source_location_function_name_key_id_( |
| context->storage->InternString( |
| "track_event.log_message.function_name")), |
| log_message_source_location_file_name_key_id_( |
| context->storage->InternString("track_event.log_message.file_name")), |
| log_message_source_location_line_number_key_id_( |
| context->storage->InternString( |
| "track_event.log_message.line_number")), |
| source_location_function_name_key_id_( |
| context->storage->InternString("source.function_name")), |
| source_location_file_name_key_id_( |
| context->storage->InternString("source.file_name")), |
| source_location_line_number_key_id_( |
| context->storage->InternString("source.line_number")), |
| raw_legacy_event_id_( |
| context->storage->InternString("track_event.legacy_event")), |
| legacy_event_passthrough_utid_id_( |
| context->storage->InternString("legacy_event.passthrough_utid")), |
| legacy_event_category_key_id_( |
| context->storage->InternString("legacy_event.category")), |
| legacy_event_name_key_id_( |
| context->storage->InternString("legacy_event.name")), |
| legacy_event_phase_key_id_( |
| context->storage->InternString("legacy_event.phase")), |
| legacy_event_duration_ns_key_id_( |
| context->storage->InternString("legacy_event.duration_ns")), |
| legacy_event_thread_timestamp_ns_key_id_( |
| context->storage->InternString("legacy_event.thread_timestamp_ns")), |
| legacy_event_thread_duration_ns_key_id_( |
| context->storage->InternString("legacy_event.thread_duration_ns")), |
| legacy_event_thread_instruction_count_key_id_( |
| context->storage->InternString( |
| "legacy_event.thread_instruction_count")), |
| legacy_event_thread_instruction_delta_key_id_( |
| context->storage->InternString( |
| "legacy_event.thread_instruction_delta")), |
| legacy_event_use_async_tts_key_id_( |
| context->storage->InternString("legacy_event.use_async_tts")), |
| legacy_event_unscoped_id_key_id_( |
| context->storage->InternString("legacy_event.unscoped_id")), |
| legacy_event_global_id_key_id_( |
| context->storage->InternString("legacy_event.global_id")), |
| legacy_event_local_id_key_id_( |
| context->storage->InternString("legacy_event.local_id")), |
| legacy_event_id_scope_key_id_( |
| context->storage->InternString("legacy_event.id_scope")), |
| legacy_event_bind_id_key_id_( |
| context->storage->InternString("legacy_event.bind_id")), |
| legacy_event_bind_to_enclosing_key_id_( |
| context->storage->InternString("legacy_event.bind_to_enclosing")), |
| legacy_event_flow_direction_key_id_( |
| context->storage->InternString("legacy_event.flow_direction")), |
| histogram_name_key_id_( |
| context->storage->InternString("chrome_histogram_sample.name")), |
| flow_direction_value_in_id_(context->storage->InternString("in")), |
| flow_direction_value_out_id_(context->storage->InternString("out")), |
| flow_direction_value_inout_id_(context->storage->InternString("inout")), |
| chrome_legacy_ipc_class_args_key_id_( |
| context->storage->InternString("legacy_ipc.class")), |
| chrome_legacy_ipc_line_args_key_id_( |
| context->storage->InternString("legacy_ipc.line")), |
| chrome_host_app_package_name_id_( |
| context->storage->InternString("chrome.host_app_package_name")), |
| chrome_crash_trace_id_name_id_( |
| context->storage->InternString("chrome.crash_trace_id")), |
| chrome_process_label_flat_key_id_( |
| context->storage->InternString("chrome.process_label")), |
| chrome_process_type_id_( |
| context_->storage->InternString("chrome.process_type")), |
| event_category_key_id_(context_->storage->InternString("event.category")), |
| event_name_key_id_(context_->storage->InternString("event.name")), |
| chrome_string_lookup_(context->storage.get()), |
| counter_unit_ids_{{kNullStringId, context_->storage->InternString("ns"), |
| context_->storage->InternString("count"), |
| context_->storage->InternString("bytes")}}, |
| active_chrome_processes_tracker_(context) { |
| args_parser_.AddParsingOverrideForField( |
| "chrome_mojo_event_info.mojo_interface_method_iid", |
| [](const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| return MaybeParseUnsymbolizedSourceLocation( |
| "chrome_mojo_event_info.mojo_interface_method.native_symbol", field, |
| delegate); |
| }); |
| // Switch |source_location_iid| into its interned data variant. |
| args_parser_.AddParsingOverrideForField( |
| "begin_impl_frame_args.current_args.source_location_iid", |
| [](const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| return MaybeParseSourceLocation("begin_impl_frame_args.current_args", |
| field, delegate); |
| }); |
| args_parser_.AddParsingOverrideForField( |
| "begin_impl_frame_args.last_args.source_location_iid", |
| [](const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| return MaybeParseSourceLocation("begin_impl_frame_args.last_args", |
| field, delegate); |
| }); |
| args_parser_.AddParsingOverrideForField( |
| "begin_frame_observer_state.last_begin_frame_args.source_location_iid", |
| [](const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| return MaybeParseSourceLocation( |
| "begin_frame_observer_state.last_begin_frame_args", field, |
| delegate); |
| }); |
| args_parser_.AddParsingOverrideForField( |
| "chrome_memory_pressure_notification.creation_location_iid", |
| [](const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| return MaybeParseSourceLocation("chrome_memory_pressure_notification", |
| field, delegate); |
| }); |
| |
| // Parse DebugAnnotations. |
| args_parser_.AddParsingOverrideForType( |
| ".perfetto.protos.DebugAnnotation", |
| [&](util::ProtoToArgsParser::ScopedNestedKeyContext& key, |
| const protozero::ConstBytes& data, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| // Do not add "debug_annotations" to the final key. |
| key.RemoveFieldSuffix(); |
| util::DebugAnnotationParser annotation_parser(args_parser_); |
| return annotation_parser.Parse(data, delegate); |
| }); |
| |
| args_parser_.AddParsingOverrideForField( |
| "active_processes.pid", [&](const protozero::Field& field, |
| util::ProtoToArgsParser::Delegate& delegate) { |
| AddActiveProcess(delegate.packet_timestamp(), field.as_int32()); |
| // Fallthrough so that the parser adds pid as a regular arg. |
| return std::nullopt; |
| }); |
| |
| for (uint16_t index : kReflectFields) { |
| reflect_fields_.push_back(index); |
| } |
| } |
| |
| void TrackEventParser::ParseTrackDescriptor( |
| int64_t packet_timestamp, |
| protozero::ConstBytes track_descriptor, |
| uint32_t packet_sequence_id) { |
| protos::pbzero::TrackDescriptor::Decoder decoder(track_descriptor); |
| |
| // Ensure that the track and its parents are resolved. This may start a new |
| // process and/or thread (i.e. new upid/utid). |
| TrackId track_id = *track_event_tracker_->GetDescriptorTrack( |
| decoder.uuid(), kNullStringId, packet_sequence_id); |
| |
| if (decoder.has_thread()) { |
| UniqueTid utid = ParseThreadDescriptor(decoder.thread()); |
| if (decoder.has_chrome_thread()) |
| ParseChromeThreadDescriptor(utid, decoder.chrome_thread()); |
| } else if (decoder.has_process()) { |
| UniquePid upid = |
| ParseProcessDescriptor(packet_timestamp, decoder.process()); |
| if (decoder.has_chrome_process()) |
| ParseChromeProcessDescriptor(upid, decoder.chrome_process()); |
| } else if (decoder.has_counter()) { |
| ParseCounterDescriptor(track_id, decoder.counter()); |
| } |
| |
| // Override the name with the most recent name seen (after sorting by ts). |
| if (decoder.has_name()) { |
| auto* tracks = context_->storage->mutable_track_table(); |
| StringId name_id = context_->storage->InternString(decoder.name()); |
| tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id), name_id); |
| } |
| } |
| |
| UniquePid TrackEventParser::ParseProcessDescriptor( |
| int64_t packet_timestamp, |
| protozero::ConstBytes process_descriptor) { |
| protos::pbzero::ProcessDescriptor::Decoder decoder(process_descriptor); |
| UniquePid upid = context_->process_tracker->GetOrCreateProcess( |
| static_cast<uint32_t>(decoder.pid())); |
| active_chrome_processes_tracker_.AddProcessDescriptor(packet_timestamp, upid); |
| if (decoder.has_process_name() && decoder.process_name().size) { |
| // Don't override system-provided names. |
| context_->process_tracker->SetProcessNameIfUnset( |
| upid, context_->storage->InternString(decoder.process_name())); |
| } |
| if (decoder.has_start_timestamp_ns() && decoder.start_timestamp_ns() > 0) { |
| context_->process_tracker->SetStartTsIfUnset(upid, |
| decoder.start_timestamp_ns()); |
| } |
| // TODO(skyostil): Remove parsing for legacy chrome_process_type field. |
| if (decoder.has_chrome_process_type()) { |
| StringId name_id = |
| chrome_string_lookup_.GetProcessName(decoder.chrome_process_type()); |
| // Don't override system-provided names. |
| context_->process_tracker->SetProcessNameIfUnset(upid, name_id); |
| } |
| int label_index = 0; |
| for (auto it = decoder.process_labels(); it; it++) { |
| StringId label_id = context_->storage->InternString(*it); |
| std::string key = "chrome.process_label["; |
| key.append(std::to_string(label_index++)); |
| key.append("]"); |
| context_->process_tracker->AddArgsTo(upid).AddArg( |
| chrome_process_label_flat_key_id_, |
| context_->storage->InternString(base::StringView(key)), |
| Variadic::String(label_id)); |
| } |
| return upid; |
| } |
| |
| void TrackEventParser::ParseChromeProcessDescriptor( |
| UniquePid upid, |
| protozero::ConstBytes chrome_process_descriptor) { |
| protos::pbzero::ChromeProcessDescriptor::Decoder decoder( |
| chrome_process_descriptor); |
| |
| StringId name_id = |
| chrome_string_lookup_.GetProcessName(decoder.process_type()); |
| // Don't override system-provided names. |
| context_->process_tracker->SetProcessNameIfUnset(upid, name_id); |
| |
| ArgsTracker::BoundInserter process_args = |
| context_->process_tracker->AddArgsTo(upid); |
| // For identifying Chrome processes in system traces. |
| process_args.AddArg(chrome_process_type_id_, Variadic::String(name_id)); |
| if (decoder.has_host_app_package_name()) { |
| process_args.AddArg(chrome_host_app_package_name_id_, |
| Variadic::String(context_->storage->InternString( |
| decoder.host_app_package_name()))); |
| } |
| if (decoder.has_crash_trace_id()) { |
| process_args.AddArg(chrome_crash_trace_id_name_id_, |
| Variadic::UnsignedInteger(decoder.crash_trace_id())); |
| } |
| } |
| |
| UniqueTid TrackEventParser::ParseThreadDescriptor( |
| protozero::ConstBytes thread_descriptor) { |
| protos::pbzero::ThreadDescriptor::Decoder decoder(thread_descriptor); |
| UniqueTid utid = context_->process_tracker->UpdateThread( |
| static_cast<uint32_t>(decoder.tid()), |
| static_cast<uint32_t>(decoder.pid())); |
| StringId name_id = kNullStringId; |
| if (decoder.has_thread_name() && decoder.thread_name().size) { |
| name_id = context_->storage->InternString(decoder.thread_name()); |
| } else if (decoder.has_chrome_thread_type()) { |
| // TODO(skyostil): Remove parsing for legacy chrome_thread_type field. |
| name_id = chrome_string_lookup_.GetThreadName(decoder.chrome_thread_type()); |
| } |
| context_->process_tracker->UpdateThreadNameByUtid( |
| utid, name_id, ThreadNamePriority::kTrackDescriptor); |
| return utid; |
| } |
| |
| void TrackEventParser::ParseChromeThreadDescriptor( |
| UniqueTid utid, |
| protozero::ConstBytes chrome_thread_descriptor) { |
| protos::pbzero::ChromeThreadDescriptor::Decoder decoder( |
| chrome_thread_descriptor); |
| if (!decoder.has_thread_type()) |
| return; |
| |
| StringId name_id = chrome_string_lookup_.GetThreadName(decoder.thread_type()); |
| context_->process_tracker->UpdateThreadNameByUtid( |
| utid, name_id, ThreadNamePriority::kTrackDescriptorThreadType); |
| } |
| |
| void TrackEventParser::ParseCounterDescriptor( |
| TrackId track_id, |
| protozero::ConstBytes counter_descriptor) { |
| using protos::pbzero::CounterDescriptor; |
| |
| CounterDescriptor::Decoder decoder(counter_descriptor); |
| auto* counter_tracks = context_->storage->mutable_counter_track_table(); |
| |
| size_t unit_index = static_cast<size_t>(decoder.unit()); |
| if (unit_index >= counter_unit_ids_.size()) |
| unit_index = CounterDescriptor::UNIT_UNSPECIFIED; |
| |
| auto opt_track_idx = counter_tracks->id().IndexOf(track_id); |
| if (!opt_track_idx) { |
| context_->storage->IncrementStats(stats::track_event_parser_errors); |
| return; |
| } |
| |
| auto track_idx = *opt_track_idx; |
| |
| switch (decoder.type()) { |
| case CounterDescriptor::COUNTER_UNSPECIFIED: |
| break; |
| case CounterDescriptor::COUNTER_THREAD_TIME_NS: |
| unit_index = CounterDescriptor::UNIT_TIME_NS; |
| counter_tracks->mutable_name()->Set(track_idx, |
| counter_name_thread_time_id_); |
| break; |
| case CounterDescriptor::COUNTER_THREAD_INSTRUCTION_COUNT: |
| unit_index = CounterDescriptor::UNIT_COUNT; |
| counter_tracks->mutable_name()->Set( |
| track_idx, counter_name_thread_instruction_count_id_); |
| break; |
| } |
| |
| counter_tracks->mutable_unit()->Set(track_idx, counter_unit_ids_[unit_index]); |
| } |
| |
| void TrackEventParser::ParseTrackEvent(int64_t ts, |
| const TrackEventData* event_data, |
| ConstBytes blob, |
| uint32_t packet_sequence_id) { |
| const auto range_of_interest_start_us = |
| track_event_tracker_->range_of_interest_start_us(); |
| if (context_->config.drop_track_event_data_before == |
| DropTrackEventDataBefore::kTrackEventRangeOfInterest && |
| range_of_interest_start_us && ts < *range_of_interest_start_us * 1000) { |
| // The event is outside of the range of interest, and dropping is enabled. |
| // So we drop the event. |
| context_->storage->IncrementStats( |
| stats::track_event_dropped_packets_outside_of_range_of_interest); |
| return; |
| } |
| util::Status status = |
| EventImporter(this, ts, event_data, std::move(blob), packet_sequence_id) |
| .Import(); |
| if (!status.ok()) { |
| context_->storage->IncrementStats(stats::track_event_parser_errors); |
| PERFETTO_DLOG("ParseTrackEvent error: %s", status.c_message()); |
| } |
| } |
| |
| void TrackEventParser::AddActiveProcess(int64_t packet_timestamp, int32_t pid) { |
| UniquePid upid = |
| context_->process_tracker->GetOrCreateProcess(static_cast<uint32_t>(pid)); |
| active_chrome_processes_tracker_.AddActiveProcessMetadata(packet_timestamp, |
| upid); |
| } |
| |
| void TrackEventParser::NotifyEndOfFile() { |
| active_chrome_processes_tracker_.NotifyEndOfFile(); |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |