JAPAn
Just Another Parity Analyzer
Loading...
Searching...
No Matches
QwTrendHandler.cc
Go to the documentation of this file.
1/*!
2 * \file QwTrendHandler.cc
3 * \brief Implementation of the time-ordered TProfile strip-chart data handler
4 * \author GitHub Copilot
5 * \date 2026-05-14
6 */
7
8#include "QwTrendHandler.h"
9
10// ROOT headers
11#include "TDirectory.h"
12#include "TH1.h"
13#include "TProfile.h"
14
15// Qweak headers
16#include "QwLog.h"
17#include "QwParameterFile.h"
18#include "VQwHardwareChannel.h"
19
20
21/// \brief Constructor with name
23: VQwDataHandler(name),
24 fNbins(200),
25 fBinWidth(1.0),
27 fCounter(0),
28 fRolling(kFALSE)
29{
30 ParseSeparator = ":";
31}
32
33/// \brief Copy constructor (histograms are rebuilt, not copied)
35: VQwDataHandler(source),
36 fNbins(source.fNbins),
37 fBinWidth(source.fBinWidth),
39 fCounter(0),
40 fRolling(kFALSE)
41{
42 // The TProfiles are owned by the ROOT output directory and are recreated in
43 // ConstructHistograms(); a cloned handler must not share those pointers.
44}
45
46/// Destructor
48{
49 // The TProfiles are owned by the ROOT output directory (SetDirectory), so
50 // they must not be deleted here.
51 fTrend.clear();
52}
53
54/** Load the list of channels to trend, plus optional binning.
55 *
56 * Map format (one channel token per line):
57 * \code
58 * nbins = 200 # optional, default 200
59 * binwidth = 1 # optional initial event(s) per bin, default 1
60 * mask = 0x0 # optional error-flag mask, default 0 (fill always)
61 * asym:bcm_an_ds
62 * diff:bpm1c10WSX
63 * yield:sam1
64 * \endcode
65 * The type prefix (asym/diff/yield/mps) selects which subsystem array supplies
66 * the channel; the remainder is the channel name.
67 *
68 * @param mapfile Filename of map file
69 * @return Zero on success
70 */
71Int_t QwTrendHandler::LoadChannelMap(const std::string& mapfile)
72{
73 QwParameterFile map(mapfile);
74
75 // Read optional binning/mask settings (searched over the whole file)
76 TString value;
77 if (map.FileHasVariablePair("=", "nbins", value)) {
78 fNbins = value.Atoi();
79 }
80 if (map.FileHasVariablePair("=", "binwidth", value)) {
81 fBinWidth = value.Atof();
82 if (fBinWidth <= 0.0) fBinWidth = 1.0;
83 }
84 if (map.FileHasVariablePair("=", "mask", value)) {
86 }
87
88 // Read the channel list, one token per line (this map has no [sections])
90 while (map.ReadNextLine()) {
91 map.TrimComment();
92 map.TrimWhitespace();
93 if (map.LineIsEmpty()) continue;
94 if (map.LineHasSectionHeader()) continue;
95 // Skip the key=value settings lines handled above
96 if (map.GetLine().find('=') != std::string::npos) continue;
97
98 std::string token = map.GetNextToken(" \t,");
99 if (token.empty()) continue;
100
101 std::pair<EQwHandleType, std::string> type_name = ParseHandledVariable(token);
102 fDependentType.push_back(type_name.first);
103 fDependentName.push_back(type_name.second);
104 fDependentFull.push_back(token);
105 }
106
107 QwMessage << "QwTrendHandler " << fName << ": trending "
108 << fDependentName.size() << " channel(s) into TProfiles ("
109 << fNbins << " bins, initial bin width " << fBinWidth
110 << " event(s), x auto-extends)." << QwLog::endl;
111 return 0;
112}
113
114/** Connect to the requested channels in the per-MPS (event) subsystem array. */
116{
118
119 for (size_t dv = 0; dv < fDependentName.size(); dv++) {
120 // Only yield/mps tokens are meaningful for the per-MPS array
121 if (fDependentType.at(dv) == kHandleTypeAsym ||
123 continue;
124 }
125 const VQwHardwareChannel* dv_ptr =
126 event.RequestExternalPointer(fDependentName.at(dv));
127 if (dv_ptr == NULL) {
128 QwWarning << "QwTrendHandler::ConnectChannels(event): channel "
129 << fDependentName.at(dv) << " was not found." << QwLog::endl;
130 continue;
131 }
132 fDependentVar.push_back(dv_ptr);
133 }
134 return 0;
135}
136
137/** Read the current value of each connected channel. */
139{
140 if (fDependentValues.size() != fDependentVar.size()) {
141 fDependentValues.resize(fDependentVar.size(), 0.0);
142 }
143 for (size_t i = 0; i < fDependentVar.size(); i++) {
144 fDependentValues.at(i) =
145 (fDependentVar.at(i) != NULL) ? fDependentVar.at(i)->GetValue() : 0.0;
146 }
147}
148
149/** Book one TProfile strip chart per connected channel. */
150void QwTrendHandler::ConstructHistograms(TDirectory* folder, TString& prefix)
151{
152 // folder == NULL means we are booking into a live, fixed-size TMapFile, where
153 // the x-axis must not auto-extend; draw a rolling strip chart instead.
154 fRolling = (folder == NULL);
155 fTrend.assign(fDependentVar.size(), NULL);
156 for (size_t i = 0; i < fDependentVar.size(); i++) {
157 if (fDependentVar.at(i) == NULL) continue;
158
159 TString channel = fDependentVar.at(i)->GetElementName();
160 TString name = prefix + fName + "_" + channel;
161 TString title = TString("Trend ") + channel
162 + ";event index;" + channel;
163
164 // In file mode 'folder' is the target directory (e.g. mul_histo). In live
165 // TMapFile mode QwRootFile passes a NULL folder, so every trend handler
166 // books into the single flat mapfile directory. The same handler is loaded
167 // in both the pattern (mul) and burst arrays, which would create two
168 // profiles with identical names; the second construction silently replaces
169 // the first in the directory and only the survivor is streamed. Keep the
170 // names unique so the first (pattern-scope, per-pattern) profile is not
171 // overwritten by the later burst-scope copy.
172 if (folder == NULL && gDirectory) {
173 TString unique = name;
174 int dup = 1;
175 while (gDirectory->FindObject(unique) != NULL) {
176 unique = name + Form("_%d", ++dup);
177 }
178 name = unique;
179 }
180
181 TH1* chart = NULL;
182 if (fRolling) {
183 // Live TMapFile mode: use a plain TH1D value trace. TProfile keeps
184 // several independent internal arrays (fArray, fSumw2, fBinEntries,
185 // fBinSumw2) that TMapFile::Update() does not marshal robustly and which
186 // deterministically corrupt the shared-memory region after a few hundred
187 // updates. A TH1D has a single fixed fArray, is filled with
188 // SetBinContent (no reallocation), and streams reliably in a TMapFile
189 // exactly like the subsystem monitoring histograms. The chart stays
190 // attached to the current directory (the mapfile) so it is re-streamed.
191 chart = new TH1D(name, title, fNbins, 0.0, fNbins * fBinWidth);
192 } else {
193 // File mode: a TProfile gives the mean +/- RMS per bin and can safely
194 // auto-extend its x-axis for runs of arbitrary length.
195 TProfile* prof =
196 new TProfile(name, title, fNbins, 0.0, fNbins * fBinWidth);
197 prof->SetCanExtend(TH1::kXaxis);
198 prof->SetDirectory(folder);
199 chart = prof;
200 }
201 fTrend.at(i) = chart;
202 }
203}
204
205/** Fill each strip chart at the current time-ordering index. */
207{
208 const bool good =
209 !(fErrorFlagMask != 0 && fErrorFlagPtr != NULL &&
210 (*fErrorFlagPtr & fErrorFlagMask) != 0);
211
212 // In rolling (live mapfile) mode the fixed x-range spans this many event
213 // indices; the time index wraps within it so the chart sweeps continuously.
214 const Long64_t span =
215 (Long64_t)(fNbins * fBinWidth) > 0 ? (Long64_t)(fNbins * fBinWidth) : 1;
216 const Double_t xpos = fRolling ? (Double_t)(fCounter % span) : (Double_t) fCounter;
217
218 // NOTE: In live TMapFile mode we deliberately do NOT call TProfile::Reset()
219 // between sweeps. Reset() (and Fill's lazy buffer/array management) can
220 // change the object's serialized footprint inside the fixed-size mmap and
221 // crash TMapFile::Update(). Instead the fixed x-range simply accumulates:
222 // each bin holds the running mean over all sweeps, which is stable in shared
223 // memory. (DIAGNOSTIC: reset disabled to isolate the TMapFile::Update crash.)
224
225 if (good) {
226 for (size_t i = 0; i < fTrend.size(); i++) {
227 if (fTrend.at(i) != NULL && i < fDependentValues.size()) {
228 if (fRolling) {
229 // Live rolling trace: overwrite this event index's bin with the
230 // latest value. SetBinContent never reallocates, so the object's
231 // footprint is stable in the fixed-size TMapFile.
232 const Int_t bin = fTrend.at(i)->GetXaxis()->FindBin(xpos);
233 fTrend.at(i)->SetBinContent(bin, fDependentValues.at(i));
234 } else {
235 // File mode: accumulate mean +/- RMS in the TProfile.
236 static_cast<TProfile*>(fTrend.at(i))->Fill(xpos, fDependentValues.at(i));
237 }
238 }
239 }
240 }
241 // Advance the time index even for skipped events so the x-axis stays aligned
242 // with the true event/pattern ordering.
243 fCounter++;
244}
Parameter file parsing and management.
A logfile class, based on an identical class in the Hermes analyzer.
#define QwWarning
Predefined log drain for warnings.
Definition QwLog.h:44
#define QwMessage
Predefined log drain for regular messages.
Definition QwLog.h:49
Data handler that books time-ordered TProfile "strip charts".
static std::ostream & endl(std::ostream &)
End of the line.
Definition QwLog.cc:297
Configuration file parser with flexible tokenization and search capabilities.
Bool_t LineHasSectionHeader()
void TrimWhitespace(TString::EStripType head_tail=TString::kBoth)
std::string GetLine()
void TrimComment(const char commentchar)
std::string GetNextToken(const std::string &separatorchars)
Get next token as a string.
static UInt_t GetUInt(const TString &varvalue)
Bool_t FileHasVariablePair(const std::string &separatorchars, const std::string &varname, std::string &varvalue)
Abstract base for concrete hardware channels implementing dual-operator pattern.
Subsystem array container specialized for parity analysis with asymmetry calculations.
const UInt_t * GetEventcutErrorFlagPointer() const
Long64_t fCounter
Time-ordering index incremented once per filled event/pattern.
std::vector< TH1 * > fTrend
~QwTrendHandler() override
Virtual destructor.
void ProcessData() override
Read the current value of each connected channel.
Int_t ConnectChannels(QwSubsystemArrayParity &event) override
Connect to channels from the per-MPS (event) subsystem array.
Int_t fNbins
Number of x bins for the strip-chart profiles (bounded storage)
void FillHistograms() override
Fill each strip chart at the current time-ordering index.
QwTrendHandler(const TString &name)
Constructor with name.
void ConstructHistograms(TDirectory *folder, TString &prefix) override
Book one TProfile strip chart per connected channel.
UInt_t fErrorFlagMask
Skip filling when (errorflag & mask) != 0 (0 disables the check)
QwTrendHandler()
Default constructor (protected for factory/child access)
std::vector< std::string > fDependentFull
void SetEventcutErrorFlagPointer(const UInt_t *errorflagptr)
std::vector< Double_t > fDependentValues
std::vector< const VQwHardwareChannel * > fDependentVar
VQwDataHandler(const TString &name)
const UInt_t * fErrorFlagPtr
Error flag pointer.
std::string ParseSeparator
std::pair< EQwHandleType, std::string > ParseHandledVariable(const std::string &variable)
std::vector< EQwHandleType > fDependentType
std::vector< std::string > fDependentName