/* * 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 #include #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 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) }; switch (arg1Type) { case TRACE_VALUE_TYPE_BOOL: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, SkToBool(arg1Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_UINT: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_INT: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, static_cast(arg1Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_DOUBLE: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, sk_bit_cast(arg1Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_POINTER: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, skia_private::TraceValueAsPointer(arg1Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_COPY_STRING: [[fallthrough]]; // Perfetto always copies string data case TRACE_VALUE_TYPE_STRING: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, skia_private::TraceValueAsString(arg1Val), [&](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 all the types supported by SetTraceValue. */ template 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}; switch (arg2Type) { case TRACE_VALUE_TYPE_BOOL: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, SkToBool(arg2Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_UINT: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, arg2Val, [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_INT: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, static_cast(arg2Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_DOUBLE: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, sk_bit_cast(arg2Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_POINTER: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, skia_private::TraceValueAsPointer(arg2Val), [&](perfetto::EventContext ctx) { ctx.event()->set_name(eventName); }); break; } case TRACE_VALUE_TYPE_COPY_STRING: [[fallthrough]]; case TRACE_VALUE_TYPE_STRING: { TRACE_EVENT_BEGIN(category, nullptr, arg1Name, arg1Val, arg2Name, skia_private::TraceValueAsString(arg2Val), [&](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) }; switch (arg1Type) { case TRACE_VALUE_TYPE_BOOL: { begin_event_with_second_arg(category, eventName, arg1Name, SkToBool(arg1Val), arg2Name, arg2Type, arg2Val); break; } case TRACE_VALUE_TYPE_UINT: { begin_event_with_second_arg(category, eventName, arg1Name, arg1Val, arg2Name, arg2Type, arg2Val); break; } case TRACE_VALUE_TYPE_INT: { begin_event_with_second_arg(category, eventName, arg1Name, static_cast(arg1Val), arg2Name, arg2Type, arg2Val); break; } case TRACE_VALUE_TYPE_DOUBLE: { begin_event_with_second_arg(category, eventName, arg1Name, sk_bit_cast(arg1Val), arg2Name, arg2Type, arg2Val); break; } case TRACE_VALUE_TYPE_POINTER: { begin_event_with_second_arg(category, eventName, arg1Name, skia_private::TraceValueAsPointer(arg1Val), arg2Name, arg2Type, arg2Val); break; } case TRACE_VALUE_TYPE_COPY_STRING: [[fallthrough]]; case TRACE_VALUE_TYPE_STRING: { begin_event_with_second_arg(category, eventName, arg1Name, skia_private::TraceValueAsString(arg1Val), arg2Name, arg2Type, arg2Val); break; } default: { SkUNREACHABLE; } } } void SkPerfettoTrace::newTracingSection(const char* name) { if (perfetto::Tracing::IsInitialized()) { this->closeTracingSession(); } this->openNewTracingSession(name); }