blob: 69c3860b1fa509363f217ad0f2a2f3e455ee184d [file] [log] [blame]
// 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