SST 16.1.0
Structural Simulation Toolkit
ObjTreeHelpers.h
1// Copyright 2009-2026 NTESS. Under the terms
2// of Contract DE-NA0003525 with NTESS, the U.S.
3// Government retains certain rights in this software.
4//
5// Copyright (c) 2009-2026, NTESS
6// All rights reserved.
7//
8// This file is part of the SST software package. For license
9// information, see the LICENSE file in the top level directory of the
10// distribution.
11
12#ifndef SST_CORE_SERIALIZATION_OBJECTTREEHELPERS_DEBUGGER_H
13#define SST_CORE_SERIALIZATION_OBJECTTREEHELPERS_DEBUGGER_H
14
15#include "sst/core/serialization/ObjTree.h"
16
17#include <climits>
18#include <cstddef>
19#include <cstdint>
20#include <deque>
21#include <iostream>
22#include <limits.h>
23#include <map>
24#include <memory>
25#include <ostream>
26#include <sstream>
27#include <string>
28#include <utility>
29#include <vector>
30
31namespace SST::Core::Serialization {
32
33class ObjTreeComparison
34{
35public:
36 enum class Op : std::uint8_t { LT, LTE, GT, GTE, EQ, NEQ, CHANGED, INVALID };
37 enum class valType : std::uint8_t { OBJ, CONST, UNKNOWN };
38 static Op getOperationFromString(const std::string& op)
39 {
40 if ( op == "<" ) return Op::LT;
41 if ( op == "<=" ) return Op::LTE;
42 if ( op == ">" ) return Op::GT;
43 if ( op == ">=" ) return Op::GTE;
44 if ( op == "==" ) return Op::EQ;
45 if ( op == "!=" ) return Op::NEQ;
46 if ( op == "changed" ) return Op::CHANGED; // Could also use <>
47 return Op::INVALID;
48 }
49
50 static std::string getStringFromOp(Op op)
51 {
52 switch ( op ) {
53 case Op::LT:
54 return "<";
55 case Op::LTE:
56 return "<+";
57 case Op::GT:
58 return ">";
59 case Op::GTE:
60 return ">=";
61 case Op::EQ:
62 return "==";
63 case Op::NEQ:
64 return "!=";
65 case Op::CHANGED:
66 return "CHANGED";
67 case Op::INVALID:
68 return "INVALID";
69 default:
70 return "Invalid Op";
71 }
72 }
73 // TODO: add some limits on these
74 // The full path and name to the objects for compairson. Vector contains the full path to each object stored as
75 // individual
76 // elements within the dequeue. Ex: /comp0/subcomp/target --> [comp0][subcomp][target] within the dequeue
77 std::vector<std::tuple<std::deque<std::string>, valType, std::unique_ptr<ObjTreeCont>>> objectsToCompare;
78 std::vector<Op> operators;
79
80 void print(std::stringstream& s, unsigned startIdx, unsigned stopIdx) const
81 {
82 if ( objectsToCompare.empty() || operators.empty() ) {
83 return;
84 }
85 if ( objectsToCompare.size() < startIdx || objectsToCompare.size() < stopIdx ) {
86 return;
87 }
88 for ( size_t i = startIdx; i < stopIdx; i++ ) {
89 if ( std::get<valType>(objectsToCompare[i]) == valType::UNKNOWN ) {
90 continue;
91 }
92 if ( std::get<valType>(objectsToCompare[i]) == valType::CONST ) {
93 s << std::get<std::deque<std::string>>(objectsToCompare[i]).back();
94 s << " ";
95 continue;
96 }
97 else {
98 for ( size_t j = 0; j < std::get<std::deque<std::string>>(objectsToCompare[i]).size(); j++ ) {
99 s << "/";
100 s << std::get<std::deque<std::string>>(objectsToCompare[i])[j];
101 }
102 }
103 if ( (i % 2 == 0) && (i / 2 < operators.size()) ) {
104 s << " " << getStringFromOp(operators[i / 2]);
105 }
106 s << " ";
107 }
108 // s << std::endl;
109 }
110
111 bool evaluateComparison(unsigned index, [[maybe_unused]] SST::Core::Serialization::ObjTreeCont* treeRoot);
112
113 ObjTreeComparison() = default;
114 ~ObjTreeComparison() = default;
115};
116
117class ObjTreeTraceBuffer
118{
119
120public:
121 ObjTreeTraceBuffer(size_t sz, size_t pdelay) :
122 bufSize_(sz),
123 postDelay_(pdelay)
124 {
125 tagBuffer_.resize(bufSize_);
126 cycleBuffer_.resize(bufSize_);
127 handlerBuffer_.resize(bufSize_);
128 }
129
130 virtual ~ObjTreeTraceBuffer() = default;
131
132 void setBufferReset() { reset_ = true; }
133
134 void resetTraceBuffer()
135 {
136 postCount_ = 0;
137 cur_ = 0;
138 first_ = 0;
139 numRecs_ = 0;
140 samplesLost_ = 0;
141 isOverrun_ = false;
142 reset_ = false;
143 state_ = CLEAR;
144
145 // should we do a syncFromSim on the remaining objBuffers?
146 for ( size_t obj = 0; obj < objBuffers_.size(); obj++ ) {
147 auto& [bufVec, storedTriggerIdx] = objBuffers_[obj];
148 auto e = bufVec.end();
149 e--;
150 bufVec.erase(bufVec.begin(), e);
151 bufVec.front()->syncFromSim();
152 storedTriggerIdx = INT_MAX;
153 }
154 }
155
156 size_t getBufferSize() { return bufSize_; }
157
158 void addObjectBuffer(std::unique_ptr<ObjTreeCont> vb)
159 {
160 std::vector<std::unique_ptr<ObjTreeCont>> newBuf;
161 newBuf.push_back(std::move(vb));
162 objBuffers_.emplace_back(std::move(newBuf), INT_MAX);
163 }
164
165 enum BufferState : int {
166 CLEAR, // 0 Pre Trigger
167 TRIGGER, // 1 Trigger
168 POSTTRIGGER, // 2 Post Trigger
169 OVERRUN // 3 Overrun
170 };
171
172 const std::map<BufferState, char> state2char { { CLEAR, '-' }, { TRIGGER, '!' }, { POSTTRIGGER, '+' },
173 { OVERRUN, 'o' } };
174
175 bool sampleT(bool trigger, uint64_t cycle, const std::string& handler)
176 {
177 size_t start_state = state_;
178 bool invokeAction = false;
179
180 // if Trigger == TRUE
181 if ( trigger ) {
182 if ( start_state == CLEAR ) { // Not previously triggered
183 state_ = TRIGGER; // State becomes trigger record
184 }
185
186 } // if trigger
187
188 if ( start_state == TRIGGER || start_state == POSTTRIGGER ) { // trigger record or post trigger
189 state_ = POSTTRIGGER; // State becomes post trigger
190 }
191
192 // Circular buffer
193 if ( !(state_ == POSTTRIGGER && postCount_ >= postDelay_) ) {
194#ifdef _OBJMAP_DEBUG_
195 std::cout << " Sample:" << handler << ": numRecs:" << numRecs_ << " first:" << first_ << " cur:" << cur_
196 << " state:" << state2char.at(state_) << " isOverrun:" << isOverrun_
197 << " samplesLost:" << samplesLost_ << std::endl;
198#endif
199 cycleBuffer_[cur_] = cycle;
200 handlerBuffer_[cur_] = handler;
201 if ( trigger ) {
202 triggerCycle = cycle;
203 }
204
205 // Sample all the trace object buffers
206 ObjTreeCont* varBuffer_;
207
208 for ( size_t obj = 0; obj < objBuffers_.size(); obj++ ) {
209 auto& [bufVec, storedTriggerIdx] = objBuffers_[obj];
210 varBuffer_ = bufVec.back().get();
211 if ( storedTriggerIdx != INT_MAX ) {
212 std::unique_ptr<ObjTreeCont> updatedBuf(varBuffer_->clone());
213 updatedBuf->syncFromSim();
214 bufVec.push_back(std::move(updatedBuf));
215 unsigned triggerIdx = trigger ? bufVec.size() - 1 : storedTriggerIdx;
216 storedTriggerIdx = triggerIdx;
217 }
218 else {
219 bufVec.back()->syncFromSim();
220 storedTriggerIdx = 0;
221 }
222 // DDD: Add a way to flag which of these values was the trigger value
223 }
224
225 if ( numRecs_ < bufSize_ ) {
226 tagBuffer_[cur_] = state_;
227 numRecs_++;
228 cur_ = (cur_ + 1) % bufSize_;
229 if ( cur_ == 0 ) first_ = 0; // 1;
230 }
231 else { // Buffer full
232 // Check to see if we are overwriting trigger
233 if ( tagBuffer_[cur_] == TRIGGER ) {
234 isOverrun_ = true;
235 }
236 tagBuffer_[cur_] = state_;
237 numRecs_++;
238 cur_ = (cur_ + 1) % bufSize_;
239 first_ = cur_;
240 for ( size_t obj = 0; obj < objBuffers_.size(); obj++ ) {
241 auto& [bufVec, storedTriggerIdx] = objBuffers_[obj];
242 bufVec.erase(bufVec.begin());
243 if ( storedTriggerIdx == bufVec.size() ) {
244 storedTriggerIdx--;
245 }
246 }
247 }
248 }
249
250 if ( isOverrun_ ) {
251 samplesLost_++;
252 }
253
254 if ( (state_ == TRIGGER) && (postDelay_ == 0) ) {
255 invokeAction = true;
256 }
257
258 if ( state_ == POSTTRIGGER ) {
259 postCount_++;
260 if ( postCount_ >= postDelay_ ) {
261 invokeAction = true;
262 }
263 }
264
265 return invokeAction;
266 }
267
268 void dumpTraceBufferT(std::ostream& os = std::cout)
269 {
270 if ( numRecs_ == 0 ) return;
271
272 size_t start;
273 size_t end;
274
275 start = first_;
276 if ( cur_ == 0 ) {
277 end = bufSize_ - 1;
278 }
279 else {
280 end = cur_ - 1;
281 }
282 size_t buf = 0;
283 for ( int j = start;; j++ ) {
284 size_t i = j % bufSize_;
285
286 os << "buf[" << i << "] " << handlerBuffer_.at(i) << " @" << cycleBuffer_.at(i) << " ("
287 << state2char.at(tagBuffer_.at(i)) << ") ";
288
289 std::ostringstream tmpBuf;
290 std::string objNames;
291 for ( size_t obj = 0; obj < objBuffers_.size(); obj++ ) {
292 ObjTreeCont* varBuffer_ =
293 std::get<std::vector<std::unique_ptr<ObjTreeCont>>>(objBuffers_[obj])[buf].get(); // DDD: Maybe?
294 varBuffer_->Dump(1, std::ios_base::dec, tmpBuf);
295 objNames.append(tmpBuf.str());
296 if ( !objNames.empty() && objNames.back() == '\n' ) {
297 objNames.pop_back();
298 objNames.push_back(' ');
299 }
300 tmpBuf.str("");
301 tmpBuf.clear();
302 }
303 os << objNames << std::endl;
304 buf++;
305
306 if ( i == end ) {
307 break;
308 }
309 }
310 }
311
312 void dumpTriggerRecord(std::ostream& os = std::cout)
313 {
314 if ( numRecs_ == 0 ) {
315 std::cout << "No trace samples in current buffer" << std::endl;
316 return;
317 }
318 if ( state_ != CLEAR ) {
319 std::stringstream ss; // Need to buffer in a stream so it doesn't get split in parallel execution
320 std::ostringstream tmpBuf;
321 std::string objNames;
322 unsigned idxToPrint = 0;
323 // print trigger value for the one variable (variables?) that actually triggered
324 ss << "LastTriggerRecord:@cycle" << triggerCycle << ": SamplesLost=" << samplesLost_ << ": ";
325 for ( size_t obj = 0; obj < objBuffers_.size(); obj++ ) {
326 unsigned triggerIdx = std::get<unsigned>(objBuffers_[obj]);
327 auto* record = &std::get<std::vector<std::unique_ptr<ObjTreeCont>>>(objBuffers_[obj]);
328 if ( triggerIdx < record->size() ) {
329 idxToPrint = triggerIdx;
330 }
331 else {
332 idxToPrint = record->size() - 1;
333 }
334 ObjTreeCont* varBuffer_ = (*record)[idxToPrint].get();
335 // OK, so we have to use Dump() to get the value, but it adds a \n after each call, so strip it out
336 varBuffer_->Dump(1, std::ios_base::dec, tmpBuf);
337 objNames.append(tmpBuf.str());
338 if ( !objNames.empty() && objNames.back() == '\n' ) {
339 objNames.pop_back();
340 objNames.push_back(' ');
341 }
342 tmpBuf.str("");
343 tmpBuf.clear();
344 }
345 ss << objNames << std::endl;
346 os << ss.str(); // Print everything at once to avoid splitting
347 }
348 }
349
350 void printVars(std::stringstream& ss)
351 {
352 for ( size_t obj = 0; obj < objBuffers_.size(); obj++ ) {
353 ObjTreeCont* varBuffer_ =
354 std::get<std::vector<std::unique_ptr<ObjTreeCont>>>(objBuffers_[obj]).back().get();
355 ss << varBuffer_->getObjName() << " ";
356 }
357 }
358
359 void printConfig(std::stringstream& ss)
360 {
361 ss << "bufsize = " << bufSize_ << " postDelay = " << postDelay_ << " : ";
362 printVars(ss);
363 }
364
365 // private:
366 size_t bufSize_ = 64;
367 size_t postDelay_ = 8;
368 size_t postCount_ = 0;
369 size_t cur_ = 0;
370 size_t first_ = 0;
371 size_t numRecs_ = 0;
372 bool isOverrun_ = false;
373 size_t samplesLost_ = 0;
374 bool reset_ = false;
375 BufferState state_ = CLEAR;
376
377 std::vector<BufferState> tagBuffer_;
378 std::vector<std::string> handlerBuffer_;
379 // Vector of ObjTreeCont - organized as all the sampled values with the index of the last
380 // triggered value
381 std::vector<std::tuple<std::vector<std::unique_ptr<ObjTreeCont>>, unsigned>> objBuffers_;
382 std::vector<uint64_t> cycleBuffer_;
383 uint64_t triggerCycle;
384
385}; // class TraceBuffer
386} // namespace SST::Core::Serialization
387#endif
The Object Tree (ObjTree) is intended to be a developer friendly interface to SST’s existing serializ...
Definition ObjTreeLeaves.h:41
virtual void Dump(const int verbosity, std::ios_base::fmtflags base=std::ios_base::dec, std::ostream &os=std::cout)
Print the contents of the node to the stream specified by os.
Definition ObjTreeLeaves.h:248