blob: 057cc4920f0b93e8fe0233ea3fa6260f12b35a60 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/impl/perfetto_trace_logging_platform.h"
#include <fcntl.h>
#include <unistd.h>
#include <chrono>
#include <fstream>
#include <string>
#include <vector>
#include "perfetto/tracing/core/trace_config.h"
#include "perfetto/tracing/tracing.h"
#include "perfetto/tracing/track_event.h"
#include "platform/impl/logging.h"
#include "util/chrono_helpers.h"
PERFETTO_DEFINE_CATEGORIES(
::perfetto::Category("any").SetDescription("General category"),
::perfetto::Category("mdns").SetDescription("mDNS protocol"),
::perfetto::Category("quic").SetDescription("QUIC protocol"),
::perfetto::Category("ssl").SetDescription("SSL/TLS"),
::perfetto::Category("presentation").SetDescription("Presentation API"),
::perfetto::Category("standalone_receiver")
.SetDescription("Standalone Receiver"),
::perfetto::Category("discovery").SetDescription("Discovery"),
::perfetto::Category("standalone_sender")
.SetDescription("Standalone Sender"),
::perfetto::Category("receiver").SetDescription("Cast Receiver"),
::perfetto::Category("sender").SetDescription("Cast Sender"));
PERFETTO_TRACK_EVENT_STATIC_STORAGE();
namespace openscreen {
PerfettoTraceLoggingPlatform::PerfettoTraceLoggingPlatform() {
perfetto::TracingInitArgs args;
// The in-process backend allows recording into a file or memory buffer
// from within the same process.
args.backends = perfetto::kInProcessBackend;
perfetto::Tracing::Initialize(args);
perfetto::TrackEvent::Register();
perfetto::TraceConfig cfg;
cfg.add_buffers()->set_size_kb(10 * 1024);
auto* ds_cfg = cfg.add_data_sources()->mutable_config();
ds_cfg->set_name("track_event");
tracing_session_ = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
tracing_session_->Setup(cfg);
tracing_session_->StartBlocking();
StartTracing(this);
}
PerfettoTraceLoggingPlatform::~PerfettoTraceLoggingPlatform() {
StopTracing();
tracing_session_->StopBlocking();
const std::vector<char> trace_data(tracing_session_->ReadTraceBlocking());
const std::string filename = std::format("openscreen_{}.pftrace", getpid());
std::ofstream output_file(filename, std::ios::out | std::ios::binary);
output_file.write(trace_data.data(), trace_data.size());
output_file.close();
OSP_LOG_INFO << "Perfetto trace log written to: " << filename;
}
bool PerfettoTraceLoggingPlatform::IsTraceLoggingEnabled(
TraceCategory category) {
// Perfetto checks if the category is enabled before tracing, so this method
// would only be helpful for avoiding instantiating heavy objects. However,
// based on how we are using Perfetto currently it is likely that the effort
// to check if the category is enabled cumulatively using
// perfetto::DynamicString is more effort than occasionally constructing a
// large trace object. So just return true here.
return true;
}
void PerfettoTraceLoggingPlatform::LogTrace(TraceEvent event,
Clock::time_point end_time) {
const auto start_ns =
to_nanoseconds(event.start_time.time_since_epoch()).count();
const auto end_ns = to_nanoseconds(end_time.time_since_epoch()).count();
const char* category_name = ToString(event.category);
perfetto::TrackEvent::Trace([&](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(start_ns);
auto* track_event = packet->set_track_event();
track_event->set_type(
perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN);
track_event->add_categories(category_name);
track_event->set_name(event.name);
for (const auto& arg : event.arguments) {
auto* debug = track_event->add_debug_annotations();
debug->set_name(arg.first);
debug->set_string_value(arg.second);
}
});
perfetto::TrackEvent::Trace([&](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(end_ns);
auto* track_event = packet->set_track_event();
track_event->set_type(perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
track_event->add_categories(category_name);
});
}
void PerfettoTraceLoggingPlatform::LogAsyncStart(TraceEvent event) {
const char* category_name = ToString(event.category);
const auto start_ns =
to_nanoseconds(event.start_time.time_since_epoch()).count();
const uint64_t flow_id = event.ids.current;
perfetto::TrackEvent::Trace([&](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(start_ns);
auto* track_event = packet->set_track_event();
track_event->set_type(
perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_BEGIN);
track_event->set_track_uuid(flow_id);
track_event->add_categories(category_name);
track_event->set_name(event.name);
for (const auto& arg : event.arguments) {
auto* debug = track_event->add_debug_annotations();
debug->set_name(arg.first);
debug->set_string_value(arg.second);
}
});
}
void PerfettoTraceLoggingPlatform::LogAsyncEnd(TraceEvent event) {
const char* category_name = ToString(event.category);
const auto end_ns =
to_nanoseconds(event.start_time.time_since_epoch()).count();
const uint64_t flow_id = event.ids.current;
perfetto::TrackEvent::Trace([&](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(end_ns);
auto* track_event = packet->set_track_event();
track_event->set_type(perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
track_event->set_track_uuid(flow_id);
track_event->add_categories(category_name);
});
}
void PerfettoTraceLoggingPlatform::LogFlow(TraceEvent event, FlowType type) {
const char* category_name = ToString(event.category);
const auto timestamp_ns =
to_nanoseconds(event.start_time.time_since_epoch()).count();
// Use root ID for flow correlation if available, otherwise current.
const uint64_t flow_id =
(event.ids.root != kUnsetTraceId && event.ids.root != kEmptyTraceId)
? event.ids.root
: event.ids.current;
perfetto::TrackEvent::Trace([&](perfetto::TrackEvent::TraceContext ctx) {
auto packet = ctx.NewTracePacket();
packet->set_timestamp(timestamp_ns);
auto* track_event = packet->set_track_event();
track_event->set_type(perfetto::protos::pbzero::TrackEvent::TYPE_INSTANT);
track_event->add_categories(category_name);
track_event->set_name(event.name);
if (type == FlowType::kFlowEnd) {
track_event->add_terminating_flow_ids(flow_id);
} else {
track_event->add_flow_ids(flow_id);
}
for (const auto& arg : event.arguments) {
auto* debug = track_event->add_debug_annotations();
debug->set_name(arg.first);
debug->set_string_value(arg.second);
}
});
}
} // namespace openscreen