The Gaudi Framework  master (9da49fa3)
Loading...
Searching...
No Matches
MessageSvcSink.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 1998-2025 CERN for the benefit of the LHCb and ATLAS collaborations *
3* *
4* This software is distributed under the terms of the Apache version 2 licence, *
5* copied verbatim in the file "LICENSE". *
6* *
7* In applying this licence, CERN does not waive the privileges and immunities *
8* granted to it by virtue of its status as an Intergovernmental Organization *
9* or submit itself to any jurisdiction. *
10\***********************************************************************************/
12#include <Gaudi/BaseSink.h>
14#include <Gaudi/MonitoringHub.h>
16#include <boost/algorithm/string.hpp>
17#include <fmt/core.h>
18#include <fmt/format.h>
19#include <map>
20#include <sstream>
21#include <string_view>
22
23namespace {
35 static const auto registry = std::map<std::string_view, std::string_view>{
36 { "counter", "{0:nEntries|10d}" }, // all unknown counters, and default
37 { "counter:AveragingCounter", "{0:nEntries|10d} |{0:sum|11.7g} |{0:mean|#11.5g}" },
38 { "counter:SigmaCounter", "{0:nEntries|10d} |{0:sum|11.7g} |{0:mean|#11.5g} |{0:standard_deviation|#11.5g}" },
39 { "counter:StatCounter", "{0:nEntries|10d} |{0:sum|11.7g} |{0:mean|#11.5g} |{0:standard_deviation|#11.5g} "
40 "|{0:min|#12.5g} |{0:max|#12.5g}" },
41 { "counter:BinomialCounter",
42 "{0:nEntries|10d} |{0:nTrueEntries|11d} |({0:efficiency|#9.7p} +- {0:efficiencyErr|-#8.7p})%" },
43 };
44
45 // the int says in which bunch of counters to put the histogram. We separate indeed
46 // regular counters (0) from 1d/2d/3d histogram (1, 2 and 3) from 1d/2d/3d profile
47 // histograms (4, 5 and 6)
48 using HistoBinHandler =
49 std::pair<unsigned int,
50 std::function<std::string( std::string_view, Gaudi::Monitoring::Hub::Entity const&, unsigned int )>>;
51 namespace Acc = ::Gaudi::Accumulators;
56 std::map<std::type_index, HistoBinHandler> const binRegistry = {
57 { std::type_index( typeid( Acc::StaticProfileHistogram<1u, Acc::atomicity::full, double> ) ),
59 { std::type_index( typeid( Acc::StaticProfileHistogram<1u, Acc::atomicity::none, double> ) ),
61 { std::type_index( typeid( Acc::StaticProfileHistogram<1u, Acc::atomicity::full, float> ) ),
63 { std::type_index( typeid( Acc::StaticProfileHistogram<1u, Acc::atomicity::none, float> ) ),
65 { std::type_index( typeid( Acc::StaticProfileHistogram<2u, Acc::atomicity::full, double> ) ),
67 { std::type_index( typeid( Acc::StaticProfileHistogram<2u, Acc::atomicity::none, double> ) ),
69 { std::type_index( typeid( Acc::StaticProfileHistogram<2u, Acc::atomicity::full, float> ) ),
71 { std::type_index( typeid( Acc::StaticProfileHistogram<2u, Acc::atomicity::none, float> ) ),
73 { std::type_index( typeid( Acc::StaticProfileHistogram<3u, Acc::atomicity::full, double> ) ),
75 { std::type_index( typeid( Acc::StaticProfileHistogram<3u, Acc::atomicity::none, double> ) ),
77 { std::type_index( typeid( Acc::StaticProfileHistogram<3u, Acc::atomicity::full, float> ) ),
79 { std::type_index( typeid( Acc::StaticProfileHistogram<3u, Acc::atomicity::none, float> ) ),
81 };
82
83 // Helper to fix custom formatting of nlohmann::json version 3.10.5
84 // See https://gitlab.cern.ch/gaudi/Gaudi/-/issues/220
85 struct json_fmt_arg {
86 json_fmt_arg( const nlohmann::json& j ) : payload{ j } {}
87 const nlohmann::json& payload;
88 };
89
90} // namespace
91
101template <>
102class fmt::formatter<json_fmt_arg> {
103public:
104 template <typename ParseContext>
105 constexpr auto parse( ParseContext& ctx ) {
106 auto fmt_begin = ctx.begin();
107 auto fmt_end = std::find( fmt_begin, ctx.end(), '}' );
108 if ( fmt_begin == fmt_end ) {
109 // we are dealing with {}, only make sure currentFormat is empty
110 currentFormat = "";
111 } else {
112 // non empty format, let's split name from format
113 auto fmt_colon = std::find( fmt_begin, fmt_end, '|' );
114 currentName = std::string( fmt_begin, fmt_colon - fmt_begin );
115 currentFormat = std::string( fmt_colon + 1, fmt_end - fmt_colon - 1 );
116 }
117 return fmt_end;
118 }
119 template <typename FormatContext>
120 auto format( const json_fmt_arg& json_arg, FormatContext& ctx ) const {
121 const auto& j = json_arg.payload;
122 if ( currentFormat.size() == 0 ) {
123 // dealing with {} format, let's find entry for our type in registry
124 const auto type = j.at( "type" ).get<std::string>();
125 // first looking for the entry, then we drop on ":abc" suffix at a time and try again
126 std::string_view type_key{ type };
127 // look for the full entry
128 auto entry = registry.find( type_key );
129 // we check if we have type separators before entering the loop
130 auto sep = type_key.rfind( ':' );
131 while ( sep != type_key.npos && entry == registry.end() ) {
132 // not found, remove the trailing ":abc" section
133 type_key.remove_suffix( type_key.size() - sep );
134 // check if we have another chunk to strip
135 sep = type_key.rfind( ':' );
136 // see if the shorter key works
137 entry = registry.find( type_key );
138 }
139 // if still not found, we use the basic "counter"
140 if ( entry == registry.end() ) entry = registry.find( "counter" );
141 assert( entry != registry.end() );
142 // print the json string according to format found
143 // This actually will call this formatter again a number of times
144 return fmt::format_to( ctx.out(), fmt::runtime( entry->second ), json_arg );
145 } else {
146 // dealing with a {:name|fmt} format
147 auto actualFormat = "{:" + currentFormat + '}';
148 switch ( currentFormat.back() ) {
149 case 'd':
150 return fmt::format_to( ctx.out(), fmt::runtime( actualFormat ),
151 j.at( currentName ).template get<unsigned int>() );
152 case 'g':
153 return fmt::format_to( ctx.out(), fmt::runtime( actualFormat ), j.at( currentName ).template get<double>() );
154 case 'p':
155 actualFormat[actualFormat.size() - 2] = 'g';
156 return fmt::format_to( ctx.out(), fmt::runtime( actualFormat ),
157 j.at( currentName ).template get<double>() * 100 );
158 default:
159 return fmt::format_to( ctx.out(), "Unknown counter format : {}", currentFormat );
160 }
161 }
162 }
163
164private:
165 std::string currentFormat;
166 std::string currentName;
167};
168
169namespace {
170
171 void printCounter( std::ostringstream& log, std::string_view id, const nlohmann::json& j ) {
172 const auto type = j.at( "type" ).get<std::string>();
173 // for backward compatibility, we need to deal with statentity in a special way
174 // this block should be dropped when StatEntities are gone
175 if ( type == "statentity" ) {
176 using boost::algorithm::icontains;
177 bool isBinomial = icontains( id, "eff" ) || icontains( id, "acc" ) || icontains( id, "filt" ) ||
178 icontains( id, "fltr" ) || icontains( id, "pass" );
179 auto nj = j;
180 nj["type"] = isBinomial ? "counter:BinomialCounter" : "counter:StatCounter";
181 return printCounter( log, id, nj );
182 }
183 // binomial counters are slightly different ('*' character)
184 // fmt::runtime is required when compiling with GCC 11 but
185 // can be dropped when GCC 11 is no longer supported
186 log << fmt::format( fmt::runtime( " |{}{:48}|{} |" ),
187 ( std::string_view{ type }.substr( 0, 23 ) == "counter:BinomialCounter" ? '*' : ' ' ),
188 fmt::format( fmt::runtime( "\"{}\"" ), id ), json_fmt_arg{ j } );
189 }
190
191} // namespace
192
193namespace Gaudi::Monitoring {
194
203 MessageSvcSink( const std::string& name, ISvcLocator* svcloc ) : BaseSink( name, svcloc ) {
204 // only deal with counters, statentity and histograms
205 setProperty( "TypesToSave", std::vector<std::string>{ "counter:.*", "statentity", "histogram:.*" } )
206 .orThrow( "Unable to set TypesToSave property", "Gaudi::Monitoring::MessageSvcSink" );
207 // require a non-empty name for the owner
208 setProperty( "ComponentsToSave", std::vector<std::string>{ "^.+$" } )
209 .orThrow( "Unable to set ComponentsToSave property", "Gaudi::Monitoring::MessageSvcSink" );
210 }
211
212 void flush( bool ) override;
214 "length of histograms names and titles in the output" };
215 };
216
217 DECLARE_COMPONENT( MessageSvcSink )
218} // namespace Gaudi::Monitoring
219
221 std::string curAlgo = "";
222 // an array of counters, separating (in this order)
223 // regular counters from 1d / 2d / 3d histograms, from
224 // 1d / 2d / 3d profile histograms
225 std::array<std::ostringstream, 7> curLog;
226 // associated counters of non empty entities, in order
227 // to know whether to print or not the given section
228 std::array<unsigned int, 7> nbNonEmptyEntities{};
229 auto dumpAlgoCounters = [&]() {
230 MsgStream log{ msgSvc(), curAlgo };
231 // Regular counters first
232 if ( nbNonEmptyEntities[0] > 0 ) {
233 log << MSG::INFO << "Number of counters : " << nbNonEmptyEntities[0] << "\n"
234 << " | Counter | # | "
235 << " sum | mean/eff^* | rms/err^* | min | max |";
236 log << curLog[0].str() << endmsg;
237 }
238 // Now histograms
239 unsigned int nHistos = nbNonEmptyEntities[1] + nbNonEmptyEntities[2] + nbNonEmptyEntities[3] +
240 nbNonEmptyEntities[4] + nbNonEmptyEntities[5] + nbNonEmptyEntities[6];
241 if ( nHistos > 0 ) {
242 log << MSG::INFO << "Booked " << nHistos << " Histogram(s) : 1D=" << nbNonEmptyEntities[1]
243 << " 2D=" << nbNonEmptyEntities[2] << " 3D=" << nbNonEmptyEntities[3] << " 1DProf=" << nbNonEmptyEntities[4]
244 << " 2DProf=" << nbNonEmptyEntities[5] << " 3DProf=" << nbNonEmptyEntities[6] << endmsg;
245 if ( nbNonEmptyEntities[1] > 0 ) {
246 log << MSG::INFO << "1D histograms in directory \"" << curAlgo << "\" : " << nbNonEmptyEntities[1] << "\n"
247 << fmt::format(
248 fmt::runtime(
249 " | {:{}s} | {:{}s} | # | Mean | RMS | Skewness | Kurtosis |" ),
250 "ID", m_histoStringsWidth.value(), "Title", m_histoStringsWidth.value() )
251 << curLog[1].str() << endmsg;
252 }
253 if ( nbNonEmptyEntities[2] > 0 ) {
254 log << MSG::INFO << "2D histograms in directory \"" << curAlgo << "\" : " << nbNonEmptyEntities[2]
255 << curLog[2].str() << endmsg;
256 }
257 if ( nbNonEmptyEntities[3] > 0 ) {
258 log << MSG::INFO << "3D histograms in directory \"" << curAlgo << "\" : " << nbNonEmptyEntities[3]
259 << curLog[3].str() << endmsg;
260 }
261 if ( nbNonEmptyEntities[4] > 0 ) {
262 log << MSG::INFO << "1D profile histograms in directory \"" << curAlgo << "\" : " << nbNonEmptyEntities[4]
263 << "\n"
264 << fmt::format(
265 fmt::runtime(
266 " | {:{}s} | {:{}s} | # | Mean | RMS | Skewness | Kurtosis |" ),
267 "ID", m_histoStringsWidth.value(), "Title", m_histoStringsWidth.value() )
268 << curLog[4].str() << endmsg;
269 }
270 if ( nbNonEmptyEntities[5] > 0 ) {
271 log << MSG::INFO << "2D profile histograms in directory \"" << curAlgo << "\" : " << nbNonEmptyEntities[5]
272 << curLog[5].str() << endmsg;
273 }
274 if ( nbNonEmptyEntities[6] > 0 ) {
275 log << MSG::INFO << "3D profile histograms in directory \"" << curAlgo << "\" : " << nbNonEmptyEntities[6]
276 << curLog[6].str() << endmsg;
277 }
278 }
279 };
281 [&]( std::string const& algo, std::string const& name, Monitoring::Hub::Entity const& ent ) {
282 // Did we change to new component ?
283 if ( algo != curAlgo ) {
284 dumpAlgoCounters();
285 curAlgo = algo;
286 nbNonEmptyEntities = { 0, 0, 0, 0, 0, 0, 0 };
287 curLog = { std::ostringstream{}, std::ostringstream{}, std::ostringstream{}, std::ostringstream{},
288 std::ostringstream{}, std::ostringstream{}, std::ostringstream{} };
289 }
290 // first try dedicated binary printers for profile histograms
291 auto typeIndex = ent.typeIndex();
292 auto binWriter = binRegistry.find( typeIndex );
293 if ( binWriter != binRegistry.end() ) {
294 auto index = binWriter->second.first;
295 ++nbNonEmptyEntities[index];
296 std::string logLine = binWriter->second.second( name, ent, m_histoStringsWidth );
297 if ( logLine.size() > 0 ) { curLog[index] << "\n" << logLine; }
298 } else {
299 // use json representation of the entity
300 nlohmann::json const j = ent;
301 // do we have an histogram ?
302 const auto type = j.at( "type" ).get<std::string>();
303 if ( type.find( "histogram" ) == 0 ) {
304 if ( !j.at( "empty" ).template get<bool>() ) {
305 unsigned int d = j.at( "dimension" ).get<int>();
306 auto subtype = std::string_view( type ).substr( 10 ); // remove "histogram:" in front
307 bool isProfile = subtype.substr( 0, 15 ) == "WeightedProfile" || subtype.substr( 0, 7 ) == "Profile";
308 unsigned int index = ( isProfile ? 3 : 0 ) + d;
309 auto title = j.at( "title" ).get<std::string>();
310 std::string logLine{ "" };
311 switch ( d ) {
312 case 1:
314 break;
315 case 2:
317 break;
318 case 3:
320 break;
321 }
322 if ( logLine.size() > 0 ) {
323 curLog[index] << "\n" << logLine;
324 ++nbNonEmptyEntities[index];
325 }
326 }
327 } else {
328 // regular counter. Is current counter empty ?
329 if ( !j.at( "empty" ).template get<bool>() ) {
330 ++nbNonEmptyEntities[0];
331 curLog[0] << "\n";
332 printCounter( curLog[0], name, j );
333 }
334 }
335 }
336 } );
337 // last component
338 dumpAlgoCounters();
339}
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition MsgStream.h:198
#define DECLARE_COMPONENT(type)
const SmartIF< IMessageSvc > & msgSvc() const
The standard message service.
Base class for all Sinks registering to the Monitoring Hub Should be extended by actual Sinks.
Definition BaseSink.h:41
void applyToAllSortedEntities(Callable func) const
applies a callable to all monitoring entities ordered by component the callable will be called once p...
Definition BaseSink.h:111
Wrapper class for arbitrary monitoring objects.
std::type_index typeIndex() const
function to get internal type
Implementation of property with value of concrete type.
Definition PropertyFwd.h:27
The ISvcLocator is the interface implemented by the Service Factory in the Application Manager to loc...
Definition ISvcLocator.h:42
Definition of the MsgStream class used to transmit messages.
Definition MsgStream.h:29
const std::string & name() const override
Retrieve name of the service.
Definition Service.cpp:333
constexpr auto parse(ParseContext &ctx)
auto format(const json_fmt_arg &json_arg, FormatContext &ctx) const
Efficient counter implementations for Gaudi.
std::string printProfileHisto3D(std::string_view name, Gaudi::Monitoring::Hub::Entity const &ent, unsigned int stringsWidth=45)
Definition Utils.h:760
std::string printProfileHisto1D(std::string_view name, Gaudi::Monitoring::Hub::Entity const &ent, unsigned int stringsWidth=45)
Definition Utils.h:642
std::string printHistogram1D(std::string_view type, std::string_view name, std::string_view title, const nlohmann::json &j, unsigned int stringsWidth=45)
Definition Utils.h:861
std::string printHistogram2D(std::string_view type, std::string_view name, std::string_view title, const nlohmann::json &j, unsigned int stringsWidth=45)
Definition Utils.h:905
std::string printHistogram3D(std::string_view type, std::string_view name, std::string_view title, const nlohmann::json &j, unsigned int stringsWidth=45)
Definition Utils.h:950
std::string printProfileHisto2D(std::string_view name, Gaudi::Monitoring::Hub::Entity const &ent, unsigned int stringsWidth=45)
Definition Utils.h:701
@ INFO
Definition IMessageSvc.h:22
void flush(bool) override
stop method, handles the printing
Gaudi::Property< unsigned int > m_histoStringsWidth
MessageSvcSink(const std::string &name, ISvcLocator *svcloc)