From 1fb679d4390cff6152531f3b68d4dee561625756 Mon Sep 17 00:00:00 2001 From: Rosen Matev <rosen.matev@cern.ch> Date: Wed, 24 Aug 2022 01:42:46 +0200 Subject: [PATCH] Add filter to select events that emitted a certain message. --- Hlt/HLTScheduler/src/ConfigurableDummy.cpp | 10 +- Kernel/LHCbAlgs/CMakeLists.txt | 1 + Kernel/LHCbAlgs/src/MessageSvcEventFilter.cpp | 141 ++++++++++++++++++ Kernel/LHCbAlgs/tests/options/error_stream.py | 49 ++++++ .../qmtest/lhcbalgs.qms/error_stream.qmt | 32 ++++ 5 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 Kernel/LHCbAlgs/src/MessageSvcEventFilter.cpp create mode 100644 Kernel/LHCbAlgs/tests/options/error_stream.py create mode 100644 Kernel/LHCbAlgs/tests/qmtest/lhcbalgs.qms/error_stream.qmt diff --git a/Hlt/HLTScheduler/src/ConfigurableDummy.cpp b/Hlt/HLTScheduler/src/ConfigurableDummy.cpp index 39128c7a548..97e16aaeb1f 100644 --- a/Hlt/HLTScheduler/src/ConfigurableDummy.cpp +++ b/Hlt/HLTScheduler/src/ConfigurableDummy.cpp @@ -26,8 +26,8 @@ public: StatusCode initialize() override; private: - /// Pick up late-attributed data outputs - Gaudi::Property<int> m_CFD{this, "CFD", 1, "ControlFlowDecision is true every Nth events"}; + Gaudi::Property<int> m_CFD{this, "CFD", 1, "ControlFlowDecision is true every Nth events"}; + Gaudi::Property<bool> m_decisionWarning{this, "DecisionWarning", false, "Emit a warning for false decisions"}; Gaudi::Property<std::vector<std::string>> m_inpKeys{this, "inpKeys", {}, ""}; Gaudi::Property<std::vector<std::string>> m_outKeys{this, "outKeys", {}, ""}; @@ -93,6 +93,8 @@ StatusCode ConfigurableDummy::execute( EventContext const& context ) const // th outputHandle->put( std::make_unique<DataObject>() ); } - if ( m_CFD > 0 && context.evt() % m_CFD == 0 ) return Gaudi::Functional::FilterDecision::PASSED; - return Gaudi::Functional::FilterDecision::FAILED; + bool decision = m_CFD > 0 && context.evt() % m_CFD == 0; + if ( m_decisionWarning && !decision ) { warning() << "Event did not pass" << endmsg; } + + return decision ? Gaudi::Functional::FilterDecision::PASSED : Gaudi::Functional::FilterDecision::FAILED; } diff --git a/Kernel/LHCbAlgs/CMakeLists.txt b/Kernel/LHCbAlgs/CMakeLists.txt index 27746f04ecc..6a563185143 100644 --- a/Kernel/LHCbAlgs/CMakeLists.txt +++ b/Kernel/LHCbAlgs/CMakeLists.txt @@ -66,6 +66,7 @@ gaudi_add_module(LHCbAlgs src/TimingTool.cpp src/TrajPoca.cpp src/createODIN.cpp + src/MessageSvcEventFilter.cpp LINK AIDA::aida Boost::filesystem diff --git a/Kernel/LHCbAlgs/src/MessageSvcEventFilter.cpp b/Kernel/LHCbAlgs/src/MessageSvcEventFilter.cpp new file mode 100644 index 00000000000..7851b4ff599 --- /dev/null +++ b/Kernel/LHCbAlgs/src/MessageSvcEventFilter.cpp @@ -0,0 +1,141 @@ +/*****************************************************************************\ +* (c) Copyright 2022-2024 CERN for the benefit of the LHCb Collaboration * +* * +* This software is distributed under the terms of the GNU General Public * +* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". * +* * +* 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 "LHCbAlgs/FilterPredicate.h" + +/** @class MessageSvcEventFilter + * + * Pass when the processing of the event caused a certain message. + * + * This filter can be configured to filter on ERROR/FATAL messages, for example, + * and can be inserted in the control flow before a writer. + * In this way one can create an "error stream" and save events that + * exhibit errors in the processing. + * + * The algorithm hooks into the MessageSvc by modifying the default stream (at start). + * The modified ostream matches the formatted messages it sees with a regex pattern. + * (Take care to synchronise the MessageSvc format with the pattern.) + * If the message matches, a "flag" object is put on the TES (in the slot assigned to + * the algorithm's execution that emitted the message). + * Then, when the MessageSvcEventFilter algorithm is executed, it simply checks the existence of + * the flag object on the TES and passes only if it finds one. + * + * Note on implementation. The interface msgSvc()->insertStream() seems more appropriate but we + * cannot use it. Using it would look like this + * msgSvc()->insertStream( MSG::WARNING, "default_stream", msgSvc()->defaultStream() ); + * msgSvc()->insertStream( MSG::WARNING, name() + "_spy_stream", m_spyStream.get() ); + * ... + * msgSvc()->eraseStream( MSG::WARNING ); + * The problem is that the MessageSvc as implemented does not apply formatting in the case + * of explicitly inserted streams. It is impossible to apply it here (we only see strings). + * TODO report/fix this in Gaudi + * + */ + +namespace { + using DataHandle = DataObjectWriteHandle<DataObject>; + + std::ostream* s_origStream = nullptr; + + class spystreambuf : public std::stringbuf { + public: + explicit spystreambuf( std::string pattern, DataHandle& dh ) : m_pattern( pattern ), m_dataHandle( dh ) {} + + private: + int sync() override { + std::ptrdiff_t n = pptr() - pbase(); + std::string temp; + temp.assign( pbase(), n ); + *s_origStream << temp << std::flush; + pbump( -n ); + return 0; + } + + std::streamsize xsputn( const char_type* s, std::streamsize count ) override { + std::string temp; + temp.assign( s, count ); + *s_origStream << temp; + // FIXME actually use pattern here + if ( m_dataHandle.getIfExists() == nullptr ) { + try { + m_dataHandle.put( std::make_unique<DataObject>() ); + } catch ( GaudiException& e ) { std::cerr << e.message() << std::endl; }; + } + return count; + } + + int_type overflow( int_type ch ) override { + *s_origStream << (char)ch; + return std::stringbuf::overflow( ch ); + } + + // spystreambuf( const spystreambuf& ); + // spystreambuf& operator=( const spystreambuf& ); + + std::string m_pattern; + DataHandle& m_dataHandle; + }; + +} // namespace + +class MessageSvcEventFilter : public LHCb::Algorithm::FilterPredicate<bool()> { +public: + using FilterPredicate::FilterPredicate; + + StatusCode start() override; + StatusCode stop() override; + bool operator()() const override; + +private: + Gaudi::Property<std::string> m_pattern{ + this, + "PatternRegex", + "", + [this]( const auto& ) { this->m_regex = this->m_pattern.value(); }, + "std::regex pattern that matches messages", + }; + + DataHandle m_flag{this, "FlagLocation", ""}; + + mutable Gaudi::Accumulators::BinomialCounter<> m_counter{this, "Accepted"}; + + std::regex m_regex; + std::unique_ptr<spystreambuf> m_spyBuf; + std::unique_ptr<std::ostream> m_spyStream; +}; + +DECLARE_COMPONENT( MessageSvcEventFilter ) + +StatusCode MessageSvcEventFilter::start() { + return FilterPredicate::start().andThen( [&] { + if ( !s_origStream ) { + s_origStream = msgSvc()->defaultStream(); + // TODO treat missing (nullptr) default stream? + m_spyStream.reset(); // in case we're called a second time, destroy the stream before the buffer + m_spyBuf = std::make_unique<spystreambuf>( "pattern", m_flag ); + m_spyStream = std::make_unique<std::ostream>( m_spyBuf.get() ); + msgSvc()->setDefaultStream( m_spyStream.get() ); + } + } ); +} + +StatusCode MessageSvcEventFilter::stop() { + if ( s_origStream ) { + msgSvc()->setDefaultStream( s_origStream ); + s_origStream = nullptr; + } + return FilterPredicate::stop(); +} + +bool MessageSvcEventFilter::operator()() const { + bool pass = m_flag.getIfExists() != nullptr; + m_counter += pass; + return pass; +} diff --git a/Kernel/LHCbAlgs/tests/options/error_stream.py b/Kernel/LHCbAlgs/tests/options/error_stream.py new file mode 100644 index 00000000000..af95b181a00 --- /dev/null +++ b/Kernel/LHCbAlgs/tests/options/error_stream.py @@ -0,0 +1,49 @@ +############################################################################### +# (c) Copyright 2022-2024 CERN for the benefit of the LHCb Collaboration # +# # +# This software is distributed under the terms of the GNU General Public # +# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". # +# # +# 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. # +############################################################################### +from PyConf.Algorithms import ( + MessageSvcEventFilter, + ConfigurableDummy, +) +from PyConf.application import ApplicationOptions, configure, configure_input +from PyConf.control_flow import CompositeNode, NodeLogic + +message_producer = ConfigurableDummy( + CFD=3, DecisionWarning=True) # 33% prescale + +algs_node = CompositeNode( + "algs", [message_producer], + combine_logic=NodeLogic.LAZY_AND, + force_order=True) + +event_filter = MessageSvcEventFilter() + +writer_node = CompositeNode( + "writer", [event_filter], + combine_logic=NodeLogic.LAZY_AND, + force_order=True) + +top = CompositeNode( + "top", [algs_node, writer_node], + combine_logic=NodeLogic.NONLAZY_AND, + force_order=True) + +# Define the application environment and run it +options = ApplicationOptions(_enabled=False) +options.n_threads = 4 +options.n_event_slots = 4 +options.evt_max = 50 +options.input_type = "NONE" +options.dddb_tag = "dummy" +options.conddb_tag = "dummy" +options.simulation = False + +config2 = configure_input(options) +config = configure(options, top) diff --git a/Kernel/LHCbAlgs/tests/qmtest/lhcbalgs.qms/error_stream.qmt b/Kernel/LHCbAlgs/tests/qmtest/lhcbalgs.qms/error_stream.qmt new file mode 100644 index 00000000000..cf7fd7182c3 --- /dev/null +++ b/Kernel/LHCbAlgs/tests/qmtest/lhcbalgs.qms/error_stream.qmt @@ -0,0 +1,32 @@ +<?xml version="1.0" ?><!DOCTYPE extension PUBLIC '-//QM/2.3/Extension//EN' 'http://www.codesourcery.com/qm/dtds/2.3/-//qm/2.3/extension//en.dtd'> +<!-- + (c) Copyright 2000-2024 CERN for the benefit of the LHCb Collaboration + + This software is distributed under the terms of the GNU General Public + Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". + + 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. +--> +<extension class="GaudiTest.GaudiExeTest" kind="test"> + <argument name="program"><text>gaudirun.py</text></argument> + <argument name="args"><set> + <text>../options/error_stream.py</text> + </set></argument> + <argument name="use_temp_dir"><enumeral>per-test</enumeral></argument> + <argument name="validator"> + <text> +countErrorLines({"FATAL": 0, "ERROR": 0, "WARNING": 33}) +# Being determinisitic, the emulator should always accept the same events and +# hence emulate the same accept fractions +findReferenceBlock(""" +NONLAZY_AND: top #=50 Sum=0 Eff=|( 0.000000 +- 0.00000 )%| + LAZY_AND: algs #=50 Sum=17 Eff=|( 34.00000 +- 6.69925 )%| + ConfigurableDummy/ConfigurableDummy_b0635f1e #=50 Sum=17 Eff=|( 34.00000 +- 6.69925 )%| + LAZY_AND: writer #=50 Sum=33 Eff=|( 66.00000 +- 6.69925 )%| + MessageSvcEventFilter/MessageSvcEventFilter_56e8af36 #=50 Sum=33 Eff=|( 66.00000 +- 6.69925 )%| +""") + </text> + </argument> +</extension> -- GitLab