blob: ca6d895f8869bb7c9c91de6f45b8a57c8e781b53 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/chromeos/arc/tracing/arc_tracing_graphics_model.h"
#include <inttypes.h>
#include <algorithm>
#include <set>
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/trace_event/common/trace_event_common.h"
#include "chrome/browser/chromeos/arc/tracing/arc_graphics_jank_detector.h"
#include "chrome/browser/chromeos/arc/tracing/arc_tracing_event.h"
#include "chrome/browser/chromeos/arc/tracing/arc_tracing_event_matcher.h"
#include "chrome/browser/chromeos/arc/tracing/arc_tracing_model.h"
#include "components/arc/arc_util.h"
#include "ui/events/types/event_type.h"
namespace arc {
namespace {
using BufferEvent = ArcTracingGraphicsModel::BufferEvent;
using BufferEvents = ArcTracingGraphicsModel::BufferEvents;
using BufferEventType = ArcTracingGraphicsModel::BufferEventType;
constexpr char kCustomTracePrefix[] = "customTrace";
constexpr char kInputEventPrefix[] = "InputEvent: ";
constexpr char kDeliverInputEvent[] = "deliverInputEvent";
constexpr char kUnknownActivity[] = "unknown";
constexpr char kArgumentAppId[] = "app_id";
constexpr char kArgumentBufferId[] = "buffer_id";
constexpr char kArgumentPutOffset[] = "put_offset";
constexpr char kArgumentTimestamp[] = "timestamp";
constexpr char kArgumentType[] = "type";
constexpr char kKeyActivity[] = "activity";
constexpr char kKeyAndroid[] = "android";
constexpr char kKeyBuffers[] = "buffers";
constexpr char kKeyChrome[] = "chrome";
constexpr char kKeyDuration[] = "duration";
constexpr char kKeyGlobalEvents[] = "global_events";
constexpr char kKeyIcon[] = "icon";
constexpr char kKeyInformation[] = "information";
constexpr char kKeyInput[] = "input";
constexpr char kKeyViews[] = "views";
constexpr char kKeyPlatform[] = "platform";
constexpr char kKeySystem[] = "system";
constexpr char kKeyTaskId[] = "task_id";
constexpr char kKeyTimestamp[] = "timestamp";
constexpr char kKeyTitle[] = "title";
constexpr char kAcquireBufferQuery[] =
"android:onMessageReceived/android:handleMessageInvalidate/"
"android:latchBuffer/android:updateTexImage/android:acquireBuffer";
constexpr char kReleaseBufferQuery[] =
"android:onMessageReceived/android:handleMessageRefresh/"
"android:postComposition/android:releaseBuffer";
constexpr char kDequeueBufferQuery[] = "android:dequeueBuffer";
constexpr char kQueueBufferQuery[] = "android:queueBuffer";
constexpr char kInputQuery[] =
"android:Choreographer#doFrame/android:input/android:";
constexpr char kBarrierOrderingSubQuery[] =
"gpu:CommandBufferProxyImpl::OrderingBarrier";
constexpr char kBufferInUseQuery[] = "exo:BufferInUse";
constexpr char kHandleMessageRefreshQuery[] =
"android:onMessageReceived/android:handleMessageRefresh";
constexpr char kHandleMessageInvalidateQuery[] =
"android:onMessageReceived/android:handleMessageInvalidate";
constexpr char kChromeTopEventsQuery[] =
"viz,benchmark:Graphics.Pipeline.DrawAndSwap";
constexpr char kSurfaceFlingerVsyncHandlerQuery[] = "android:HW_VSYNC_0|*";
constexpr char kArcVsyncTimestampQuery[] = "android:ARC_VSYNC|*";
constexpr char kBarrierFlushMatcher[] = "gpu:CommandBufferStub::OnAsyncFlush";
constexpr char kExoSurfaceAttachMatcher[] = "exo:Surface::Attach";
constexpr char kExoSurfaceCommitMatcher[] = "exo:Surface::Commit";
constexpr char kExoBufferProduceResourceMatcher[] =
"exo:Buffer::ProduceTransferableResource";
constexpr char kExoBufferReleaseContentsMatcher[] =
"exo:Buffer::ReleaseContents";
constexpr char kExoInputEventMatcher[] = "exo:Input::OnInputEvent";
constexpr ssize_t kInvalidBufferIndex = -1;
// Helper factory class that produces graphic buffer events from the giving
// |ArcTracingEvent| generic events. Each |ArcTracingEvent| may produce graphics
// event |ArcTracingGraphicsModel::BufferEvent| on start or/and on finish of the
// event |ArcTracingEvent|. This is organized in form of map
// |ArcTracingEventMatcher| to the pair of |BufferEventType| which indicates
// what to generate on start and on finish of the event.
class BufferGraphicsEventMapper {
public:
struct MappingRule {
using EventTimeCallback =
base::RepeatingCallback<uint64_t(const ArcTracingEventMatcher&,
const ArcTracingEvent& event)>;
MappingRule(std::unique_ptr<ArcTracingEventMatcher> matcher,
BufferEventType map_start,
BufferEventType map_finish,
EventTimeCallback start_time_callback = EventTimeCallback())
: matcher(std::move(matcher)),
map_start(map_start),
map_finish(map_finish),
event_start_time_callback(start_time_callback) {}
bool Produce(const ArcTracingEvent& event,
ArcTracingGraphicsModel::BufferEvents* collector) const {
if (!matcher->Match(event))
return false;
if (map_start != BufferEventType::kNone) {
uint64_t start_timestamp = event.GetTimestamp();
if (event_start_time_callback) {
start_timestamp = event_start_time_callback.Run(*matcher, event);
}
collector->push_back(
ArcTracingGraphicsModel::BufferEvent(map_start, start_timestamp));
}
if (map_finish != BufferEventType::kNone) {
collector->push_back(ArcTracingGraphicsModel::BufferEvent(
map_finish, event.GetEndTimestamp()));
}
return true;
}
std::unique_ptr<ArcTracingEventMatcher> matcher;
BufferEventType map_start;
BufferEventType map_finish;
EventTimeCallback event_start_time_callback;
};
using MappingRules = std::vector<MappingRule>;
BufferGraphicsEventMapper() {
// exo rules
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(kExoSurfaceAttachMatcher),
BufferEventType::kExoSurfaceAttach, BufferEventType::kNone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(kExoSurfaceCommitMatcher),
BufferEventType::kNone, BufferEventType::kExoSurfaceCommit));
rules_.emplace_back(MappingRule(std::make_unique<ArcTracingEventMatcher>(
kExoBufferProduceResourceMatcher),
BufferEventType::kExoProduceResource,
BufferEventType::kNone));
rules_.emplace_back(MappingRule(std::make_unique<ArcTracingEventMatcher>(
kExoBufferReleaseContentsMatcher),
BufferEventType::kNone,
BufferEventType::kExoReleased));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>("exo:BufferInUse(step=bound)"),
BufferEventType::kExoBound, BufferEventType::kNone));
rules_.emplace_back(MappingRule(std::make_unique<ArcTracingEventMatcher>(
"exo:BufferInUse(step=pending_query)"),
BufferEventType::kExoPendingQuery,
BufferEventType::kNone));
// gpu rules
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(
"gpu:CommandBufferProxyImpl::OrderingBarrier"),
BufferEventType::kChromeBarrierOrder, BufferEventType::kNone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(kBarrierFlushMatcher),
BufferEventType::kNone, BufferEventType::kChromeBarrierFlush));
// android rules
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(kDequeueBufferQuery),
BufferEventType::kBufferQueueDequeueStart,
BufferEventType::kBufferQueueDequeueDone));
rules_.emplace_back(
MappingRule(std::make_unique<ArcTracingEventMatcher>(kQueueBufferQuery),
BufferEventType::kBufferQueueQueueStart,
BufferEventType::kBufferQueueQueueDone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>("android:acquireBuffer"),
BufferEventType::kBufferQueueAcquire, BufferEventType::kNone));
rules_.push_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>("android:releaseBuffer"),
BufferEventType::kNone, BufferEventType::kBufferQueueReleased));
rules_.emplace_back(
MappingRule(std::make_unique<ArcTracingEventMatcher>(
"android:handleMessageInvalidate"),
BufferEventType::kSurfaceFlingerInvalidationStart,
BufferEventType::kSurfaceFlingerInvalidationDone));
rules_.emplace_back(
MappingRule(std::make_unique<ArcTracingEventMatcher>(
"android:handleMessageRefresh"),
BufferEventType::kSurfaceFlingerCompositionStart,
BufferEventType::kSurfaceFlingerCompositionDone));
// viz,benchmark rules
auto matcher = std::make_unique<ArcTracingEventMatcher>(
"viz,benchmark:Graphics.Pipeline.DrawAndSwap");
matcher->SetPhase(TRACE_EVENT_PHASE_ASYNC_BEGIN);
rules_.emplace_back(MappingRule(std::move(matcher),
BufferEventType::kChromeOSDraw,
BufferEventType::kNone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(
"viz,benchmark:Graphics.Pipeline.DrawAndSwap(step=Draw)"),
BufferEventType::kNone, BufferEventType::kNone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(
"viz,benchmark:Graphics.Pipeline.DrawAndSwap(step=Swap)"),
BufferEventType::kChromeOSSwap, BufferEventType::kNone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(
"viz,benchmark:Graphics.Pipeline.DrawAndSwap(step=WaitForAck)"),
BufferEventType::kChromeOSWaitForAck, BufferEventType::kNone));
rules_.emplace_back(MappingRule(
std::make_unique<ArcTracingEventMatcher>(
"viz,benchmark:Graphics.Pipeline.DrawAndSwap(step="
"WaitForPresentation)"),
BufferEventType::kChromeOSPresentationDone, BufferEventType::kNone));
matcher = std::make_unique<ArcTracingEventMatcher>(
"viz,benchmark:Graphics.Pipeline.DrawAndSwap");
matcher->SetPhase(TRACE_EVENT_PHASE_ASYNC_END);
rules_.emplace_back(MappingRule(std::move(matcher), BufferEventType::kNone,
BufferEventType::kChromeOSSwapDone));
}
~BufferGraphicsEventMapper() = default;
void Produce(const ArcTracingEvent& event,
ArcTracingGraphicsModel::BufferEvents* collector) const {
for (const auto& rule : rules_) {
if (rule.Produce(event, collector))
return;
}
LOG(ERROR) << "Unsupported event: " << event.ToString();
}
private:
MappingRules rules_;
DISALLOW_COPY_AND_ASSIGN(BufferGraphicsEventMapper);
};
BufferGraphicsEventMapper& GetEventMapper() {
static base::NoDestructor<BufferGraphicsEventMapper> instance;
return *instance;
}
// Maps particular buffer to its events.
using BufferToEvents =
std::map<std::string, ArcTracingGraphicsModel::BufferEvents>;
bool SortByTimestampPred(const ArcTracingGraphicsModel::BufferEvent& a,
const ArcTracingGraphicsModel::BufferEvent& b) {
if (a.timestamp != b.timestamp)
return a.timestamp < b.timestamp;
return static_cast<int>(a.type) < static_cast<int>(b.type);
}
void SortBufferEventsByTimestamp(BufferEvents* events) {
std::sort(events->begin(), events->end(), SortByTimestampPred);
}
std::string RouteToSelector(const std::vector<const ArcTracingEvent*>& route) {
std::string result;
for (const ArcTracingEvent* segment : route)
result = result + "/" + segment->GetCategory() + ":" + segment->GetName();
return result;
}
void DetermineHierarchy(std::vector<const ArcTracingEvent*>* route,
const ArcTracingEvent* event,
const ArcTracingEventMatcher& matcher,
std::string* out_query) {
if (!out_query->empty())
return;
route->emplace_back(event);
if (matcher.Match(*event)) {
*out_query = RouteToSelector(*route);
} else {
for (const auto& child : event->children())
DetermineHierarchy(route, child.get(), matcher, out_query);
}
route->pop_back();
}
// Extracts buffer id from the surface flinger event. For example:
// android|releaseBuffer
// android|com.android.vending/com.android.vending.AssetBrowserActivity#0: 2
// Buffer id appears as a child event where name if the combination of the
// current view of the Activity, its index and the number of buffer starting
// from 0. This helps to exactly identify the particular buffer in context of
// Android. Buffer id for this example is
// "com.android.vending/com.android.vending.AssetBrowserActivity#0: 2"
bool ExtractBufferIdFromSurfaceFlingerEvent(const ArcTracingEvent& event,
std::string* id) {
for (const auto& child : event.children()) {
if (child->GetPhase() != TRACE_EVENT_PHASE_COMPLETE)
continue;
const std::string& name = child->GetName();
size_t index = name.find(": ");
if (index == std::string::npos)
continue;
index += 2;
if (index >= name.length())
continue;
bool all_digits = true;
while (index < name.length() && all_digits) {
all_digits &= (name[index] >= '0' && name[index] <= '9');
++index;
}
if (!all_digits)
continue;
*id = name;
return true;
}
return false;
}
// Extracts the activity name from the buffer id by discarding the buffer id
// and view index. For example, activity name for buffer id
// "com.android.vending/com.android.vending.AssetBrowserActivity#0: 2"
// is "com.android.vending/com.android.vending.AssetBrowserActivity".
// If the activity cannot be extracted then default |kUnknownActivity| is
// returned.
std::string GetActivityFromBufferName(const std::string& android_buffer_name) {
const size_t position = android_buffer_name.find('#');
if (position == std::string::npos)
return kUnknownActivity;
return android_buffer_name.substr(0, position);
}
// Processes surface flinger events. It selects events using |query| from the
// model. Buffer id is extracted for the each returned event and new events are
// grouped by its buffer id. If |surface_flinger_pid| is set to the positive
// value than this activates filtering events by process id to avoid the case
// when android:queueBuffer/dequeueBuffer events may appear in context of child
// process and could be processed as surface flinger event. Returns positive
// process id in case surface flinger events are found and belong to the same
// process. In case of error -1 is returned.
int ProcessSurfaceFlingerEvents(const ArcTracingModel& common_model,
const std::string& query,
BufferToEvents* buffer_to_events,
int surface_flinger_pid) {
int detected_pid = -1;
const ArcTracingModel::TracingEventPtrs surface_flinger_events =
common_model.Select(query);
std::string buffer_id;
for (const ArcTracingEvent* event : surface_flinger_events) {
if (surface_flinger_pid > 0 && event->GetPid() != surface_flinger_pid)
continue;
if (!ExtractBufferIdFromSurfaceFlingerEvent(*event, &buffer_id)) {
LOG(ERROR) << "Failed to get buffer id from surface flinger event";
continue;
}
if (detected_pid < 0) {
DCHECK_GE(event->GetPid(), 0);
detected_pid = event->GetPid();
} else if (detected_pid != event->GetPid()) {
LOG(ERROR) << "Found multiple surface flinger process ids "
<< detected_pid << "/" << event->GetPid();
return -1;
}
ArcTracingGraphicsModel::BufferEvents& graphics_events =
(*buffer_to_events)[buffer_id];
GetEventMapper().Produce(*event, &graphics_events);
}
return detected_pid;
}
// Processes Android events acquireBuffer, releaseBuffer, dequeueBuffer and
// queueBuffer. It returns map buffer id to the list of sorted by timestamp
// events.
bool GetSurfaceFlingerEvents(const ArcTracingModel& common_model,
BufferToEvents* out_events) {
// Detect surface_flinger_pid using |kAcquireBufferQuery| that has unique
// hierarchy.
const int surface_flinger_pid =
ProcessSurfaceFlingerEvents(common_model, kAcquireBufferQuery, out_events,
-1 /* surface_flinger_pid */);
if (surface_flinger_pid <= 0) {
LOG(ERROR) << "Failed to detect acquireBuffer events.";
return false;
}
const int surface_flinger_release_buffer_pid =
ProcessSurfaceFlingerEvents(common_model, kReleaseBufferQuery, out_events,
-1 /* surface_flinger_pid */);
if (surface_flinger_release_buffer_pid <= 0) {
LOG(ERROR) << "Failed to detect releaseBuffer events.";
return false;
}
if (surface_flinger_pid != surface_flinger_release_buffer_pid) {
LOG(ERROR) << "Detected acquireBuffer and releaseBuffer from"
" different processes.";
return false;
}
// queueBuffer and dequeueBuffer may appear in context of client task.
// Use detected |surface_flinger_pid| to filter out such events.
if (ProcessSurfaceFlingerEvents(common_model, kQueueBufferQuery, out_events,
surface_flinger_pid) < 0) {
LOG(ERROR) << "Failed to detect queueBuffer events.";
return false;
}
if (ProcessSurfaceFlingerEvents(common_model, kDequeueBufferQuery, out_events,
surface_flinger_pid) < 0) {
LOG(ERROR) << "Failed to detect dequeueBuffer events.";
return false;
}
for (auto& buffer : *out_events)
SortBufferEventsByTimestamp(&buffer.second);
return true;
}
// Represents input event in Wayland. It contains input timestamp that shows the
// time of event creation, event timestamp in this case shows the time when this
// event was dispatched using Wayland interface. Type defines the input type.
class ExoInputEvent {
public:
ExoInputEvent(const ArcTracingEvent* event,
uint64_t input_timestamp,
ui::EventType type)
: event_(event), input_timestamp_(input_timestamp), type_(type) {}
~ExoInputEvent() = default;
// Parses |event| and extracts information for Wayland input event. Returns
// nullptr in case |event| could not be parsed.
static std::unique_ptr<ExoInputEvent> Create(const ArcTracingEvent* event) {
const uint64_t timestamp =
event->GetArgAsDouble(kArgumentTimestamp, 0 /* default_value */);
const int type =
event->GetArgAsInteger(kArgumentType, 0 /* default_value */);
if (!timestamp || !type) {
LOG(ERROR) << "Could not parse timestamp or type of event: "
<< event->ToString();
return nullptr;
}
return std::make_unique<ExoInputEvent>(event, timestamp,
static_cast<ui::EventType>(type));
}
const ArcTracingEvent* event() const { return event_; }
uint64_t input_timestamp() const { return input_timestamp_; }
ui::EventType type() const { return type_; }
private:
const ArcTracingEvent* event_;
// Time of the creation of the event. Normally, it is before the event
// timestamp that indicates when event was seen in Wayland.
const uint64_t input_timestamp_;
// Type of the event;
const ui::EventType type_;
DISALLOW_COPY_AND_ASSIGN(ExoInputEvent);
};
bool SortExoByInputTimestampPred(const std::unique_ptr<ExoInputEvent>& a,
const uint64_t input_timestamp) {
return a->input_timestamp() < input_timestamp;
}
// Represents input event in Android. It contains input timestamp that shows the
// time of the creation event in Chrome. Source defines the origin of the input
// event, mouse, keyboard, touch and so on. Sequence id is the unique
// identifier of the input event in context of one application.
class AndroidInputEvent {
public:
enum Source {
Keyboard = 0x101,
Touch = 0x1002,
Mouse = 0x2002,
};
AndroidInputEvent(const ArcTracingEvent* event,
std::vector<uint64_t> input_timestamps,
Source source,
int sequence_id)
: event_(event),
input_timestamps_(std::move(input_timestamps)),
source_(source),
sequence_id_(sequence_id) {}
~AndroidInputEvent() = default;
// Parses |event| and extracts information for Android input event. Returns
// nullptr in case |event| could not be parsed.
static std::unique_ptr<AndroidInputEvent> Create(
const ArcTracingEvent* event) {
if (!base::StartsWith(event->GetName(), kInputEventPrefix,
base::CompareCase::SENSITIVE)) {
return nullptr;
}
// Has following structure
// InputEvent: source timestamps sequence_id|0|
const std::string body =
event->GetName().substr(base::size(kInputEventPrefix) - 1);
base::StringTokenizer tokenizer(body, " ");
std::vector<std::string> tokens;
while (tokenizer.GetNext())
tokens.emplace_back(tokenizer.token());
if (tokens.size() != 3) {
LOG(ERROR) << "Failed to parse input event: " << event->ToString();
return nullptr;
}
uint32_t source = -1;
sscanf(tokens[0].c_str(), "%" PRId32, &source);
switch (static_cast<Source>(source)) {
case Source::Keyboard:
case Source::Mouse:
case Source::Touch:
break;
default:
LOG(ERROR) << "Unrecognized source: " << event->ToString();
return nullptr;
}
std::vector<uint64_t> input_timestamps;
// Timestamps are separated by comma. It is used in case event is synthetic
// and is composed from one more physical events.
base::StringTokenizer tokenizer_timestamps(tokens[1], ",");
while (tokenizer_timestamps.GetNext()) {
uint64_t timestamp;
if (sscanf(tokenizer_timestamps.token().c_str(), "%" PRId64,
&timestamp) != 1) {
LOG(ERROR) << "Failed to parse timestamp: " << event->ToString();
return nullptr;
}
// Convert nanosecond to microseconds, as it is use in tracing.
input_timestamps.emplace_back(timestamp / 1000L);
}
if (input_timestamps.empty()) {
LOG(ERROR) << "Timestamp is not found: " << event->ToString();
return nullptr;
}
int sequence_id;
if (sscanf(tokens[2].c_str(), "%" PRId32 "|0|", &sequence_id) != 1) {
LOG(ERROR) << "Sequence id is not found: " << event->ToString();
return nullptr;
}
return std::make_unique<AndroidInputEvent>(
event, std::move(input_timestamps), static_cast<Source>(source),
sequence_id);
}
const ArcTracingEvent* event() const { return event_; }
std::vector<uint64_t> input_timestamps() const { return input_timestamps_; }
Source source() const { return source_; }
int sequence_id() const { return sequence_id_; }
private:
const ArcTracingEvent* event_;
// Time of the creation of the event. Note that Wayland passes only
// milliseconds. So for events coming from Chrome, it is expected 0 for
// microsecond and nanosecond fraction. There is special case for motion
// events, when Android motion event is composed from the set of actual
// events with prediction of trajectory. In last case |input_timestamps_|
// contains all events, used for calculation of synthetic move event.
const std::vector<uint64_t> input_timestamps_;
const Source source_;
const int sequence_id_;
DISALLOW_COPY_AND_ASSIGN(AndroidInputEvent);
};
// Maps Android sources to possible input types from Chrome.
struct InputEventMapper {
InputEventMapper() {
source_to_type[AndroidInputEvent::Source::Keyboard] = {
ui::EventType::ET_KEY_PRESSED, ui::EventType::ET_KEY_RELEASED};
source_to_type[AndroidInputEvent::Source::Touch] = {
ui::EventType::ET_TOUCH_RELEASED, ui::EventType::ET_TOUCH_PRESSED,
ui::EventType::ET_TOUCH_MOVED, ui::EventType::ET_TOUCH_CANCELLED};
source_to_type[AndroidInputEvent::Source::Mouse] = {
ui::EventType::ET_MOUSE_PRESSED,
ui::EventType::ET_MOUSE_RELEASED,
ui::EventType::ET_MOUSE_MOVED,
};
}
std::map<int, std::set<ui::EventType>> source_to_type;
};
const InputEventMapper& GetInputEventMapper() {
static base::NoDestructor<InputEventMapper> instance;
return *instance;
}
// Finds the corresponded Wayland input event among |exo_input_events| for the
// given |android_input_timestamp| and |source|. Return nullptr in case event
// could not be found or more than one event matches the
// |android_input_timestamp| and |source|. |exo_input_events| is sorted by input
// timestamp.
const ExoInputEvent* FindExoInputEventForAndroidEvent(
const std::vector<std::unique_ptr<ExoInputEvent>>& exo_input_events,
uint64_t android_input_timestamp,
int source) {
const InputEventMapper& mapper = GetInputEventMapper();
const auto& allowed_set = mapper.source_to_type.find(source);
if (allowed_set == mapper.source_to_type.end()) {
LOG(ERROR) << "Input source is not recognized " << source;
return nullptr;
}
// Wayland does not pass microsecond and nanosecond fraction. If it set, that
// means we deal with synthetic Android input event and does not have match in
// Wayland.
if (android_input_timestamp % 1000L)
return nullptr;
const ExoInputEvent* exo_input_event = nullptr;
// Seek in the range of 1 millisecond once |android_input_timestamp| has
// millisecond resolution.
const uint64_t max_input_timestamp = android_input_timestamp + 1000L;
auto it =
std::lower_bound(exo_input_events.begin(), exo_input_events.end(),
android_input_timestamp, SortExoByInputTimestampPred);
while (it != exo_input_events.end() &&
it->get()->input_timestamp() < max_input_timestamp) {
if (allowed_set->second.count(it->get()->type())) {
// Check if detected more than one exo input event.
if (exo_input_event) {
LOG(WARNING) << "More than one exo input event found for timestamp "
<< android_input_timestamp;
return nullptr;
}
exo_input_event = it->get();
}
++it;
}
return exo_input_event;
}
// Finds the timestamps when input event was delivered to the target app. This
// finds the pair of asynchronous events deliverInputEvent that has the same
// sequence id and belongs to the same process as |input_event|. Returns true
// in case |out_start_delivery_timestamp| and |out_end_delivery_timestamp| are
// set.
bool GetDeliverInputEventTimestamp(const ArcTracingModel& common_model,
const AndroidInputEvent* input_event,
uint64_t* out_start_delivery_timestamp,
uint64_t* out_end_delivery_timestamp) {
const ArcTracingModel::TracingEventPtrs group_events =
common_model.GetGroupEvents(
base::StringPrintf("%d", input_event->sequence_id()));
const ArcTracingEvent* start_event = nullptr;
const ArcTracingEvent* end_event = nullptr;
for (const ArcTracingEvent* event : group_events) {
if (event->GetName() != kDeliverInputEvent)
continue;
// Potentially events from different tasks/processes may overlap.
if (event->GetPid() != input_event->event()->GetPid())
continue;
switch (event->GetPhase()) {
case TRACE_EVENT_PHASE_ASYNC_BEGIN:
if (start_event) {
LOG(ERROR) << "Double start event found " << start_event->ToString();
return false;
}
start_event = event;
break;
case TRACE_EVENT_PHASE_ASYNC_END:
if (end_event) {
LOG(ERROR) << "Double end event found " << end_event->ToString();
return false;
}
end_event = event;
break;
}
}
if (!start_event || !end_event)
return false;
*out_start_delivery_timestamp = start_event->GetTimestamp();
*out_end_delivery_timestamp = end_event->GetTimestamp();
if (*out_start_delivery_timestamp > *out_end_delivery_timestamp) {
LOG(ERROR) << "Start event is after end event " << start_event->ToString();
return false;
}
if (*out_start_delivery_timestamp > input_event->event()->GetTimestamp() ||
*out_end_delivery_timestamp < input_event->event()->GetTimestamp()) {
LOG(ERROR) << "Wrong start/end timestamps for "
<< input_event->event()->ToString();
return false;
}
return true;
}
std::string InputTypeToString(ui::EventType type) {
switch (type) {
case ui::EventType::ET_MOUSE_PRESSED:
return "mouse pressed";
case ui::EventType::ET_MOUSE_RELEASED:
return "mouse released";
case ui::EventType::ET_MOUSE_MOVED:
return "mouse moved";
case ui::EventType::ET_KEY_PRESSED:
return "key pressed";
case ui::EventType::ET_KEY_RELEASED:
return "key released";
case ui::EventType::ET_TOUCH_RELEASED:
return "touch released";
case ui::EventType::ET_TOUCH_PRESSED:
return "touch pressed";
case ui::EventType::ET_TOUCH_MOVED:
return "touch moved";
default:
return base::StringPrintf("type: %d", type);
}
}
std::string SourceToString(AndroidInputEvent::Source source) {
switch (source) {
case AndroidInputEvent::Source::Keyboard:
return "keyboard";
case AndroidInputEvent::Source::Mouse:
return "mouse";
case AndroidInputEvent::Source::Touch:
return "touch";
default:
return base::StringPrintf("source: %d", static_cast<int>(source));
}
}
// Analyzes |common_model| and reconstructs input events activity.
void GetInputEvents(
const ArcTracingModel& common_model,
ArcTracingGraphicsModel::EventsContainer* out_events_container) {
// Determine route to Wayland input events.
const ArcTracingModel::TracingEventPtrs top_level_events =
common_model.Select("toplevel:");
std::vector<const ArcTracingEvent*> route;
std::string exo_input_event_query;
for (const ArcTracingEvent* top_level_event : top_level_events) {
DetermineHierarchy(&route, top_level_event,
ArcTracingEventMatcher(kExoInputEventMatcher),
&exo_input_event_query);
}
// Collect list of input events, seen in Wayaland and sort them based on input
// event creation time.
std::vector<std::unique_ptr<ExoInputEvent>> exo_input_events;
if (!exo_input_event_query.empty()) {
const ArcTracingModel::TracingEventPtrs input_events =
common_model.Select(exo_input_event_query);
for (const ArcTracingEvent* input_event : input_events) {
std::unique_ptr<ExoInputEvent> exo_input_event =
ExoInputEvent::Create(input_event);
if (exo_input_event)
exo_input_events.emplace_back(std::move(exo_input_event));
}
}
std::sort(exo_input_events.begin(), exo_input_events.end(),
[](const auto& lhs, const auto& rhs) {
if (lhs->input_timestamp() != rhs->input_timestamp())
return lhs->input_timestamp() < rhs->input_timestamp();
return lhs->event()->GetTimestamp() <
rhs->event()->GetTimestamp();
});
// Extracts Android input events that can appear as root events or under the
// |kInputQuery| hierarchy.
std::vector<std::unique_ptr<AndroidInputEvent>> android_input_events;
ArcTracingModel::TracingEventPtrs android_input_event_candidates =
common_model.GetRoots();
const ArcTracingModel::TracingEventPtrs inputs =
common_model.Select(kInputQuery);
android_input_event_candidates.insert(android_input_event_candidates.end(),
inputs.begin(), inputs.end());
for (const ArcTracingEvent* root : android_input_event_candidates) {
// It is logged as counter.
if (root->GetPhase() != TRACE_EVENT_PHASE_COUNTER)
continue;
std::unique_ptr<AndroidInputEvent> android_input_event =
AndroidInputEvent::Create(root);
if (android_input_event)
android_input_events.emplace_back(std::move(android_input_event));
}
std::sort(android_input_events.begin(), android_input_events.end(),
[](const auto& lhs, const auto& rhs) {
if (lhs->input_timestamps()[0] != rhs->input_timestamps()[0])
return lhs->input_timestamps()[0] < rhs->input_timestamps()[0];
return lhs->event()->GetTimestamp() <
rhs->event()->GetTimestamp();
});
// Group of events per each input.
std::vector<BufferEvents> events_group;
for (const auto& android_input_event : android_input_events) {
uint64_t start_delivery_timestamp;
uint64_t end_delivery_timestamp;
if (!GetDeliverInputEventTimestamp(common_model, android_input_event.get(),
&start_delivery_timestamp,
&end_delivery_timestamp)) {
LOG(ERROR) << "Deliver input event is not found for "
<< android_input_event->event()->ToString();
continue;
}
BufferEvents input_events;
input_events.emplace_back(BufferEventType::kInputEventDeliverStart,
start_delivery_timestamp);
input_events.emplace_back(BufferEventType::kInputEventDeliverEnd,
end_delivery_timestamp);
// In case of synthetic move event, last timestamp is generated timestamp,
// discard it.
constexpr size_t one = 1;
const size_t timestamp_count =
std::max(one, android_input_event->input_timestamps().size() - 1);
for (size_t i = 0; i < timestamp_count; ++i) {
const uint64_t input_timestamp =
android_input_event->input_timestamps()[i];
const ExoInputEvent* exo_input_event = FindExoInputEventForAndroidEvent(
exo_input_events, input_timestamp, android_input_event->source());
if (exo_input_event) {
const uint64_t dispatch_timestamp =
exo_input_event->event()->GetTimestamp();
// crbug.com/968324, input timestamp might be set in the future, in this
// case use dispatch_timestamp as creation timestamp.
const uint64_t creation_timestamp = exo_input_event->input_timestamp();
if (creation_timestamp <= dispatch_timestamp) {
input_events.emplace_back(BufferEventType::kInputEventCreated,
creation_timestamp,
InputTypeToString(exo_input_event->type()));
} else {
// Sort by type.
input_events.emplace_back(
BufferEventType::kInputEventCreated, dispatch_timestamp,
InputTypeToString(exo_input_event->type()) + " - estimated");
}
input_events.emplace_back(BufferEventType::kInputEventWaylandDispatched,
dispatch_timestamp);
} else {
// Could not map Wayland event. Let use best we can, which precision
// rounded down to millisecond.
// crbug.com/968324, input timestamp might be set in the future, in this
// case, clamp to |start_delivery_timestamp|.
if (input_timestamp <= start_delivery_timestamp) {
input_events.emplace_back(
BufferEventType::kInputEventCreated, input_timestamp,
SourceToString(android_input_event->source()));
} else {
input_events.emplace_back(
BufferEventType::kInputEventCreated, start_delivery_timestamp,
SourceToString(android_input_event->source()) + " - estimated");
}
}
}
SortBufferEventsByTimestamp(&input_events);
DCHECK_EQ(BufferEventType::kInputEventCreated, input_events.begin()->type);
DCHECK_EQ(BufferEventType::kInputEventDeliverEnd,
input_events.rbegin()->type);
// Try to put series of events to the default first bar. However, in case
// series of events overlap, use another bar to keep them separated.
size_t target_buffer = 0;
for (target_buffer = 0;
target_buffer < out_events_container->buffer_events().size();
++target_buffer) {
if (out_events_container->buffer_events()[target_buffer]
.rbegin()
->timestamp < input_events.begin()->timestamp) {
break;
}
}
if (target_buffer == out_events_container->buffer_events().size())
out_events_container->buffer_events().emplace_back(BufferEvents());
out_events_container->buffer_events()[target_buffer].insert(
out_events_container->buffer_events()[target_buffer].end(),
input_events.begin(), input_events.end());
}
}
// Processes exo events Surface::Attach and Buffer::ReleaseContents. Each event
// has argument buffer_id that identifies graphics buffer on Chrome side.
// buffer_id is just row pointer to internal class. If |buffer_id_to_task_id| is
// set then it is updated to map buffer id to task id.
void ProcessChromeEvents(const ArcTracingModel& common_model,
const std::string& query,
BufferToEvents* buffer_to_events,
std::map<std::string, int>* buffer_id_to_task_id) {
const ArcTracingModel::TracingEventPtrs chrome_events =
common_model.Select(query);
for (const ArcTracingEvent* event : chrome_events) {
const std::string buffer_id = event->GetArgAsString(
kArgumentBufferId, std::string() /* default_value */);
if (buffer_id.empty()) {
LOG(ERROR) << "Failed to get buffer id from event: " << event->ToString();
continue;
}
if (buffer_id_to_task_id) {
const std::string app_id = event->GetArgAsString(
kArgumentAppId, std::string() /* default_value */);
if (app_id.empty()) {
LOG(ERROR) << "Failed to get app id from event: " << event->ToString();
continue;
}
int task_id = GetTaskIdFromWindowAppId(app_id);
if (task_id == kNoTaskId) {
LOG(ERROR) << "Failed to parse app id from event: "
<< event->ToString();
continue;
}
(*buffer_id_to_task_id)[buffer_id] = task_id;
}
ArcTracingGraphicsModel::BufferEvents& graphics_events =
(*buffer_to_events)[buffer_id];
GetEventMapper().Produce(*event, &graphics_events);
}
}
BufferToEvents GetChromeEvents(
const ArcTracingModel& common_model,
std::map<std::string, int>* buffer_id_to_task_id) {
// The tracing hierarchy may be easy changed any time in Chrome. This makes
// using static queries fragile and dependent of many external components. To
// provide the reliable way of requesting the needed information, let scan
// |common_model| for top level events and determine the hierarchy of
// interesting events dynamically.
const ArcTracingModel::TracingEventPtrs top_level_events =
common_model.Select("toplevel:");
std::vector<const ArcTracingEvent*> route;
std::string barrier_flush_query;
const ArcTracingEventMatcher barrier_flush_matcher(kBarrierFlushMatcher);
std::string attach_surface_query;
const ArcTracingEventMatcher attach_surface_matcher(kExoSurfaceAttachMatcher);
std::string commit_surface_query;
const ArcTracingEventMatcher commit_surface_matcher(kExoSurfaceCommitMatcher);
std::string produce_resource_query;
const ArcTracingEventMatcher produce_resource_matcher(
kExoBufferProduceResourceMatcher);
std::string release_contents_query;
const ArcTracingEventMatcher release_contents_matcher(
kExoBufferReleaseContentsMatcher);
for (const ArcTracingEvent* top_level_event : top_level_events) {
DetermineHierarchy(&route, top_level_event, barrier_flush_matcher,
&barrier_flush_query);
DetermineHierarchy(&route, top_level_event, attach_surface_matcher,
&attach_surface_query);
DetermineHierarchy(&route, top_level_event, commit_surface_matcher,
&commit_surface_query);
DetermineHierarchy(&route, top_level_event, produce_resource_matcher,
&produce_resource_query);
DetermineHierarchy(&route, top_level_event, release_contents_matcher,
&release_contents_query);
}
BufferToEvents per_buffer_chrome_events;
// Only exo:Surface::Attach has app id argument.
ProcessChromeEvents(common_model, attach_surface_query,
&per_buffer_chrome_events, buffer_id_to_task_id);
ProcessChromeEvents(common_model, commit_surface_query,
&per_buffer_chrome_events,
nullptr /* buffer_id_to_task_id */);
ProcessChromeEvents(common_model, release_contents_query,
&per_buffer_chrome_events,
nullptr /* buffer_id_to_task_id */);
// Handle ProduceTransferableResource events. They have extra link to barrier
// events. Use buffer_id to bind events for the same graphics buffer.
const ArcTracingModel::TracingEventPtrs produce_resource_events =
common_model.Select(produce_resource_query);
std::map<int, std::string> put_offset_to_buffer_id_map;
for (const ArcTracingEvent* event : produce_resource_events) {
const std::string buffer_id = event->GetArgAsString(
kArgumentBufferId, std::string() /* default_value */);
if (buffer_id.empty()) {
LOG(ERROR) << "Failed to get buffer id from event: " << event->ToString();
continue;
}
ArcTracingGraphicsModel::BufferEvents& graphics_events =
per_buffer_chrome_events[buffer_id];
GetEventMapper().Produce(*event, &graphics_events);
const ArcTracingModel::TracingEventPtrs ordering_barrier_events =
common_model.Select(event, kBarrierOrderingSubQuery);
if (ordering_barrier_events.size() != 1) {
LOG(ERROR) << "Expected only one " << kBarrierOrderingSubQuery << ". Got "
<< ordering_barrier_events.size();
continue;
}
const int put_offset = ordering_barrier_events[0]->GetArgAsInteger(
kArgumentPutOffset, 0 /* default_value */);
if (!put_offset) {
LOG(ERROR) << "No " << kArgumentPutOffset
<< " argument in: " << ordering_barrier_events[0]->ToString();
continue;
}
if (put_offset_to_buffer_id_map.count(put_offset) &&
put_offset_to_buffer_id_map[put_offset] != buffer_id) {
LOG(ERROR) << put_offset << " is already mapped to "
<< put_offset_to_buffer_id_map[put_offset]
<< ". Skip mapping to " << buffer_id;
continue;
}
put_offset_to_buffer_id_map[put_offset] = buffer_id;
GetEventMapper().Produce(*ordering_barrier_events[0], &graphics_events);
}
// Find associated barrier flush event using put_offset argument.
const ArcTracingModel::TracingEventPtrs barrier_flush_events =
common_model.Select(barrier_flush_query);
for (const ArcTracingEvent* event : barrier_flush_events) {
const int put_offset =
event->GetArgAsInteger(kArgumentPutOffset, 0 /* default_value */);
if (!put_offset_to_buffer_id_map.count(put_offset))
continue;
ArcTracingGraphicsModel::BufferEvents& graphics_events =
per_buffer_chrome_events[put_offset_to_buffer_id_map[put_offset]];
GetEventMapper().Produce(*event, &graphics_events);
}
// Handle BufferInUse async events.
const ArcTracingModel::TracingEventPtrs buffer_in_use_events =
common_model.Select(kBufferInUseQuery);
std::map<std::string, std::string> buffer_in_use_id_to_buffer_id;
for (const ArcTracingEvent* event : buffer_in_use_events) {
// Only start event has buffer_id association.
if (event->GetPhase() != TRACE_EVENT_PHASE_ASYNC_BEGIN)
continue;
const std::string id = event->GetId();
const std::string buffer_id = event->GetArgAsString(
kArgumentBufferId, std::string() /* default_value */);
if (buffer_id.empty() || id.empty()) {
LOG(ERROR) << "Cannot map id to buffer id for event: "
<< event->ToString();
continue;
}
if (buffer_in_use_id_to_buffer_id.count(id) &&
buffer_in_use_id_to_buffer_id[id] != buffer_id) {
LOG(ERROR) << id << " is already mapped to "
<< buffer_in_use_id_to_buffer_id[id] << ". Skip mapping to "
<< buffer_id;
continue;
}
buffer_in_use_id_to_buffer_id[id] = buffer_id;
}
for (const ArcTracingEvent* event : buffer_in_use_events) {
if (event->GetPhase() != TRACE_EVENT_PHASE_ASYNC_STEP_INTO)
continue;
const std::string id = event->GetId();
if (!buffer_in_use_id_to_buffer_id.count(id)) {
LOG(ERROR) << "Found non-mapped event: " << event->ToString();
continue;
}
ArcTracingGraphicsModel::BufferEvents& graphics_events =
per_buffer_chrome_events[buffer_in_use_id_to_buffer_id[id]];
GetEventMapper().Produce(*event, &graphics_events);
}
for (auto& buffer : per_buffer_chrome_events)
SortBufferEventsByTimestamp(&buffer.second);
return per_buffer_chrome_events;
}
void ScanForCustomEvents(
const ArcTracingEvent* event,
ArcTracingGraphicsModel::BufferEvents* out_custom_events) {
if (base::StartsWith(event->GetName(), kCustomTracePrefix,
base::CompareCase::SENSITIVE)) {
DCHECK(!event->GetArgs() || event->GetArgs()->empty());
out_custom_events->emplace_back(
ArcTracingGraphicsModel::BufferEventType::kCustomEvent,
event->GetTimestamp(),
event->GetName().substr(base::size(kCustomTracePrefix) - 1));
}
for (const auto& child : event->children())
ScanForCustomEvents(child.get(), out_custom_events);
}
// Extracts custom events from the model. Custom events start from customTrace
ArcTracingGraphicsModel::BufferEvents GetCustomEvents(
const ArcTracingModel& common_model) {
ArcTracingGraphicsModel::BufferEvents custom_events;
for (const ArcTracingEvent* root : common_model.GetRoots())
ScanForCustomEvents(root, &custom_events);
return custom_events;
}
// Helper that finds a event of particular type in the list of events |events|
// starting from the index |start_index|. Returns |kInvalidBufferIndex| if event
// cannot be found.
ssize_t FindEvent(const ArcTracingGraphicsModel::BufferEvents& events,
BufferEventType type,
size_t start_index) {
for (size_t i = start_index; i < events.size(); ++i) {
if (events[i].type == type)
return i;
}
return kInvalidBufferIndex;
}
// Helper that finds valid pair of events for acquire/release buffer.
// |kBufferQueueReleased| should go immediately after |kBufferQueueAcquire|
// event with one exception of |kBufferQueueDequeueStart| that is allowed due to
// asynchronous flow of requesting buffers in Android. Returns
// |kInvalidBufferIndex| if such pair cannot be found.
ssize_t FindAcquireReleasePair(
const ArcTracingGraphicsModel::BufferEvents& events,
size_t start_index) {
const ssize_t index_acquire =
FindEvent(events, BufferEventType::kBufferQueueAcquire, start_index);
if (index_acquire == kInvalidBufferIndex)
return kInvalidBufferIndex;
// kBufferQueueDequeueStart is allowed between kBufferQueueAcquire and
// kBufferQueueReleased.
for (size_t i = index_acquire + 1; i < events.size(); ++i) {
if (events[i].type == BufferEventType::kBufferQueueDequeueStart) {
continue;
}
if (events[i].type == BufferEventType::kBufferQueueReleased) {
return index_acquire;
}
break;
}
return kInvalidBufferIndex;
}
// Helper that performs bisection search of event of type |type| in the ordered
// list of events |events|. Found event should have timestamp not later than
// |timestamp|. Returns |kInvalidBufferIndex| in case event is not found.
ssize_t FindNotLaterThan(const ArcTracingGraphicsModel::BufferEvents& events,
BufferEventType type,
uint64_t timestamp) {
if (events.empty() || events[0].timestamp > timestamp)
return kInvalidBufferIndex;
size_t min_range = 0;
size_t result = events.size() - 1;
while (events[result].timestamp > timestamp) {
const size_t next = (result + min_range + 1) / 2;
if (events[next].timestamp <= timestamp)
min_range = next;
else
result = next - 1;
}
for (ssize_t i = result; i >= 0; --i) {
if (events[i].type == type)
return i;
}
return kInvalidBufferIndex;
}
// Tries to match Android graphics buffer events and Chrome graphics buffer
// events. There is no direct id usable to say if the same buffer is used or
// not. This tests if two set of events potentially belong the same buffer and
// return the maximum number of matching sequences. In case impossible
// combination is found then it returns 0 score. Impossible combination for
// example when we detect Chrome buffer was attached while it was not held by
// Android between |kBufferQueueAcquire| and |kBufferQueueRelease.
// The process of merging buffers continues while we can merge something. At
// each iteration buffers with maximum merge score get merged. Practically,
// having 20+ cycles (assuming 4 buffers in use) is enough to exactly identify
// the same buffer in Chrome and Android. If needed more similar checks can be
// added.
size_t GetMergeScore(
const ArcTracingGraphicsModel::BufferEvents& surface_flinger_events,
const ArcTracingGraphicsModel::BufferEvents& chrome_events) {
ssize_t attach_index = -1;
ssize_t acquire_index = -1;
while (true) {
acquire_index =
FindAcquireReleasePair(surface_flinger_events, acquire_index + 1);
if (acquire_index == kInvalidBufferIndex)
return 0;
attach_index =
FindNotLaterThan(chrome_events, BufferEventType::kExoSurfaceAttach,
surface_flinger_events[acquire_index + 1].timestamp);
if (attach_index >= 0)
break;
}
// From here buffers must be in sync. Attach should happen between acquire and
// release.
size_t score = 0;
while (true) {
const int64_t timestamp_from =
surface_flinger_events[acquire_index].timestamp;
const int64_t timestamp_to =
surface_flinger_events[acquire_index + 1].timestamp;
const int64_t timestamp = chrome_events[attach_index].timestamp;
if (timestamp < timestamp_from || timestamp > timestamp_to) {
return 0;
}
acquire_index =
FindAcquireReleasePair(surface_flinger_events, acquire_index + 2);
attach_index = FindEvent(chrome_events, BufferEventType::kExoSurfaceAttach,
attach_index + 1);
if (acquire_index == kInvalidBufferIndex ||
attach_index == kInvalidBufferIndex) {
break;
}
++score;
}
return score;
}
// Adds jank events into |ArcTracingGraphicsModel::EventsContainer|.
// |pulse_event_type| defines the type of the event that should appear
// periodically. Once it is missed in analyzed buffer events, new jank event is
// added. |jank_event_type| defines the type of jank.
void AddJanks(ArcTracingGraphicsModel::EventsContainer* result,
BufferEventType pulse_event_type,
BufferEventType jank_event_type) {
// Detect rate first.
BufferEvents pulse_events;
for (const auto& it : result->buffer_events()) {
for (const auto& it_event : it) {
if (it_event.type == pulse_event_type)
pulse_events.emplace_back(it_event);
}
}
SortBufferEventsByTimestamp(&pulse_events);
ArcGraphicsJankDetector jank_detector(base::BindRepeating(
[](BufferEventType jank_event_type, BufferEvents* out_janks,
const base::Time& timestamp) {
out_janks->emplace_back(
BufferEvent(jank_event_type,
timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds()));
},
jank_event_type, &result->global_events()));
for (const auto& it : pulse_events) {
jank_detector.OnSample(base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(it.timestamp)));
if (jank_detector.stage() == ArcGraphicsJankDetector::Stage::kActive)
break;
}
// At this point, no janks should be reported. We are detecting the rate.
if (jank_detector.stage() != ArcGraphicsJankDetector::Stage::kActive)
return;
// Period is defined. Pass all samples to detect janks.
jank_detector.SetPeriodFixed(jank_detector.period());
for (const auto& it : pulse_events) {
jank_detector.OnSample(base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(it.timestamp)));
}
}
// Helper that performs query in |common_model| for top level Chrome GPU events
// and returns bands of sorted list of built events.
void GetChromeTopLevelEvents(const ArcTracingModel& common_model,
ArcTracingGraphicsModel::EventsContainer* result) {
// There is a chance that Chrome top level events may overlap. This may happen
// in case on non-trivial GPU load. In this case notification about swap or
// presentation done may come after the next frame draw is started. As a
// result, it leads to confusion in case displayed on the same event band.
// Solution is to allocate extra band and interchange events per buffer.
// Events are grouped per frame's id that starts from 0x100000000 and has
// monotonous increment. So we can simple keep it in the tree map that
// provides us the right ordering.
std::map<std::string, std::vector<const ArcTracingEvent*>> per_frame_events;
for (const ArcTracingEvent* event :
common_model.Select(kChromeTopEventsQuery)) {
per_frame_events[event->GetId()].emplace_back(event);
}
size_t band_index = 0;
result->buffer_events().resize(2);
for (const auto& it_frame : per_frame_events) {
for (const ArcTracingEvent* event : it_frame.second)
GetEventMapper().Produce(*event, &result->buffer_events()[band_index]);
band_index = (band_index + 1) % result->buffer_events().size();
}
for (auto& chrome_top_level_band : result->buffer_events())
SortBufferEventsByTimestamp(&chrome_top_level_band);
AddJanks(result, BufferEventType::kChromeOSDraw,
BufferEventType::kChromeOSJank);
}
// Helper that extracts top level Android events, such as refresh, vsync.
void GetAndroidTopEvents(const ArcTracingModel& common_model,
ArcTracingGraphicsModel::EventsContainer* result) {
result->buffer_events().resize(1);
for (const ArcTracingEvent* event :
common_model.Select(kHandleMessageRefreshQuery)) {
GetEventMapper().Produce(*event, &result->buffer_events()[0]);
}
for (const ArcTracingEvent* event :
common_model.Select(kHandleMessageInvalidateQuery)) {
GetEventMapper().Produce(*event, &result->buffer_events()[0]);
}
const BufferGraphicsEventMapper::MappingRule
mapArcTimestampTraceEventToVsyncTimestamp(
std::make_unique<ArcTracingEventMatcher>(kArcVsyncTimestampQuery),
BufferEventType::kVsyncTimestamp, BufferEventType::kNone,
base::BindRepeating([](const ArcTracingEventMatcher& matcher,
const ArcTracingEvent& event) {
// kVsyncTimestamp is special in that we get the actual/ideal
// vsync timestamp which is provided as extra metadata encoded in
// the event name string, rather than looking at the the timestamp
// at which the event was recorded.
base::Optional<int64_t> timestamp =
matcher.ReadAndroidEventInt64(event);
// The encoded int64 timestamp is in nanoseconds. Convert to
// microseconds as that is what the event timestamps are in.
return timestamp ? (*timestamp / 1000) : event.GetTimestamp();
}));
bool hasArcVsyncTimestampEvents = false;
for (const ArcTracingEvent* event :
common_model.Select(kArcVsyncTimestampQuery)) {
if (mapArcTimestampTraceEventToVsyncTimestamp.Produce(
*event, &result->global_events())) {
hasArcVsyncTimestampEvents = true;
}
}
if (!hasArcVsyncTimestampEvents) {
// For backwards compatibility, if there are no events that match
// kArcVsyncTimestampQuery, we use the timestamp of the events that match
// kSurfaceFlingerVsyncHandlerQuery as the kVsyncTimestamp event, though
// this does not accurately represent the vsync event timestamp.
const BufferGraphicsEventMapper::MappingRule
mapVsyncHandlerTraceToVsyncTimestamp(
std::make_unique<ArcTracingEventMatcher>(
kSurfaceFlingerVsyncHandlerQuery),
BufferEventType::kVsyncTimestamp, BufferEventType::kNone);
for (const ArcTracingEvent* event :
common_model.Select(kSurfaceFlingerVsyncHandlerQuery))
mapVsyncHandlerTraceToVsyncTimestamp.Produce(*event,
&result->global_events());
}
const BufferGraphicsEventMapper::MappingRule
mapVsyncHandlerTraceToVsyncHandlerEvent(
std::make_unique<ArcTracingEventMatcher>(
kSurfaceFlingerVsyncHandlerQuery),
BufferEventType::kSurfaceFlingerVsyncHandler, BufferEventType::kNone);
for (const ArcTracingEvent* event :
common_model.Select(kSurfaceFlingerVsyncHandlerQuery))
mapVsyncHandlerTraceToVsyncHandlerEvent.Produce(*event,
&result->global_events());
SortBufferEventsByTimestamp(&result->buffer_events()[0]);
AddJanks(result, BufferEventType::kSurfaceFlingerCompositionStart,
BufferEventType::kSurfaceFlingerCompositionJank);
SortBufferEventsByTimestamp(&result->global_events());
}
// Helper that serializes events |events| to the |base::ListValue|.
base::ListValue SerializeEvents(
const ArcTracingGraphicsModel::BufferEvents& events) {
base::ListValue list;
for (const auto& event : events) {
base::ListValue event_value;
event_value.Append(base::Value(static_cast<int>(event.type)));
event_value.Append(base::Value(static_cast<double>(event.timestamp)));
if (!event.content.empty())
event_value.Append(base::Value(event.content));
list.Append(std::move(event_value));
}
return list;
}
// Helper that serializes |events| to the |base::DictionaryValue|.
base::DictionaryValue SerializeEventsContainer(
const ArcTracingGraphicsModel::EventsContainer& events) {
base::DictionaryValue dictionary;
base::ListValue buffer_list;
for (auto& buffer : events.buffer_events())
buffer_list.Append(SerializeEvents(buffer));
dictionary.SetKey(kKeyBuffers, std::move(buffer_list));
dictionary.SetKey(kKeyGlobalEvents, SerializeEvents(events.global_events()));
return dictionary;
}
bool IsInRange(BufferEventType type,
BufferEventType type_from_inclusive,
BufferEventType type_to_inclusive) {
return type >= type_from_inclusive && type <= type_to_inclusive;
}
// Helper that loads events from |base::Value|. Returns true in case events were
// read successfully. Events must be sorted and be known.
bool LoadEvents(const base::Value* value,
ArcTracingGraphicsModel::BufferEvents* out_events) {
DCHECK(out_events);
if (!value || !value->is_list())
return false;
int64_t previous_timestamp = 0;
for (const auto& entry : value->GetList()) {
if (!entry.is_list() || entry.GetList().size() < 2)
return false;
if (!entry.GetList()[0].is_int())
return false;
const BufferEventType type =
static_cast<BufferEventType>(entry.GetList()[0].GetInt());
if (!IsInRange(type, BufferEventType::kBufferQueueDequeueStart,
BufferEventType::kBufferFillJank) &&
!IsInRange(type, BufferEventType::kExoSurfaceAttach,
BufferEventType::kExoSurfaceCommit) &&
!IsInRange(type, BufferEventType::kChromeBarrierOrder,
BufferEventType::kChromeBarrierFlush) &&
!IsInRange(type, BufferEventType::kSurfaceFlingerVsyncHandler,
BufferEventType::kVsyncTimestamp) &&
!IsInRange(type, BufferEventType::kChromeOSDraw,
BufferEventType::kChromeOSJank) &&
!IsInRange(type, BufferEventType::kCustomEvent,
BufferEventType::kCustomEvent) &&
!IsInRange(type, BufferEventType::kInputEventCreated,
BufferEventType::kInputEventDeliverEnd)) {
return false;
}
if (!entry.GetList()[1].is_double() && !entry.GetList()[1].is_int())
return false;
const int64_t timestamp = entry.GetList()[1].GetDouble();
if (timestamp < previous_timestamp)
return false;
if (entry.GetList().size() == 3) {
if (!entry.GetList()[2].is_string())
return false;
out_events->emplace_back(type, timestamp, entry.GetList()[2].GetString());
} else {
out_events->emplace_back(type, timestamp);
}
previous_timestamp = timestamp;
}
return true;
}
bool LoadEventsContainer(const base::Value* value,
ArcTracingGraphicsModel::EventsContainer* out_events) {
DCHECK(out_events->buffer_events().empty());
DCHECK(out_events->global_events().empty());
if (!value || !value->is_dict())
return false;
const base::DictionaryValue* dictionary = nullptr;
value->GetAsDictionary(&dictionary);
DCHECK(dictionary);
const base::Value* buffer_entries =
dictionary->FindKeyOfType(kKeyBuffers, base::Value::Type::LIST);
if (!buffer_entries)
return false;
for (const auto& buffer_entry : buffer_entries->GetList()) {
BufferEvents events;
if (!LoadEvents(&buffer_entry, &events))
return false;
out_events->buffer_events().emplace_back(std::move(events));
}
const base::Value* const global_events =
dictionary->FindKeyOfType(kKeyGlobalEvents, base::Value::Type::LIST);
if (!LoadEvents(global_events, &out_events->global_events()))
return false;
return true;
}
bool ReadDuration(const base::Value* root, uint32_t* duration) {
const base::Value* duration_value = root->FindKey(kKeyDuration);
if (!duration_value ||
(!duration_value->is_double() && !duration_value->is_int())) {
return false;
}
*duration = duration_value->GetDouble();
if (*duration < 0)
return false;
return true;
}
} // namespace
ArcTracingGraphicsModel::BufferEvent::BufferEvent(BufferEventType type,
int64_t timestamp)
: type(type), timestamp(timestamp) {}
ArcTracingGraphicsModel::BufferEvent::BufferEvent(BufferEventType type,
int64_t timestamp,
const std::string& content)
: type(type), timestamp(timestamp), content(content) {}
bool ArcTracingGraphicsModel::BufferEvent::operator==(
const BufferEvent& other) const {
return type == other.type && timestamp == other.timestamp &&
content == other.content;
}
ArcTracingGraphicsModel::ViewId::ViewId(int task_id,
const std::string& activity)
: task_id(task_id), activity(activity) {}
bool ArcTracingGraphicsModel::ViewId::operator<(const ViewId& other) const {
if (task_id != other.task_id)
return task_id < other.task_id;
return activity.compare(other.activity) < 0;
}
bool ArcTracingGraphicsModel::ViewId::operator==(const ViewId& other) const {
return task_id == other.task_id && activity == other.activity;
}
ArcTracingGraphicsModel::ArcTracingGraphicsModel() = default;
ArcTracingGraphicsModel::~ArcTracingGraphicsModel() = default;
// static
void ArcTracingGraphicsModel::TrimEventsContainer(
ArcTracingGraphicsModel::EventsContainer* container,
int64_t trim_timestamp,
const std::set<ArcTracingGraphicsModel::BufferEventType>& start_types) {
// For trim point use |ArcTracingGraphicsModel::BufferEventType::kNone| that
// is less than any other event type.
const ArcTracingGraphicsModel::BufferEvent trim_point(
ArcTracingGraphicsModel::BufferEventType::kNone, trim_timestamp);
// Global events are trimmed by timestamp only.
auto global_cut_pos = std::lower_bound(container->global_events().begin(),
container->global_events().end(),
trim_point, SortByTimestampPred);
container->global_events() = ArcTracingGraphicsModel::BufferEvents(
global_cut_pos, container->global_events().end());
for (auto& buffer : container->buffer_events()) {
auto cut_pos = std::lower_bound(buffer.begin(), buffer.end(), trim_point,
SortByTimestampPred);
while (cut_pos != buffer.end()) {
if (start_types.count(cut_pos->type))
break;
++cut_pos;
}
buffer = ArcTracingGraphicsModel::BufferEvents(cut_pos, buffer.end());
}
}
bool ArcTracingGraphicsModel::Build(const ArcTracingModel& common_model) {
Reset();
BufferToEvents per_buffer_surface_flinger_events;
if (!GetSurfaceFlingerEvents(common_model,
&per_buffer_surface_flinger_events)) {
if (!skip_structure_validation_)
return false;
}
BufferToEvents per_buffer_chrome_events =
GetChromeEvents(common_model, &chrome_buffer_id_to_task_id_);
// Try to merge surface flinger events and Chrome events. See |GetMergeScore|
// for more details.
while (true) {
size_t max_merge_score = 0;
std::string surface_flinger_buffer_id;
std::string chrome_buffer_id;
for (const auto& surface_flinger_buffer :
per_buffer_surface_flinger_events) {
for (const auto& chrome_buffer : per_buffer_chrome_events) {
const size_t merge_score =
GetMergeScore(surface_flinger_buffer.second, chrome_buffer.second);
if (merge_score > max_merge_score) {
max_merge_score = merge_score;
surface_flinger_buffer_id = surface_flinger_buffer.first;
chrome_buffer_id = chrome_buffer.first;
}
}
}
if (!max_merge_score)
break; // No more merge candidates.
const ViewId view_id(GetTaskIdFromBufferName(chrome_buffer_id),
GetActivityFromBufferName(surface_flinger_buffer_id));
std::vector<BufferEvents>& view_buffers =
view_buffers_[view_id].buffer_events();
view_buffers.push_back(std::move(
per_buffer_surface_flinger_events[surface_flinger_buffer_id]));
per_buffer_surface_flinger_events.erase(surface_flinger_buffer_id);
view_buffers.back().insert(
view_buffers.back().end(),
per_buffer_chrome_events[chrome_buffer_id].begin(),
per_buffer_chrome_events[chrome_buffer_id].end());
per_buffer_chrome_events.erase(chrome_buffer_id);
SortBufferEventsByTimestamp(&view_buffers.back());
}
for (auto& buffer : per_buffer_surface_flinger_events) {
LOG(WARNING) << "Failed to merge events for buffer: " << buffer.first;
view_buffers_[ViewId(-1 /* task_id */,
GetActivityFromBufferName(buffer.first))]
.buffer_events()
.emplace_back(std::move(buffer.second));
}
for (auto& buffer : per_buffer_chrome_events) {
LOG(WARNING) << "Failed to merge events for buffer: " << buffer.first;
view_buffers_[ViewId(GetTaskIdFromBufferName(buffer.first),
kUnknownActivity)]
.buffer_events()
.emplace_back(std::move(buffer.second));
}
if (view_buffers_.empty()) {
LOG(ERROR) << "No buffer events";
if (!skip_structure_validation_)
return false;
}
// TODO(khmel): Add more information to resolve owner of custom events. At
// this moment add custom events to each view.
const ArcTracingGraphicsModel::BufferEvents custom_events =
GetCustomEvents(common_model);
for (auto& it : view_buffers_) {
AddJanks(&it.second, BufferEventType::kBufferQueueDequeueStart,
BufferEventType::kBufferFillJank);
AddJanks(&it.second, BufferEventType::kExoSurfaceAttach,
BufferEventType::kExoJank);
it.second.global_events().insert(it.second.global_events().end(),
custom_events.begin(),
custom_events.end());
SortBufferEventsByTimestamp(&it.second.global_events());
}
GetChromeTopLevelEvents(common_model, &chrome_top_level_);
if (chrome_top_level_.buffer_events().empty()) {
LOG(ERROR) << "No Chrome top events";
if (!skip_structure_validation_)
return false;
}
GetAndroidTopEvents(common_model, &android_top_level_);
if (android_top_level_.buffer_events().empty()) {
LOG(ERROR) << "No Android events";
if (!skip_structure_validation_)
return false;
}
GetInputEvents(common_model, &input_);
system_model_.CopyFrom(common_model.system_model());
VsyncTrim();
NormalizeTimestamps();
system_model_.CloseRangeForValueEvents(duration_ - 1 /* max_timestamp */);
return true;
}
void ArcTracingGraphicsModel::NormalizeTimestamps() {
std::vector<BufferEvents*> all_buffers;
for (auto& view : view_buffers_) {
for (auto& buffer : view.second.buffer_events())
all_buffers.emplace_back(&buffer);
all_buffers.emplace_back(&view.second.global_events());
}
std::vector<EventsContainer*> containers{&android_top_level_,
&chrome_top_level_, &input_};
for (EventsContainer* container : containers) {
for (auto& buffer : container->buffer_events())
all_buffers.emplace_back(&buffer);
all_buffers.emplace_back(&container->global_events());
}
uint64_t min = std::numeric_limits<uint64_t>::max();
uint64_t max = std::numeric_limits<uint64_t>::min();
for (const BufferEvents* buffer : all_buffers) {
if (!buffer->empty()) {
min = std::min(min, buffer->front().timestamp);
max = std::max(max, buffer->back().timestamp);
}
}
for (const auto& cpu_events : system_model_.all_cpu_events()) {
if (!cpu_events.empty()) {
min = std::min(min, cpu_events.front().timestamp);
max = std::max(max, cpu_events.back().timestamp);
}
}
if (!system_model_.memory_events().empty()) {
min = std::min(min, system_model_.memory_events().front().timestamp);
max = std::max(max, system_model_.memory_events().back().timestamp);
}
duration_ = max - min + 1;
for (BufferEvents* buffer : all_buffers) {
for (auto& event : *buffer)
event.timestamp -= min;
}
for (auto& cpu_events : system_model_.all_cpu_events()) {
for (auto& cpu_event : cpu_events)
cpu_event.timestamp -= min;
}
for (auto& memory_event : system_model_.memory_events())
memory_event.timestamp -= min;
}
void ArcTracingGraphicsModel::Reset() {
chrome_top_level_.Reset();
android_top_level_.Reset();
input_.Reset();
view_buffers_.clear();
chrome_buffer_id_to_task_id_.clear();
system_model_.Reset();
duration_ = 0;
app_title_ = std::string();
app_icon_png_.clear();
platform_ = std::string();
timestamp_ = base::Time();
}
void ArcTracingGraphicsModel::VsyncTrim() {
int64_t trim_timestamp = -1;
for (const auto& it : android_top_level_.global_events()) {
if (it.type == BufferEventType::kVsyncTimestamp) {
trim_timestamp = it.timestamp;
break;
}
}
if (trim_timestamp < 0) {
LOG(ERROR) << "VSYNC event was not found, could not trim.";
return;
}
TrimEventsContainer(&chrome_top_level_, trim_timestamp,
{BufferEventType::kChromeOSDraw});
TrimEventsContainer(&android_top_level_, trim_timestamp,
{BufferEventType::kSurfaceFlingerInvalidationStart,
BufferEventType::kSurfaceFlingerCompositionStart});
TrimEventsContainer(&input_, trim_timestamp,
{BufferEventType::kInputEventCreated});
for (auto& view_buffer : view_buffers_) {
TrimEventsContainer(&view_buffer.second, trim_timestamp,
{BufferEventType::kBufferQueueDequeueStart,
BufferEventType::kExoSurfaceAttach,
BufferEventType::kChromeBarrierOrder});
}
system_model_.Trim(trim_timestamp);
}
int ArcTracingGraphicsModel::GetTaskIdFromBufferName(
const std::string& chrome_buffer_name) const {
const auto it = chrome_buffer_id_to_task_id_.find(chrome_buffer_name);
if (it == chrome_buffer_id_to_task_id_.end())
return -1;
return it->second;
}
std::unique_ptr<base::DictionaryValue> ArcTracingGraphicsModel::Serialize()
const {
std::unique_ptr<base::DictionaryValue> root =
std::make_unique<base::DictionaryValue>();
// Views
base::ListValue view_list;
for (auto& view : view_buffers_) {
base::DictionaryValue view_value = SerializeEventsContainer(view.second);
view_value.SetKey(kKeyActivity, base::Value(view.first.activity));
view_value.SetKey(kKeyTaskId, base::Value(view.first.task_id));
view_list.Append(std::move(view_value));
}
root->SetKey(kKeyViews, std::move(view_list));
// Android top events.
root->SetKey(kKeyAndroid, SerializeEventsContainer(android_top_level_));
// Chrome top events
root->SetKey(kKeyChrome, SerializeEventsContainer(chrome_top_level_));
// Input events
root->SetKey(kKeyInput, SerializeEventsContainer(input_));
// System.
root->SetKey(kKeySystem, system_model_.Serialize());
// Information
base::DictionaryValue information;
information.SetKey(kKeyDuration, base::Value(static_cast<double>(duration_)));
if (!platform_.empty())
information.SetKey(kKeyPlatform, base::Value(platform_));
if (!timestamp_.is_null())
information.SetKey(kKeyTimestamp, base::Value(timestamp_.ToJsTime()));
if (!app_title_.empty())
information.SetKey(kKeyTitle, base::Value(app_title_));
if (!app_icon_png_.empty()) {
const std::string png_data_as_string(
reinterpret_cast<const char*>(&app_icon_png_[0]), app_icon_png_.size());
std::string icon_content;
base::Base64Encode(png_data_as_string, &icon_content);
information.SetKey(kKeyIcon, base::Value(icon_content));
}
root->SetKey(kKeyInformation, std::move(information));
return root;
}
std::string ArcTracingGraphicsModel::SerializeToJson() const {
std::unique_ptr<base::DictionaryValue> root = Serialize();
DCHECK(root);
std::string output;
if (!base::JSONWriter::WriteWithOptions(
*root, base::JSONWriter::OPTIONS_PRETTY_PRINT, &output)) {
LOG(ERROR) << "Failed to serialize model";
}
return output;
}
bool ArcTracingGraphicsModel::LoadFromJson(const std::string& json_data) {
Reset();
const std::unique_ptr<base::DictionaryValue> root =
base::DictionaryValue::From(base::JSONReader::ReadDeprecated(json_data));
if (!root)
return false;
return LoadFromValue(*root);
}
bool ArcTracingGraphicsModel::LoadFromValue(const base::DictionaryValue& root) {
Reset();
const base::Value* view_list =
root.FindKeyOfType(kKeyViews, base::Value::Type::LIST);
if (!view_list || view_list->GetList().empty())
return false;
for (const auto& view_entry : view_list->GetList()) {
if (!view_entry.is_dict())
return false;
const base::Value* activity =
view_entry.FindKeyOfType(kKeyActivity, base::Value::Type::STRING);
const base::Value* task_id =
view_entry.FindKeyOfType(kKeyTaskId, base::Value::Type::INTEGER);
if (!activity || !task_id)
return false;
const ViewId view_id(task_id->GetInt(), activity->GetString());
if (view_buffers_.find(view_id) != view_buffers_.end())
return false;
if (!LoadEventsContainer(&view_entry, &view_buffers_[view_id]))
return false;
}
if (!LoadEventsContainer(root.FindKey(kKeyAndroid), &android_top_level_))
return false;
if (!LoadEventsContainer(root.FindKey(kKeyChrome), &chrome_top_level_))
return false;
const base::Value* input_value = root.FindKey(kKeyInput);
if (input_value && !LoadEventsContainer(input_value, &input_))
return false;
if (!system_model_.Load(root.FindKey(kKeySystem)))
return false;
const base::Value* informaton =
root.FindKeyOfType(kKeyInformation, base::Value::Type::DICTIONARY);
if (informaton) {
if (!ReadDuration(informaton, &duration_))
return false;
const base::Value* platform_value =
informaton->FindKeyOfType(kKeyPlatform, base::Value::Type::STRING);
if (platform_value)
platform_ = platform_value->GetString();
const base::Value* title_value =
informaton->FindKeyOfType(kKeyTitle, base::Value::Type::STRING);
if (title_value)
app_title_ = title_value->GetString();
const base::Value* icon_value =
informaton->FindKeyOfType(kKeyIcon, base::Value::Type::STRING);
if (icon_value) {
std::string icon_content;
if (!base::Base64Decode(icon_value->GetString(), &icon_content))
return false;
app_icon_png_ =
std::vector<unsigned char>(icon_content.begin(), icon_content.end());
}
const base::Value* timestamp_value =
informaton->FindKeyOfType(kKeyTimestamp, base::Value::Type::DOUBLE);
if (timestamp_value)
timestamp_ = base::Time::FromJsTime(timestamp_value->GetDouble());
} else {
if (!ReadDuration(&root, &duration_))
return false;
}
return true;
}
ArcTracingGraphicsModel::EventsContainer::EventsContainer() = default;
ArcTracingGraphicsModel::EventsContainer::~EventsContainer() = default;
void ArcTracingGraphicsModel::EventsContainer::Reset() {
buffer_events_.clear();
global_events_.clear();
}
bool ArcTracingGraphicsModel::EventsContainer::operator==(
const EventsContainer& other) const {
return buffer_events() == other.buffer_events() &&
global_events() == other.global_events();
}
std::ostream& operator<<(std::ostream& os,
ArcTracingGraphicsModel::BufferEventType event_type) {
return os << static_cast<typename std::underlying_type<
ArcTracingGraphicsModel::BufferEventType>::type>(event_type);
}
} // namespace arc