xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/art_method/art_method_tokenizer.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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