| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "src/trace_processor/systrace_trace_parser.h" |
| |
| #include "perfetto/ext/base/string_splitter.h" |
| #include "perfetto/ext/base/string_utils.h" |
| #include "src/trace_processor/args_tracker.h" |
| #include "src/trace_processor/event_tracker.h" |
| #include "src/trace_processor/process_tracker.h" |
| #include "src/trace_processor/slice_tracker.h" |
| #include "src/trace_processor/systrace_parser.h" |
| |
| #include <inttypes.h> |
| #include <string> |
| #include <unordered_map> |
| |
| namespace perfetto { |
| namespace trace_processor { |
| |
| namespace { |
| |
| std::string SubstrTrim(const std::string& input, size_t start, size_t end) { |
| std::string s = input.substr(start, end - start); |
| s.erase(s.begin(), std::find_if(s.begin(), s.end(), |
| [](int ch) { return !std::isspace(ch); })); |
| s.erase(std::find_if(s.rbegin(), s.rend(), |
| [](int ch) { return !std::isspace(ch); }) |
| .base(), |
| s.end()); |
| return s; |
| } |
| |
| std::pair<size_t, size_t> FindTask(const std::string& line) { |
| size_t start; |
| for (start = 0; start < line.size() && isspace(line[start]); ++start) |
| ; |
| size_t length; |
| for (length = 0; start + length < line.size() && line[start + length] != '-'; |
| ++length) |
| ; |
| return std::pair<size_t, size_t>(start, length); |
| } |
| |
| } // namespace |
| |
| SystraceTraceParser::SystraceTraceParser(TraceProcessorContext* ctx) |
| : context_(ctx), |
| sched_wakeup_name_id_(ctx->storage->InternString("sched_wakeup")), |
| cpu_idle_name_id_(ctx->storage->InternString("cpuidle")) {} |
| SystraceTraceParser::~SystraceTraceParser() = default; |
| |
| util::Status SystraceTraceParser::Parse(std::unique_ptr<uint8_t[]> owned_buf, |
| size_t size) { |
| if (state_ == ParseState::kEndOfSystrace) |
| return util::OkStatus(); |
| partial_buf_.insert(partial_buf_.end(), &owned_buf[0], &owned_buf[size]); |
| |
| if (state_ == ParseState::kBeforeParse) { |
| state_ = partial_buf_[0] == '<' ? ParseState::kHtmlBeforeSystrace |
| : ParseState::kSystrace; |
| } |
| |
| const char kSystraceStart[] = |
| R"(<script class="trace-data" type="application/text">)"; |
| auto start_it = partial_buf_.begin(); |
| for (;;) { |
| auto line_it = std::find(start_it, partial_buf_.end(), '\n'); |
| if (line_it == partial_buf_.end()) |
| break; |
| |
| std::string buffer(start_it, line_it); |
| if (state_ == ParseState::kHtmlBeforeSystrace) { |
| if (base::Contains(buffer, kSystraceStart)) { |
| state_ = ParseState::kSystrace; |
| } |
| } else if (state_ == ParseState::kSystrace) { |
| if (base::Contains(buffer, R"(</script>)")) { |
| state_ = kEndOfSystrace; |
| break; |
| } else if (!base::StartsWith(buffer, "#")) { |
| ParseSingleSystraceEvent(buffer); |
| } |
| } |
| start_it = line_it + 1; |
| } |
| if (state_ == ParseState::kEndOfSystrace) { |
| partial_buf_.clear(); |
| } else { |
| partial_buf_.erase(partial_buf_.begin(), start_it); |
| } |
| return util::OkStatus(); |
| } |
| |
| // TODO(hjd): This should be more robust to being passed random input. |
| // This can happen if we mess up detecting a gzip trace for example. |
| util::Status SystraceTraceParser::ParseSingleSystraceEvent( |
| const std::string& buffer) { |
| // An example line from buffer looks something like the following: |
| // <idle>-0 (-----) [000] d..1 16500.715638: cpu_idle: state=0 cpu_id=0 |
| // |
| // However, sometimes the tgid can be missing and buffer looks like this: |
| // <idle>-0 [000] ...2 0.002188: task_newtask: pid=1 ... |
| |
| size_t task_start; |
| size_t task_length; |
| std::tie<size_t, size_t>(task_start, task_length) = FindTask(buffer); |
| |
| size_t task_idx = task_start + task_length; |
| std::string task = buffer.substr(task_start, task_length); |
| |
| // Try and figure out whether tgid is present by searching for '(' but only |
| // if it occurs before the start of cpu (indiciated by '[') - this is because |
| // '(' can also occur in the args of an event. |
| auto tgid_idx = buffer.find('(', task_idx + 1); |
| auto cpu_idx = buffer.find('[', task_idx + 1); |
| bool has_tgid = tgid_idx != std::string::npos && tgid_idx < cpu_idx; |
| |
| if (cpu_idx == std::string::npos) { |
| return util::Status("Could not find [ in " + buffer); |
| } |
| |
| auto pid_end = has_tgid ? cpu_idx : tgid_idx; |
| std::string pid_str = SubstrTrim(buffer, task_idx + 1, pid_end); |
| auto pid = static_cast<uint32_t>(std::stoi(pid_str)); |
| context_->process_tracker->GetOrCreateThread(pid); |
| |
| if (has_tgid) { |
| auto tgid_end = buffer.find(')', tgid_idx + 1); |
| std::string tgid_str = SubstrTrim(buffer, tgid_idx + 1, tgid_end); |
| if (tgid_str != "-----") { |
| context_->process_tracker->UpdateThread( |
| pid, static_cast<uint32_t>(std::stoi(tgid_str))); |
| } |
| } |
| |
| auto cpu_end = buffer.find(']', cpu_idx + 1); |
| std::string cpu_str = SubstrTrim(buffer, cpu_idx + 1, cpu_end); |
| auto cpu = static_cast<uint32_t>(std::stoi(cpu_str)); |
| |
| auto ts_idx = buffer.find(' ', cpu_end + 2); |
| auto ts_end = buffer.find(':', ts_idx + 1); |
| std::string ts_str = SubstrTrim(buffer, ts_idx + 1, ts_end); |
| auto ts_float = std::stod(ts_str) * 1e9; |
| auto ts = static_cast<int64_t>(ts_float); |
| |
| auto fn_idx = buffer.find(':', ts_end + 2); |
| std::string fn = SubstrTrim(buffer, ts_end + 2, fn_idx); |
| |
| std::string args_str = SubstrTrim(buffer, fn_idx + 2, buffer.size()); |
| |
| std::unordered_map<std::string, std::string> args; |
| for (base::StringSplitter ss(args_str.c_str(), ' '); ss.Next();) { |
| std::string key; |
| std::string value; |
| for (base::StringSplitter inner(ss.cur_token(), '='); inner.Next();) { |
| if (key.empty()) { |
| key = inner.cur_token(); |
| } else { |
| value = inner.cur_token(); |
| } |
| } |
| args.emplace(std::move(key), std::move(value)); |
| } |
| if (fn == "sched_switch") { |
| auto prev_state_str = args["prev_state"]; |
| int64_t prev_state = |
| ftrace_utils::TaskState(prev_state_str.c_str()).raw_state(); |
| |
| auto prev_pid = std::stoi(args["prev_pid"]); |
| auto prev_comm = base::StringView(args["prev_comm"]); |
| auto prev_prio = std::stoi(args["prev_prio"]); |
| auto next_pid = std::stoi(args["next_pid"]); |
| auto next_comm = base::StringView(args["next_comm"]); |
| auto next_prio = std::stoi(args["next_prio"]); |
| |
| context_->event_tracker->PushSchedSwitch( |
| static_cast<uint32_t>(cpu), ts, static_cast<uint32_t>(prev_pid), |
| prev_comm, prev_prio, prev_state, static_cast<uint32_t>(next_pid), |
| next_comm, next_prio); |
| } else if (fn == "tracing_mark_write" || fn == "0" || fn == "print") { |
| context_->systrace_parser->ParsePrintEvent(ts, pid, args_str.c_str()); |
| } else if (fn == "sched_wakeup") { |
| auto comm = args["comm"]; |
| uint32_t wakee_pid = static_cast<uint32_t>(std::stoi(args["pid"])); |
| |
| StringId name_id = context_->storage->InternString(base::StringView(comm)); |
| auto wakee_utid = |
| context_->process_tracker->UpdateThreadName(wakee_pid, name_id); |
| context_->event_tracker->PushInstant(ts, sched_wakeup_name_id_, |
| 0 /* value */, wakee_utid, |
| RefType::kRefUtid); |
| } else if (fn == "cpu_idle") { |
| auto new_state = static_cast<double>(std::stoul(args["state"])); |
| uint32_t event_cpu = static_cast<uint32_t>(std::stoi(args["cpu_id"])); |
| context_->event_tracker->PushCounter(ts, new_state, cpu_idle_name_id_, |
| event_cpu, RefType::kRefCpuId); |
| } |
| |
| return util::OkStatus(); |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |