xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/systrace/systrace_trace_parser.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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_trace_parser.h"
18 
19 #include "perfetto/base/logging.h"
20 #include "perfetto/base/status.h"
21 #include "perfetto/ext/base/string_splitter.h"
22 #include "perfetto/ext/base/string_utils.h"
23 #include "src/trace_processor/forwarding_trace_parser.h"
24 #include "src/trace_processor/importers/common/process_tracker.h"
25 #include "src/trace_processor/sorter/trace_sorter.h"
26 
27 #include <cctype>
28 #include <cinttypes>
29 #include <string>
30 #include <unordered_map>
31 
32 namespace perfetto {
33 namespace trace_processor {
34 namespace {
35 
SplitOnSpaces(base::StringView str)36 std::vector<base::StringView> SplitOnSpaces(base::StringView str) {
37   std::vector<base::StringView> result;
38   for (size_t i = 0; i < str.size(); ++i) {
39     // Consume all spaces.
40     for (; i < str.size() && str.data()[i] == ' '; ++i)
41       ;
42     // If we haven't reached the end consume all non-spaces and add result.
43     if (i != str.size()) {
44       size_t start = i;
45       for (; i < str.size() && str.data()[i] != ' '; ++i)
46         ;
47       result.push_back(base::StringView(str.data() + start, i - start));
48     }
49   }
50   return result;
51 }
52 
IsProcessDumpShortHeader(const std::vector<base::StringView> & tokens)53 bool IsProcessDumpShortHeader(const std::vector<base::StringView>& tokens) {
54   return tokens.size() == 4 && tokens[0] == "USER" && tokens[1] == "PID" &&
55          tokens[2] == "TID" && tokens[3] == "CMD";
56 }
57 
IsProcessDumpLongHeader(const std::vector<base::StringView> & tokens)58 bool IsProcessDumpLongHeader(const std::vector<base::StringView>& tokens) {
59   return tokens.size() > 4 && tokens[0] == "USER" && tokens[1] == "PID" &&
60          tokens[2] == "PPID" && tokens[3] == "VSZ";
61 }
62 
63 }  // namespace
64 
SystraceTraceParser(TraceProcessorContext * ctx)65 SystraceTraceParser::SystraceTraceParser(TraceProcessorContext* ctx)
66     : line_parser_(ctx), ctx_(ctx) {}
67 SystraceTraceParser::~SystraceTraceParser() = default;
68 
Parse(TraceBlobView blob)69 util::Status SystraceTraceParser::Parse(TraceBlobView blob) {
70   if (state_ == ParseState::kEndOfSystrace)
71     return util::OkStatus();
72   partial_buf_.insert(partial_buf_.end(), blob.data(),
73                       blob.data() + blob.size());
74 
75   if (state_ == ParseState::kBeforeParse) {
76     // Remove anything before the TRACE:\n marker, which is emitted when
77     // obtaining traces via  `adb shell "atrace -t 1 sched" > out.txt`.
78     std::array<uint8_t, 7> kAtraceMarker = {'T', 'R', 'A', 'C', 'E', ':', '\n'};
79     auto search_end = partial_buf_.begin() +
80                       static_cast<int>(std::min(partial_buf_.size(),
81                                                 kGuessTraceMaxLookahead));
82     auto it = std::search(partial_buf_.begin(), search_end,
83                           kAtraceMarker.begin(), kAtraceMarker.end());
84     if (it != search_end)
85       partial_buf_.erase(partial_buf_.begin(), it + kAtraceMarker.size());
86 
87     // Deal with HTML traces.
88     state_ = partial_buf_[0] == '<' ? ParseState::kHtmlBeforeSystrace
89                                     : ParseState::kSystrace;
90   }
91 
92   // There can be multiple trace data sections in an HTML trace, we want to
93   // ignore any that don't contain systrace data. In the future it would be
94   // good to also parse the process dump section.
95   const char kTraceDataSection[] =
96       R"(<script class="trace-data" type="application/text">)";
97   auto start_it = partial_buf_.begin();
98   for (;;) {
99     auto line_it = std::find(start_it, partial_buf_.end(), '\n');
100     if (line_it == partial_buf_.end())
101       break;
102 
103     std::string buffer(start_it, line_it);
104 
105     if (state_ == ParseState::kHtmlBeforeSystrace) {
106       if (base::Contains(buffer, kTraceDataSection)) {
107         state_ = ParseState::kTraceDataSection;
108       }
109     } else if (state_ == ParseState::kTraceDataSection) {
110       if (base::StartsWith(buffer, "#") && base::Contains(buffer, "TASK-PID")) {
111         state_ = ParseState::kSystrace;
112       } else if (base::StartsWith(buffer, "PROCESS DUMP")) {
113         state_ = ParseState::kProcessDumpLong;
114       } else if (base::StartsWith(buffer, "CGROUP DUMP")) {
115         state_ = ParseState::kCgroupDump;
116       } else if (base::Contains(buffer, R"(</script>)")) {
117         state_ = ParseState::kHtmlBeforeSystrace;
118       }
119     } else if (state_ == ParseState::kSystrace) {
120       if (base::Contains(buffer, R"(</script>)")) {
121         state_ = ParseState::kEndOfSystrace;
122         break;
123       } else if (!base::StartsWith(buffer, "#") && !buffer.empty()) {
124         SystraceLine line;
125         util::Status status = line_tokenizer_.Tokenize(buffer, &line);
126         if (status.ok()) {
127           line_parser_.ParseLine(std::move(line));
128         } else {
129           ctx_->storage->IncrementStats(stats::systrace_parse_failure);
130         }
131       }
132     } else if (state_ == ParseState::kProcessDumpLong ||
133                state_ == ParseState::kProcessDumpShort) {
134       if (base::Contains(buffer, R"(</script>)")) {
135         state_ = ParseState::kHtmlBeforeSystrace;
136       } else {
137         std::vector<base::StringView> tokens =
138             SplitOnSpaces(base::StringView(buffer));
139         if (IsProcessDumpShortHeader(tokens)) {
140           state_ = ParseState::kProcessDumpShort;
141         } else if (IsProcessDumpLongHeader(tokens)) {
142           state_ = ParseState::kProcessDumpLong;
143         } else if (state_ == ParseState::kProcessDumpLong &&
144                    tokens.size() >= 10) {
145           // Format is:
146           // user pid ppid vsz rss wchan pc s name my cmd line
147           const std::optional<uint32_t> pid =
148               base::StringToUInt32(tokens[1].ToStdString());
149           const std::optional<uint32_t> ppid =
150               base::StringToUInt32(tokens[2].ToStdString());
151           base::StringView name = tokens[8];
152           // Command line may contain spaces, merge all remaining tokens:
153           const char* cmd_start = tokens[9].data();
154           base::StringView cmd(
155               cmd_start,
156               static_cast<size_t>((buffer.data() + buffer.size()) - cmd_start));
157           if (!pid || !ppid) {
158             PERFETTO_ELOG("Could not parse line '%s'", buffer.c_str());
159             return util::ErrStatus("Could not parse PROCESS DUMP line");
160           }
161           ctx_->process_tracker->SetProcessMetadata(pid.value(), ppid, name,
162                                                     base::StringView());
163         } else if (state_ == ParseState::kProcessDumpShort &&
164                    tokens.size() >= 4) {
165           // Format is:
166           // username pid tid my cmd line
167           const std::optional<uint32_t> tgid =
168               base::StringToUInt32(tokens[1].ToStdString());
169           const std::optional<uint32_t> tid =
170               base::StringToUInt32(tokens[2].ToStdString());
171           // Command line may contain spaces, merge all remaining tokens:
172           const char* cmd_start = tokens[3].data();
173           base::StringView cmd(
174               cmd_start,
175               static_cast<size_t>((buffer.data() + buffer.size()) - cmd_start));
176           StringId cmd_id =
177               ctx_->storage->mutable_string_pool()->InternString(cmd);
178           if (!tid || !tgid) {
179             PERFETTO_ELOG("Could not parse line '%s'", buffer.c_str());
180             return util::ErrStatus("Could not parse PROCESS DUMP line");
181           }
182           UniqueTid utid =
183               ctx_->process_tracker->UpdateThread(tid.value(), tgid.value());
184           ctx_->process_tracker->UpdateThreadNameByUtid(
185               utid, cmd_id, ThreadNamePriority::kOther);
186         }
187       }
188     } else if (state_ == ParseState::kCgroupDump) {
189       if (base::Contains(buffer, R"(</script>)")) {
190         state_ = ParseState::kHtmlBeforeSystrace;
191       }
192       // TODO(lalitm): see if it is important to parse this.
193     }
194     start_it = line_it + 1;
195   }
196   if (state_ == ParseState::kEndOfSystrace) {
197     partial_buf_.clear();
198   } else {
199     partial_buf_.erase(partial_buf_.begin(), start_it);
200   }
201   return util::OkStatus();
202 }
203 
NotifyEndOfFile()204 base::Status SystraceTraceParser::NotifyEndOfFile() {
205   return base::OkStatus();
206 }
207 
208 }  // namespace trace_processor
209 }  // namespace perfetto
210