// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/test/trace_event_analyzer.h" #include #include #include #include "base/bind.h" #include "base/json/json_reader.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted_memory.h" #include "base/ranges/algorithm.h" #include "base/run_loop.h" #include "base/strings/pattern.h" #include "base/trace_event/trace_buffer.h" #include "base/trace_event/trace_config.h" #include "base/trace_event/trace_log.h" #include "base/values.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace { void OnTraceDataCollected(base::OnceClosure quit_closure, base::trace_event::TraceResultBuffer* buffer, const scoped_refptr& json, bool has_more_events) { buffer->AddFragment(json->data()); if (!has_more_events) std::move(quit_closure).Run(); } } // namespace namespace trace_analyzer { // TraceEvent TraceEvent::TraceEvent() : thread(0, 0) {} TraceEvent::TraceEvent(TraceEvent&& other) = default; TraceEvent::~TraceEvent() = default; TraceEvent& TraceEvent::operator=(TraceEvent&& rhs) = default; bool TraceEvent::SetFromJSON(const base::Value* event_value) { if (!event_value->is_dict()) { LOG(ERROR) << "Value must be Type::DICTIONARY"; return false; } const base::Value::Dict& event_dict = event_value->GetDict(); const std::string* maybe_phase = event_dict.FindString("ph"); if (!maybe_phase) { LOG(ERROR) << "ph is missing from TraceEvent JSON"; return false; } phase = *maybe_phase->data(); bool may_have_duration = (phase == TRACE_EVENT_PHASE_COMPLETE); bool require_origin = (phase != TRACE_EVENT_PHASE_METADATA); bool require_id = (phase == TRACE_EVENT_PHASE_ASYNC_BEGIN || phase == TRACE_EVENT_PHASE_ASYNC_STEP_INTO || phase == TRACE_EVENT_PHASE_ASYNC_STEP_PAST || phase == TRACE_EVENT_PHASE_MEMORY_DUMP || phase == TRACE_EVENT_PHASE_CREATE_OBJECT || phase == TRACE_EVENT_PHASE_DELETE_OBJECT || phase == TRACE_EVENT_PHASE_SNAPSHOT_OBJECT || phase == TRACE_EVENT_PHASE_ASYNC_END); if (require_origin) { absl::optional maybe_process_id = event_dict.FindInt("pid"); if (!maybe_process_id) { LOG(ERROR) << "pid is missing from TraceEvent JSON"; return false; } thread.process_id = *maybe_process_id; absl::optional maybe_thread_id = event_dict.FindInt("tid"); if (!maybe_thread_id) { LOG(ERROR) << "tid is missing from TraceEvent JSON"; return false; } thread.thread_id = *maybe_thread_id; absl::optional maybe_timestamp = event_dict.FindDouble("ts"); if (!maybe_timestamp) { LOG(ERROR) << "ts is missing from TraceEvent JSON"; return false; } timestamp = *maybe_timestamp; } if (may_have_duration) { absl::optional maybe_duration = event_dict.FindDouble("dur"); if (maybe_duration) duration = *maybe_duration; } const std::string* maybe_category = event_dict.FindString("cat"); if (!maybe_category) { LOG(ERROR) << "cat is missing from TraceEvent JSON"; return false; } category = *maybe_category; const std::string* maybe_name = event_dict.FindString("name"); if (!maybe_name) { LOG(ERROR) << "name is missing from TraceEvent JSON"; return false; } name = *maybe_name; const base::Value::Dict* maybe_args = event_dict.FindDict("args"); if (!maybe_args) { // If argument filter is enabled, the arguments field contains a string // value. const std::string* maybe_stripped_args = event_dict.FindString("args"); if (!maybe_stripped_args || *maybe_stripped_args != "__stripped__") { LOG(ERROR) << "args is missing from TraceEvent JSON"; return false; } } const base::Value::Dict* maybe_id2 = nullptr; if (require_id) { const std::string* maybe_id = event_dict.FindString("id"); maybe_id2 = event_dict.FindDict("id2"); if (!maybe_id && !maybe_id2) { LOG(ERROR) << "id/id2 is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON"; return false; } if (maybe_id) id = *maybe_id; } absl::optional maybe_thread_duration = event_dict.FindDouble("tdur"); if (maybe_thread_duration) { thread_duration = *maybe_thread_duration; } absl::optional maybe_thread_timestamp = event_dict.FindDouble("tts"); if (maybe_thread_timestamp) { thread_timestamp = *maybe_thread_timestamp; } const std::string* maybe_scope = event_dict.FindString("scope"); if (maybe_scope) { scope = *maybe_scope; } const std::string* maybe_bind_id = event_dict.FindString("bind_id"); if (maybe_bind_id) { bind_id = *maybe_bind_id; } absl::optional maybe_flow_out = event_dict.FindBool("flow_out"); if (maybe_flow_out) { flow_out = *maybe_flow_out; } absl::optional maybe_flow_in = event_dict.FindBool("flow_in"); if (maybe_flow_in) { flow_in = *maybe_flow_in; } if (maybe_id2) { const std::string* maybe_global_id2 = maybe_id2->FindString("global"); if (maybe_global_id2) { global_id2 = *maybe_global_id2; } const std::string* maybe_local_id2 = maybe_id2->FindString("local"); if (maybe_local_id2) { local_id2 = *maybe_local_id2; } } // For each argument, copy the type and create a trace_analyzer::TraceValue. // TODO(crbug.com/1303874): Add BINARY and LIST arg types if needed. if (maybe_args) { for (auto pair : *maybe_args) { switch (pair.second.type()) { case base::Value::Type::STRING: arg_strings[pair.first] = pair.second.GetString(); break; case base::Value::Type::INTEGER: arg_numbers[pair.first] = static_cast(pair.second.GetInt()); break; case base::Value::Type::BOOLEAN: arg_numbers[pair.first] = pair.second.GetBool() ? 1.0 : 0.0; break; case base::Value::Type::DOUBLE: arg_numbers[pair.first] = pair.second.GetDouble(); break; case base::Value::Type::DICT: arg_dicts[pair.first] = pair.second.GetDict().Clone(); break; default: break; } } } return true; } double TraceEvent::GetAbsTimeToOtherEvent() const { return fabs(other_event->timestamp - timestamp); } bool TraceEvent::GetArgAsString(const std::string& arg_name, std::string* arg) const { const auto it = arg_strings.find(arg_name); if (it != arg_strings.end()) { *arg = it->second; return true; } return false; } bool TraceEvent::GetArgAsNumber(const std::string& arg_name, double* arg) const { const auto it = arg_numbers.find(arg_name); if (it != arg_numbers.end()) { *arg = it->second; return true; } return false; } bool TraceEvent::GetArgAsDict(const std::string& arg_name, base::Value::Dict* arg) const { const auto it = arg_dicts.find(arg_name); if (it != arg_dicts.end()) { *arg = it->second.Clone(); return true; } return false; } bool TraceEvent::HasStringArg(const std::string& arg_name) const { return (arg_strings.find(arg_name) != arg_strings.end()); } bool TraceEvent::HasNumberArg(const std::string& arg_name) const { return (arg_numbers.find(arg_name) != arg_numbers.end()); } bool TraceEvent::HasDictArg(const std::string& arg_name) const { return (arg_dicts.find(arg_name) != arg_dicts.end()); } std::string TraceEvent::GetKnownArgAsString(const std::string& arg_name) const { std::string arg_string; bool result = GetArgAsString(arg_name, &arg_string); DCHECK(result); return arg_string; } double TraceEvent::GetKnownArgAsDouble(const std::string& arg_name) const { double arg_double = 0; bool result = GetArgAsNumber(arg_name, &arg_double); DCHECK(result); return arg_double; } int TraceEvent::GetKnownArgAsInt(const std::string& arg_name) const { double arg_double = 0; bool result = GetArgAsNumber(arg_name, &arg_double); DCHECK(result); return static_cast(arg_double); } bool TraceEvent::GetKnownArgAsBool(const std::string& arg_name) const { double arg_double = 0; bool result = GetArgAsNumber(arg_name, &arg_double); DCHECK(result); return (arg_double != 0.0); } base::Value::Dict TraceEvent::GetKnownArgAsDict( const std::string& arg_name) const { base::Value::Dict arg_dict; bool result = GetArgAsDict(arg_name, &arg_dict); DCHECK(result); return arg_dict; } // QueryNode QueryNode::QueryNode(const Query& query) : query_(query) { } QueryNode::~QueryNode() = default; // Query Query::Query(TraceEventMember member) : type_(QUERY_EVENT_MEMBER), operator_(OP_INVALID), member_(member), number_(0), is_pattern_(false) { } Query::Query(TraceEventMember member, const std::string& arg_name) : type_(QUERY_EVENT_MEMBER), operator_(OP_INVALID), member_(member), number_(0), string_(arg_name), is_pattern_(false) { } Query::Query(const Query& query) = default; Query::~Query() = default; Query Query::String(const std::string& str) { return Query(str); } Query Query::Double(double num) { return Query(num); } Query Query::Int(int32_t num) { return Query(static_cast(num)); } Query Query::Uint(uint32_t num) { return Query(static_cast(num)); } Query Query::Bool(bool boolean) { return Query(boolean ? 1.0 : 0.0); } Query Query::Phase(char phase) { return Query(static_cast(phase)); } Query Query::Pattern(const std::string& pattern) { Query query(pattern); query.is_pattern_ = true; return query; } bool Query::Evaluate(const TraceEvent& event) const { // First check for values that can convert to bool. // double is true if != 0: double bool_value = 0.0; bool is_bool = GetAsDouble(event, &bool_value); if (is_bool) return (bool_value != 0.0); // string is true if it is non-empty: std::string str_value; bool is_str = GetAsString(event, &str_value); if (is_str) return !str_value.empty(); DCHECK_EQ(QUERY_BOOLEAN_OPERATOR, type_) << "Invalid query: missing boolean expression"; DCHECK(left_.get()); DCHECK(right_.get() || is_unary_operator()); if (is_comparison_operator()) { DCHECK(left().is_value() && right().is_value()) << "Invalid query: comparison operator used between event member and " "value."; bool compare_result = false; if (CompareAsDouble(event, &compare_result)) return compare_result; if (CompareAsString(event, &compare_result)) return compare_result; return false; } // It's a logical operator. switch (operator_) { case OP_AND: return left().Evaluate(event) && right().Evaluate(event); case OP_OR: return left().Evaluate(event) || right().Evaluate(event); case OP_NOT: return !left().Evaluate(event); default: NOTREACHED(); return false; } } bool Query::CompareAsDouble(const TraceEvent& event, bool* result) const { double lhs, rhs; if (!left().GetAsDouble(event, &lhs) || !right().GetAsDouble(event, &rhs)) return false; switch (operator_) { case OP_EQ: *result = (lhs == rhs); return true; case OP_NE: *result = (lhs != rhs); return true; case OP_LT: *result = (lhs < rhs); return true; case OP_LE: *result = (lhs <= rhs); return true; case OP_GT: *result = (lhs > rhs); return true; case OP_GE: *result = (lhs >= rhs); return true; default: NOTREACHED(); return false; } } bool Query::CompareAsString(const TraceEvent& event, bool* result) const { std::string lhs, rhs; if (!left().GetAsString(event, &lhs) || !right().GetAsString(event, &rhs)) return false; switch (operator_) { case OP_EQ: if (right().is_pattern_) *result = base::MatchPattern(lhs, rhs); else if (left().is_pattern_) *result = base::MatchPattern(rhs, lhs); else *result = (lhs == rhs); return true; case OP_NE: if (right().is_pattern_) *result = !base::MatchPattern(lhs, rhs); else if (left().is_pattern_) *result = !base::MatchPattern(rhs, lhs); else *result = (lhs != rhs); return true; case OP_LT: *result = (lhs < rhs); return true; case OP_LE: *result = (lhs <= rhs); return true; case OP_GT: *result = (lhs > rhs); return true; case OP_GE: *result = (lhs >= rhs); return true; default: NOTREACHED(); return false; } } bool Query::EvaluateArithmeticOperator(const TraceEvent& event, double* num) const { DCHECK_EQ(QUERY_ARITHMETIC_OPERATOR, type_); DCHECK(left_.get()); DCHECK(right_.get() || is_unary_operator()); double lhs = 0, rhs = 0; if (!left().GetAsDouble(event, &lhs)) return false; if (!is_unary_operator() && !right().GetAsDouble(event, &rhs)) return false; switch (operator_) { case OP_ADD: *num = lhs + rhs; return true; case OP_SUB: *num = lhs - rhs; return true; case OP_MUL: *num = lhs * rhs; return true; case OP_DIV: *num = lhs / rhs; return true; case OP_MOD: *num = static_cast(static_cast(lhs) % static_cast(rhs)); return true; case OP_NEGATE: *num = -lhs; return true; default: NOTREACHED(); return false; } } bool Query::GetAsDouble(const TraceEvent& event, double* num) const { switch (type_) { case QUERY_ARITHMETIC_OPERATOR: return EvaluateArithmeticOperator(event, num); case QUERY_EVENT_MEMBER: return GetMemberValueAsDouble(event, num); case QUERY_NUMBER: *num = number_; return true; default: return false; } } bool Query::GetAsString(const TraceEvent& event, std::string* str) const { switch (type_) { case QUERY_EVENT_MEMBER: return GetMemberValueAsString(event, str); case QUERY_STRING: *str = string_; return true; default: return false; } } const TraceEvent* Query::SelectTargetEvent(const TraceEvent* event, TraceEventMember member) { if (member >= OTHER_FIRST_MEMBER && member <= OTHER_LAST_MEMBER) return event->other_event; if (member >= PREV_FIRST_MEMBER && member <= PREV_LAST_MEMBER) return event->prev_event; return event; } bool Query::GetMemberValueAsDouble(const TraceEvent& event, double* num) const { DCHECK_EQ(QUERY_EVENT_MEMBER, type_); // This could be a request for a member of |event| or a member of |event|'s // associated previous or next event. Store the target event in the_event: const TraceEvent* the_event = SelectTargetEvent(&event, member_); // Request for member of associated event, but there is no associated event. if (!the_event) return false; switch (member_) { case EVENT_PID: case OTHER_PID: case PREV_PID: *num = static_cast(the_event->thread.process_id); return true; case EVENT_TID: case OTHER_TID: case PREV_TID: *num = static_cast(the_event->thread.thread_id); return true; case EVENT_TIME: case OTHER_TIME: case PREV_TIME: *num = the_event->timestamp; return true; case EVENT_DURATION: if (!the_event->has_other_event()) return false; *num = the_event->GetAbsTimeToOtherEvent(); return true; case EVENT_COMPLETE_DURATION: if (the_event->phase != TRACE_EVENT_PHASE_COMPLETE) return false; *num = the_event->duration; return true; case EVENT_PHASE: case OTHER_PHASE: case PREV_PHASE: *num = static_cast(the_event->phase); return true; case EVENT_HAS_STRING_ARG: case OTHER_HAS_STRING_ARG: case PREV_HAS_STRING_ARG: *num = (the_event->HasStringArg(string_) ? 1.0 : 0.0); return true; case EVENT_HAS_NUMBER_ARG: case OTHER_HAS_NUMBER_ARG: case PREV_HAS_NUMBER_ARG: *num = (the_event->HasNumberArg(string_) ? 1.0 : 0.0); return true; case EVENT_ARG: case OTHER_ARG: case PREV_ARG: { // Search for the argument name and return its value if found. auto num_i = the_event->arg_numbers.find(string_); if (num_i == the_event->arg_numbers.end()) return false; *num = num_i->second; return true; } case EVENT_HAS_OTHER: // return 1.0 (true) if the other event exists *num = event.other_event ? 1.0 : 0.0; return true; case EVENT_HAS_PREV: *num = event.prev_event ? 1.0 : 0.0; return true; default: return false; } } bool Query::GetMemberValueAsString(const TraceEvent& event, std::string* str) const { DCHECK_EQ(QUERY_EVENT_MEMBER, type_); // This could be a request for a member of |event| or a member of |event|'s // associated previous or next event. Store the target event in the_event: const TraceEvent* the_event = SelectTargetEvent(&event, member_); // Request for member of associated event, but there is no associated event. if (!the_event) return false; switch (member_) { case EVENT_CATEGORY: case OTHER_CATEGORY: case PREV_CATEGORY: *str = the_event->category; return true; case EVENT_NAME: case OTHER_NAME: case PREV_NAME: *str = the_event->name; return true; case EVENT_ID: case OTHER_ID: case PREV_ID: *str = the_event->id; return true; case EVENT_ARG: case OTHER_ARG: case PREV_ARG: { // Search for the argument name and return its value if found. auto str_i = the_event->arg_strings.find(string_); if (str_i == the_event->arg_strings.end()) return false; *str = str_i->second; return true; } default: return false; } } Query::Query(const std::string& str) : type_(QUERY_STRING), operator_(OP_INVALID), member_(EVENT_INVALID), number_(0), string_(str), is_pattern_(false) { } Query::Query(double num) : type_(QUERY_NUMBER), operator_(OP_INVALID), member_(EVENT_INVALID), number_(num), is_pattern_(false) { } const Query& Query::left() const { return left_->query(); } const Query& Query::right() const { return right_->query(); } Query Query::operator==(const Query& rhs) const { return Query(*this, rhs, OP_EQ); } Query Query::operator!=(const Query& rhs) const { return Query(*this, rhs, OP_NE); } Query Query::operator<(const Query& rhs) const { return Query(*this, rhs, OP_LT); } Query Query::operator<=(const Query& rhs) const { return Query(*this, rhs, OP_LE); } Query Query::operator>(const Query& rhs) const { return Query(*this, rhs, OP_GT); } Query Query::operator>=(const Query& rhs) const { return Query(*this, rhs, OP_GE); } Query Query::operator&&(const Query& rhs) const { return Query(*this, rhs, OP_AND); } Query Query::operator||(const Query& rhs) const { return Query(*this, rhs, OP_OR); } Query Query::operator!() const { return Query(*this, OP_NOT); } Query Query::operator+(const Query& rhs) const { return Query(*this, rhs, OP_ADD); } Query Query::operator-(const Query& rhs) const { return Query(*this, rhs, OP_SUB); } Query Query::operator*(const Query& rhs) const { return Query(*this, rhs, OP_MUL); } Query Query::operator/(const Query& rhs) const { return Query(*this, rhs, OP_DIV); } Query Query::operator%(const Query& rhs) const { return Query(*this, rhs, OP_MOD); } Query Query::operator-() const { return Query(*this, OP_NEGATE); } Query::Query(const Query& left, const Query& right, Operator binary_op) : operator_(binary_op), left_(new QueryNode(left)), right_(new QueryNode(right)), member_(EVENT_INVALID), number_(0) { type_ = (binary_op < OP_ADD ? QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); } Query::Query(const Query& left, Operator unary_op) : operator_(unary_op), left_(new QueryNode(left)), member_(EVENT_INVALID), number_(0) { type_ = (unary_op < OP_ADD ? QUERY_BOOLEAN_OPERATOR : QUERY_ARITHMETIC_OPERATOR); } namespace { // Search |events| for |query| and add matches to |output|. size_t FindMatchingEvents(const std::vector& events, const Query& query, TraceEventVector* output, bool ignore_metadata_events) { for (const auto& i : events) { if (ignore_metadata_events && i.phase == TRACE_EVENT_PHASE_METADATA) continue; if (query.Evaluate(i)) output->push_back(&i); } return output->size(); } bool ParseEventsFromJson(const std::string& json, std::vector* output) { absl::optional root = base::JSONReader::Read(json); if (!root) return false; base::Value::List* list = nullptr; if (root->is_list()) { list = &root->GetList(); } else if (root->is_dict()) { list = root->GetDict().FindList("traceEvents"); } if (!list) return false; for (const auto& item : *list) { TraceEvent event; if (!event.SetFromJSON(&item)) return false; output->push_back(std::move(event)); } return true; } } // namespace // TraceAnalyzer TraceAnalyzer::TraceAnalyzer() : ignore_metadata_events_(false), allow_association_changes_(true) {} TraceAnalyzer::~TraceAnalyzer() = default; // static std::unique_ptr TraceAnalyzer::Create( const std::string& json_events) { std::unique_ptr analyzer(new TraceAnalyzer()); if (analyzer->SetEvents(json_events)) return analyzer; return nullptr; } bool TraceAnalyzer::SetEvents(const std::string& json_events) { raw_events_.clear(); if (!ParseEventsFromJson(json_events, &raw_events_)) return false; base::ranges::stable_sort(raw_events_); ParseMetadata(); return true; } void TraceAnalyzer::AssociateBeginEndEvents() { using trace_analyzer::Query; Query begin(Query::EventPhaseIs(TRACE_EVENT_PHASE_BEGIN)); Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_END)); Query match(Query::EventName() == Query::OtherName() && Query::EventCategory() == Query::OtherCategory() && Query::EventTid() == Query::OtherTid() && Query::EventPid() == Query::OtherPid()); AssociateEvents(begin, end, match); } void TraceAnalyzer::AssociateAsyncBeginEndEvents(bool match_pid) { using trace_analyzer::Query; Query begin( Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_BEGIN) || Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) || Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST)); Query end(Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_END) || Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_INTO) || Query::EventPhaseIs(TRACE_EVENT_PHASE_ASYNC_STEP_PAST)); Query match(Query::EventCategory() == Query::OtherCategory() && Query::EventId() == Query::OtherId()); if (match_pid) { match = match && Query::EventPid() == Query::OtherPid(); } AssociateEvents(begin, end, match); } void TraceAnalyzer::AssociateEvents(const Query& first, const Query& second, const Query& match) { DCHECK(allow_association_changes_) << "AssociateEvents not allowed after FindEvents"; // Search for matching begin/end event pairs. When a matching end is found, // it is associated with the begin event. std::vector begin_stack; for (auto& this_event : raw_events_) { if (second.Evaluate(this_event)) { // Search stack for matching begin, starting from end. for (int stack_index = static_cast(begin_stack.size()) - 1; stack_index >= 0; --stack_index) { TraceEvent& begin_event = *begin_stack[stack_index]; // Temporarily set other to test against the match query. const TraceEvent* other_backup = begin_event.other_event; begin_event.other_event = &this_event; if (match.Evaluate(begin_event)) { // Found a matching begin/end pair. // Set the associated previous event this_event.prev_event = &begin_event; // Erase the matching begin event index from the stack. begin_stack.erase(begin_stack.begin() + stack_index); break; } // Not a match, restore original other and continue. begin_event.other_event = other_backup; } } // Even if this_event is a |second| event that has matched an earlier // |first| event, it can still also be a |first| event and be associated // with a later |second| event. if (first.Evaluate(this_event)) { begin_stack.push_back(&this_event); } } } void TraceAnalyzer::MergeAssociatedEventArgs() { for (auto& i : raw_events_) { // Merge all associated events with the first event. const TraceEvent* other = i.other_event; // Avoid looping by keeping set of encountered TraceEvents. std::set encounters; encounters.insert(&i); while (other && encounters.find(other) == encounters.end()) { encounters.insert(other); i.arg_numbers.insert(other->arg_numbers.begin(), other->arg_numbers.end()); i.arg_strings.insert(other->arg_strings.begin(), other->arg_strings.end()); other = other->other_event; } } } size_t TraceAnalyzer::FindEvents(const Query& query, TraceEventVector* output) { allow_association_changes_ = false; output->clear(); return FindMatchingEvents( raw_events_, query, output, ignore_metadata_events_); } const TraceEvent* TraceAnalyzer::FindFirstOf(const Query& query) { TraceEventVector output; if (FindEvents(query, &output) > 0) return output.front(); return nullptr; } const TraceEvent* TraceAnalyzer::FindLastOf(const Query& query) { TraceEventVector output; if (FindEvents(query, &output) > 0) return output.back(); return nullptr; } const std::string& TraceAnalyzer::GetThreadName( const TraceEvent::ProcessThreadID& thread) { // If thread is not found, just add and return empty string. return thread_names_[thread]; } void TraceAnalyzer::ParseMetadata() { for (const auto& this_event : raw_events_) { // Check for thread name metadata. if (this_event.phase != TRACE_EVENT_PHASE_METADATA || this_event.name != "thread_name") continue; std::map::const_iterator string_it = this_event.arg_strings.find("name"); if (string_it != this_event.arg_strings.end()) thread_names_[this_event.thread] = string_it->second; } } // Utility functions for collecting process-local traces and creating a // |TraceAnalyzer| from the result. void Start(const std::string& category_filter_string) { DCHECK(!base::trace_event::TraceLog::GetInstance()->IsEnabled()); base::trace_event::TraceLog::GetInstance()->SetEnabled( base::trace_event::TraceConfig(category_filter_string, ""), base::trace_event::TraceLog::RECORDING_MODE); } std::unique_ptr Stop() { DCHECK(base::trace_event::TraceLog::GetInstance()->IsEnabled()); base::trace_event::TraceLog::GetInstance()->SetDisabled(); base::trace_event::TraceResultBuffer buffer; base::trace_event::TraceResultBuffer::SimpleOutput trace_output; buffer.SetOutputCallback(trace_output.GetCallback()); base::RunLoop run_loop; buffer.Start(); base::trace_event::TraceLog::GetInstance()->Flush( base::BindRepeating(&OnTraceDataCollected, run_loop.QuitClosure(), base::Unretained(&buffer))); run_loop.Run(); buffer.Finish(); return TraceAnalyzer::Create(trace_output.json_output); } // TraceEventVector utility functions. bool GetRateStats(const TraceEventVector& events, RateStats* stats, const RateStatsOptions* options) { DCHECK(stats); // Need at least 3 events to calculate rate stats. const size_t kMinEvents = 3; if (events.size() < kMinEvents) { LOG(ERROR) << "Not enough events: " << events.size(); return false; } std::vector deltas; size_t num_deltas = events.size() - 1; for (size_t i = 0; i < num_deltas; ++i) { double delta = events.at(i + 1)->timestamp - events.at(i)->timestamp; if (delta < 0.0) { LOG(ERROR) << "Events are out of order"; return false; } deltas.push_back(delta); } base::ranges::sort(deltas); if (options) { if (options->trim_min + options->trim_max > events.size() - kMinEvents) { LOG(ERROR) << "Attempt to trim too many events"; return false; } deltas.erase(deltas.begin(), deltas.begin() + options->trim_min); deltas.erase(deltas.end() - options->trim_max, deltas.end()); } num_deltas = deltas.size(); double delta_sum = 0.0; for (size_t i = 0; i < num_deltas; ++i) delta_sum += deltas[i]; stats->min_us = *base::ranges::min_element(deltas); stats->max_us = *base::ranges::max_element(deltas); stats->mean_us = delta_sum / static_cast(num_deltas); double sum_mean_offsets_squared = 0.0; for (size_t i = 0; i < num_deltas; ++i) { double offset = fabs(deltas[i] - stats->mean_us); sum_mean_offsets_squared += offset * offset; } stats->standard_deviation_us = sqrt(sum_mean_offsets_squared / static_cast(num_deltas - 1)); return true; } bool FindFirstOf(const TraceEventVector& events, const Query& query, size_t position, size_t* return_index) { DCHECK(return_index); for (size_t i = position; i < events.size(); ++i) { if (query.Evaluate(*events[i])) { *return_index = i; return true; } } return false; } bool FindLastOf(const TraceEventVector& events, const Query& query, size_t position, size_t* return_index) { DCHECK(return_index); for (size_t i = std::min(position + 1, events.size()); i != 0; --i) { if (query.Evaluate(*events[i - 1])) { *return_index = i - 1; return true; } } return false; } bool FindClosest(const TraceEventVector& events, const Query& query, size_t position, size_t* return_closest, size_t* return_second_closest) { DCHECK(return_closest); if (events.empty() || position >= events.size()) return false; size_t closest = events.size(); size_t second_closest = events.size(); for (size_t i = 0; i < events.size(); ++i) { if (!query.Evaluate(*events.at(i))) continue; if (closest == events.size()) { closest = i; continue; } if (fabs(events.at(i)->timestamp - events.at(position)->timestamp) < fabs(events.at(closest)->timestamp - events.at(position)->timestamp)) { second_closest = closest; closest = i; } else if (second_closest == events.size()) { second_closest = i; } } if (closest < events.size() && (!return_second_closest || second_closest < events.size())) { *return_closest = closest; if (return_second_closest) *return_second_closest = second_closest; return true; } return false; } size_t CountMatches(const TraceEventVector& events, const Query& query, size_t begin_position, size_t end_position) { if (begin_position >= events.size()) return 0u; end_position = (end_position < events.size()) ? end_position : events.size(); size_t count = 0u; for (size_t i = begin_position; i < end_position; ++i) { if (query.Evaluate(*events.at(i))) ++count; } return count; } } // namespace trace_analyzer