| // Copyright 2012 The Chromium Authors |
| // 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 <math.h> |
| |
| #include <algorithm> |
| #include <set> |
| |
| #include "base/functional/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<base::RefCountedString>& 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::DICT"; |
| 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<int> 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<int> 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<double> 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<double> 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<double> maybe_thread_duration = event_dict.FindDouble("tdur"); |
| if (maybe_thread_duration) { |
| thread_duration = *maybe_thread_duration; |
| } |
| absl::optional<double> 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<bool> maybe_flow_out = event_dict.FindBool("flow_out"); |
| if (maybe_flow_out) { |
| flow_out = *maybe_flow_out; |
| } |
| absl::optional<bool> 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<double>(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<int>(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<double>(num)); |
| } |
| |
| Query Query::Uint(uint32_t num) { |
| return Query(static_cast<double>(num)); |
| } |
| |
| Query Query::Bool(bool boolean) { |
| return Query(boolean ? 1.0 : 0.0); |
| } |
| |
| Query Query::Phase(char phase) { |
| return Query(static_cast<double>(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<double>(static_cast<int64_t>(lhs) % |
| static_cast<int64_t>(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<double>(the_event->thread.process_id); |
| return true; |
| case EVENT_TID: |
| case OTHER_TID: |
| case PREV_TID: |
| *num = static_cast<double>(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<double>(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<TraceEvent>& 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<TraceEvent>* output) { |
| absl::optional<base::Value> 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> TraceAnalyzer::Create( |
| const std::string& json_events) { |
| std::unique_ptr<TraceAnalyzer> 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<TraceEvent*> 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<int>(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<const TraceEvent*> 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<std::string, std::string>::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<TraceAnalyzer> 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<double> 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<double>(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<double>(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 |