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