| /* |
| * 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/android_probes_parser.h" |
| |
| #include <optional> |
| |
| #include "perfetto/ext/base/string_utils.h" |
| #include "perfetto/ext/traced/sys_stats_counters.h" |
| #include "src/trace_processor/importers/common/args_tracker.h" |
| #include "src/trace_processor/importers/common/async_track_set_tracker.h" |
| #include "src/trace_processor/importers/common/clock_tracker.h" |
| #include "src/trace_processor/importers/common/event_tracker.h" |
| #include "src/trace_processor/importers/common/metadata_tracker.h" |
| #include "src/trace_processor/importers/common/process_tracker.h" |
| #include "src/trace_processor/importers/syscalls/syscall_tracker.h" |
| #include "src/trace_processor/types/tcp_state.h" |
| #include "src/trace_processor/types/trace_processor_context.h" |
| |
| #include "protos/perfetto/common/android_energy_consumer_descriptor.pbzero.h" |
| #include "protos/perfetto/common/android_log_constants.pbzero.h" |
| #include "protos/perfetto/common/builtin_clock.pbzero.h" |
| #include "protos/perfetto/config/trace_config.pbzero.h" |
| #include "protos/perfetto/trace/android/android_game_intervention_list.pbzero.h" |
| #include "protos/perfetto/trace/android/android_log.pbzero.h" |
| #include "protos/perfetto/trace/android/android_system_property.pbzero.h" |
| #include "protos/perfetto/trace/android/initial_display_state.pbzero.h" |
| #include "protos/perfetto/trace/power/android_energy_estimation_breakdown.pbzero.h" |
| #include "protos/perfetto/trace/power/android_entity_state_residency.pbzero.h" |
| #include "protos/perfetto/trace/power/battery_counters.pbzero.h" |
| #include "protos/perfetto/trace/power/power_rails.pbzero.h" |
| #include "protos/perfetto/trace/ps/process_stats.pbzero.h" |
| #include "protos/perfetto/trace/ps/process_tree.pbzero.h" |
| #include "protos/perfetto/trace/sys_stats/sys_stats.pbzero.h" |
| #include "protos/perfetto/trace/system_info.pbzero.h" |
| |
| #include "src/trace_processor/importers/proto/android_probes_tracker.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| AndroidProbesParser::AndroidProbesParser(TraceProcessorContext* context) |
| : context_(context), |
| batt_charge_id_(context->storage->InternString("batt.charge_uah")), |
| batt_capacity_id_(context->storage->InternString("batt.capacity_pct")), |
| batt_current_id_(context->storage->InternString("batt.current_ua")), |
| batt_current_avg_id_( |
| context->storage->InternString("batt.current.avg_ua")), |
| screen_state_id_(context->storage->InternString("ScreenState")), |
| device_state_id_(context->storage->InternString("DeviceStateChanged")), |
| battery_status_id_(context->storage->InternString("BatteryStatus")), |
| plug_type_id_(context->storage->InternString("PlugType")) {} |
| |
| void AndroidProbesParser::ParseBatteryCounters(int64_t ts, ConstBytes blob) { |
| protos::pbzero::BatteryCounters::Decoder evt(blob.data, blob.size); |
| StringId batt_charge_id = batt_charge_id_; |
| StringId batt_capacity_id = batt_capacity_id_; |
| StringId batt_current_id = batt_current_id_; |
| StringId batt_current_avg_id = batt_current_avg_id_; |
| if (evt.has_name()) { |
| std::string batt_name = evt.name().ToStdString(); |
| batt_charge_id = context_->storage->InternString(base::StringView( |
| std::string("batt.").append(batt_name).append(".charge_uah"))); |
| batt_capacity_id = context_->storage->InternString(base::StringView( |
| std::string("batt.").append(batt_name).append(".capacity_pct"))); |
| batt_current_id = context_->storage->InternString(base::StringView( |
| std::string("batt.").append(batt_name).append(".current_ua"))); |
| batt_current_avg_id = context_->storage->InternString(base::StringView( |
| std::string("batt.").append(batt_name).append(".current.avg_ua"))); |
| } |
| if (evt.has_charge_counter_uah()) { |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(batt_charge_id); |
| context_->event_tracker->PushCounter( |
| ts, static_cast<double>(evt.charge_counter_uah()), track); |
| } else if (evt.has_energy_counter_uwh() && evt.has_voltage_uv()) { |
| // Calculate charge counter from energy counter and voltage. |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(batt_charge_id); |
| auto energy = evt.energy_counter_uwh(); |
| auto voltage = evt.voltage_uv(); |
| if (voltage > 0) { |
| context_->event_tracker->PushCounter( |
| ts, static_cast<double>(energy * 1000000 / voltage), track); |
| } |
| } |
| |
| if (evt.has_capacity_percent()) { |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(batt_capacity_id); |
| context_->event_tracker->PushCounter( |
| ts, static_cast<double>(evt.capacity_percent()), track); |
| } |
| if (evt.has_current_ua()) { |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(batt_current_id); |
| context_->event_tracker->PushCounter( |
| ts, static_cast<double>(evt.current_ua()), track); |
| } |
| if (evt.has_current_avg_ua()) { |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(batt_current_avg_id); |
| context_->event_tracker->PushCounter( |
| ts, static_cast<double>(evt.current_avg_ua()), track); |
| } |
| } |
| |
| void AndroidProbesParser::ParsePowerRails(int64_t ts, ConstBytes blob) { |
| protos::pbzero::PowerRails::Decoder evt(blob.data, blob.size); |
| |
| // Descriptors should have been processed at tokenization time. |
| PERFETTO_DCHECK(evt.has_energy_data()); |
| |
| // Because we have some special code in the tokenization phase, we |
| // will only every get one EnergyData message per packet. Therefore, |
| // we can just read the data directly. |
| auto it = evt.energy_data(); |
| protos::pbzero::PowerRails::EnergyData::Decoder desc(*it); |
| |
| auto* tracker = AndroidProbesTracker::GetOrCreate(context_); |
| auto opt_track = tracker->GetPowerRailTrack(desc.index()); |
| if (opt_track.has_value()) { |
| // The tokenization makes sure that this field is always present and |
| // is equal to the packet's timestamp (as the packet was forged in |
| // the tokenizer). |
| PERFETTO_DCHECK(desc.has_timestamp_ms()); |
| PERFETTO_DCHECK(ts / 1000000 == static_cast<int64_t>(desc.timestamp_ms())); |
| context_->event_tracker->PushCounter(ts, static_cast<double>(desc.energy()), |
| *opt_track); |
| } else { |
| context_->storage->IncrementStats(stats::power_rail_unknown_index); |
| } |
| |
| // DCHECK that we only got one message. |
| PERFETTO_DCHECK(!++it); |
| } |
| |
| void AndroidProbesParser::ParseEnergyBreakdown(int64_t ts, ConstBytes blob) { |
| protos::pbzero::AndroidEnergyEstimationBreakdown::Decoder event(blob.data, |
| blob.size); |
| |
| if (!event.has_energy_consumer_id() || !event.has_energy_uws()) { |
| context_->storage->IncrementStats(stats::energy_breakdown_missing_values); |
| return; |
| } |
| |
| auto consumer_id = event.energy_consumer_id(); |
| auto* tracker = AndroidProbesTracker::GetOrCreate(context_); |
| auto energy_consumer_specs = |
| tracker->GetEnergyBreakdownDescriptor(consumer_id); |
| |
| if (!energy_consumer_specs) { |
| context_->storage->IncrementStats(stats::energy_breakdown_missing_values); |
| return; |
| } |
| |
| auto total_energy = static_cast<double>(event.energy_uws()); |
| auto consumer_name = energy_consumer_specs->name; |
| auto consumer_type = energy_consumer_specs->type; |
| auto ordinal = energy_consumer_specs->ordinal; |
| |
| TrackId energy_track = context_->track_tracker->InternEnergyCounterTrack( |
| consumer_name, consumer_id, consumer_type, ordinal); |
| context_->event_tracker->PushCounter(ts, total_energy, energy_track); |
| |
| // Consumers providing per-uid energy breakdown |
| for (auto it = event.per_uid_breakdown(); it; ++it) { |
| protos::pbzero::AndroidEnergyEstimationBreakdown_EnergyUidBreakdown::Decoder |
| breakdown(*it); |
| |
| if (!breakdown.has_uid() || !breakdown.has_energy_uws()) { |
| context_->storage->IncrementStats( |
| stats::energy_uid_breakdown_missing_values); |
| continue; |
| } |
| |
| TrackId energy_uid_track = |
| context_->track_tracker->InternEnergyPerUidCounterTrack( |
| consumer_name, consumer_id, breakdown.uid()); |
| context_->event_tracker->PushCounter( |
| ts, static_cast<double>(breakdown.energy_uws()), energy_uid_track); |
| } |
| } |
| |
| void AndroidProbesParser::ParseEntityStateResidency(int64_t ts, |
| ConstBytes blob) { |
| protos::pbzero::EntityStateResidency::Decoder event(blob.data, blob.size); |
| |
| if (!event.has_residency()) { |
| context_->storage->IncrementStats(stats::entity_state_residency_invalid); |
| return; |
| } |
| |
| auto* tracker = AndroidProbesTracker::GetOrCreate(context_); |
| |
| for (auto it = event.residency(); it; ++it) { |
| protos::pbzero::EntityStateResidency::StateResidency::Decoder residency( |
| *it); |
| |
| auto entity_state = tracker->GetEntityStateDescriptor( |
| residency.entity_index(), residency.state_index()); |
| if (!entity_state) { |
| context_->storage->IncrementStats( |
| stats::entity_state_residency_lookup_failed); |
| return; |
| } |
| |
| TrackId track = context_->track_tracker->InternGlobalCounterTrack( |
| entity_state->overall_name); |
| context_->event_tracker->PushCounter( |
| ts, double(residency.total_time_in_state_ms()), track); |
| } |
| } |
| |
| void AndroidProbesParser::ParseAndroidLogPacket(ConstBytes blob) { |
| protos::pbzero::AndroidLogPacket::Decoder packet(blob.data, blob.size); |
| for (auto it = packet.events(); it; ++it) |
| ParseAndroidLogEvent(*it); |
| |
| if (packet.has_stats()) |
| ParseAndroidLogStats(packet.stats()); |
| } |
| |
| void AndroidProbesParser::ParseAndroidLogEvent(ConstBytes blob) { |
| // TODO(primiano): Add events and non-stringified fields to the "raw" table. |
| protos::pbzero::AndroidLogPacket::LogEvent::Decoder evt(blob.data, blob.size); |
| int64_t ts = static_cast<int64_t>(evt.timestamp()); |
| uint32_t pid = static_cast<uint32_t>(evt.pid()); |
| uint32_t tid = static_cast<uint32_t>(evt.tid()); |
| uint8_t prio = static_cast<uint8_t>(evt.prio()); |
| StringId tag_id = context_->storage->InternString( |
| evt.has_tag() ? evt.tag() : base::StringView()); |
| StringId msg_id = context_->storage->InternString( |
| evt.has_message() ? evt.message() : base::StringView()); |
| |
| char arg_msg[4096]; |
| char* arg_str = &arg_msg[0]; |
| *arg_str = '\0'; |
| auto arg_avail = [&arg_msg, &arg_str]() { |
| size_t used = static_cast<size_t>(arg_str - arg_msg); |
| PERFETTO_CHECK(used <= sizeof(arg_msg)); |
| return sizeof(arg_msg) - used; |
| }; |
| for (auto it = evt.args(); it; ++it) { |
| protos::pbzero::AndroidLogPacket::LogEvent::Arg::Decoder arg(*it); |
| if (!arg.has_name()) |
| continue; |
| arg_str += base::SprintfTrunc(arg_str, arg_avail(), |
| " %.*s=", static_cast<int>(arg.name().size), |
| arg.name().data); |
| if (arg.has_string_value()) { |
| arg_str += base::SprintfTrunc(arg_str, arg_avail(), "\"%.*s\"", |
| static_cast<int>(arg.string_value().size), |
| arg.string_value().data); |
| } else if (arg.has_int_value()) { |
| arg_str += |
| base::SprintfTrunc(arg_str, arg_avail(), "%" PRId64, arg.int_value()); |
| } else if (arg.has_float_value()) { |
| arg_str += base::SprintfTrunc(arg_str, arg_avail(), "%f", |
| static_cast<double>(arg.float_value())); |
| } |
| } |
| |
| if (prio == 0) |
| prio = protos::pbzero::AndroidLogPriority::PRIO_INFO; |
| |
| if (arg_str != &arg_msg[0]) { |
| PERFETTO_DCHECK(msg_id.is_null()); |
| // Skip the first space char (" foo=1 bar=2" -> "foo=1 bar=2"). |
| msg_id = context_->storage->InternString(&arg_msg[1]); |
| } |
| UniquePid utid = tid ? context_->process_tracker->UpdateThread(tid, pid) : 0; |
| base::StatusOr<int64_t> trace_time = context_->clock_tracker->ToTraceTime( |
| protos::pbzero::BUILTIN_CLOCK_REALTIME, ts); |
| if (!trace_time.ok()) { |
| static std::atomic<uint32_t> dlog_count(0); |
| if (dlog_count++ < 10) |
| PERFETTO_DLOG("%s", trace_time.status().c_message()); |
| return; |
| } |
| |
| // Log events are NOT required to be sorted by trace_time. The virtual table |
| // will take care of sorting on-demand. |
| context_->storage->mutable_android_log_table()->Insert( |
| {trace_time.value(), utid, prio, tag_id, msg_id}); |
| } |
| |
| void AndroidProbesParser::ParseAndroidLogStats(ConstBytes blob) { |
| protos::pbzero::AndroidLogPacket::Stats::Decoder evt(blob.data, blob.size); |
| if (evt.has_num_failed()) { |
| context_->storage->SetStats(stats::android_log_num_failed, |
| static_cast<int64_t>(evt.num_failed())); |
| } |
| |
| if (evt.has_num_skipped()) { |
| context_->storage->SetStats(stats::android_log_num_skipped, |
| static_cast<int64_t>(evt.num_skipped())); |
| } |
| |
| if (evt.has_num_total()) { |
| context_->storage->SetStats(stats::android_log_num_total, |
| static_cast<int64_t>(evt.num_total())); |
| } |
| } |
| |
| void AndroidProbesParser::ParseStatsdMetadata(ConstBytes blob) { |
| protos::pbzero::TraceConfig::StatsdMetadata::Decoder metadata(blob.data, |
| blob.size); |
| if (metadata.has_triggering_subscription_id()) { |
| context_->metadata_tracker->SetMetadata( |
| metadata::statsd_triggering_subscription_id, |
| Variadic::Integer(metadata.triggering_subscription_id())); |
| } |
| } |
| |
| void AndroidProbesParser::ParseAndroidGameIntervention(ConstBytes blob) { |
| protos::pbzero::AndroidGameInterventionList::Decoder intervention_list( |
| blob.data, blob.size); |
| constexpr static int kGameModeStandard = 1; |
| constexpr static int kGameModePerformance = 2; |
| constexpr static int kGameModeBattery = 3; |
| |
| context_->storage->SetStats(stats::game_intervention_has_read_errors, |
| intervention_list.read_error()); |
| context_->storage->SetStats(stats::game_intervention_has_parse_errors, |
| intervention_list.parse_error()); |
| |
| for (auto pkg_it = intervention_list.game_packages(); pkg_it; ++pkg_it) { |
| protos::pbzero::AndroidGameInterventionList_GamePackageInfo::Decoder |
| game_pkg(*pkg_it); |
| int64_t uid = static_cast<int64_t>(game_pkg.uid()); |
| int32_t cur_mode = static_cast<int32_t>(game_pkg.current_mode()); |
| |
| bool is_standard_mode = false; |
| std::optional<double> standard_downscale; |
| std::optional<int32_t> standard_angle; |
| std::optional<double> standard_fps; |
| |
| bool is_performance_mode = false; |
| std::optional<double> perf_downscale; |
| std::optional<int32_t> perf_angle; |
| std::optional<double> perf_fps; |
| |
| bool is_battery_mode = false; |
| std::optional<double> battery_downscale; |
| std::optional<int32_t> battery_angle; |
| std::optional<double> battery_fps; |
| |
| for (auto mode_it = game_pkg.game_mode_info(); mode_it; ++mode_it) { |
| protos::pbzero::AndroidGameInterventionList_GameModeInfo::Decoder |
| game_mode(*mode_it); |
| |
| uint32_t mode_num = game_mode.mode(); |
| if (mode_num == kGameModeStandard) { |
| is_standard_mode = true; |
| standard_downscale = |
| static_cast<double>(game_mode.resolution_downscale()); |
| standard_angle = game_mode.use_angle(); |
| standard_fps = static_cast<double>(game_mode.fps()); |
| } else if (mode_num == kGameModePerformance) { |
| is_performance_mode = true; |
| perf_downscale = static_cast<double>(game_mode.resolution_downscale()); |
| perf_angle = game_mode.use_angle(); |
| perf_fps = static_cast<double>(game_mode.fps()); |
| } else if (mode_num == kGameModeBattery) { |
| is_battery_mode = true; |
| battery_downscale = |
| static_cast<double>(game_mode.resolution_downscale()); |
| battery_angle = game_mode.use_angle(); |
| battery_fps = static_cast<double>(game_mode.fps()); |
| } |
| } |
| |
| context_->storage->mutable_android_game_intervenion_list_table()->Insert( |
| {context_->storage->InternString(game_pkg.name()), uid, cur_mode, |
| is_standard_mode, standard_downscale, standard_angle, standard_fps, |
| is_performance_mode, perf_downscale, perf_angle, perf_fps, |
| is_battery_mode, battery_downscale, battery_angle, battery_fps}); |
| } |
| } |
| |
| void AndroidProbesParser::ParseInitialDisplayState(int64_t ts, |
| ConstBytes blob) { |
| protos::pbzero::InitialDisplayState::Decoder state(blob.data, blob.size); |
| |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(screen_state_id_); |
| context_->event_tracker->PushCounter(ts, state.display_state(), track); |
| } |
| |
| void AndroidProbesParser::ParseAndroidSystemProperty(int64_t ts, |
| ConstBytes blob) { |
| protos::pbzero::AndroidSystemProperty::Decoder properties(blob.data, |
| blob.size); |
| for (auto it = properties.values(); it; ++it) { |
| protos::pbzero::AndroidSystemProperty::PropertyValue::Decoder kv(*it); |
| base::StringView name(kv.name()); |
| std::optional<StringId> mapped_name_id; |
| |
| if (name == "debug.tracing.device_state") { |
| auto state = kv.value(); |
| |
| StringId state_id = context_->storage->InternString(state); |
| auto track_set_id = |
| context_->async_track_set_tracker->InternGlobalTrackSet( |
| device_state_id_); |
| TrackId track_id = |
| context_->async_track_set_tracker->Scoped(track_set_id, ts, 0); |
| context_->slice_tracker->Scoped(ts, track_id, kNullStringId, state_id, 0); |
| } else if (name.StartsWith("debug.tracing.battery_stats.") || |
| name == "debug.tracing.mcc" || name == "debug.tracing.mnc") { |
| StringId name_id = context_->storage->InternString( |
| name.substr(strlen("debug.tracing."))); |
| std::optional<int32_t> state = |
| base::StringToInt32(kv.value().ToStdString()); |
| if (state) { |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(name_id); |
| context_->event_tracker->PushCounter(ts, *state, track); |
| } |
| } else if (name == "debug.tracing.screen_state") { |
| mapped_name_id = screen_state_id_; |
| } else if (name == "debug.tracing.battery_status") { |
| mapped_name_id = battery_status_id_; |
| } else if (name == "debug.tracing.plug_type") { |
| mapped_name_id = plug_type_id_; |
| } |
| if (mapped_name_id) { |
| std::optional<int32_t> state = |
| base::StringToInt32(kv.value().ToStdString()); |
| if (state) { |
| TrackId track = |
| context_->track_tracker->InternGlobalCounterTrack(*mapped_name_id); |
| context_->event_tracker->PushCounter(ts, *state, track); |
| } |
| } |
| } |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |