blob: ac16506c3dd3095dc04c5da97aa98d0e480f86e8 [file] [log] [blame]
/*
* Copyright (C) 2018 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/systrace/systrace_parser.h"
#include <optional>
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/importers/common/async_track_set_tracker.h"
#include "src/trace_processor/importers/common/event_tracker.h"
#include "src/trace_processor/importers/common/process_tracker.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
namespace perfetto {
namespace trace_processor {
SystraceParser::SystraceParser(TraceProcessorContext* ctx)
: context_(ctx),
lmk_id_(ctx->storage->InternString("mem.lmk")),
oom_score_adj_id_(ctx->storage->InternString("oom_score_adj")),
screen_state_id_(ctx->storage->InternString("ScreenState")),
cookie_id_(ctx->storage->InternString("cookie")) {}
SystraceParser::~SystraceParser() = default;
void SystraceParser::ParsePrintEvent(int64_t ts,
uint32_t pid,
base::StringView event) {
systrace_utils::SystraceTracePoint point{};
switch (ParseSystraceTracePoint(event, &point)) {
case systrace_utils::SystraceParseResult::kSuccess:
ParseSystracePoint(ts, pid, point);
break;
case systrace_utils::SystraceParseResult::kFailure:
context_->storage->IncrementStats(stats::systrace_parse_failure);
break;
case systrace_utils::SystraceParseResult::kUnsupported:
// Silently ignore unsupported results.
break;
}
}
void SystraceParser::ParseZeroEvent(int64_t ts,
uint32_t pid,
int32_t flag,
base::StringView name,
uint32_t tgid,
int64_t value) {
systrace_utils::SystraceTracePoint point{};
point.name = name;
point.int_value = value;
point.tgid = tgid;
// The value of these constants can be found in the msm-google kernel.
constexpr int32_t kSystraceEventBegin = 1 << 0;
constexpr int32_t kSystraceEventEnd = 1 << 1;
constexpr int32_t kSystraceEventInt64 = 1 << 2;
if ((flag & kSystraceEventBegin) != 0) {
point.phase = 'B';
} else if ((flag & kSystraceEventEnd) != 0) {
point.phase = 'E';
} else if ((flag & kSystraceEventInt64) != 0) {
point.phase = 'C';
} else {
context_->storage->IncrementStats(stats::systrace_parse_failure);
return;
}
// Note: for counter (C) events, we cannot assume that pid is within tgid.
// See ParseKernelTracingMarkWrite for rationale.
ParseSystracePoint(ts, pid, point);
}
void SystraceParser::ParseKernelTracingMarkWrite(int64_t ts,
uint32_t pid,
char trace_type,
bool trace_begin,
base::StringView trace_name,
uint32_t tgid,
int64_t value) {
systrace_utils::SystraceTracePoint point{};
point.name = trace_name;
point.int_value = value;
point.tgid = tgid;
// Some versions of this trace point fill trace_type with one of (B/E/C),
// others use the trace_begin boolean and only support begin/end events:
if (trace_type == 0) {
point.phase = trace_begin ? 'B' : 'E';
} else if (trace_type == 'B' || trace_type == 'E' || trace_type == 'C') {
point.phase = trace_type;
} else {
context_->storage->IncrementStats(stats::systrace_parse_failure);
return;
}
// Note: |pid| is the thread id of the emitting thread, |tgid| is taken from
// the event payload. The begin/end event kernel atrace macros seem well
// behaved (i.e. they always put current->tgid into the payload). However the
// counter events have cases where a placeholder/unrelated pid is used (e.g.
// 0, 1, or a specific kthread, see g2d_frame_* counters for an example).
//
// Further, the counter events expect to be grouped at the process (tgid)
// level (multiple distinct pids will be emitting values for the same
// logical counter).
//
// Therefore we must never assume that pid is within tgid for counter events,
// but still trust that the tgid value is for a valid process (which will
// usually fall onto swapper/init or some kthread) to have a
// process_counter_track for the counter values.
ParseSystracePoint(ts, pid, point);
}
// TODO(rsavitski): try to remove most special casing of tgid 0, as it is valid
// for kernel systrace points (due to systrace from interrupts).
void SystraceParser::ParseSystracePoint(
int64_t ts,
uint32_t pid,
systrace_utils::SystraceTracePoint point) {
auto get_utid = [pid, &point, this]() {
if (point.tgid == 0)
return context_->process_tracker->GetOrCreateThread(pid);
return context_->process_tracker->UpdateThread(pid, point.tgid);
};
switch (point.phase) {
case 'B': {
StringId name_id = context_->storage->InternString(point.name);
UniqueTid utid = get_utid();
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
context_->slice_tracker->Begin(ts, track_id, kNullStringId /* cat */,
name_id);
PostProcessSpecialSliceBegin(ts, point.name);
break;
}
case 'E': {
// |point.tgid| can be 0 in older android versions where the end event
// would not contain the value.
UniqueTid utid;
if (point.tgid == 0) {
// If we haven't seen this thread before, there can't have been a Begin
// event for it so just ignore the event.
auto opt_utid = context_->process_tracker->GetThreadOrNull(pid);
if (!opt_utid)
break;
utid = *opt_utid;
} else {
utid = context_->process_tracker->UpdateThread(pid, point.tgid);
}
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
context_->slice_tracker->End(ts, track_id);
break;
}
case 'S':
case 'F': {
StringId name_id = context_->storage->InternString(point.name);
int64_t cookie = point.int_value;
UniquePid upid =
context_->process_tracker->GetOrCreateProcess(point.tgid);
auto track_set_id =
context_->async_track_set_tracker
->InternAndroidLegacyUnnestableTrackSet(upid, name_id);
if (point.phase == 'S') {
// Historically, async slices on Android did not support nesting async
// slices (i.e. you could not have a stack of async slices). If clients
// were implemented correctly, we would simply be able to use the normal
// Begin method and we could rely on the traced code to never emit two
// 'S' events back to back on the same track.
// However, there exists buggy code in Android (in Wakelock class of
// PowerManager) which emits an arbitrary number of 'S' events and
// expects only the first one to be tracked. Moreover, this issue is
// compounded by an unfortunate implementation of async slices in
// Catapult (the legacy trace viewer) which simply tracks the details of
// the *most recent* emitted 'S' event which leads even more inaccurate
// behaviour. To support these quirks, we have the special 'unnestable'
// slice concept which implements workarounds for these very specific
// issues. No other code should ever use |BeginLegacyUnnestable|.
tables::SliceTable::Row row;
row.ts = ts;
row.track_id =
context_->async_track_set_tracker->Begin(track_set_id, cookie);
row.name = name_id;
context_->slice_tracker->BeginLegacyUnnestable(
row, [this, cookie](ArgsTracker::BoundInserter* inserter) {
inserter->AddArg(cookie_id_, Variadic::Integer(cookie));
});
} else {
TrackId track_id =
context_->async_track_set_tracker->End(track_set_id, cookie);
context_->slice_tracker->End(ts, track_id);
}
break;
}
case 'I': {
StringId name_id = context_->storage->InternString(point.name);
UniqueTid utid = get_utid();
TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
break;
}
case 'N':
case 'G':
case 'H': {
StringId name_id = context_->storage->InternString(point.name);
StringId track_name_id = context_->storage->InternString(point.str_value);
UniquePid upid =
context_->process_tracker->GetOrCreateProcess(point.tgid);
// Promote DeviceStateChanged to its own top level track.
AsyncTrackSetTracker::TrackSetId track_set_id;
if (point.str_value == "DeviceStateChanged") {
track_set_id = context_->async_track_set_tracker->InternGlobalTrackSet(
track_name_id);
} else {
track_set_id = context_->async_track_set_tracker->InternProcessTrackSet(
upid, track_name_id);
}
if (point.phase == 'N') {
TrackId track_id =
context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id,
0);
} else if (point.phase == 'G') {
TrackId track_id = context_->async_track_set_tracker->Begin(
track_set_id, point.int_value);
context_->slice_tracker->Begin(ts, track_id, kNullStringId, name_id);
} else if (point.phase == 'H') {
TrackId track_id = context_->async_track_set_tracker->End(
track_set_id, point.int_value);
context_->slice_tracker->End(ts, track_id);
}
break;
}
// Warning: counter event handling must never assume that the |pid| thread
// is within the |tgid| process due to kernel systrace quirks. If you need
// to change this, update ParseKernelTracingMarkWrite and ParseZeroEvent to
// pretend that pid is the same as tgid for C events.
case 'C': {
// LMK events from userspace are hacked as counter events with the "value"
// of the counter representing the pid of the killed process which is
// reset to 0 once the kill is complete.
// Homogenise this with kernel LMK events as an instant event, ignoring
// the resets to 0.
if (point.name == "kill_one_process") {
auto killed_pid = static_cast<uint32_t>(point.int_value);
if (killed_pid != 0) {
UniquePid killed_upid =
context_->process_tracker->GetOrCreateProcess(killed_pid);
TrackId track =
context_->track_tracker->InternProcessTrack(killed_upid);
context_->slice_tracker->Scoped(ts, track, kNullStringId, lmk_id_, 0);
}
// TODO(lalitm): we should not add LMK events to the counters table
// once the UI has support for displaying instants.
} else if (point.name == "ScreenState") {
// Promote ScreenState to its own top level counter.
TrackId track =
context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
context_->event_tracker->PushCounter(
ts, static_cast<double>(point.int_value), track);
return;
} else if (point.name.StartsWith("battery_stats.")) {
// Promote battery_stats conters to global tracks.
StringId name_id = context_->storage->InternString(point.name);
TrackId track =
context_->track_tracker->InternGlobalCounterTrack(name_id);
context_->event_tracker->PushCounter(
ts, static_cast<double>(point.int_value), track);
return;
}
// This is per upid on purpose. Some long-standing counters are pushed
// from arbitrary threads but expect to be per process (b/123560328).
// This affects both userspace and kernel counters.
StringId name_id = context_->storage->InternString(point.name);
UniquePid upid =
context_->process_tracker->GetOrCreateProcess(point.tgid);
TrackId track_id =
context_->track_tracker->InternProcessCounterTrack(name_id, upid);
context_->event_tracker->PushCounter(
ts, static_cast<double>(point.int_value), track_id);
}
}
}
void SystraceParser::PostProcessSpecialSliceBegin(int64_t ts,
base::StringView name) {
if (name.StartsWith("lmk,")) {
// LMK events introduced with http://aosp/1782391 are treated specially
// to parse the killed process oom_score_adj out of them.
// Format is 'lmk,pid,reason,oom adj,...'
std::vector<std::string> toks = base::SplitString(name.ToStdString(), ",");
if (toks.size() < 4) {
return;
}
auto killed_pid = base::StringToUInt32(toks[1]);
auto oom_score_adj = base::StringToInt32(toks[3]);
if (!killed_pid || !oom_score_adj) {
return;
}
UniquePid killed_upid =
context_->process_tracker->GetOrCreateProcess(*killed_pid);
// Add the oom score entry
TrackId counter_track = context_->track_tracker->InternProcessCounterTrack(
oom_score_adj_id_, killed_upid);
context_->event_tracker->PushCounter(ts, *oom_score_adj, counter_track);
// Add mem.lmk instant event for consistency with other methods.
TrackId track = context_->track_tracker->InternProcessTrack(killed_upid);
context_->slice_tracker->Scoped(ts, track, kNullStringId, lmk_id_, 0);
}
}
} // namespace trace_processor
} // namespace perfetto