STM unpacking latest changes and updates - #1931
Conversation
…airty. Changed STMFragmentSummary to include optional tools for debugging.
…ing (for debugging)
Also changed STMPrint to have three general options for debugging
|
Hi @Etho-b02,
which require these tests: build. @Mu2e/fnalbuild-users, @Mu2e/write have access to CI actions on main. 📝 The author of this pull request is not a member of the Mu2e github organisation. |
AndrewEdmonds11
left a comment
There was a problem hiding this comment.
All looks good to me. Thanks, Bryan!
|
@FNALbuild run build test |
oksuzian
left a comment
There was a problem hiding this comment.
PR Review Summary — #1931
Reviewed at head 685c2c19. First pass.
Decision
- 🔴 request changes — one committed fcl cannot start (finding 1), plus three S1 items. Everything below is checkable from the diff; nothing here is a style objection.
Scope understood
- Rewrite of
STMDigisFromFragments(+1183/−753): per-detector state objects replacing parallel scalars, new raw-header validation (length, anchors, bad/missing, prescale), and a reinterpretation of the PH payload. - New print switches in
STMPrintFragments; four new counters on theSTMFragmentSummarydata product; plotting/fcl updates.
Findings
-
🔴 [S0]
plotSTMWaveformDigis.fclnames an analyzer that does not exist — the job cannot start.- Evidence:
STMReco/fcl/plotSTMWaveformDigis.fcl:68TheanaPath : [ plotRawWaveformDigisHPGe, plotRawWaveformDigisTimeHPGe, plotRawWaveformDigisEvTimeHPGe, plotZSWaveformDigisHPGe, ... ]analyzersblock (lines 23–65) definesplotRawWaveformDigisHPGe,
plotRawWaveformDigisTimeHPGe,plotZSWaveformDigisHPGe,
plotRawWaveformDigisLaBr,plotZSWaveformDigisLaBr. There is no
plotRawWaveformDigisEvTimeHPGe, and neither included prolog defines one. - Impact: art rejects an unknown module label on a path at configuration time.
mu2e -c Offline/STMReco/fcl/plotSTMWaveformDigis.fclfails before the first
event, so the file as committed is unusable. - Suggested fix: add the missing analyzer (presumably the
event_timetwin of
plotRawWaveformDigisTimeHPGe, i.e.xAxis : "event_time"), or drop the label
fromanaPath.
- Evidence:
-
🟠 [S1] The raw payload length is taken from the header and used unchecked — a corrupt
RAW_LENreads past the end of the fragment.- Evidence:
payloadWords()in the overlay returnsrawLength()for a raw
fragment — a header field (stm::RawHeader::RAW_LEN), not a size derived from
the fragment.DAQ/src/STMDigisFromFragments_module.ccthen walks and copies
that many words:The new guardauto payloadPtr = stm_frag.payloadBegin(); // data_ + RawHeader::WORDS auto payloadWords = stm_frag.payloadWords(); // == rawLength(), from the header for (size_t k = 0; k < payloadWords; ++k) { if (payloadPtr[k] != 0) ... } ... stm_waveform.set_data(payloadWords, payloadPtr);
if (stm_frag.dataWords() < stm::RawHeader::WORDS)only
establishes that the 22-word header fits; it says nothing about whether
rawLength()words follow it. - Impact: a raw header whose
RAW_LENexceeds the real payload produces an
out-of-bounds read and aSTMWaveformDigifilled with whatever follows the
fragment — silently, since the all-zeros scan will usually find a non-zero byte
and classify it "good". - This is in scope even though the same pattern is on
main: the PR's purpose
here is to add exactly this validation, and its own ZS path already does the
check properly —The raw path needs the equivalent.if (currentZSLength > static_cast<size_t>(dataEndPtr - adc)) { malformedZS = true; break; }
- Suggested fix: before using
payloadWords, require
stm::RawHeader::WORDS + payloadWords <= stm_frag.dataWords(); on failure count
it with the other invalid-header cases andcontinue.
- Evidence:
-
🟠 [S1] PH digis are silently truncated to the header's count, and a zero count is misreported as a zero-filled fragment.
- Evidence:
DAQ/src/STMDigisFromFragments_module.ccsize_t const nPairsInFragment = payloadWords / 2; size_t const nPairsToRead = extractedPHInfo ? std::min<size_t>(nPairsInFragment, phCount) : nPairsInFragment; for (size_t k = 0; k < nPairsToRead; ++k) { if (payloadPtr[k*2+1] != 0) { allPHAreZeros = false; break; } } if (allPHAreZeros) { ++eventMetrics.ph.zero; ... continue; }
- Impact: when the header's
PH_NUMdisagrees with what the payload actually
carries, the excess pairs are dropped with no counter and no message. In the
limitphCount == 0,nPairsToReadis 0, the zero-check loop never executes,
allPHAreZerosstays at its initialtrue, and a fragment full of real pulse
heights is discarded and tallied as zero-filled — the diagnostics then say the
data was empty rather than that the header was inconsistent. - Note the inconsistency with the ZS path in the same file, where the identical
class of header/payload disagreement is fatal (ZS LENGTH MISMATCH,
ZS REGION COUNT MISMATCH). One of the two treatments should change. - Suggested fix: compare
nPairsInFragmentagainstphCountexplicitly, count and
report the mismatch, and decide deliberately whether to throw (as ZS does) or
read the fragment's own count. InitializeallPHAreZerosfrom whether the loop
ran, not totrue.
- Evidence:
-
🟠 [S1] The new
STMPrintFragmentsswitch cannot be set — the fcl key and theConfigname differ.- Evidence:
DAQ/src/STMPrintFragments_module.ccdeclares
fhicl::Atom<bool> printInnerFrags {fhicl::Name("printInnerFrags"), ...}with no
default, so it is a required parameter.DAQ/test/inspectSTMFile.fcl:51sets
printInnerFragments: false. - Impact: as committed nothing fails, only because
stmPrintis on no path
(end_pathsis commented out — see finding 7). The moment anyone puts it on a
path, art reports an unknown keyprintInnerFragmentsand a missing required
printInnerFrags. So the switch advertised in the PR description is not
reachable from the one fcl that configures it. - Suggested fix: rename the fcl key to
printInnerFrags, and putstmPrintback
on a path so the three new switches are exercised at least once.
- Evidence:
-
🟡 [S2] The PH payload is reinterpreted from "one word per pulse height" to "(time, height) pairs" — a data-format change the PR body does not mention.
- Evidence: before, every payload word became a digi with time hard-coded to 0:
after, words are consumed in pairs and the time field is filled from the payload.
for (size_t i_PH = 0; i_PH < digiWords ; ++i_PH){ int16_t PH = digiPtr[i_PH]; mu2e::STMPHDigi PH_digi(0, PH);
- Impact: the number of
STMPHDigiper fragment halves andtime()becomes
meaningful. Anyone comparing against previously produced files will see both. - I believe the new reading is the correct one and worth stating plainly in the PR
body rather than leaving to be discovered: the raw header carriesPH_NUM
(stm::RawHeader::PH_NUM),STMPHDigihas always had a_timemember that the
old code never populated, and the companion histogram change in
PlotSTMPHSpectrum_module.ccmoves the spectrum from(1000, 0, 1e4)to
(10000, -10000, 0)— consistent with the old plot having been polluted by
positive time words sitting in an otherwise negative-going ADC spectrum. - Suggested fix: say so in the PR description, and show one before/after PH
spectrum from the same input file as validation.
- Evidence: before, every payload word became a digi with time hard-coded to 0:
-
🟡 [S2] Raw-header and parent state are reset only when a raw fragment arrives, never after being consumed.
- Evidence:
headerState = RawHeaderState{}and theparentStatereset both live
at the top of theisRaw()branch. The old code cleared its ZS expectations
inside the ZS branch immediately after copying them out ("After copying
variables, reset detector specific variables"). - Impact: for one raw followed by several ZS fragments this is an improvement —
every ZS in the set now gets the parentart::Ptr, where before only the first
did. But if a ZS fragment can ever arrive without a preceding raw fragment in the
same event, it now inherits the previous set'sexpectedZSLength/
expectedZSRegionsand is checked against them — which throws
STM_UNPACKINGand kills the job — and would attach a parentart::Ptrpointing
at an unrelated raw waveform. - I could not determine from the code whether the DAQ can emit ZS without raw, so
this is a question rather than an observed defect: can it? If yes, the state
needs a "consumed" reset or a per-set sequence check; if no, a comment saying the
set is guaranteed complete would stop the next reader worrying about it.
- Evidence:
-
🟡 [S2] Both test fcl files are committed in personal working state.
- Evidence:
DAQ/test/inspectSTMFile.fcl:58,63comment out the output path and
end_paths, so the job now produces products and discards all of them;
outputs.stmOutputis left defined but unreachable. Line 74 writes to
data/dig.stm.art, andSTMReco/fcl/plotSTMWaveformDigis.fcl:17writes
data/stmWaveformDigis.root. - Impact: the
data/prefix requires that directory to already exist in the
working directory — RootOutput and TFileService both fail otherwise, so these
files only run from one person's sandbox layout. AndinspectSTMFile.fclno
longer demonstrates the thing it is named for. - Suggested fix: drop the
data/prefixes and restoreend_paths : [e1].
- Evidence:
-
⚪ [S3] Collected minor items — grouped deliberately, none of these gate anything.
uint16_t outerFragID;is now declared uninitialized (it was{0}); it is
assigned before use, but the standard asks for initialization at declaration.- Dead state and commented-out code:
//size_t unknownFragsThisEvent{0};,
// headerState.rawHeaderIsValid = stm_frag.hasValidAnchors();leaving
RawHeaderState::rawHeaderIsValidwritten nowhere and read nowhere; the two
commented-out accessor/member blocks inSTMFragmentSummary.hh;//uint16_t lastLen = 0;inSTMPrintFragments.containsZSInfoandcontainsPHInfoare
always assignedtruetogether at the same point, so they carry no information
theRawHeaderStateobject's freshness does not already carry. PlotSTMWaveformDigis_module.cctests_xAxis == "waveform_time [nsec]", but
STMUtils::getBinningonly acceptssample_number,waveform_time,
event_timeand throws otherwise — so that branch is unreachable and the
waveform_timehistograms get no x-axis title.PlotSTMPHSpectrumgives the 2D plot an x-range of(-1000, 1000)while the
filled quantity isbinBlock = eventCount/100, which is never negative.- The file was reindented from 2 to 4 spaces in the same commits as the logic
rewrite, which is most of why the diff is 1900 lines. Separating a pure
reformat from behaviour changes makes both reviewable; worth doing next time
rather than redoing now.
Verified, no action needed
- 🟢 The six
save*fcl parameters were renamed consistently.saveRawWaveform_HPGe
and its five siblings appear nowhere inOfflineoutside the module and
inspectSTMFile.fcl(both updated here), andSTMDigisFromFragmentsis not
referenced anywhere inProduction. No orphaned keys. - 🟢 Every new overlay accessor the module now calls —
phCount(),badData(),
missing(),hasValidAnchors(),rawPrescaled(),rawPrescaleValue(),
zsPrescaled(),zsPrescaleValue(),stm::RawHeader::WORDS— is present in
artdaq_core_mu2e v9_06_00, which is whatmainpins (envset p106, bumped
2026-08-09). No companion release is needed. This module is the first consumer of
them inOffline, which is why the build matters here. - 🟢 Removing
plotPHWaveformDigisHPGe/plotPHWaveformDigisLaBris a real fix, not
just cleanup: they pointedPlotSTMWaveformDigisatmakeSTMDigis:phHPGe, which is
anSTMPHDigiCollection, not theSTMWaveformDigiCollectionthat module consumes. - 🟢 Adding four members to
STMFragmentSummaryneeds noClassVersionbump —
0 of the 295 entries inRecoDataProducts/src/classes_def.xmlcarry one, so
auto-versioning is the convention throughout this package.
Validation check
- Build/tests run by reviewer: none. All findings above are from reading the diff and
the currentmain/overlay sources; no code was compiled or executed. - CI at the reviewed head: no build result.
mu2e/buildtesthas beenpending
since the PR was opened (2026-08-12T22:43Z) andjenkins/ghprbis the only green
context. FNALbuild notes the author is not a member of the Mu2e organisation, so
the build needs a member to trigger it — I have requested one. - Config contract check: fail — findings 1 and 4.
- Cross-repo consistency: pass — external overlay verified against the pinned
adcm v9_06_00; noProductionormu2e-trig-configimpact.
Residual risk
- Nothing here has been compiled. Findings 1 and 4 are configuration errors that a
build will not catch — only running the fcl does. - Finding 6 depends on a DAQ format guarantee I could not establish from the code;
if ZS-without-raw is possible, the consequence is a thrown exception mid-job. - The PH reinterpretation (finding 5) has no before/after evidence attached to the
PR, so its correctness rests on the header layout rather than on a demonstration.
Author follow-ups
- Fix
anaPathinplotSTMWaveformDigis.fcl— the file cannot run as committed. - Bound the raw payload against the real fragment size before reading
rawLength()
words, mirroring what the ZS path already does. - Decide and make consistent how a header/payload count disagreement is handled:
ZS throws, PH silently truncates. - Rename
printInnerFragments→printInnerFragsand putstmPrintback on a path. - Answer whether a ZS fragment can arrive without a preceding raw fragment in the
same event. - State the PH
(time, height)pair change in the PR description and attach a
before/after spectrum. - Restore
end_pathsand drop thedata/output prefixes from both fcl files.
|
⌛ The following tests have been triggered for 685c2c1: build (Build queue - API unavailable) |
|
☀️ The build tests passed at 685c2c1.
N.B. These results were obtained from a build of this Pull Request at 685c2c1 after being merged into the base branch at 38f6943. For more information, please check the job page here. |
Changes to the Offline in Unpacking flow