Chromium Code Reviews| Index: base/debug/trace_event_test_utils.h |
| diff --git a/base/debug/trace_event_test_utils.h b/base/debug/trace_event_test_utils.h |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..238b92c815b3d500b9cba82dffb878e182558ec8 |
| --- /dev/null |
| +++ b/base/debug/trace_event_test_utils.h |
| @@ -0,0 +1,401 @@ |
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
|
nduca
2011/10/11 20:33:37
trace_event_analyzer.h
move tests of the analyzer
jbates
2011/10/12 22:35:20
Done.
|
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +// Use trace::Query with TraceAnalyzer to search for specific trace events. |
|
nduca
2011/10/11 20:33:37
Advertise the namespace that these are all in? I h
jbates
2011/10/12 22:35:20
Done.
|
| +// Queries can be combined using boolean and comparison operators. |
| +// |
| +// Construct TraceAnalyzer with the json trace string retrieved from TraceLog: |
| +// TraceAnalyzer analyzer(json_events); |
|
nduca
2011/10/11 20:33:37
What happens if it fails to parse? Maybe a TraceAn
jbates
2011/10/12 22:35:20
This should be okay, because the parse would only
|
| +// ConstEventPtrVector events; |
|
nduca
2011/10/11 20:33:37
A blurb explaining the basic idea of trace analysi
jbates
2011/10/12 22:35:20
Done.
|
| +// |
| +// To find begin events called "my_event" with duration > 1 second: |
| +// Query q = (Query(EVENT_NAME) == "my_event" && |
| +// Query(EVENT_PHASE) == TRACE_EVENT_PHASE_BEGIN && |
| +// Query(EVENT_DURATION) > 1000000.0); |
| +// analyzer.FindEvents(q, &events); |
| +// |
| +// By default, only begin/end events on the same thread will ever be associated. |
| +// If the test needs to analyze events that begin and end on different threads, |
|
nduca
2011/10/11 20:33:37
Recapping our verbal discussion, I think should ta
jbates
2011/10/12 22:35:20
Sounds good. On second look though, the trace_even
|
| +// it can specify custom assocations. The typical procedure is to include a |
| +// unique ID as one of the TRACE_EVENT arguments that only matches a single |
| +// begin/end pair across all Chrome processes and threads. |
| +// |
| +// For example: |
| +// [Thread 1] |
| +// TRACE_EVENT_BEGIN1("test_latency", "timing1", "id", 3); |
| +// [Later, on Thread 2] |
| +// TRACE_EVENT_END1("test_latency", "timing1", "id", 3); |
|
nduca
2011/10/11 20:33:37
This trace wont' even parse in the trace_event_vie
jbates
2011/10/12 22:35:20
Fixed the example to use TRACE_EVENT_INSTANT, whic
|
| +// Then, to associate these begin/end pairs: |
| +// Query begin(Query(EVENT_PHASE) == TRACE_EVENT_PHASE_BEGIN); |
| +// Query end(Query(EVENT_PHASE) == TRACE_EVENT_PHASE_END); |
| +// Query match(Query(EVENT_NAME) == Query(OTHER_NAME) && |
| +// Query(EVENT_ARG, "id") == Query(OTHER_ARG, "id")); |
| +// analyzer.AssociateEvents(begin, end, match); |
|
nduca
2011/10/11 20:33:37
Based on the first example, I was hoping we'd see
jbates
2011/10/12 22:35:20
You're right, it should have been there... Done.
|
| +// Then you can search for "timing1" events and evaluate their durations. |
| + |
| + |
| +#ifndef BASE_DEBUG_TRACE_EVENT_TEST_UTILS_H_ |
| +#define BASE_DEBUG_TRACE_EVENT_TEST_UTILS_H_ |
| +#pragma once |
| + |
| +#include <map> |
| + |
| +#include "base/debug/trace_event.h" |
| + |
| +namespace base { |
| +class Value; |
| + |
| +namespace debug { |
| + |
| +namespace trace { |
| +class QueryNode; |
| +} |
| + |
| +// TestTraceEvent is a more convenient form of the TraceEvent class to make |
| +// tracing-based tests easier to write. |
| +struct TestTraceEvent { |
|
nduca
2011/10/11 20:33:37
I'm confused about base::trace vs base::debug::tra
jbates
2011/10/12 22:35:20
I've been thinking it would be great to move traci
|
| + // PidTid contains a Process ID and Thread ID. |
| + struct PidTid { |
| + PidTid() : pid(0), tid(0) {} |
| + PidTid(int pid, int tid) : pid(pid), tid(tid) {} |
| + bool operator< (PidTid rhs) const { |
| + if (pid != rhs.pid) |
| + return pid < rhs.pid; |
| + return tid < rhs.tid; |
| + } |
| + int pid; |
| + int tid; |
| + }; |
| + |
| + TestTraceEvent(); |
| + TestTraceEvent(const base::Value* event_value); |
| + ~TestTraceEvent(); |
| + |
| + // Convert JSON string to array of TestTraceEvent. |
| + // |output| is appended with the parsed events. |
| + static bool ParseEventsFromJson(const std::string& json, |
|
nduca
2011/10/11 20:33:37
Makes more sense on traceanalyzer?
jbates
2011/10/12 22:35:20
This is only dependent on TestTraceEvent, so I thi
|
| + std::vector<TestTraceEvent>* output); |
| + |
| + bool operator< (const TestTraceEvent& rhs) const { |
| + return timestamp < rhs.timestamp; |
| + } |
| + |
| + // Returns duration if it's available. |
|
nduca
2011/10/11 20:33:37
Add a bool has_other_event() const;
Modify commen
jbates
2011/10/12 22:35:20
Done.
|
| + bool GetDuration(double* duration) const; |
| + |
| + // Return the argument value if it exists. |
| + bool IsArg(const std::string& name) const; |
|
nduca
2011/10/11 20:33:37
HasArg
jbates
2011/10/12 22:35:20
Removed it - it wasn't used anywhere :)
|
| + // Return the argument value if it exists and it is a string. |
| + bool GetArgAsString(const std::string& name, std::string* arg) const; |
| + // Return the argument value if it exists and it is a number. |
| + bool GetArgAsNumber(const std::string& name, double* arg) const; |
| + |
| + // Called by TraceAnalyzer to associate this event with another event. |
|
nduca
2011/10/11 20:33:37
Use friending so that this isn't visible?
jbates
2011/10/12 22:35:20
Also removed, it was unnecessary.
|
| + void set_other_event(const TestTraceEvent* event) { |
| + other_event = event; |
| + } |
| + |
| + // Process ID and Thread ID. |
|
nduca
2011/10/11 20:33:37
Are these really supposed to be public? Makes me a
jbates
2011/10/12 22:35:20
This is for tests, so I think it's best to keep it
|
| + PidTid pid_tid; |
| + // Time since epoch in microseconds. |
| + // Stored as double to match its JSON representation. |
| + double timestamp; |
| + TraceEventPhase phase; |
| + std::string category; |
| + std::string name; |
| + // All numbers and bool values from TraceEvent args are cast to double. |
| + // bool becomes 1.0 (true) or 0.0 (false). |
| + std::map<std::string, double> arg_numbers; |
| + std::map<std::string, std::string> arg_strings; |
| + // The other event associated with this event (or NULL). |
| + const TestTraceEvent* other_event; |
| +}; |
| + |
| +// By using the trace namespace, tests can use short terms like "Query". |
| +namespace trace { |
| + |
| +// Pass these values to Query to compare with the corresponding member of a |
| +// TestTraceEvent. |
| +enum TraceEventMember { |
| + EVENT_INVALID, |
| + // Use these to access the event members: |
| + EVENT_PID, |
| + EVENT_TID, |
| + // Return the timestamp of the event in microseconds since epoch. |
| + EVENT_TIME, |
| + // Return the duration of an event in seconds. |
| + // Only works for events with associated BEGIN/END: Query(OTHER_EXISTS). |
| + EVENT_DURATION, |
| + EVENT_PHASE, |
| + EVENT_CATEGORY, |
| + EVENT_NAME, |
| + EVENT_HAS_ARG, |
| + EVENT_ARG, |
| + // Return true if associated event exists. |
| + // (Typically BEGIN for END or END for BEGIN). |
| + EVENT_HAS_OTHER, |
| + // Use these to access the associated event's members: |
| + OTHER_PID, |
| + OTHER_TID, |
| + OTHER_TIME, |
| + OTHER_PHASE, |
| + OTHER_CATEGORY, |
| + OTHER_NAME, |
| + OTHER_HAS_ARG, |
| + OTHER_ARG |
| +}; |
| + |
| +class Query { |
| + public: |
| + // Compare with the given member. |
| + Query(TraceEventMember member); |
| + |
| + // Compare with the given member argument value. |
| + Query(TraceEventMember member, const std::string& arg_name); |
| + |
| + // Compare with the given string. |
| + Query(const std::string& str); |
| + Query(const char* str); |
| + |
| + // Compare with the given number. |
| + Query(double num); |
| + Query(float num); |
| + Query(int num); |
| + Query(uint32 num); |
| + |
| + // Compare with the given bool. |
| + Query(bool boolean); |
| + |
| + // Compare with the given phase. |
| + Query(TraceEventPhase phase); |
| + |
| + Query(const Query& query); |
| + |
| + ~Query(); |
| + |
| + // Compare with the given string pattern. Only works with == and != operators. |
| + // Example: Query(EVENT_NAME) == Query::Pattern("bla_*") |
| + static Query Pattern(const std::string& pattern); |
| + |
| + // Common queries: |
| + |
| + // Find BEGIN events that have a corresponding END event. |
| + static Query MatchBeginWithEnd() { |
| + return (Query(EVENT_PHASE) == TRACE_EVENT_PHASE_BEGIN) && |
| + Query(EVENT_HAS_OTHER); |
| + } |
| + |
| + // Find END events that have a corresponding BEGIN event. |
| + static Query MatchEndWithBegin() { |
| + return (Query(EVENT_PHASE) == TRACE_EVENT_PHASE_END) && |
| + Query(EVENT_HAS_OTHER); |
| + } |
| + |
| + // Find BEGIN events of given |name| which also have associated END events. |
| + static Query MatchBeginName(const std::string& name) { |
| + return (Query(EVENT_NAME) == name) && MatchBeginWithEnd(); |
| + } |
| + |
| + // Match given Process ID and Thread ID. |
| + static Query MatchPidTid(base::debug::TestTraceEvent::PidTid pid_tid) { |
| + return (Query(EVENT_PID) == pid_tid.pid) && |
| + (Query(EVENT_TID) == pid_tid.tid); |
| + } |
| + |
| + // Match BEGIN/END event pair that spans multiple threads. |
| + static Query MatchCrossPidTid() { |
| + return (Query(EVENT_PID) != Query(OTHER_PID)) || |
| + (Query(EVENT_TID) != Query(OTHER_TID)); |
| + } |
| + |
| + // Boolean operators: |
| + Query operator==(const Query& rhs) const; |
| + Query operator!=(const Query& rhs) const; |
| + Query operator< (const Query& rhs) const; |
| + Query operator<=(const Query& rhs) const; |
| + Query operator> (const Query& rhs) const; |
| + Query operator>=(const Query& rhs) const; |
| + Query operator&&(const Query& rhs) const; |
| + Query operator||(const Query& rhs) const; |
| + Query operator! () const; |
| + |
| + // Arithmetic operators: |
| + Query operator+(const Query& rhs) const; |
| + Query operator-(const Query& rhs) const; |
| + Query operator*(const Query& rhs) const; |
| + Query operator/(const Query& rhs) const; |
| + Query operator%(const Query& rhs) const; |
| + Query operator-() const; |
| + |
| + // Return true if the given event matches this query tree. |
| + // This is a recursive method that walks the query tree. |
| + bool Evaluate(const TestTraceEvent& event) const; |
| + |
| + private: |
| + enum Operator { |
| + OP_INVALID, |
| + // Boolean operators: |
| + OP_EQ, |
| + OP_NE, |
| + OP_LT, |
| + OP_LE, |
| + OP_GT, |
| + OP_GE, |
| + OP_AND, |
| + OP_OR, |
| + OP_NOT, |
| + // Arithmetic operators: |
| + OP_ADD, |
| + OP_SUB, |
| + OP_MUL, |
| + OP_DIV, |
| + OP_MOD, |
| + OP_NEGATE |
| + }; |
| + |
| + enum QueryType { |
| + QUERY_BOOLEAN_OPERATOR, |
| + QUERY_ARITHMETIC_OPERATOR, |
| + QUERY_EVENT_MEMBER, |
| + QUERY_NUMBER, |
| + QUERY_STRING |
| + }; |
| + |
| + // Construct a boolean Query that returns (left <binary_op> right). |
| + Query(const Query& left, const Query& right, Operator binary_op); |
| + |
| + // Construct a boolean Query that returns (<binary_op> left). |
| + Query(const Query& left, Operator unary_op); |
| + |
| + // Try to compare left_ against right_ based on operator_. |
| + // If either left or right does not convert to double, false is returned. |
| + // Otherwise, true is returned and |result| is set to the comparison result. |
| + bool CompareAsDouble(const TestTraceEvent& event, bool* result) const; |
| + |
| + // Try to compare left_ against right_ based on operator_. |
| + // If either left or right does not convert to string, false is returned. |
| + // Otherwise, true is returned and |result| is set to the comparison result. |
| + bool CompareAsString(const TestTraceEvent& event, bool* result) const; |
| + |
| + // Attempt to convert this Query to a double. On success, true is returned |
| + // and the double value is stored in |num|. |
| + bool GetAsDouble(const TestTraceEvent& event, double* num) const; |
| + |
| + // Attempt to convert this Query to a string. On success, true is returned |
| + // and the string value is stored in |str|. |
| + bool GetAsString(const TestTraceEvent& event, std::string* str) const; |
| + |
| + // Evaluate this Query as an arithmetic operator on left_ and right_. |
| + bool EvaluateArithmeticOperator(const TestTraceEvent& event, |
| + double* num) const; |
| + |
| + // For QUERY_EVENT_MEMBER Query: attempt to get the value of the Query. |
| + // The TraceValue will either be TRACE_TYPE_DOUBLE, TRACE_TYPE_STRING, |
| + // or if requested member does not exist, it will be TRACE_TYPE_UNDEFINED. |
| + TraceValue GetMemberValue(const TestTraceEvent& event) const; |
| + |
| + // Does this Query represent a value? |
| + bool is_value() const { return type_ != QUERY_BOOLEAN_OPERATOR; } |
| + |
| + bool is_unary_operator() const { |
| + return operator_ == OP_NOT || operator_ == OP_NEGATE; |
| + } |
| + |
| + const Query& left() const; |
| + const Query& right() const; |
| + |
| + QueryType type_; |
| + Operator operator_; |
| + scoped_refptr<QueryNode> left_; |
| + scoped_refptr<QueryNode> right_; |
| + TraceEventMember member_; |
| + double number_; |
| + std::string string_; |
| + bool is_pattern_; |
| +}; |
| + |
| +// QueryNode allows Query to store a ref-counted query tree. |
| +class QueryNode : public RefCounted<QueryNode> { |
| + public: |
| + QueryNode(const Query& query); |
| + const Query& query() const { return query_; } |
| + |
| + private: |
| + friend class RefCounted<QueryNode>; |
| + ~QueryNode(); |
| + |
| + Query query_; |
| +}; |
| + |
| +} // namespace trace |
| + |
| +// TraceAnalyzer is designed to make tracing-based tests easier to write. |
| +class TraceAnalyzer { |
| + public: |
| + typedef std::vector<base::debug::TestTraceEvent> EventVector; |
| + typedef std::vector<const base::debug::TestTraceEvent*> ConstEventPtrVector; |
| + |
| + TraceAnalyzer(); |
| + TraceAnalyzer(const std::string& json_events); |
| + TraceAnalyzer(const EventVector& events); |
| + ~TraceAnalyzer(); |
| + |
| + // Replace all events with |json_events|. |
| + void SetEvents(const std::string& json_events); |
|
nduca
2011/10/11 20:33:37
Why this rather than creating another analyzer?
jbates
2011/10/12 22:35:20
The code is about the same either way - may as wel
|
| + // Replace all events with |events|. |
| + void SetEvents(const EventVector& events); |
| + |
| + // SetEvents calls this internally to match up typical begin/end pairs of |
| + // events. This allows Query(OTHER_*) to access the associated event and |
| + // enables Query(EVENT_DURATION). |
| + // By default, an end event will match the most recent begin event with the |
| + // same name, category, process ID and thread ID. |
| + void SetDefaultAssociations(); |
|
nduca
2011/10/11 20:33:37
Confused what this does. Does an end-user writing
jbates
2011/10/12 22:35:20
For now I think it makes sense to keep it availabl
|
| + |
| + // Clear existing event associations. |
| + void ClearAssociations(); |
|
nduca
2011/10/11 20:33:37
Same here... what does this do? Is the idea here t
jbates
2011/10/12 22:35:20
Correct -- AssociateEvents can be called multiple
|
| + |
| + // By default, matching begin and end events are associated with each other as |
| + // described in SetDefaultAssociations. |
| + // AssociateEvents can be used to customize begin/end event associations. |
| + // The only assumption is that end events occur after begin events. |
| + // |
| + // |begin| - Eligible begin events match this query. |
| + // |end| - Eligible end events match this query. |
| + // |match| - This query is run on the begin event. The OTHER event members |
| + // will point to an eligible end event. The query should evaluate to |
| + // true if the begin/end pair is a match. |
| + // |
| + // When a match is found, the pair will be associated by having their |
| + // other_event member point to each other. Non-matching events are left with |
| + // their existing assocations, so you may also want to call ClearAssociations. |
| + void AssociateEvents(const trace::Query& begin, |
| + const trace::Query& end, |
| + const trace::Query& match); |
| + |
|
nduca
2011/10/11 20:33:37
If we had B/E only work on threads, always using
jbates
2011/10/12 22:35:20
It might, but as we discussed, I'd like to start s
|
| + const EventVector& events() { return raw_events_; } |
| + |
| + const std::string& GetThreadName(const base::debug::TestTraceEvent::PidTid& |
| + pid_tid); |
| + |
| + // Find all events that match query and replace output vector. |
| + size_t FindEvents(const trace::Query& query, |
| + ConstEventPtrVector* output) const; |
| + |
| + // Helper method: find first event that matches query |
| + const base::debug::TestTraceEvent* FindEvent( |
| + const trace::Query& query) const; |
| + |
| + private: |
| + // Read metadata (thread names, etc) from events. |
| + void ParseMetadata(); |
| + |
| + std::map<base::debug::TestTraceEvent::PidTid, std::string> thread_names_; |
| + EventVector raw_events_; |
| +}; |
| + |
| +} // namespace debug |
| +} // namespace base |
| + |
| +#endif // BASE_DEBUG_TRACE_EVENT_TEST_UTILS_H_ |