| /* | |
| * 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 "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]); | |
| } | |
| 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_COUNTER == phase) { | |
| SkASSERT(numArgs == 1); | |
| // Ignore argNames[0] as it's always set to "value" | |
| this->triggerTraceCount(categoryEnabledFlag, name, argTypes[0], argValues[0]); | |
| } 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<int64_t>(arg1Val), | |
| [&](perfetto::EventContext ctx) { | |
| ctx.event()->set_name(eventName); }); | |
| break; | |
| } | |
| case TRACE_VALUE_TYPE_DOUBLE: { | |
| TRACE_EVENT_BEGIN(category, nullptr, arg1Name, sk_bit_cast<double>(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; | |
| } | |
| } | |
| } | |
| void SkPerfettoTrace::triggerTraceCount(const uint8_t* categoryEnabledFlag, const char* eventName, | |
| const uint8_t& arg1Type, const uint64_t& arg1Val) { | |
| perfetto::DynamicCategory category{ this->getCategoryGroupName(categoryEnabledFlag) }; | |
| perfetto::CounterTrack counter{eventName}; | |
| switch (arg1Type) { | |
| case TRACE_VALUE_TYPE_UINT: { | |
| TRACE_COUNTER(category, counter, arg1Val); | |
| break; | |
| } | |
| case TRACE_VALUE_TYPE_INT: { | |
| TRACE_COUNTER(category, counter, static_cast<int64_t>(arg1Val)); | |
| break; | |
| } | |
| case TRACE_VALUE_TYPE_DOUBLE: { | |
| TRACE_COUNTER(category, counter, sk_bit_cast<double>(arg1Val)); | |
| 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 <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}; | |
| 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<int64_t>(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<double>(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<int64_t>(arg1Val), | |
| arg2Name, arg2Type, arg2Val); | |
| break; | |
| } | |
| case TRACE_VALUE_TYPE_DOUBLE: { | |
| begin_event_with_second_arg(category, eventName, arg1Name, sk_bit_cast<double>(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); | |
| } |