• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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