353 lines
15 KiB
C++
353 lines
15 KiB
C++
/*
|
|
* Copyright 2022 Google LLC
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "tools/trace/SkPerfettoTrace.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <fstream>
|
|
#include "src/core/SkTraceEvent.h"
|
|
#include "src/core/SkTraceEventCommon.h"
|
|
#include "tools/flags/CommandLineFlags.h"
|
|
|
|
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
|
|
|
|
static DEFINE_string(perfettoOutputDir, "./",
|
|
"Output directory for perfetto trace file(s).\n"
|
|
"Note: not the name of the file itself.\n"
|
|
"Will only have an effect if perfetto tracing is enabled. See --trace.");
|
|
static DEFINE_string(perfettoOutputFileName, "trace",
|
|
"Output file name (excluding path and file extension) for the perfetto trace"
|
|
"file.\nNote: When splitting trace files by benchmark (see "
|
|
"--splitPerfettoTracesByBenchmark), file name will be determined by the "
|
|
"benchmark name.\n"
|
|
"Will only have an effect if perfetto tracing is enabled. See --trace.");
|
|
static DEFINE_string(perfettoOutputFileExtension, ".perfetto-trace",
|
|
"Output file extension for perfetto trace file(s).\n"
|
|
"Will only have an effect if perfetto tracing is enabled. See --trace.");
|
|
static DEFINE_bool(longPerfettoTrace, false,
|
|
"Perfetto within Skia is optimized for tracing performance of 'smaller' traces"
|
|
"(~10 seconds or less). Set this flag to true to optimize for longer tracing"
|
|
"sessions.\n"
|
|
"Will only have an effect if perfetto tracing is enabled. See --trace.");
|
|
|
|
SkPerfettoTrace::SkPerfettoTrace() {
|
|
fOutputPath = FLAGS_perfettoOutputDir[0];
|
|
fOutputFileExtension = FLAGS_perfettoOutputFileExtension[0];
|
|
this->openNewTracingSession(FLAGS_perfettoOutputFileName[0]);
|
|
}
|
|
|
|
SkPerfettoTrace::~SkPerfettoTrace() {
|
|
this->closeTracingSession();
|
|
}
|
|
|
|
void SkPerfettoTrace::openNewTracingSession(const std::string& baseFileName) {
|
|
perfetto::TracingInitArgs args;
|
|
/* Store the current tracing session's output file path as a member attribute so it can
|
|
* be referenced when closing a tracing session (needed for short traces where writing to
|
|
* the output file occurs at the end of all tracing). */
|
|
fCurrentSessionFullOutputPath = fOutputPath + baseFileName + fOutputFileExtension;
|
|
|
|
/* Enable using only the in-process backend (recording only within the app itself). This is as
|
|
* opposed to additionally including perfetto::kSystemBackend, which uses a Perfetto daemon. */
|
|
args.backends |= perfetto::kInProcessBackend;
|
|
|
|
if (FLAGS_longPerfettoTrace) {
|
|
/* Set the shared memory buffer size higher than the default of 256 KB to
|
|
reduce trace writer packet loss occurrences associated with larger traces. */
|
|
args.shmem_size_hint_kb = 2000;
|
|
}
|
|
perfetto::Tracing::Initialize(args);
|
|
perfetto::TrackEvent::Register();
|
|
|
|
// Set up event tracing configuration.
|
|
perfetto::protos::gen::TrackEventConfig track_event_cfg;
|
|
perfetto::TraceConfig cfg;
|
|
|
|
/* Set the central memory buffer size - will record up to this amount of data. */
|
|
cfg.add_buffers()->set_size_kb(32000);
|
|
|
|
if (FLAGS_longPerfettoTrace) {
|
|
/* Enable continuous file writing/"streaming mode" to output trace data throughout the
|
|
* program instead of one large dump at the end. */
|
|
cfg.set_write_into_file(true);
|
|
/* If set to a value other than the default, set how often trace data gets written to the
|
|
* output file. */
|
|
cfg.set_file_write_period_ms(5000);
|
|
/* Force periodic commitment of shared memory buffer pages to the central buffer.
|
|
* Helps prevent out-of-order event slices with long traces. */
|
|
cfg.set_flush_period_ms(10000);
|
|
}
|
|
|
|
auto* ds_cfg = cfg.add_data_sources()->mutable_config();
|
|
ds_cfg->set_name("track_event");
|
|
ds_cfg->set_track_event_config_raw(track_event_cfg.SerializeAsString());
|
|
|
|
// Begin a tracing session.
|
|
tracingSession = perfetto::Tracing::NewTrace();
|
|
if (FLAGS_longPerfettoTrace) {
|
|
fd = open(fCurrentSessionFullOutputPath.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600);
|
|
tracingSession->Setup(cfg, fd);
|
|
} else {
|
|
tracingSession->Setup(cfg);
|
|
}
|
|
tracingSession->StartBlocking();
|
|
}
|
|
|
|
void SkPerfettoTrace::closeTracingSession() {
|
|
perfetto::TrackEvent::Flush();
|
|
tracingSession->StopBlocking();
|
|
if (!FLAGS_longPerfettoTrace) {
|
|
std::vector<char> trace_data(tracingSession->ReadTraceBlocking());
|
|
std::ofstream output;
|
|
output.open(fCurrentSessionFullOutputPath, std::ios::out | std::ios::binary);
|
|
output.write(&trace_data[0], trace_data.size());
|
|
output.close();
|
|
} else {
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
SkEventTracer::Handle SkPerfettoTrace::addTraceEvent(char phase,
|
|
const uint8_t* categoryEnabledFlag,
|
|
const char* name,
|
|
uint64_t id,
|
|
int numArgs,
|
|
const char** argNames,
|
|
const uint8_t* argTypes,
|
|
const uint64_t* argValues,
|
|
uint8_t flags) {
|
|
perfetto::DynamicCategory category{ this->getCategoryGroupName(categoryEnabledFlag) };
|
|
if (TRACE_EVENT_PHASE_COMPLETE == phase ||
|
|
TRACE_EVENT_PHASE_INSTANT == phase) {
|
|
switch (numArgs) {
|
|
case 0: {
|
|
this->triggerTraceEvent(categoryEnabledFlag, name);
|
|
break;
|
|
}
|
|
case 1: {
|
|
this->triggerTraceEvent(categoryEnabledFlag, name, argNames[0], argTypes[0],
|
|
argValues[0]);
|
|
break;
|
|
}
|
|
case 2: {
|
|
this->triggerTraceEvent(categoryEnabledFlag, name, argNames[0], argTypes[0],
|
|
argValues[0], argNames[1], argTypes[1], argValues[1]);
|
|
break;
|
|
}
|
|
}
|
|
} else if (TRACE_EVENT_PHASE_END == phase) {
|
|
TRACE_EVENT_END(category);
|
|
}
|
|
|
|
if (TRACE_EVENT_PHASE_INSTANT == phase) {
|
|
TRACE_EVENT_END(category);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void SkPerfettoTrace::updateTraceEventDuration(const uint8_t* categoryEnabledFlag,
|
|
const char* name,
|
|
SkEventTracer::Handle handle) {
|
|
// This is only ever called from a scoped trace event, so we will just end the event.
|
|
perfetto::DynamicCategory category{ this->getCategoryGroupName(categoryEnabledFlag) };
|
|
TRACE_EVENT_END(category);
|
|
}
|
|
|
|
const uint8_t* SkPerfettoTrace::getCategoryGroupEnabled(const char* name) {
|
|
return fCategories.getCategoryGroupEnabled(name);
|
|
}
|
|
|
|
const char* SkPerfettoTrace::getCategoryGroupName(const uint8_t* categoryEnabledFlag) {
|
|
return fCategories.getCategoryGroupName(categoryEnabledFlag);
|
|
}
|
|
|
|
void SkPerfettoTrace::triggerTraceEvent(const uint8_t* categoryEnabledFlag,
|
|
const char* eventName) {
|
|
perfetto::DynamicCategory category{ this->getCategoryGroupName(categoryEnabledFlag) };
|
|
TRACE_EVENT_BEGIN(category, nullptr, [&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName);
|
|
});
|
|
}
|
|
|
|
void SkPerfettoTrace::triggerTraceEvent(const uint8_t* categoryEnabledFlag, const char* eventName,
|
|
const char* arg1Name, const uint8_t& arg1Type,
|
|
const uint64_t& arg1Val) {
|
|
perfetto::DynamicCategory category{ this->getCategoryGroupName(categoryEnabledFlag) };
|
|
skia_private::TraceValueUnion value;
|
|
value.as_uint = arg1Val;
|
|
|
|
switch (arg1Type) {
|
|
case TRACE_VALUE_TYPE_BOOL: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_bool,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_UINT: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_uint,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_INT: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_int,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_DOUBLE: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_double,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_POINTER: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_pointer,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_STRING: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_string,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_COPY_STRING: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, value.as_string,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
default: {
|
|
SkUNREACHABLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/* Define a template to help handle all the possible TRACE_EVENT_BEGIN macro call
|
|
* combinations with 2 arguments of various types (defined in TraceValueUnion).
|
|
*/
|
|
template <typename T>
|
|
void begin_event_with_second_arg(const char * categoryName, const char* eventName,
|
|
const char* arg1Name, T arg1Val, const char* arg2Name,
|
|
const uint8_t& arg2Type, const uint64_t& arg2Val) {
|
|
perfetto::DynamicCategory category{categoryName};
|
|
skia_private::TraceValueUnion value;
|
|
value.as_uint = arg2Val;
|
|
|
|
switch (arg2Type) {
|
|
case TRACE_VALUE_TYPE_BOOL: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, value.as_bool,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_UINT: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, value.as_uint,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_INT: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg1Name, value.as_int,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_DOUBLE: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, value.as_double,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_POINTER: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, value.as_pointer,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_STRING: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, value.as_string,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_COPY_STRING: {
|
|
TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, value.as_string,
|
|
[&](perfetto::EventContext ctx) {
|
|
ctx.event()->set_name(eventName); });
|
|
break;
|
|
}
|
|
default: {
|
|
SkUNREACHABLE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} // anonymous namespace
|
|
|
|
void SkPerfettoTrace::triggerTraceEvent(const uint8_t* categoryEnabledFlag,
|
|
const char* eventName, const char* arg1Name,
|
|
const uint8_t& arg1Type, const uint64_t& arg1Val,
|
|
const char* arg2Name, const uint8_t& arg2Type,
|
|
const uint64_t& arg2Val) {
|
|
|
|
const char * category{ this->getCategoryGroupName(categoryEnabledFlag) };
|
|
skia_private::TraceValueUnion value;
|
|
value.as_uint = arg1Val;
|
|
|
|
switch (arg1Type) {
|
|
case TRACE_VALUE_TYPE_BOOL: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_bool, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_UINT: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_uint, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_INT: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_int, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_DOUBLE: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_double, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_POINTER: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_pointer, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_STRING: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_string, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
case TRACE_VALUE_TYPE_COPY_STRING: {
|
|
begin_event_with_second_arg(category, eventName, arg1Name, value.as_string, arg2Name,
|
|
arg2Type, arg2Val);
|
|
break;
|
|
}
|
|
default: {
|
|
SkUNREACHABLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkPerfettoTrace::newTracingSection(const char* name) {
|
|
if (perfetto::Tracing::IsInitialized()) {
|
|
this->closeTracingSession();
|
|
}
|
|
this->openNewTracingSession(name);
|
|
}
|