diff options
Diffstat (limited to 'base/test/trace_event_analyzer.cc')
-rw-r--r-- | base/test/trace_event_analyzer.cc | 1069 |
1 files changed, 1069 insertions, 0 deletions
diff --git a/base/test/trace_event_analyzer.cc b/base/test/trace_event_analyzer.cc new file mode 100644 index 0000000000..cab2899613 --- /dev/null +++ b/base/test/trace_event_analyzer.cc @@ -0,0 +1,1069 @@ +// 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 <math.h> + +#include <algorithm> +#include <set> + +#include "base/json/json_reader.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted_memory.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" + +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), + timestamp(0), + duration(0), + phase(TRACE_EVENT_PHASE_BEGIN), + other_event(nullptr) {} + +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->type() != base::Value::Type::DICTIONARY) { + LOG(ERROR) << "Value must be Type::DICTIONARY"; + return false; + } + const base::DictionaryValue* dictionary = + static_cast<const base::DictionaryValue*>(event_value); + + std::string phase_str; + const base::DictionaryValue* args = nullptr; + + if (!dictionary->GetString("ph", &phase_str)) { + LOG(ERROR) << "ph is missing from TraceEvent JSON"; + return false; + } + + phase = *phase_str.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_ENTER_CONTEXT || + phase == TRACE_EVENT_PHASE_LEAVE_CONTEXT || + 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 && !dictionary->GetInteger("pid", &thread.process_id)) { + LOG(ERROR) << "pid is missing from TraceEvent JSON"; + return false; + } + if (require_origin && !dictionary->GetInteger("tid", &thread.thread_id)) { + LOG(ERROR) << "tid is missing from TraceEvent JSON"; + return false; + } + if (require_origin && !dictionary->GetDouble("ts", ×tamp)) { + LOG(ERROR) << "ts is missing from TraceEvent JSON"; + return false; + } + if (may_have_duration) { + dictionary->GetDouble("dur", &duration); + } + if (!dictionary->GetString("cat", &category)) { + LOG(ERROR) << "cat is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetString("name", &name)) { + LOG(ERROR) << "name is missing from TraceEvent JSON"; + return false; + } + if (!dictionary->GetDictionary("args", &args)) { + LOG(ERROR) << "args is missing from TraceEvent JSON"; + return false; + } + if (require_id && !dictionary->GetString("id", &id)) { + LOG(ERROR) << "id is missing from ASYNC_BEGIN/ASYNC_END TraceEvent JSON"; + return false; + } + + dictionary->GetDouble("tdur", &thread_duration); + dictionary->GetDouble("tts", &thread_timestamp); + dictionary->GetString("scope", &scope); + dictionary->GetString("bind_id", &bind_id); + dictionary->GetBoolean("flow_out", &flow_out); + dictionary->GetBoolean("flow_in", &flow_in); + + const base::DictionaryValue* id2; + if (dictionary->GetDictionary("id2", &id2)) { + id2->GetString("global", &global_id2); + id2->GetString("local", &local_id2); + } + + // For each argument, copy the type and create a trace_analyzer::TraceValue. + for (base::DictionaryValue::Iterator it(*args); !it.IsAtEnd(); + it.Advance()) { + std::string str; + bool boolean = false; + int int_num = 0; + double double_num = 0.0; + if (it.value().GetAsString(&str)) { + arg_strings[it.key()] = str; + } else if (it.value().GetAsInteger(&int_num)) { + arg_numbers[it.key()] = static_cast<double>(int_num); + } else if (it.value().GetAsBoolean(&boolean)) { + arg_numbers[it.key()] = static_cast<double>(boolean ? 1 : 0); + } else if (it.value().GetAsDouble(&double_num)) { + arg_numbers[it.key()] = double_num; + } + // Record all arguments as values. + arg_values[it.key()] = it.value().CreateDeepCopy(); + } + + return true; +} + +double TraceEvent::GetAbsTimeToOtherEvent() const { + return fabs(other_event->timestamp - timestamp); +} + +bool TraceEvent::GetArgAsString(const std::string& name, + std::string* arg) const { + const auto it = arg_strings.find(name); + if (it != arg_strings.end()) { + *arg = it->second; + return true; + } + return false; +} + +bool TraceEvent::GetArgAsNumber(const std::string& name, + double* arg) const { + const auto it = arg_numbers.find(name); + if (it != arg_numbers.end()) { + *arg = it->second; + return true; + } + return false; +} + +bool TraceEvent::GetArgAsValue(const std::string& name, + std::unique_ptr<base::Value>* arg) const { + const auto it = arg_values.find(name); + if (it != arg_values.end()) { + *arg = it->second->CreateDeepCopy(); + return true; + } + return false; +} + +bool TraceEvent::HasStringArg(const std::string& name) const { + return (arg_strings.find(name) != arg_strings.end()); +} + +bool TraceEvent::HasNumberArg(const std::string& name) const { + return (arg_numbers.find(name) != arg_numbers.end()); +} + +bool TraceEvent::HasArg(const std::string& name) const { + return (arg_values.find(name) != arg_values.end()); +} + +std::string TraceEvent::GetKnownArgAsString(const std::string& name) const { + std::string arg_string; + bool result = GetArgAsString(name, &arg_string); + DCHECK(result); + return arg_string; +} + +double TraceEvent::GetKnownArgAsDouble(const std::string& name) const { + double arg_double = 0; + bool result = GetArgAsNumber(name, &arg_double); + DCHECK(result); + return arg_double; +} + +int TraceEvent::GetKnownArgAsInt(const std::string& name) const { + double arg_double = 0; + bool result = GetArgAsNumber(name, &arg_double); + DCHECK(result); + return static_cast<int>(arg_double); +} + +bool TraceEvent::GetKnownArgAsBool(const std::string& name) const { + double arg_double = 0; + bool result = GetArgAsNumber(name, &arg_double); + DCHECK(result); + return (arg_double != 0.0); +} + +std::unique_ptr<base::Value> TraceEvent::GetKnownArgAsValue( + const std::string& name) const { + std::unique_ptr<base::Value> arg_value; + bool result = GetArgAsValue(name, &arg_value); + DCHECK(result); + return arg_value; +} + +// 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; + } else if (member >= PREV_FIRST_MEMBER && member <= PREV_LAST_MEMBER) { + return event->prev_event; + } else { + 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. + std::map<std::string, double>::const_iterator 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. + std::map<std::string, std::string>::const_iterator 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 (size_t i = 0; i < events.size(); ++i) { + if (ignore_metadata_events && events[i].phase == TRACE_EVENT_PHASE_METADATA) + continue; + if (query.Evaluate(events[i])) + output->push_back(&events[i]); + } + return output->size(); +} + +bool ParseEventsFromJson(const std::string& json, + std::vector<TraceEvent>* output) { + std::unique_ptr<base::Value> root = base::JSONReader::Read(json); + + base::ListValue* root_list = nullptr; + if (!root.get() || !root->GetAsList(&root_list)) + return false; + + for (size_t i = 0; i < root_list->GetSize(); ++i) { + base::Value* item = nullptr; + if (root_list->Get(i, &item)) { + TraceEvent event; + if (event.SetFromJSON(item)) + output->push_back(std::move(event)); + else + return false; + } + } + + return true; +} + +} // namespace + +// TraceAnalyzer + +TraceAnalyzer::TraceAnalyzer() + : ignore_metadata_events_(false), allow_association_changes_(true) {} + +TraceAnalyzer::~TraceAnalyzer() = default; + +// static +TraceAnalyzer* TraceAnalyzer::Create(const std::string& json_events) { + std::unique_ptr<TraceAnalyzer> analyzer(new TraceAnalyzer()); + if (analyzer->SetEvents(json_events)) + return analyzer.release(); + return nullptr; +} + +bool TraceAnalyzer::SetEvents(const std::string& json_events) { + raw_events_.clear(); + if (!ParseEventsFromJson(json_events, &raw_events_)) + return false; + std::stable_sort(raw_events_.begin(), raw_events_.end()); + 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 (size_t event_index = 0; event_index < raw_events_.size(); + ++event_index) { + + TraceEvent& this_event = raw_events_[event_index]; + + 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 (size_t i = 0; i < raw_events_.size(); ++i) { + // Merge all associated events with the first event. + const TraceEvent* other = raw_events_[i].other_event; + // Avoid looping by keeping set of encountered TraceEvents. + std::set<const TraceEvent*> encounters; + encounters.insert(&raw_events_[i]); + while (other && encounters.find(other) == encounters.end()) { + encounters.insert(other); + raw_events_[i].arg_numbers.insert( + other->arg_numbers.begin(), + other->arg_numbers.end()); + raw_events_[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 (size_t i = 0; i < raw_events_.size(); ++i) { + TraceEvent& this_event = raw_events_[i]; + // 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 base::WrapUnique(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); + } + + std::sort(deltas.begin(), deltas.end()); + + 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 = *std::min_element(deltas.begin(), deltas.end()); + stats->max_us = *std::max_element(deltas.begin(), deltas.end()); + 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 |