From 7c343eaaa75b4dbb28bcde31a4fddc9d8dcad50b Mon Sep 17 00:00:00 2001 From: Rosen Matev <rosen.matev@cern.ch> Date: Fri, 6 Jan 2023 23:03:49 +0100 Subject: [PATCH] Add algorithm to programmatically enable/disable perf --- GaudiProfiling/CMakeLists.txt | 12 +++ .../src/component/perf/PerfProfile.cpp | 78 +++++++++++++++++++ .../tests/pytest/test_perf_profile.py | 51 ++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 GaudiProfiling/src/component/perf/PerfProfile.cpp create mode 100644 GaudiProfiling/tests/pytest/test_perf_profile.py diff --git a/GaudiProfiling/CMakeLists.txt b/GaudiProfiling/CMakeLists.txt index a3da42da5e..8f39c939d4 100644 --- a/GaudiProfiling/CMakeLists.txt +++ b/GaudiProfiling/CMakeLists.txt @@ -111,6 +111,16 @@ gaudi_add_module(GaudiJemalloc LINK GaudiKernel GaudiAlgLib) +#----------------------------------- +# Linux perf +#----------------------------------- +# TODO: The PerfProfile algorithm should only be compiled for Linux +# (and consequently the test should only run if PerfProfile is there). +gaudi_add_module(GaudiPerf + SOURCES src/component/perf/PerfProfile.cpp + LINK GaudiKernel + GaudiAlgLib) + # Special handling of unresolved symbols in Jemmalloc. # The profilers need to have libjemalloc.so pre-loaded to # work, so it's better if the symbols stay undefined in case somebody tries to @@ -122,6 +132,8 @@ if(GAUDI_USE_JEMALLOC) COMMAND run ${CMAKE_COMMAND} -E env LD_PRELOAD=$<TARGET_PROPERTY:jemalloc::jemalloc,LOCATION> gaudirun.py) endif() +gaudi_add_tests(pytest) + # Install python modules gaudi_install(PYTHON) # Install other scripts diff --git a/GaudiProfiling/src/component/perf/PerfProfile.cpp b/GaudiProfiling/src/component/perf/PerfProfile.cpp new file mode 100644 index 0000000000..a3914e49e9 --- /dev/null +++ b/GaudiProfiling/src/component/perf/PerfProfile.cpp @@ -0,0 +1,78 @@ +/***********************************************************************************\ +* (c) Copyright 1998-2023 CERN for the benefit of the LHCb and ATLAS collaborations * +* * +* This software is distributed under the terms of the Apache version 2 licence, * +* copied verbatim in the file "LICENSE". * +* * +* In applying this licence, CERN does not waive the privileges and immunities * +* granted to it by virtue of its status as an Intergovernmental Organization * +* or submit itself to any jurisdiction. * +\***********************************************************************************/ +#include "GaudiAlg/Consumer.h" +#include <fcntl.h> +#include <string_view> +#include <sys/stat.h> + +/** Algorithm to enable/disable profiling with Linux perf at given events. + * + * Needs at least perf 5.9. To control perf record, start it as + * + * perf record -D -1 --control fifo:GaudiPerfProfile.fifo ... gaudirun.py ... + * + * The path to the control fifo (GaudiPerfProfile.fifo) is configurable + * with the FIFOPath property. The fifo must be created before running perf. + * + */ +struct PerfProfile final : Gaudi::Functional::Consumer<void()> { + using Consumer::Consumer; + + StatusCode initialize() override { + return Consumer::initialize().andThen( [this]() { + m_fifo = ::open( m_fifoPath.value().c_str(), O_WRONLY | O_NONBLOCK ); + if ( m_fifo == -1 ) { + fatal() << "open(\"" << m_fifoPath.value() << "\"): " << ::strerror( errno ) << endmsg; + return StatusCode::FAILURE; + } + return StatusCode::SUCCESS; + } ); + } + + StatusCode finalize() override { + if ( m_fifo != -1 ) { ::close( m_fifo ); } + return Consumer::finalize(); + } + + void operator()() const override { + // We could use EventContext::evt(), however it is not always valid, as is the case with EvtSel="NONE". Instead, use + // an atomic counter. + auto eventNumber = m_eventNumber++; + if ( eventNumber == m_nStartFromEvent.value() ) { + warning() << "Starting perf profile at event " << eventNumber << endmsg; + fifo_write( "enable\n" ); + } + + if ( m_nStopAtEvent > 0 && eventNumber == m_nStopAtEvent.value() ) { + warning() << "Stopping perf profile at event " << eventNumber << endmsg; + fifo_write( "disable\n" ); + } + } + +private: + void fifo_write( std::string_view s ) const { + if ( ::write( m_fifo, s.data(), s.size() ) < ssize_t( s.size() ) ) { + error() << "Write of \"" << s << "\" to FIFO failed: " << ::strerror( errno ) << endmsg; + } + } + + mutable std::atomic<long unsigned> m_eventNumber = 0; + int m_fifo = -1; + + Gaudi::Property<std::string> m_fifoPath{ this, "FIFOPath", "GaudiPerfProfile.fifo", "Path to perf control FIFO." }; + Gaudi::Property<unsigned long> m_nStartFromEvent{ this, "StartFromEventN", 1, + "After what event we start profiling." }; + Gaudi::Property<unsigned long> m_nStopAtEvent{ + this, "StopAtEventN", 0, + "After what event we stop profiling. If 0 than we also profile finalization stage. Default = 0." }; +}; + +DECLARE_COMPONENT( PerfProfile ) diff --git a/GaudiProfiling/tests/pytest/test_perf_profile.py b/GaudiProfiling/tests/pytest/test_perf_profile.py new file mode 100644 index 0000000000..ae7b5b2827 --- /dev/null +++ b/GaudiProfiling/tests/pytest/test_perf_profile.py @@ -0,0 +1,51 @@ +##################################################################################### +# (c) Copyright 2022-2023 CERN for the benefit of the LHCb and ATLAS collaborations # +# # +# This software is distributed under the terms of the Apache version 2 licence, # +# copied verbatim in the file "LICENSE". # +# # +# In applying this licence, CERN does not waive the privileges and immunities # +# granted to it by virtue of its status as an Intergovernmental Organization # +# or submit itself to any jurisdiction. # +##################################################################################### +import os +from threading import Thread + +from GaudiTests import run_gaudi + + +def config(): + from Configurables import ApplicationMgr, PerfProfile + + prof = PerfProfile( + FIFOPath="control.fifo", + StartFromEventN=3, + StopAtEventN=8, + ) + ApplicationMgr( + EvtSel="NONE", + EvtMax=10, + TopAlg=[prof], + ) + + +def test(tmp_path): + """Emulate perf record --control""" + fifo = tmp_path / "control.fifo" + fifo_lines = [] + os.mkfifo(fifo) + + def reader(): + # open blocks until FIFO is opened for writing by PerfProfile::initialize(), so run it in a thread + with open(fifo) as f: + for line in f: + fifo_lines.append(line) + + t = Thread(target=reader) + t.start() + + run_gaudi(f"{__file__}:config", check=True, cwd=tmp_path) + + t.join() + + assert fifo_lines == ["enable\n", "disable\n"] -- GitLab