1 /*
2 * Copyright (C) 2018 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/systrace/systrace_parser.h"
18
19 #include <optional>
20
21 #include "perfetto/ext/base/string_utils.h"
22 #include "src/trace_processor/importers/common/async_track_set_tracker.h"
23 #include "src/trace_processor/importers/common/event_tracker.h"
24 #include "src/trace_processor/importers/common/process_tracker.h"
25 #include "src/trace_processor/importers/common/slice_tracker.h"
26 #include "src/trace_processor/importers/common/track_tracker.h"
27 #include "src/trace_processor/storage/trace_storage.h"
28
29 namespace perfetto {
30 namespace trace_processor {
31
SystraceParser(TraceProcessorContext * ctx)32 SystraceParser::SystraceParser(TraceProcessorContext* ctx)
33 : context_(ctx),
34 lmk_id_(ctx->storage->InternString("mem.lmk")),
35 oom_score_adj_id_(ctx->storage->InternString("oom_score_adj")),
36 screen_state_id_(ctx->storage->InternString("ScreenState")),
37 cookie_id_(ctx->storage->InternString("cookie")) {}
38
39 SystraceParser::~SystraceParser() = default;
40
ParsePrintEvent(int64_t ts,uint32_t pid,base::StringView event)41 void SystraceParser::ParsePrintEvent(int64_t ts,
42 uint32_t pid,
43 base::StringView event) {
44 systrace_utils::SystraceTracePoint point{};
45 switch (ParseSystraceTracePoint(event, &point)) {
46 case systrace_utils::SystraceParseResult::kSuccess:
47 ParseSystracePoint(ts, pid, point);
48 break;
49 case systrace_utils::SystraceParseResult::kFailure:
50 context_->storage->IncrementStats(stats::systrace_parse_failure);
51 break;
52 case systrace_utils::SystraceParseResult::kUnsupported:
53 // Silently ignore unsupported results.
54 break;
55 }
56 }
57
ParseZeroEvent(int64_t ts,uint32_t pid,int32_t flag,base::StringView name,uint32_t tgid,int64_t value)58 void SystraceParser::ParseZeroEvent(int64_t ts,
59 uint32_t pid,
60 int32_t flag,
61 base::StringView name,
62 uint32_t tgid,
63 int64_t value) {
64 systrace_utils::SystraceTracePoint point{};
65 point.name = name;
66 point.int_value = value;
67 point.tgid = tgid;
68
69 // The value of these constants can be found in the msm-google kernel.
70 constexpr int32_t kSystraceEventBegin = 1 << 0;
71 constexpr int32_t kSystraceEventEnd = 1 << 1;
72 constexpr int32_t kSystraceEventInt64 = 1 << 2;
73
74 if ((flag & kSystraceEventBegin) != 0) {
75 point.phase = 'B';
76 } else if ((flag & kSystraceEventEnd) != 0) {
77 point.phase = 'E';
78 } else if ((flag & kSystraceEventInt64) != 0) {
79 point.phase = 'C';
80 } else {
81 context_->storage->IncrementStats(stats::systrace_parse_failure);
82 return;
83 }
84 // Note: for counter (C) events, we cannot assume that pid is within tgid.
85 // See ParseKernelTracingMarkWrite for rationale.
86 ParseSystracePoint(ts, pid, point);
87 }
88
ParseKernelTracingMarkWrite(int64_t ts,uint32_t pid,char trace_type,bool trace_begin,base::StringView trace_name,uint32_t tgid,int64_t value)89 void SystraceParser::ParseKernelTracingMarkWrite(int64_t ts,
90 uint32_t pid,
91 char trace_type,
92 bool trace_begin,
93 base::StringView trace_name,
94 uint32_t tgid,
95 int64_t value) {
96 systrace_utils::SystraceTracePoint point{};
97 point.name = trace_name;
98 point.int_value = value;
99 point.tgid = tgid;
100
101 // Some versions of this trace point fill trace_type with one of (B/E/C/I),
102 // others use the trace_begin boolean and only support begin/end events:
103 if (trace_type == 0) {
104 point.phase = trace_begin ? 'B' : 'E';
105 } else if (trace_type == 'B' || trace_type == 'E' || trace_type == 'C' ||
106 trace_type == 'I') {
107 point.phase = trace_type;
108 } else {
109 context_->storage->IncrementStats(stats::systrace_parse_failure);
110 return;
111 }
112
113 // Note: |pid| is the thread id of the emitting thread, |tgid| is taken from
114 // the event payload. The begin/end event kernel atrace macros seem well
115 // behaved (i.e. they always put current->tgid into the payload). However the
116 // counter events have cases where a placeholder/unrelated pid is used (e.g.
117 // 0, 1, or a specific kthread, see g2d_frame_* counters for an example).
118 //
119 // Further, the counter events expect to be grouped at the process (tgid)
120 // level (multiple distinct pids will be emitting values for the same
121 // logical counter).
122 //
123 // Therefore we must never assume that pid is within tgid for counter events,
124 // but still trust that the tgid value is for a valid process (which will
125 // usually fall onto swapper/init or some kthread) to have a
126 // process_counter_track for the counter values.
127 ParseSystracePoint(ts, pid, point);
128 }
129
130 // TODO(rsavitski): try to remove most special casing of tgid 0, as it is valid
131 // for kernel systrace points (due to systrace from interrupts).
ParseSystracePoint(int64_t ts,uint32_t pid,systrace_utils::SystraceTracePoint point)132 void SystraceParser::ParseSystracePoint(
133 int64_t ts,
134 uint32_t pid,
135 systrace_utils::SystraceTracePoint point) {
136 auto get_utid = [pid, &point, this]() {
137 if (point.tgid == 0)
138 return context_->process_tracker->GetOrCreateThread(pid);
139 return context_->process_tracker->UpdateThread(pid, point.tgid);
140 };
141
142 switch (point.phase) {
143 case 'B': {
144 StringId name_id = context_->storage->InternString(point.name);
145 UniqueTid utid = get_utid();
146 TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
147 context_->slice_tracker->Begin(ts, track_id, kNullStringId /* cat */,
148 name_id);
149 PostProcessSpecialSliceBegin(ts, point.name);
150 break;
151 }
152
153 case 'E': {
154 // |point.tgid| can be 0 in older android versions where the end event
155 // would not contain the value.
156 UniqueTid utid;
157 if (point.tgid == 0) {
158 // If we haven't seen this thread before, there can't have been a Begin
159 // event for it so just ignore the event.
160 auto opt_utid = context_->process_tracker->GetThreadOrNull(pid);
161 if (!opt_utid)
162 break;
163 utid = *opt_utid;
164 } else {
165 utid = context_->process_tracker->UpdateThread(pid, point.tgid);
166 }
167 TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
168 context_->slice_tracker->End(ts, track_id);
169 break;
170 }
171
172 case 'S':
173 case 'F': {
174 StringId name_id = context_->storage->InternString(point.name);
175 int64_t cookie = point.int_value;
176 UniquePid upid =
177 context_->process_tracker->GetOrCreateProcess(point.tgid);
178
179 auto track_set_id =
180 context_->async_track_set_tracker
181 ->InternAndroidLegacyUnnestableTrackSet(upid, name_id);
182
183 if (point.phase == 'S') {
184 // Historically, async slices on Android did not support nesting async
185 // slices (i.e. you could not have a stack of async slices). If clients
186 // were implemented correctly, we would simply be able to use the normal
187 // Begin method and we could rely on the traced code to never emit two
188 // 'S' events back to back on the same track.
189 // However, there exists buggy code in Android (in Wakelock class of
190 // PowerManager) which emits an arbitrary number of 'S' events and
191 // expects only the first one to be tracked. Moreover, this issue is
192 // compounded by an unfortunate implementation of async slices in
193 // Catapult (the legacy trace viewer) which simply tracks the details of
194 // the *most recent* emitted 'S' event which leads even more inaccurate
195 // behaviour. To support these quirks, we have the special 'unnestable'
196 // slice concept which implements workarounds for these very specific
197 // issues. No other code should ever use |BeginLegacyUnnestable|.
198 tables::SliceTable::Row row;
199 row.ts = ts;
200 row.track_id =
201 context_->async_track_set_tracker->Begin(track_set_id, cookie);
202 row.name = name_id;
203 context_->slice_tracker->BeginLegacyUnnestable(
204 row, [this, cookie](ArgsTracker::BoundInserter* inserter) {
205 inserter->AddArg(cookie_id_, Variadic::Integer(cookie));
206 });
207 } else {
208 TrackId track_id =
209 context_->async_track_set_tracker->End(track_set_id, cookie);
210 context_->slice_tracker->End(ts, track_id);
211 }
212 break;
213 }
214
215 case 'I': {
216 StringId name_id = context_->storage->InternString(point.name);
217 UniqueTid utid = get_utid();
218 TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
219 context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id, 0);
220 break;
221 }
222
223 case 'N':
224 case 'G':
225 case 'H': {
226 StringId name_id = context_->storage->InternString(point.name);
227 StringId track_name_id = context_->storage->InternString(point.str_value);
228 UniquePid upid =
229 context_->process_tracker->GetOrCreateProcess(point.tgid);
230
231 // Promote DeviceStateChanged to its own top level track.
232 AsyncTrackSetTracker::TrackSetId track_set_id;
233 if (point.str_value == "DeviceStateChanged") {
234 track_set_id = context_->async_track_set_tracker->InternGlobalTrackSet(
235 track_name_id);
236 } else {
237 track_set_id = context_->async_track_set_tracker->InternProcessTrackSet(
238 upid, track_name_id);
239 }
240
241 if (point.phase == 'N') {
242 TrackId track_id =
243 context_->async_track_set_tracker->Scoped(track_set_id, ts, 0);
244 context_->slice_tracker->Scoped(ts, track_id, kNullStringId, name_id,
245 0);
246 } else if (point.phase == 'G') {
247 TrackId track_id = context_->async_track_set_tracker->Begin(
248 track_set_id, point.int_value);
249 context_->slice_tracker->Begin(ts, track_id, kNullStringId, name_id);
250 } else if (point.phase == 'H') {
251 TrackId track_id = context_->async_track_set_tracker->End(
252 track_set_id, point.int_value);
253 context_->slice_tracker->End(ts, track_id);
254 }
255 break;
256 }
257
258 // Warning: counter event handling must never assume that the |pid| thread
259 // is within the |tgid| process due to kernel systrace quirks. If you need
260 // to change this, update ParseKernelTracingMarkWrite and ParseZeroEvent to
261 // pretend that pid is the same as tgid for C events.
262 case 'C': {
263 // LMK events from userspace are hacked as counter events with the "value"
264 // of the counter representing the pid of the killed process which is
265 // reset to 0 once the kill is complete.
266 // Homogenise this with kernel LMK events as an instant event, ignoring
267 // the resets to 0.
268 if (point.name == "kill_one_process") {
269 auto killed_pid = static_cast<uint32_t>(point.int_value);
270 if (killed_pid != 0) {
271 UniquePid killed_upid =
272 context_->process_tracker->GetOrCreateProcess(killed_pid);
273 TrackId track =
274 context_->track_tracker->InternProcessTrack(killed_upid);
275 context_->slice_tracker->Scoped(ts, track, kNullStringId, lmk_id_, 0);
276 }
277 // TODO(lalitm): we should not add LMK events to the counters table
278 // once the UI has support for displaying instants.
279 } else if (point.name == "ScreenState") {
280 // Promote ScreenState to its own top level counter.
281 TrackId track = context_->track_tracker->InternGlobalCounterTrack(
282 TrackTracker::Group::kDeviceState, screen_state_id_);
283 context_->event_tracker->PushCounter(
284 ts, static_cast<double>(point.int_value), track);
285 return;
286 } else if (point.name.StartsWith("battery_stats.")) {
287 // Promote battery_stats conters to global tracks.
288 StringId name_id = context_->storage->InternString(point.name);
289 TrackId track = context_->track_tracker->InternGlobalCounterTrack(
290 TrackTracker::Group::kPower, name_id);
291 context_->event_tracker->PushCounter(
292 ts, static_cast<double>(point.int_value), track);
293 return;
294 }
295
296 // This is per upid on purpose. Some long-standing counters are pushed
297 // from arbitrary threads but expect to be per process (b/123560328).
298 // This affects both userspace and kernel counters.
299 StringId name_id = context_->storage->InternString(point.name);
300 UniquePid upid =
301 context_->process_tracker->GetOrCreateProcess(point.tgid);
302 TrackId track_id =
303 context_->track_tracker->InternProcessCounterTrack(name_id, upid);
304 context_->event_tracker->PushCounter(
305 ts, static_cast<double>(point.int_value), track_id);
306 }
307 }
308 }
309
PostProcessSpecialSliceBegin(int64_t ts,base::StringView name)310 void SystraceParser::PostProcessSpecialSliceBegin(int64_t ts,
311 base::StringView name) {
312 if (name.StartsWith("lmk,")) {
313 // LMK events introduced with http://aosp/1782391 are treated specially
314 // to parse the killed process oom_score_adj out of them.
315 // Format is 'lmk,pid,reason,oom adj,...'
316 std::vector<std::string> toks = base::SplitString(name.ToStdString(), ",");
317 if (toks.size() < 4) {
318 return;
319 }
320 auto killed_pid = base::StringToUInt32(toks[1]);
321 auto oom_score_adj = base::StringToInt32(toks[3]);
322 if (!killed_pid || !oom_score_adj) {
323 return;
324 }
325
326 UniquePid killed_upid =
327 context_->process_tracker->GetOrCreateProcess(*killed_pid);
328 // Add the oom score entry
329 TrackId counter_track = context_->track_tracker->InternProcessCounterTrack(
330 oom_score_adj_id_, killed_upid);
331 context_->event_tracker->PushCounter(ts, *oom_score_adj, counter_track);
332
333 // Add mem.lmk instant event for consistency with other methods.
334 TrackId track = context_->track_tracker->InternProcessTrack(killed_upid);
335 context_->slice_tracker->Scoped(ts, track, kNullStringId, lmk_id_, 0);
336 }
337 }
338
339 } // namespace trace_processor
340 } // namespace perfetto
341