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/art_method/art_method_tokenizer.h"
18
19 #include <cstddef>
20 #include <cstdint>
21 #include <cstring>
22 #include <optional>
23 #include <string>
24 #include <string_view>
25 #include <utility>
26 #include <vector>
27
28 #include "perfetto/base/compiler.h"
29 #include "perfetto/base/logging.h"
30 #include "perfetto/base/status.h"
31 #include "perfetto/ext/base/status_or.h"
32 #include "perfetto/ext/base/string_splitter.h"
33 #include "perfetto/ext/base/string_utils.h"
34 #include "perfetto/ext/base/string_view.h"
35 #include "perfetto/ext/base/utils.h"
36 #include "perfetto/trace_processor/trace_blob_view.h"
37 #include "src/trace_processor/importers/art_method/art_method_event.h"
38 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
39 #include "src/trace_processor/sorter/trace_sorter.h"
40 #include "src/trace_processor/storage/trace_storage.h"
41 #include "src/trace_processor/types/trace_processor_context.h"
42 #include "src/trace_processor/util/status_macros.h"
43 #include "src/trace_processor/util/trace_blob_view_reader.h"
44
45 #include "protos/perfetto/common/builtin_clock.pbzero.h"
46
47 namespace perfetto::trace_processor::art_method {
48 namespace {
49
50 constexpr uint32_t kTraceMagic = 0x574f4c53; // 'SLOW'
51 constexpr uint32_t kStreamingVersionMask = 0xF0U;
52 constexpr uint32_t kTraceHeaderLength = 32;
53
54 constexpr uint32_t kMethodsCode = 1;
55 constexpr uint32_t kThreadsCode = 2;
56 constexpr uint32_t kSummaryCode = 3;
57
ToStringView(const TraceBlobView & tbv)58 std::string_view ToStringView(const TraceBlobView& tbv) {
59 return {reinterpret_cast<const char*>(tbv.data()), tbv.size()};
60 }
61
ConstructPathname(const std::string & class_name,const std::string & pathname)62 std::string ConstructPathname(const std::string& class_name,
63 const std::string& pathname) {
64 size_t index = class_name.rfind('/');
65 if (index != std::string::npos && base::EndsWith(pathname, ".java")) {
66 return class_name.substr(0, index + 1) + pathname;
67 }
68 return pathname;
69 }
70
ToLong(const TraceBlobView & tbv)71 uint64_t ToLong(const TraceBlobView& tbv) {
72 uint64_t x = 0;
73 memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
74 return x;
75 }
76
ToInt(const TraceBlobView & tbv)77 uint32_t ToInt(const TraceBlobView& tbv) {
78 uint32_t x = 0;
79 memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
80 return x;
81 }
82
ToShort(const TraceBlobView & tbv)83 uint16_t ToShort(const TraceBlobView& tbv) {
84 uint16_t x = 0;
85 memcpy(base::AssumeLittleEndian(&x), tbv.data(), tbv.size());
86 return x;
87 }
88
89 } // namespace
90
ArtMethodTokenizer(TraceProcessorContext * ctx)91 ArtMethodTokenizer::ArtMethodTokenizer(TraceProcessorContext* ctx)
92 : context_(ctx) {}
93 ArtMethodTokenizer::~ArtMethodTokenizer() = default;
94
Parse(TraceBlobView blob)95 base::Status ArtMethodTokenizer::Parse(TraceBlobView blob) {
96 reader_.PushBack(std::move(blob));
97 if (sub_parser_.index() == base::variant_index<SubParser, Detect>()) {
98 auto smagic = reader_.SliceOff(reader_.start_offset(), 4);
99 if (!smagic) {
100 return base::OkStatus();
101 }
102 uint32_t magic = ToInt(*smagic);
103 sub_parser_ = magic == kTraceMagic ? SubParser{Streaming{this}}
104 : SubParser{NonStreaming{this}};
105 context_->clock_tracker->SetTraceTimeClock(
106 protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
107 }
108 if (sub_parser_.index() == base::variant_index<SubParser, Streaming>()) {
109 return std::get<Streaming>(sub_parser_).Parse();
110 }
111 return std::get<NonStreaming>(sub_parser_).Parse();
112 }
113
ParseMethodLine(std::string_view l)114 base::Status ArtMethodTokenizer::ParseMethodLine(std::string_view l) {
115 auto tokens = base::SplitString(base::TrimWhitespace(std::string(l)), "\t");
116 auto id = base::StringToUInt32(tokens[0], 16);
117 if (!id) {
118 return base::ErrStatus(
119 "ART method trace: unable to parse method id as integer: %s",
120 tokens[0].c_str());
121 }
122
123 std::string class_name = tokens[1];
124 std::string method_name;
125 std::string signature;
126 std::optional<StringId> pathname;
127 std::optional<uint32_t> line_number;
128 // Below logic was taken from:
129 // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:perflib/src/main/java/com/android/tools/perflib/vmtrace/VmTraceParser.java;l=251
130 // It's not clear why this complexity is strictly needed (maybe backcompat
131 // or certain configurations of method tracing) but it's best to stick
132 // closely to the official parser implementation.
133 if (tokens.size() == 6) {
134 method_name = tokens[2];
135 signature = tokens[3];
136 pathname = context_->storage->InternString(
137 base::StringView(ConstructPathname(class_name, tokens[4])));
138 line_number = base::StringToUInt32(tokens[5]);
139 } else if (tokens.size() > 2) {
140 if (base::StartsWith(tokens[3], "(")) {
141 method_name = tokens[2];
142 signature = tokens[3];
143 if (tokens.size() >= 5) {
144 pathname = context_->storage->InternString(base::StringView(tokens[4]));
145 }
146 } else {
147 pathname = context_->storage->InternString(base::StringView(tokens[2]));
148 line_number = base::StringToUInt32(tokens[3]);
149 }
150 }
151 base::StackString<2048> slice_name("%s.%s: %s", class_name.c_str(),
152 method_name.c_str(), signature.c_str());
153 method_map_[*id] = {
154 context_->storage->InternString(slice_name.string_view()),
155 pathname,
156 line_number,
157 };
158 return base::OkStatus();
159 }
160
ParseOptionLine(std::string_view l)161 base::Status ArtMethodTokenizer::ParseOptionLine(std::string_view l) {
162 std::string line(l);
163 auto res = base::SplitString(line, "=");
164 if (res.size() != 2) {
165 return base::ErrStatus(
166 "ART method tracing: unable to parse option (line %s)", line.c_str());
167 }
168 if (res[0] == "clock") {
169 if (res[1] == "dual") {
170 clock_ = kDual;
171 } else if (res[1] == "wall") {
172 clock_ = kWall;
173 } else if (res[1] == "thread-cpu") {
174 return base::ErrStatus(
175 "ART method tracing: thread-cpu clock is *not* supported. Use wall "
176 "or dual clocks");
177 } else {
178 return base::ErrStatus("ART method tracing: unknown clock %s",
179 res[1].c_str());
180 }
181 }
182 return base::OkStatus();
183 }
184
ParseRecord(uint32_t tid,const TraceBlobView & record)185 base::Status ArtMethodTokenizer::ParseRecord(uint32_t tid,
186 const TraceBlobView& record) {
187 ArtMethodEvent evt{};
188 evt.tid = tid;
189 if (auto* it = thread_map_.Find(tid); it && !it->comm_used) {
190 evt.comm = it->comm;
191 it->comm_used = true;
192 }
193
194 uint32_t methodid_action = ToInt(record.slice_off(0, 4));
195 uint32_t ts_delta = clock_ == kDual ? ToInt(record.slice_off(8, 4))
196 : ToInt(record.slice_off(4, 4));
197
198 uint32_t action = methodid_action & 0x03;
199 uint32_t method_id = methodid_action & ~0x03u;
200
201 const auto& m = method_map_[method_id];
202 evt.method = m.name;
203 evt.pathname = m.pathname;
204 evt.line_number = m.line_number;
205 switch (action) {
206 case 0:
207 evt.action = ArtMethodEvent::kEnter;
208 break;
209 case 1:
210 case 2:
211 evt.action = ArtMethodEvent::kExit;
212 break;
213 }
214 ASSIGN_OR_RETURN(int64_t ts, context_->clock_tracker->ToTraceTime(
215 protos::pbzero::BUILTIN_CLOCK_MONOTONIC,
216 (ts_ + ts_delta) * 1000));
217 context_->sorter->PushArtMethodEvent(ts, evt);
218 return base::OkStatus();
219 }
220
ParseThread(uint32_t tid,const std::string & comm)221 base::Status ArtMethodTokenizer::ParseThread(uint32_t tid,
222 const std::string& comm) {
223 thread_map_.Insert(
224 tid, {context_->storage->InternString(base::StringView(comm)), false});
225 return base::OkStatus();
226 }
227
Parse()228 base::Status ArtMethodTokenizer::Streaming::Parse() {
229 auto it = tokenizer_->reader_.GetIterator();
230 PERFETTO_CHECK(it.MaybeAdvance(it_offset_));
231 for (bool cnt = true; cnt;) {
232 switch (mode_) {
233 case kHeaderStart: {
234 ASSIGN_OR_RETURN(cnt, ParseHeaderStart(it));
235 break;
236 }
237 case kData: {
238 ASSIGN_OR_RETURN(cnt, ParseData(it));
239 break;
240 }
241 case kSummaryDone: {
242 mode_ = kDone;
243 cnt = false;
244 break;
245 }
246 case kDone: {
247 return base::ErrStatus(
248 "ART method trace: unexpected data after eof marker");
249 }
250 }
251 if (cnt) {
252 it_offset_ = it.file_offset();
253 }
254 }
255 return base::OkStatus();
256 }
257
ParseHeaderStart(Iterator & it)258 base::StatusOr<bool> ArtMethodTokenizer::Streaming::ParseHeaderStart(
259 Iterator& it) {
260 auto header = it.MaybeRead(kTraceHeaderLength);
261 if (!header) {
262 return false;
263 }
264 uint32_t magic = ToInt(header->slice_off(0, 4));
265 if (magic != kTraceMagic) {
266 return base::ErrStatus("ART Method trace: expected start-header magic");
267 }
268 tokenizer_->version_ =
269 ToShort(header->slice_off(4, 2)) ^ kStreamingVersionMask;
270 tokenizer_->ts_ = static_cast<int64_t>(ToLong(header->slice_off(8, 8)));
271 switch (tokenizer_->version_) {
272 case 1:
273 tokenizer_->record_size_ = 9;
274 break;
275 case 2:
276 tokenizer_->record_size_ = 10;
277 break;
278 case 3:
279 tokenizer_->record_size_ = ToShort(header->slice_off(16, 2));
280 break;
281 default:
282 PERFETTO_FATAL("Illegal version %u", tokenizer_->version_);
283 }
284 mode_ = kData;
285 return true;
286 }
287
ParseData(Iterator & it)288 base::StatusOr<bool> ArtMethodTokenizer::Streaming::ParseData(Iterator& it) {
289 std::optional<TraceBlobView> op_tbv = it.MaybeRead(2);
290 if (!op_tbv) {
291 return false;
292 }
293 uint32_t op = ToShort(*op_tbv);
294 if (op != 0) {
295 // Just skip past the record: this will be handled later.
296 // -2 because we already the tid above which forms part of the record.
297 return it.MaybeAdvance(tokenizer_->record_size_ - 2);
298 }
299 std::optional<TraceBlobView> code_tbv = it.MaybeRead(1);
300 if (!code_tbv) {
301 return false;
302 }
303 uint8_t code = *code_tbv->data();
304 switch (code) {
305 case kSummaryCode: {
306 std::optional<TraceBlobView> summary_len_tbv = it.MaybeRead(4);
307 if (!summary_len_tbv) {
308 return false;
309 }
310 uint32_t summary_len = ToInt(*summary_len_tbv);
311 std::optional<TraceBlobView> summary_tbv = it.MaybeRead(summary_len);
312 if (!summary_tbv) {
313 return false;
314 }
315 RETURN_IF_ERROR(ParseSummary(ToStringView(*summary_tbv)));
316 mode_ = kSummaryDone;
317 return true;
318 }
319 case kMethodsCode: {
320 std::optional<TraceBlobView> method_len_tbv = it.MaybeRead(2);
321 if (!method_len_tbv) {
322 return false;
323 }
324 uint32_t method_len = ToShort(*method_len_tbv);
325 std::optional<TraceBlobView> method_tbv = it.MaybeRead(method_len);
326 if (!method_tbv) {
327 return false;
328 }
329 RETURN_IF_ERROR(tokenizer_->ParseMethodLine(ToStringView(*method_tbv)));
330 return true;
331 }
332 case kThreadsCode: {
333 std::optional<TraceBlobView> tid_tbv = it.MaybeRead(2);
334 if (!tid_tbv) {
335 return false;
336 }
337 std::optional<TraceBlobView> comm_len_tbv = it.MaybeRead(2);
338 if (!comm_len_tbv) {
339 return false;
340 }
341 uint32_t comm_len = ToShort(*comm_len_tbv);
342 std::optional<TraceBlobView> comm_tbv = it.MaybeRead(comm_len);
343 if (!comm_tbv) {
344 return false;
345 }
346 RETURN_IF_ERROR(tokenizer_->ParseThread(
347 ToShort(*tid_tbv), std::string(ToStringView(*comm_tbv))));
348 return true;
349 }
350 default:
351 return base::ErrStatus("ART method trace: unknown opcode encountered %d",
352 code);
353 }
354 }
355
ParseSummary(std::string_view summary) const356 base::Status ArtMethodTokenizer::Streaming::ParseSummary(
357 std::string_view summary) const {
358 base::StringSplitter s(std::string(summary), '\n');
359
360 // First two lines should be version and line number respectively.
361 if (!s.Next() || !s.Next() || !s.Next()) {
362 return base::ErrStatus(
363 "ART method trace: unexpected format of summary section");
364 }
365
366 // Parse lines until we hit "*threads" as the line.
367 for (;;) {
368 std::string_view line(s.cur_token(), s.cur_token_size());
369 if (line == "*threads") {
370 return base::OkStatus();
371 }
372 RETURN_IF_ERROR(tokenizer_->ParseOptionLine(line));
373 if (!s.Next()) {
374 return base::ErrStatus(
375 "ART method trace: reached end of file before EOF marker");
376 }
377 }
378 }
379
NotifyEndOfFile()380 base::Status ArtMethodTokenizer::Streaming::NotifyEndOfFile() {
381 if (mode_ != kDone) {
382 return base::ErrStatus("ART Method trace: trace is incomplete");
383 }
384
385 auto it = tokenizer_->reader_.GetIterator();
386 PERFETTO_CHECK(it.MaybeAdvance(kTraceHeaderLength));
387 for (;;) {
388 std::optional<TraceBlobView> tid_tbv = it.MaybeRead(2);
389 uint32_t tid = ToShort(*tid_tbv);
390 if (tid == 0) {
391 uint8_t code = *it.MaybeRead(1)->data();
392 switch (code) {
393 case kSummaryCode:
394 return base::OkStatus();
395 case kMethodsCode: {
396 PERFETTO_CHECK(it.MaybeAdvance(ToShort(*it.MaybeRead(2))));
397 break;
398 }
399 case kThreadsCode: {
400 // Advance past the tid.
401 PERFETTO_CHECK(it.MaybeAdvance(2));
402 PERFETTO_CHECK(it.MaybeAdvance(ToShort(*it.MaybeRead(2))));
403 break;
404 }
405 default:
406 PERFETTO_FATAL("Should not be reached");
407 }
408 continue;
409 }
410 RETURN_IF_ERROR(tokenizer_->ParseRecord(
411 tid, *it.MaybeRead(tokenizer_->record_size_ - 2)));
412 }
413 }
414
Parse()415 base::Status ArtMethodTokenizer::NonStreaming::Parse() {
416 auto it = tokenizer_->reader_.GetIterator();
417 for (bool cnt = true; cnt;) {
418 switch (mode_) {
419 case kHeaderStart: {
420 ASSIGN_OR_RETURN(cnt, ParseHeaderStart(it));
421 break;
422 }
423 case kHeaderVersion: {
424 ASSIGN_OR_RETURN(cnt, ParseHeaderVersion(it));
425 break;
426 }
427 case kHeaderOptions: {
428 ASSIGN_OR_RETURN(cnt, ParseHeaderOptions(it));
429 break;
430 }
431 case kHeaderThreads: {
432 ASSIGN_OR_RETURN(cnt, ParseHeaderThreads(it));
433 break;
434 }
435 case kHeaderMethods: {
436 ASSIGN_OR_RETURN(cnt, ParseHeaderMethods(it));
437 break;
438 }
439 case kDataHeader: {
440 ASSIGN_OR_RETURN(cnt, ParseDataHeader(it));
441 break;
442 }
443 case kData: {
444 size_t s = it.file_offset();
445 for (size_t i = s;; i += tokenizer_->record_size_) {
446 auto record =
447 tokenizer_->reader_.SliceOff(i, tokenizer_->record_size_);
448 if (!record) {
449 PERFETTO_CHECK(it.MaybeAdvance(i - s));
450 cnt = false;
451 break;
452 }
453 uint32_t tid = tokenizer_->version_ == 1
454 ? record->data()[0]
455 : ToShort(record->slice_off(0, 2));
456 RETURN_IF_ERROR(tokenizer_->ParseRecord(
457 tid, record->slice_off(2, record->size() - 2)));
458 }
459 break;
460 }
461 }
462 }
463 tokenizer_->reader_.PopFrontUntil(it.file_offset());
464 return base::OkStatus();
465 }
466
NotifyEndOfFile() const467 base::Status ArtMethodTokenizer::NonStreaming::NotifyEndOfFile() const {
468 if (mode_ == NonStreaming::kData && tokenizer_->reader_.empty()) {
469 return base::OkStatus();
470 }
471 return base::ErrStatus("ART Method trace: trace is incomplete");
472 }
473
ParseHeaderStart(Iterator & it)474 base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderStart(
475 Iterator& it) {
476 auto raw = it.MaybeFindAndRead('\n');
477 if (!raw) {
478 return false;
479 }
480 RETURN_IF_ERROR(ParseHeaderSectionLine(ToStringView(*raw)));
481 return true;
482 }
483
ParseHeaderVersion(Iterator & it)484 base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderVersion(
485 Iterator& it) {
486 auto line = it.MaybeFindAndRead('\n');
487 if (!line) {
488 return false;
489 }
490 std::string version_str(ToStringView(*line));
491 auto version = base::StringToInt32(version_str);
492 if (!version || *version < 1 || *version > 3) {
493 return base::ErrStatus("ART Method trace: trace version (%s) not supported",
494 version_str.c_str());
495 }
496 tokenizer_->version_ = static_cast<uint32_t>(*version);
497 mode_ = kHeaderOptions;
498 return true;
499 }
500
ParseHeaderOptions(Iterator & it)501 base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderOptions(
502 Iterator& it) {
503 for (auto r = it.MaybeFindAndRead('\n'); r; r = it.MaybeFindAndRead('\n')) {
504 std::string_view l = ToStringView(*r);
505 if (l[0] == '*') {
506 RETURN_IF_ERROR(ParseHeaderSectionLine(l));
507 return true;
508 }
509 RETURN_IF_ERROR(tokenizer_->ParseOptionLine(l));
510 }
511 return false;
512 }
513
ParseHeaderThreads(Iterator & it)514 base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderThreads(
515 Iterator& it) {
516 for (auto r = it.MaybeFindAndRead('\n'); r; r = it.MaybeFindAndRead('\n')) {
517 std::string_view l = ToStringView(*r);
518 if (l[0] == '*') {
519 RETURN_IF_ERROR(ParseHeaderSectionLine(l));
520 return true;
521 }
522 std::string line(l);
523 auto tokens = base::SplitString(line, "\t");
524 if (tokens.size() != 2) {
525 return base::ErrStatus(
526 "ART method tracing: expected only one tab in thread line (context: "
527 "%s)",
528 line.c_str());
529 }
530 std::optional<uint32_t> tid = base::StringToUInt32(tokens[0]);
531 if (!tid) {
532 return base::ErrStatus(
533 "ART method tracing: failed parse tid in thread line (context: %s)",
534 tokens[0].c_str());
535 }
536 RETURN_IF_ERROR(tokenizer_->ParseThread(*tid, tokens[1]));
537 }
538 return false;
539 }
540
ParseHeaderMethods(Iterator & it)541 base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseHeaderMethods(
542 Iterator& it) {
543 for (auto r = it.MaybeFindAndRead('\n'); r; r = it.MaybeFindAndRead('\n')) {
544 std::string_view l = ToStringView(*r);
545 if (l[0] == '*') {
546 RETURN_IF_ERROR(ParseHeaderSectionLine(l));
547 return true;
548 }
549 RETURN_IF_ERROR(tokenizer_->ParseMethodLine(l));
550 }
551 return false;
552 }
553
ParseDataHeader(Iterator & it)554 base::StatusOr<bool> ArtMethodTokenizer::NonStreaming::ParseDataHeader(
555 Iterator& it) {
556 auto header = it.MaybeRead(kTraceHeaderLength);
557 if (!header) {
558 return false;
559 }
560 uint32_t magic = ToInt(header->slice_off(0, 4));
561 if (magic != kTraceMagic) {
562 return base::ErrStatus("ART Method trace: expected start-header magic");
563 }
564 uint16_t version = ToShort(header->slice_off(4, 2));
565 if (version != tokenizer_->version_) {
566 return base::ErrStatus(
567 "ART Method trace: trace version does not match data version");
568 }
569 tokenizer_->ts_ = static_cast<int64_t>(ToLong(header->slice_off(8, 8)));
570 switch (tokenizer_->version_) {
571 case 1:
572 tokenizer_->record_size_ = 9;
573 break;
574 case 2:
575 tokenizer_->record_size_ = 10;
576 break;
577 case 3:
578 tokenizer_->record_size_ = ToShort(header->slice_off(16, 2));
579 break;
580 default:
581 PERFETTO_FATAL("Illegal version %u", tokenizer_->version_);
582 }
583 mode_ = kData;
584 return true;
585 }
586
ParseHeaderSectionLine(std::string_view line)587 base::Status ArtMethodTokenizer::NonStreaming::ParseHeaderSectionLine(
588 std::string_view line) {
589 if (line == "*version") {
590 mode_ = kHeaderVersion;
591 return base::OkStatus();
592 }
593 if (line == "*threads") {
594 mode_ = kHeaderThreads;
595 return base::OkStatus();
596 }
597 if (line == "*methods") {
598 mode_ = kHeaderMethods;
599 return base::OkStatus();
600 }
601 if (line == "*end") {
602 mode_ = kDataHeader;
603 return base::OkStatus();
604 }
605 return base::ErrStatus(
606 "ART Method trace: unexpected line (%s) when expecting section header "
607 "(line starting with *)",
608 std::string(line).c_str());
609 }
610
NotifyEndOfFile()611 base::Status ArtMethodTokenizer::NotifyEndOfFile() {
612 switch (sub_parser_.index()) {
613 case base::variant_index<SubParser, Detect>():
614 return base::ErrStatus("ART Method trace: trace is incomplete");
615 case base::variant_index<SubParser, Streaming>():
616 return std::get<Streaming>(sub_parser_).NotifyEndOfFile();
617 case base::variant_index<SubParser, NonStreaming>():
618 return std::get<NonStreaming>(sub_parser_).NotifyEndOfFile();
619 }
620 PERFETTO_FATAL("For GCC");
621 }
622
623 } // namespace perfetto::trace_processor::art_method
624