diff --git a/CMakeLists.txt b/CMakeLists.txt index 955836b76..6c6b9db74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ option(USE_ASAN "Compile with address sanitizer" OFF) option(USE_TSAN "Compile with thread sanitizer" OFF) option(USE_CUDA "Compile CUDA-involved examples (Needed for examples/SubeventCUDAExample)." OFF) option(USE_PODIO "Compile with PODIO support" OFF) +option(USE_PERFETTO "Include Perfetto tracing SDK for performance profiling." OFF) option(BUILD_SHARED_LIBS "Build into both shared and static libs." ON) option(BUILD_EXAMPLES "Build and install examples" ON) option(BUILD_TESTS "Build and install tests" ON) @@ -134,6 +135,44 @@ if (${USE_CUDA}) find_package(CUDA REQUIRED) endif() +if (USE_PERFETTO) + set(PERFETTO_VERSION "55.3" CACHE STRING "Perfetto SDK version to download") + set(PERFETTO_HASH "443b148764c4259c9bf3e724bb508b66f3ff77d30e2ccfafbc0f1791cc08141c" CACHE STRING "Perfetto zip sha256 hash") + set(PERFETTO_SDK_URL "https://github.com/google/perfetto/releases/download/v${PERFETTO_VERSION}/perfetto-cpp-sdk-src.zip") + set(PERFETTO_SDK_ZIP "${CMAKE_BINARY_DIR}/perfetto-cpp-sdk-src-${PERFETTO_VERSION}.zip") + set(PERFETTO_SDK_DIR "${CMAKE_BINARY_DIR}/perfetto_sdk_${PERFETTO_VERSION}") + + if(NOT EXISTS "${PERFETTO_SDK_DIR}/perfetto.cc") + message(STATUS "Downloading Perfetto SDK v${PERFETTO_VERSION}...") + file(DOWNLOAD "${PERFETTO_SDK_URL}" "${PERFETTO_SDK_ZIP}" + STATUS DOWNLOAD_STATUS + TLS_VERIFY ON + EXPECTED_HASH SHA256=${PERFETTO_HASH} + ) + list(GET DOWNLOAD_STATUS 0 DOWNLOAD_ERROR_CODE) + if(DOWNLOAD_ERROR_CODE) + list(GET DOWNLOAD_STATUS 1 DOWNLOAD_ERROR_MSG) + message(FATAL_ERROR "Failed to download Perfetto SDK from ${PERFETTO_SDK_URL}: ${DOWNLOAD_ERROR_MSG}") + endif() + file(ARCHIVE_EXTRACT INPUT "${PERFETTO_SDK_ZIP}" DESTINATION "${PERFETTO_SDK_DIR}") + endif() + + add_library(perfetto_sdk STATIC ${PERFETTO_SDK_DIR}/perfetto.cc) + target_include_directories(perfetto_sdk PUBLIC + $ + $ + ) + target_compile_options(perfetto_sdk PRIVATE + $<$,$,$>: + -Wno-sign-compare -Wno-unused-parameter -Wno-missing-field-initializers> + ) + install(TARGETS perfetto_sdk EXPORT jana2_targets DESTINATION lib) + install(FILES ${PERFETTO_SDK_DIR}/perfetto.h DESTINATION include/perfetto) + set(JANA2_HAVE_PERFETTO 1) +else() + set(JANA2_HAVE_PERFETTO 0) +endif() + #--------- # Report back to the user what we've discovered @@ -178,6 +217,11 @@ if (${USE_CUDA}) else() message(STATUS "USE_CUDA Off") endif() +if (USE_PERFETTO) + message(STATUS "USE_PERFETTO On") +else() + message(STATUS "USE_PERFETTO Off") +endif() if (${USE_PODIO}) message(STATUS "USE_PODIO On --> " ${podio_DIR}) else() diff --git a/src/libraries/JANA/CMakeLists.txt b/src/libraries/JANA/CMakeLists.txt index 27a03b9a5..e2e5a9fec 100644 --- a/src/libraries/JANA/CMakeLists.txt +++ b/src/libraries/JANA/CMakeLists.txt @@ -66,6 +66,10 @@ if (NOT ${USE_XERCES}) message(STATUS "Skipping support for libJANA's JGeometryXML because USE_XERCES=Off") endif() +# Always compiled — JPerfettoService.cc contains a JANA2_HAVE_PERFETTO==0 stub +# so it links correctly even when Perfetto support is not built in. +list(APPEND JANA2_SOURCES Services/JPerfettoService.cc) + add_library(jana2 OBJECT ${JANA2_SOURCES}) find_package(Threads REQUIRED) @@ -74,6 +78,10 @@ target_link_libraries(jana2 PUBLIC ${CMAKE_DL_LIBS} Threads::Threads) target_link_libraries(jana2 PRIVATE VendoredTomlPlusPlus) target_link_libraries(jana2 PRIVATE VendoredMD5) # To pull in the header file +if (USE_PERFETTO) + target_link_libraries(jana2 PUBLIC perfetto_sdk) +endif() + if (${USE_PODIO}) target_link_libraries(jana2 PUBLIC podio::podio podio::podioRootIO) elseif (${USE_ROOT}) @@ -89,6 +97,10 @@ target_include_directories(jana2_static_lib PUBLIC $) target_link_libraries(jana2_static_lib PUBLIC ${CMAKE_DL_LIBS} Threads::Threads) target_link_libraries(jana2_static_lib PUBLIC VendoredTomlPlusPlus) +if (USE_PERFETTO) + target_link_libraries(jana2_static_lib PUBLIC perfetto_sdk) +endif() + if (${USE_PODIO}) target_link_libraries(jana2_static_lib PUBLIC podio::podio podio::podioRootIO) elseif (${USE_ROOT}) @@ -105,6 +117,10 @@ target_include_directories(jana2_shared_lib PUBLIC $) target_link_libraries(jana2_shared_lib PUBLIC ${CMAKE_DL_LIBS} Threads::Threads) target_link_libraries(jana2_shared_lib PUBLIC VendoredTomlPlusPlus) +if (USE_PERFETTO) + target_link_libraries(jana2_shared_lib PUBLIC perfetto_sdk) +endif() + if (${USE_PODIO}) target_link_libraries(jana2_shared_lib PUBLIC podio::podio podio::podioRootIO) elseif (${USE_ROOT}) diff --git a/src/libraries/JANA/Engine/JExecutionEngine.cc b/src/libraries/JANA/Engine/JExecutionEngine.cc index ebcf89448..be3669cf2 100644 --- a/src/libraries/JANA/Engine/JExecutionEngine.cc +++ b/src/libraries/JANA/Engine/JExecutionEngine.cc @@ -1,6 +1,11 @@ #include "JExecutionEngine.h" #include +#include + +#if JANA2_HAVE_PERFETTO +#include +#endif #include #include @@ -416,12 +421,21 @@ void JExecutionEngine::RunWorker(Worker worker) { LOG_DEBUG(GetLogger()) << "Launched worker thread " << worker.worker_id << LOG_END; jana2_worker_id = worker.worker_id; jana2_worker_backtrace = worker.backtrace; +#if JANA2_HAVE_PERFETTO + JPerfettoService::RegisterCurrentThread(worker.worker_id); +#endif try { Task task; while (true) { ExchangeTask(task, worker.worker_id); if (task.arrow == nullptr) break; // Exit as soon as ExchangeTask() stops blocking - task.arrow->Fire(task.input_event, task.outputs, task.output_count, task.status); + { +#if JANA2_HAVE_PERFETTO + TRACE_EVENT("jana", perfetto::DynamicString{task.arrow->GetName()}, + "worker_id", (uint64_t)worker.worker_id); +#endif + task.arrow->Fire(task.input_event, task.outputs, task.output_count, task.status); + } } LOG_DEBUG(GetLogger()) << "Stopped worker thread " << worker.worker_id << LOG_END; } diff --git a/src/libraries/JANA/JFactory.cc b/src/libraries/JANA/JFactory.cc index 685e23006..372ca9f2b 100644 --- a/src/libraries/JANA/JFactory.cc +++ b/src/libraries/JANA/JFactory.cc @@ -6,6 +6,17 @@ #include #include #include +#include + +#if JANA2_HAVE_PERFETTO +#include + +// Thread-local pointer to the factory whose span is currently open on this thread. +// Used to build inter-factory dependency flow arrows: when factory B is triggered from +// within factory A's Process(), g_current_executing_factory == A, so we can draw +// an arrow A → B in the Perfetto timeline. +static thread_local JFactory* g_current_executing_factory = nullptr; +#endif class FlagGuard { @@ -106,88 +117,157 @@ void JFactory::Create(const JEvent& event) { std::rethrow_exception(mException); } - // Make sure Init() ran, which might except... - try { - DoInit(); // This checks mInitStatus internally before calling Init() - } - catch(...) { - // If Init() excepts, we still need to store an empty collection - mStatus = Status::Excepted; - for (auto* output : GetOutputs()) { - output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + // The factory span wraps the entire activation: Init + run callbacks + input population + Process. + // factory_init spans (Init, BeginRun, ChangeRun, EndRun) appear as children of this factory span, + // making the phase structure visible when inspecting any factory span in the trace. + // Flow events connect each factory span to the parent factory that triggered it, + // drawn as arrows showing the data-dependency chain in the Perfetto UI. + { +#if JANA2_HAVE_PERFETTO + // If a parent factory is currently executing on this thread it triggered us. + // Emit a flow-start instant inside the parent's still-open span, then open + // our span with a TerminatingFlow — Perfetto draws an arrow parent → us. + JFactory* const caller = g_current_executing_factory; + // Guard flow_id computation behind the category check: the FNV hash and + // GetEventNumber() are non-trivial work that should not run when tracing + // is disabled, even though the TRACE_EVENT_* args are already lazy. + if (TRACE_EVENT_CATEGORY_ENABLED("factory") && caller != nullptr) { + // FNV-1a-inspired hash: unique 64-bit flow ID per (caller, callee, event). + uint64_t flow_id = 14695981039346656037ULL; + flow_id = (flow_id ^ static_cast(reinterpret_cast(caller))) * 1099511628211ULL; + flow_id = (flow_id ^ static_cast(reinterpret_cast(this))) * 1099511628211ULL; + flow_id = (flow_id ^ static_cast(event.GetEventNumber())) * 1099511628211ULL; + // Flow starts here — we are still executing inside the parent's open span. + TRACE_EVENT_INSTANT("factory", perfetto::DynamicString{GetFactoryName()}, + perfetto::ThreadTrack::Current(), perfetto::Flow::Global(flow_id)); + // Our span starts, consuming the arrow from the parent's instant above. + TRACE_EVENT_BEGIN("factory", perfetto::DynamicString{GetFactoryName()}, + perfetto::TerminatingFlow::Global(flow_id), + "tag", GetTag(), "event_nr", event.GetEventNumber()); + } else { + TRACE_EVENT_BEGIN("factory", perfetto::DynamicString{GetFactoryName()}, + "tag", GetTag(), "event_nr", event.GetEventNumber()); } - for (auto* output : GetVariadicOutputs()) { - output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + // RAII: close the Perfetto span on scope exit (exception-safe). + struct SpanGuard { ~SpanGuard() { TRACE_EVENT_END("factory"); } } span_guard; + // RAII: track the currently-executing factory so sub-factories can link back to us. + // Declared after span_guard — its destructor runs first, restoring the caller + // before the span closes. + struct CallerGuard { + JFactory** slot; JFactory* saved; + ~CallerGuard() { *slot = saved; } + } caller_guard{&g_current_executing_factory, caller}; + g_current_executing_factory = this; +#endif + + // Make sure Init() ran, which might except... + try { + DoInit(); // This checks mInitStatus internally before calling Init() + } + catch(...) { + // If Init() excepts, we still need to store an empty collection + mStatus = Status::Excepted; + for (auto* output : GetOutputs()) { + output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + } + for (auto* output : GetVariadicOutputs()) { + output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + } + std::rethrow_exception(mException); } - std::rethrow_exception(mException); - } - // At this point, Init() has run and has _not_ excepted + // At this point, Init() has run and has _not_ excepted - if (mStatus == Status::Excepted) { - // But Process() might have already excepted! - std::rethrow_exception(mException); - } - else if (mStatus == Status::Empty) { - // Now we know that we need to run Process() to create the data in the first place - try { - auto run_number = event.GetRunNumber(); - if (mPreviousRunNumber != run_number) { - if (m_callback_style == CallbackStyle::LegacyMode) { - if (mPreviousRunNumber != -1) { - CallWithJExceptionWrapper("JFactory::EndRun", [&](){ EndRun(); }); + if (mStatus == Status::Excepted) { + // But Process() might have already excepted! + std::rethrow_exception(mException); + } + else if (mStatus == Status::Empty) { + // Now we know that we need to run Process() to create the data in the first place + try { + auto run_number = event.GetRunNumber(); + if (mPreviousRunNumber != run_number) { + if (m_callback_style == CallbackStyle::LegacyMode) { + if (mPreviousRunNumber != -1) { + { +#if JANA2_HAVE_PERFETTO + TRACE_EVENT("factory_end_run", perfetto::DynamicString{GetFactoryName()}, + "tag", GetTag(), "run_nr", mPreviousRunNumber); +#endif + CallWithJExceptionWrapper("JFactory::EndRun", [&](){ EndRun(); }); + } + } + { +#if JANA2_HAVE_PERFETTO + TRACE_EVENT("factory_change_run", perfetto::DynamicString{GetFactoryName()}, + "tag", GetTag(), "run_nr", run_number); +#endif + CallWithJExceptionWrapper("JFactory::ChangeRun", [&](){ ChangeRun(event.shared_from_this()); }); + } + { +#if JANA2_HAVE_PERFETTO + TRACE_EVENT("factory_begin_run", perfetto::DynamicString{GetFactoryName()}, + "tag", GetTag(), "run_nr", run_number); +#endif + CallWithJExceptionWrapper("JFactory::BeginRun", [&](){ BeginRun(event.shared_from_this()); }); + } + } + else if (m_callback_style == CallbackStyle::ExpertMode) { + { +#if JANA2_HAVE_PERFETTO + TRACE_EVENT("factory_change_run", perfetto::DynamicString{GetFactoryName()}, + "tag", GetTag(), "run_nr", run_number); +#endif + CallWithJExceptionWrapper("JFactory::ChangeRun", [&](){ ChangeRun(event); }); + } } - CallWithJExceptionWrapper("JFactory::ChangeRun", [&](){ ChangeRun(event.shared_from_this()); }); - CallWithJExceptionWrapper("JFactory::BeginRun", [&](){ BeginRun(event.shared_from_this()); }); + mPreviousRunNumber = run_number; + } + for (auto* input : GetInputs()) { + input->Populate(event); + } + for (auto* input : GetVariadicInputs()) { + input->Populate(event); + } + // Process() runs inside the factory span; dependent factory spans appear as children + if (m_callback_style == CallbackStyle::LegacyMode) { + CallWithJExceptionWrapper("JFactory::Process", [&](){ Process(event.shared_from_this()); }); } else if (m_callback_style == CallbackStyle::ExpertMode) { - CallWithJExceptionWrapper("JFactory::ChangeRun", [&](){ ChangeRun(event); }); + CallWithJExceptionWrapper("JFactory::Process", [&](){ Process(event); }); + } + else { + throw JException("Invalid callback style"); } - mPreviousRunNumber = run_number; - } - for (auto* input : GetInputs()) { - input->Populate(event); - } - for (auto* input : GetVariadicInputs()) { - input->Populate(event); - } - if (m_callback_style == CallbackStyle::LegacyMode) { - CallWithJExceptionWrapper("JFactory::Process", [&](){ Process(event.shared_from_this()); }); - } - else if (m_callback_style == CallbackStyle::ExpertMode) { - CallWithJExceptionWrapper("JFactory::Process", [&](){ Process(event); }); } - else { - throw JException("Invalid callback style"); + catch (...) { + // Save everything already created even if we throw an exception + // This is so that we leave everything in a valid state just in case someone tries to catch the exception recover, + // such as EICrecon. (Remember that a missing collection in the podio frame will segfault if anyone tries to write that frame) + // Note that the collections themselves won't know that they exited early + + LOG << "Exception in JFactory::Create, prefix=" << GetPrefix(); + mStatus = Status::Excepted; + mException = std::current_exception(); + for (auto* output : GetOutputs()) { + output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + } + for (auto* output : GetVariadicOutputs()) { + output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + } + throw; } - } - catch (...) { - // Save everything already created even if we throw an exception - // This is so that we leave everything in a valid state just in case someone tries to catch the exception recover, - // such as EICrecon. (Remember that a missing collection in the podio frame will segfault if anyone tries to write that frame) - // Note that the collections themselves won't know that they exited early - LOG << "Exception in JFactory::Create, prefix=" << GetPrefix(); - mStatus = Status::Excepted; - mException = std::current_exception(); + // Save the (successfully processed) data + mStatus = Status::Processed; for (auto* output : GetOutputs()) { - output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Created); } for (auto* output : GetVariadicOutputs()) { - output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Excepted); + output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Created); } - throw; - } - - // Save the (successfully processed) data - mStatus = Status::Processed; - for (auto* output : GetOutputs()) { - output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Created); - } - for (auto* output : GetVariadicOutputs()) { - output->LagrangianStore(*event.GetFactorySet(), JDatabundle::Status::Created); } - } + } // end factory span } void JFactory::DoInit() { @@ -201,7 +281,13 @@ void JFactory::DoInit() { service->Fetch(m_app); } try { - CallWithJExceptionWrapper("JFactory::Init", [&](){ Init(); }); + { +#if JANA2_HAVE_PERFETTO + TRACE_EVENT("factory_init", perfetto::DynamicString{GetFactoryName()}, + "tag", GetTag()); +#endif + CallWithJExceptionWrapper("JFactory::Init", [&](){ Init(); }); + } mInitStatus = InitStatus::InitRun; } catch (...) { diff --git a/src/libraries/JANA/JVersion.h.in b/src/libraries/JANA/JVersion.h.in index ac495c47f..db710b399 100644 --- a/src/libraries/JANA/JVersion.h.in +++ b/src/libraries/JANA/JVersion.h.in @@ -9,6 +9,7 @@ #define JANA2_HAVE_PODIO @JANA2_HAVE_PODIO@ #define JANA2_HAVE_ROOT @JANA2_HAVE_ROOT@ #define JANA2_HAVE_XERCES @JANA2_HAVE_XERCES@ +#define JANA2_HAVE_PERFETTO @JANA2_HAVE_PERFETTO@ #define JANA2_COMMIT_HASH "@JVERSION_COMMIT_HASH@" #define JANA2_COMMIT_DATE "@JVERSION_COMMIT_DATE@" @@ -36,6 +37,7 @@ struct JVersion { static constexpr bool HasPodio() { return JANA2_HAVE_PODIO; } static constexpr bool HasROOT() { return JANA2_HAVE_ROOT; } static constexpr bool HasXerces() { return JANA2_HAVE_XERCES; } + static constexpr bool HasPerfetto() { return JANA2_HAVE_PERFETTO; } static std::string GetVersion(); static constexpr uint64_t GetVersionNumber(); diff --git a/src/libraries/JANA/Services/JPerfettoService.cc b/src/libraries/JANA/Services/JPerfettoService.cc new file mode 100644 index 000000000..f2cb4ba2e --- /dev/null +++ b/src/libraries/JANA/Services/JPerfettoService.cc @@ -0,0 +1,77 @@ +// Copyright 2026, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#include + +#if JANA2_HAVE_PERFETTO + +#include +#include + +// Allocate static storage for the categories (must be in exactly one .cc file). +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + +void JPerfettoService::Init() { + auto params = GetApplication()->GetJParameterManager(); + params->SetDefaultParameter("jana:perfetto_output", m_output_file, + "Output file path for the Perfetto trace (written on exit)."); + params->SetDefaultParameter("jana:perfetto_enabled", m_enabled, + "Enable Perfetto tracing."); + + if (!m_enabled) return; + + perfetto::TracingInitArgs args; + args.backends |= perfetto::kInProcessBackend; + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); + + perfetto::TraceConfig cfg; + cfg.add_buffers()->set_size_kb(1024 * 256); // 256 MB ring buffer + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + m_tracing_session = perfetto::Tracing::NewTrace(); + m_tracing_session->Setup(cfg); + m_tracing_session->StartBlocking(); + + LOG_INFO(GetLogger()) << "Perfetto tracing started. Output will be written to: " << m_output_file << LOG_END; +} + +JPerfettoService::~JPerfettoService() { + if (!m_enabled || !m_tracing_session) return; + + perfetto::TrackEvent::Flush(); + m_tracing_session->StopBlocking(); + + std::vector trace_data(m_tracing_session->ReadTraceBlocking()); + std::ofstream output(m_output_file, std::ios::out | std::ios::binary); + if (!output) { + std::cerr << "JPerfettoService: Failed to open Perfetto output file: " << m_output_file << "\n"; + return; + } + output.write(trace_data.data(), static_cast(trace_data.size())); + std::cout << "JPerfettoService: Perfetto trace written to: " << m_output_file + << " (" << trace_data.size() / 1024 << " kB)\n"; + std::cout << "JPerfettoService: Open the trace at https://ui.perfetto.dev\n"; +} + +void JPerfettoService::RegisterCurrentThread(int worker_id) { + std::ostringstream name; + name << "Worker " << worker_id; + perfetto::ThreadTrack track = perfetto::ThreadTrack::Current(); + perfetto::protos::gen::TrackDescriptor desc = track.Serialize(); + desc.set_name(name.str()); + perfetto::TrackEvent::SetTrackDescriptor(track, desc); +} + +#else // JANA2_HAVE_PERFETTO + +void JPerfettoService::Init() { + std::cerr << "JPerfettoService: JANA2 was not built with Perfetto support (USE_PERFETTO=OFF).\n"; +} + +JPerfettoService::~JPerfettoService() {} + +void JPerfettoService::RegisterCurrentThread(int /*worker_id*/) {} + +#endif // JANA2_HAVE_PERFETTO diff --git a/src/libraries/JANA/Services/JPerfettoService.h b/src/libraries/JANA/Services/JPerfettoService.h new file mode 100644 index 000000000..47cf13976 --- /dev/null +++ b/src/libraries/JANA/Services/JPerfettoService.h @@ -0,0 +1,40 @@ +// Copyright 2026, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#pragma once + +#include +#include + +#if JANA2_HAVE_PERFETTO +#include + +// Declare categories in a header so all TUs that use TRACE_EVENT share the same registry. +PERFETTO_DEFINE_CATEGORIES( + perfetto::Category("jana").SetDescription("JANA2 framework events (arrow dispatch)"), + perfetto::Category("factory").SetDescription("JANA2 factory Process() execution per event"), + perfetto::Category("factory_init").SetDescription("JANA2 factory Init() callback (first activation only)"), + perfetto::Category("factory_begin_run").SetDescription("JANA2 factory BeginRun() callback"), + perfetto::Category("factory_change_run").SetDescription("JANA2 factory ChangeRun() callback"), + perfetto::Category("factory_end_run").SetDescription("JANA2 factory EndRun() callback") +); +#endif // JANA2_HAVE_PERFETTO + +class JPerfettoService : public JService { + +public: + JPerfettoService() = default; + ~JPerfettoService() override; + + void Init() override; + + /// Call from each worker thread at startup to register the thread track with its worker ID. + static void RegisterCurrentThread(int worker_id); + +private: +#if JANA2_HAVE_PERFETTO + std::unique_ptr m_tracing_session; +#endif + std::string m_output_file = "jana_trace.perfetto"; + bool m_enabled = true; +}; diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index ac7680fc8..f3db8d855 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -6,4 +6,5 @@ add_subdirectory(janarate) add_subdirectory(janaroot) add_subdirectory(janaview) add_subdirectory(regressiontest) +add_subdirectory(perfetto) diff --git a/src/plugins/perfetto/CMakeLists.txt b/src/plugins/perfetto/CMakeLists.txt new file mode 100644 index 000000000..ea84d4362 --- /dev/null +++ b/src/plugins/perfetto/CMakeLists.txt @@ -0,0 +1,7 @@ +if (USE_PERFETTO) + add_jana_plugin(perfetto SOURCES perfetto_plugin.cc) + add_test(NAME jana-plugin-perfetto-tests + COMMAND jana -Pplugins=JTest,perfetto -Pjana:nevents=10) +else() + message(STATUS "Skipping plugins/perfetto because USE_PERFETTO=Off") +endif() diff --git a/src/plugins/perfetto/perfetto_plugin.cc b/src/plugins/perfetto/perfetto_plugin.cc new file mode 100644 index 000000000..bd85b9d21 --- /dev/null +++ b/src/plugins/perfetto/perfetto_plugin.cc @@ -0,0 +1,17 @@ +// Copyright 2026, Jefferson Science Associates, LLC. +// Subject to the terms in the LICENSE file found in the top-level directory. + +#include +#include +#include + +extern "C" { +void InitPlugin(JApplication* app) { + InitJANAPlugin(app); + app->ProvideService(std::make_shared()); + + // Ensure that the call graph is recorded so that factory dependency chains + // are captured alongside the Perfetto timeline. + app->SetParameterValue("record_call_stack", 1); +} +} // extern "C"