1 /*
2 * Copyright (C) 2024 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/importers/android_bugreport/android_dumpstate_event_parser_impl.h"
18
19 #include <cstdint>
20 #include <optional>
21 #include <string>
22 #include <unordered_map>
23 #include <utility>
24
25 #include "perfetto/base/logging.h"
26 #include "perfetto/base/status.h"
27 #include "perfetto/ext/base/no_destructor.h"
28 #include "perfetto/ext/base/status_or.h"
29 #include "perfetto/ext/base/string_utils.h"
30 #include "perfetto/ext/base/string_view.h"
31 #include "perfetto/ext/base/string_view_splitter.h"
32 #include "src/trace_processor/importers/android_bugreport/android_battery_stats_history_string_tracker.h"
33 #include "src/trace_processor/importers/android_bugreport/android_dumpstate_event.h"
34 #include "src/trace_processor/importers/common/event_tracker.h"
35 #include "src/trace_processor/importers/common/slice_tracker.h"
36 #include "src/trace_processor/importers/common/track_compressor.h"
37 #include "src/trace_processor/importers/common/track_tracker.h"
38 #include "src/trace_processor/importers/common/tracks.h"
39 #include "src/trace_processor/importers/common/tracks_common.h"
40 #include "src/trace_processor/importers/common/tracks_internal.h"
41 #include "src/trace_processor/storage/trace_storage.h"
42 #include "src/trace_processor/types/trace_processor_context.h"
43 #include "src/trace_processor/util/status_macros.h"
44
45 namespace perfetto::trace_processor {
46
47 namespace {
GetEventFromShortName(base::StringView short_name)48 base::StatusOr<std::string> GetEventFromShortName(base::StringView short_name) {
49 static const base::NoDestructor<
50 std::unordered_map<base::StringView, std::string> >
51 checkin_event_name_to_enum(
52 std::unordered_map<base::StringView, std::string>({
53 {"Enl", "null"}, {"Epr", "proc"},
54 {"Efg", "fg"}, {"Etp", "top"},
55 {"Esy", "sync"}, {"Ewl", "wake_lock_in"},
56 {"Ejb", "job"}, {"Eur", "user"},
57 {"Euf", "userfg"}, {"Ecn", "conn"},
58 {"Eac", "active"}, {"Epi", "pkginst"},
59 {"Epu", "pkgunin"}, {"Eal", "alarm"},
60 {"Est", "stats"}, {"Eai", "pkginactive"},
61 {"Eaa", "pkgactive"}, {"Etw", "tmpwhitelist"},
62 {"Esw", "screenwake"}, {"Ewa", "wakeupap"},
63 {"Elw", "longwake"}, {"Eec", "est_capacity"},
64 }));
65 auto result = checkin_event_name_to_enum.ref().find(short_name);
66 if (result == checkin_event_name_to_enum.ref().end()) {
67 return base::ErrStatus("Failed to find historty event name mapping");
68 }
69 return result->second;
70 }
71
72 struct StateStringTranslationInfo {
73 const std::string long_name;
74 const std::unordered_map<base::StringView, int64_t> short_string_to_value;
75 };
76
GetStateAndValueFromShortName(base::StringView state_short_name,base::StringView value_short_name,int64_t * value_out)77 base::StatusOr<std::string> GetStateAndValueFromShortName(
78 base::StringView state_short_name,
79 base::StringView value_short_name,
80 int64_t* value_out) {
81 // Mappings of all the state checkin names from BatteryStats.java and their
82 // corresponding value mappings
83 static const base::NoDestructor<
84 std::unordered_map<base::StringView, StateStringTranslationInfo> >
85 checkin_state_name_to_enum_and_values(
86 std::unordered_map<base::StringView, StateStringTranslationInfo>(
87 {{"r", {"running", {}}},
88 {"w", {"wake_lock", {}}},
89 {"s", {"sensor", {}}},
90 {"g", {"gps", {}}},
91 {"Wl", {"wifi_full_lock", {}}},
92 {"Ws", {"wifi_scan", {}}},
93 {"Wm", {"wifi_multicast", {}}},
94 {"Wr", {"wifi_radio", {}}},
95 {"Pr", {"mobile_radio", {}}},
96 {"Psc", {"phone_scanning", {}}},
97 {"a", {"audio", {}}},
98 {"S", {"screen", {}}},
99 {"BP", {"plugged", {}}},
100 {"Sd", {"screen_doze", {}}},
101 {"Pcn",
102 {"data_conn",
103 {
104 {"oos", 0}, {"gprs", 1}, {"edge", 2},
105 {"umts", 3}, {"cdma", 4}, {"evdo_0", 5},
106 {"evdo_A", 6}, {"1xrtt", 7}, {"hsdpa", 8},
107 {"hsupa", 9}, {"hspa", 10}, {"iden", 11},
108 {"evdo_b", 12}, {"lte", 13}, {"ehrpd", 14},
109 {"hspap", 15}, {"gsm", 16}, {"td_scdma", 17},
110 {"iwlan", 18}, {"lte_ca", 19}, {"nr", 20},
111 {"emngcy", 21}, {"other", 22},
112 }}},
113 {"Pst",
114 {"phone_state",
115 {
116 {"in", 0},
117 {"out", 1},
118 {"em", 2},
119 {"off", 3},
120 }}},
121 {"Pss", {"phone_signal_strength", {}}},
122 {"Sb", {"brightness", {}}},
123 {"ps", {"power_save", {}}},
124 {"v", {"video", {}}},
125 {"Ww", {"wifi_running", {}}},
126 {"W", {"wifi", {}}},
127 {"fl", {"flashlight", {}}},
128 {"di",
129 {"device_idle",
130 {
131 {"off", 0},
132 {"light", 1},
133 {"full", 2},
134 {"???", 3},
135 }}},
136 {"ch", {"charging", {}}},
137 {"Ud", {"usb_data", {}}},
138 {"Pcl", {"phone_in_call", {}}},
139 {"b", {"bluetooth", {}}},
140 {"Wss", {"wifi_signal_strength", {}}},
141 {"Wsp",
142 {"wifi_suppl",
143 {
144 {"inv", 0},
145 {"dsc", 1},
146 {"dis", 2},
147 {"inact", 3},
148 {"scan", 4},
149 {"auth", 5},
150 {"ascing", 6},
151 {"asced", 7},
152 {"4-way", 8},
153 {"group", 9},
154 {"compl", 10},
155 {"dorm", 11},
156 {"uninit", 12},
157 }}},
158 {"ca", {"camera", {}}},
159 {"bles", {"ble_scan", {}}},
160 {"Chtp", {"cellular_high_tx_power", {}}},
161 {"Gss",
162 {"gps_signal_quality",
163 {
164 {"poor", 0},
165 {"good", 1},
166 {"none", 2},
167 }}},
168 {"nrs", {"nr_state", {}}}}));
169
170 auto result =
171 checkin_state_name_to_enum_and_values.ref().find(state_short_name);
172 if (result == checkin_state_name_to_enum_and_values.ref().end()) {
173 return base::ErrStatus("Failed to find state short to long name mapping");
174 }
175
176 StateStringTranslationInfo translation_info = result->second;
177
178 // If caller isn't requesting a value, just return the item type.
179 if (value_out == nullptr) {
180 return translation_info.long_name;
181 }
182
183 // If the value short name is already a number, just do a direct conversion
184 std::optional<int64_t> possible_int_value =
185 base::StringViewToInt64(value_short_name);
186 if (possible_int_value.has_value()) {
187 *value_out = possible_int_value.value();
188 return translation_info.long_name;
189 }
190 // value has a non-numerical string, so translate it
191 auto short_name_mapping =
192 translation_info.short_string_to_value.find(value_short_name);
193 if (short_name_mapping == translation_info.short_string_to_value.end()) {
194 return base::ErrStatus("Failed to translate value for state");
195 }
196 *value_out = short_name_mapping->second;
197 return translation_info.long_name;
198 }
199
StringToStatusOrInt64(base::StringView str)200 base::StatusOr<int64_t> StringToStatusOrInt64(base::StringView str) {
201 std::optional<int64_t> possible_result = base::StringViewToInt64(str);
202 if (!possible_result.has_value()) {
203 return base::ErrStatus("Failed to convert string to int64_t");
204 }
205 return possible_result.value();
206 }
207
208 } // namespace
209
210 AndroidDumpstateEventParserImpl::~AndroidDumpstateEventParserImpl() = default;
211
ParseAndroidDumpstateEvent(int64_t ts,AndroidDumpstateEvent event)212 void AndroidDumpstateEventParserImpl::ParseAndroidDumpstateEvent(
213 int64_t ts,
214 AndroidDumpstateEvent event) {
215 switch (event.type) {
216 case AndroidDumpstateEvent::EventType::kBatteryStatsHistoryEvent:
217 ProcessBatteryStatsHistoryItem(ts, event.raw_event);
218 return;
219 case AndroidDumpstateEvent::EventType::kNull:
220 return;
221 }
222 }
223
ProcessBatteryStatsHistoryItem(int64_t ts,const std::string & raw_event)224 base::Status AndroidDumpstateEventParserImpl::ProcessBatteryStatsHistoryItem(
225 int64_t ts,
226 const std::string& raw_event) {
227 base::StringViewSplitter splitter(base::StringView(raw_event), '=');
228 TokenizedBatteryStatsHistoryItem item;
229 item.ts = ts;
230 item.key = splitter.NextToken();
231 item.value = splitter.NextToken();
232 item.prefix = "";
233 if (item.key.size() > 0 && (item.key.at(0) == '+' || item.key.at(0) == '-')) {
234 item.prefix = item.key.substr(0, 1);
235 item.key = item.key.substr(1);
236 }
237
238 // Attempt to parse the input with each sub-parser until we find one that can
239 // Successfully parse the event.
240 bool parsed = false;
241
242 // Attempt to parse the the event as a battery stats Event ("E" prefix)
243 ASSIGN_OR_RETURN(parsed, ProcessBatteryStatsHistoryEvent(item));
244 if (parsed) {
245 return base::OkStatus();
246 }
247
248 // Attempt to parse the the event as a battery stats state
249 ASSIGN_OR_RETURN(parsed, ProcessBatteryStatsHistoryState(item));
250 if (parsed) {
251 return base::OkStatus();
252 }
253
254 // Attempt to parse the event as a battery stats battery counter
255 ASSIGN_OR_RETURN(parsed, ProcessBatteryStatsHistoryBatteryCounter(item));
256 if (parsed) {
257 return base::OkStatus();
258 }
259
260 // Attempt to parse any wakelock events (eg. +w=123)
261 ASSIGN_OR_RETURN(parsed, ProcessBatteryStatsHistoryWakeLocks(item));
262 if (parsed) {
263 return base::OkStatus();
264 }
265
266 return base::ErrStatus("Unhandled battery stats event");
267 }
268
269 base::StatusOr<bool>
ProcessBatteryStatsHistoryEvent(const TokenizedBatteryStatsHistoryItem & item)270 AndroidDumpstateEventParserImpl::ProcessBatteryStatsHistoryEvent(
271 const TokenizedBatteryStatsHistoryItem& item) {
272 if (!item.key.StartsWith("E")) {
273 return false;
274 }
275 AndroidBatteryStatsHistoryStringTracker* history_string_tracker =
276 AndroidBatteryStatsHistoryStringTracker::GetOrCreate(context_);
277 // Process a history event
278 ASSIGN_OR_RETURN(std::string item_name, GetEventFromShortName(item.key));
279 ASSIGN_OR_RETURN(int64_t hsp_index, StringToStatusOrInt64(item.value));
280 const int32_t uid = history_string_tracker->GetUid(hsp_index);
281 const std::string& event_str = history_string_tracker->GetString(hsp_index);
282
283 static constexpr auto kBlueprint = tracks::SliceBlueprint(
284 "battery_stats",
285 tracks::Dimensions(tracks::StringDimensionBlueprint("bstats_item_name")),
286 tracks::FnNameBlueprint([](base::StringView item) {
287 return base::StackString<1024>("battery_stats.%.*s", int(item.size()),
288 item.data());
289 }));
290
291 base::StackString<255> slice_name("%s%s=%d:\"%s\"",
292 item.prefix.ToStdString().c_str(),
293 item_name.c_str(), uid, event_str.c_str());
294 StringId name_id = context_->storage->InternString(slice_name.c_str());
295 TrackId track_id = context_->track_tracker->InternTrack(
296 kBlueprint, tracks::Dimensions(base::StringView(item_name)));
297 context_->slice_tracker->Scoped(item.ts, track_id, kNullStringId, name_id, 0);
298 return true;
299 }
300
301 base::StatusOr<bool>
ProcessBatteryStatsHistoryState(const TokenizedBatteryStatsHistoryItem & item)302 AndroidDumpstateEventParserImpl::ProcessBatteryStatsHistoryState(
303 const TokenizedBatteryStatsHistoryItem& item) {
304 // Process a history state of the form "+state" or "-state"
305 if (!item.prefix.empty() && item.value.empty()) {
306 // To match behavior of the battery stats atrace implementation, avoid
307 // including Wakelock events in the trace as counters.
308 if (item.key == "w") {
309 return true;
310 }
311
312 ASSIGN_OR_RETURN(std::string item_name,
313 GetStateAndValueFromShortName(item.key, "", nullptr));
314 TrackId track = context_->track_tracker->InternTrack(
315 tracks::kAndroidBatteryStatsBlueprint,
316 tracks::Dimensions(
317 base::StringView(std::string("battery_stats.").append(item_name))));
318 context_->event_tracker->PushCounter(
319 item.ts, (item.prefix == "+") ? 1.0 : 0.0, track);
320 // Also add a screen events to the screen state track
321 if (item_name == "screen") {
322 track = context_->track_tracker->InternTrack(
323 tracks::kAndroidScreenStateBlueprint);
324 // battery_stats.screen event is 0 for off and 1 for on, but the
325 // ScreenState track uses the convention 1 for off and 2 for on, so add
326 // 1 to the current counter value.
327 context_->event_tracker->PushCounter(
328 item.ts, static_cast<double>((item.prefix == "+") ? 2.0 : 1.0),
329 track);
330 }
331
332 return true;
333 } else if (item.prefix.empty() && !item.value.empty()) {
334 int64_t counter_value;
335 base::StatusOr<std::string> possible_history_state_item =
336 GetStateAndValueFromShortName(item.key, item.value, &counter_value);
337 if (possible_history_state_item.ok()) {
338 std::string item_name = possible_history_state_item.value();
339 TrackId counter_track = context_->track_tracker->InternTrack(
340 tracks::kAndroidBatteryStatsBlueprint,
341 tracks::Dimensions(base::StringView(
342 std::string("battery_stats.").append(item_name))));
343 context_->event_tracker->PushCounter(
344 item.ts, static_cast<double>(counter_value), counter_track);
345 return true;
346 } else {
347 return false;
348 }
349 } else {
350 return false;
351 }
352 }
353
354 base::StatusOr<bool>
ProcessBatteryStatsHistoryBatteryCounter(const TokenizedBatteryStatsHistoryItem & item)355 AndroidDumpstateEventParserImpl::ProcessBatteryStatsHistoryBatteryCounter(
356 const TokenizedBatteryStatsHistoryItem& item) {
357 if (!item.prefix.empty() || item.value.empty() || !item.key.StartsWith("B")) {
358 return false;
359 }
360 // AndroidProbesParser will use the empty string for the battery name if no
361 // battery name is associated with the data, which is common on most pixel
362 // phones. Adopt the same convention here. Battery stats does not provide
363 // a battery name in the checking format, so we'll always have an unknown
364 // battery.
365 const base::StringView kUnknownBatteryName = "";
366
367 // process history state of form "state=12345" or "state=abcde"
368 TrackId counter_track;
369 int64_t counter_value;
370 if (item.key == "Bl") {
371 counter_track = context_->track_tracker->InternTrack(
372 tracks::kBatteryCounterBlueprint,
373 tracks::Dimensions(kUnknownBatteryName, "capacity_pct"));
374 ASSIGN_OR_RETURN(counter_value, StringToStatusOrInt64(item.value));
375 } else if (item.key == "Bcc") {
376 counter_track = context_->track_tracker->InternTrack(
377 tracks::kBatteryCounterBlueprint,
378 tracks::Dimensions(kUnknownBatteryName, "charge_uah"));
379 ASSIGN_OR_RETURN(counter_value, StringToStatusOrInt64(item.value));
380 // battery stats gives us charge in milli-amp-hours, but the track
381 // expects the value to be in micro-amp-hours
382 counter_value *= 1000;
383 } else if (item.key == "Bv") {
384 counter_track = context_->track_tracker->InternTrack(
385 tracks::kBatteryCounterBlueprint,
386 tracks::Dimensions(kUnknownBatteryName, "voltage_uv"));
387 ASSIGN_OR_RETURN(counter_value, StringToStatusOrInt64(item.value));
388 // battery stats gives us charge in milli-volts, but the track
389 // expects the value to be in micro-volts
390 counter_value *= 1000;
391 } else if (item.key == "Bs") {
392 static constexpr auto kBatteryStatusBlueprint = tracks::CounterBlueprint(
393 "battery_status", tracks::UnknownUnitBlueprint(),
394 tracks::DimensionBlueprints(),
395 tracks::StaticNameBlueprint("BatteryStatus"));
396 counter_track =
397 context_->track_tracker->InternTrack(kBatteryStatusBlueprint);
398 switch (item.value.at(0)) {
399 case '?':
400 counter_value = 1; // BatteryManager.BATTERY_STATUS_UNKNOWN
401 break;
402 case 'c':
403 counter_value = 2; // BatteryManager.BATTERY_STATUS_CHARGING
404 break;
405 case 'd':
406 counter_value = 3; // BatteryManager.BATTERY_STATUS_DISCHARGING
407 break;
408 case 'n':
409 counter_value = 4; // BatteryManager.BATTERY_STATUS_NOT_CHARGING
410 break;
411 case 'f':
412 counter_value = 5; // BatteryManager.BATTERY_STATUS_FULL
413 break;
414 default:
415 PERFETTO_ELOG("unknown battery status: %c", item.value.at(0));
416 counter_value = 0; // not a valid enum
417 }
418 } else if (item.key == "Bp") {
419 static constexpr auto kPluggedStatusBluePrint = tracks::CounterBlueprint(
420 "battery_plugged_status", tracks::UnknownUnitBlueprint(),
421 tracks::DimensionBlueprints(), tracks::StaticNameBlueprint("PlugType"));
422 counter_track =
423 context_->track_tracker->InternTrack(kPluggedStatusBluePrint);
424 switch (item.value.at(0)) {
425 case 'n':
426 counter_value = 0; // BatteryManager.BATTERY_PLUGGED_NONE
427 break;
428 case 'a':
429 counter_value = 1; // BatteryManager.BATTERY_PLUGGED_AC
430 break;
431 case 'u':
432 counter_value = 2; // BatteryManager.BATTERY_PLUGGED_USB
433 break;
434 case 'w':
435 counter_value = 4; // BatteryManager.BATTERY_PLUGGED_WIRELESS
436 break;
437 default:
438 counter_value = 0; // BatteryManager.BATTERY_PLUGGED_NONE
439 }
440 } else {
441 return false;
442 }
443
444 context_->event_tracker->PushCounter(
445 item.ts, static_cast<double>(counter_value), counter_track);
446 return true;
447 }
448
449 base::StatusOr<bool>
ProcessBatteryStatsHistoryWakeLocks(const TokenizedBatteryStatsHistoryItem & item)450 AndroidDumpstateEventParserImpl::ProcessBatteryStatsHistoryWakeLocks(
451 const TokenizedBatteryStatsHistoryItem& item) {
452 if (item.prefix.empty() || item.key != "w" || item.value.empty()) {
453 return false;
454 }
455 AndroidBatteryStatsHistoryStringTracker* history_string_tracker =
456 AndroidBatteryStatsHistoryStringTracker::GetOrCreate(context_);
457 // We can only support wakeup parsing on battery stats ver 36+ since on
458 // older versions the "-w" event does not have a history string
459 // associated with it. This history sting is needed, since we use the
460 // HSP index as the "cookie" to disambiguate overlapping wakelocks.
461 if (history_string_tracker->battery_stats_version() < 36) {
462 return base::ErrStatus("Wakelocks unsupported on batterystats ver < 36");
463 }
464
465 static constexpr auto kBlueprint = TrackCompressor::SliceBlueprint(
466 "dumpstate_wakelocks", tracks::DimensionBlueprints(),
467 tracks::StaticNameBlueprint("WakeLocks"));
468
469 ASSIGN_OR_RETURN(int64_t hsp_index, StringToStatusOrInt64(item.value));
470 if (item.prefix == "+") {
471 StringId name_id = context_->storage->InternString(
472 history_string_tracker->GetString(hsp_index));
473 TrackId id = context_->track_compressor->InternBegin(
474 kBlueprint, tracks::Dimensions(), static_cast<int64_t>(hsp_index));
475 context_->slice_tracker->Begin(item.ts, id, kNullStringId, name_id);
476 } else {
477 TrackId id = context_->track_compressor->InternEnd(
478 kBlueprint, tracks::Dimensions(), static_cast<int64_t>(hsp_index));
479 context_->slice_tracker->End(item.ts, id);
480 }
481 return true;
482 }
483
484 } // namespace perfetto::trace_processor
485