The Sparta Modeling Framework
Loading...
Searching...
No Matches
StateTracker.hpp
1// <StateTracker> -*- C++ -*-
2
3
4#pragma once
5
6#include <inttypes.h>
7#include <memory>
8#include <vector>
9#include <functional>
10#include <deque>
11#include <type_traits>
12#include <algorithm>
13#include <map>
14#include <utility>
15
16#include "sparta/utils/Enum.hpp"
18
19namespace sparta {
20
21 namespace tracker {
22
24 // an overloaded operator for a type during compile time.
25 // This is needed because in order to annotate the Histograms,
26 // we need to label each enum constant with their string names.
27 // The process of converting enum constants into string is
28 // usually done by an overload global operator <<. But we cannot
29 // blindly assume that every enum class will have this overloaded.
30 // If we assume so, this will lead to compilation failure.
31 //
32 // This can be detected using SFINAE.
33 // The idea of this technique is referred from here :
34 // en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector/.
35 namespace has_ostream_operator_impl {
36
38 typedef char no;
39
41 typedef char yes[2];
42
44 // from any object of any type.
45 struct fallback {
46 template<typename T>
47 fallback(const T &);
48 };
49
51 // any fallback object.
52 no operator << (std::ostream const &, fallback const &);
53
55 // then the return type of invoking it has to be a
56 // std::ostream &. In that case, this function will
57 // be invoked.
58 yes & test(std::ostream &);
59
61 // to begin with, it will use the global << operator
62 // defined in this namespace. It will convert itself into
63 // fallback object and then invoke this function. In this
64 // case, the return type in no.
66
67 template<typename T>
69 static std::ostream & s;
70 static T const & t;
71 static constexpr bool value {
72
74 // << operator on an object of type T. The return
75 // value that we get will tell us if this class T
76 // did have an << operator defined for it or not.
77 sizeof(test(s << t)) == sizeof(yes)};
78 };
79 }
80
81 template<typename T>
84
85 template<typename T> class StateTrackerUnit;
86 template<typename T> class StatePool;
87 template<typename T> struct StateSet;
88
90 // It contains a weak pointer pointing to the pool
91 // from where the state tracker unit was dispatched.
92 // If the weak pointer has not expired, the pool is still
93 // alive and the tracker unit gets recycled and enqueued
94 // back at the Dispatch Queue in the pool again. If the
95 // weak pointer is expired, then the tracker unit is freed.
96 template<typename T>
98 public:
99
101 // reference.
103 const std::weak_ptr<StatePool<T>> & weak_pool) :
104 weak_pool_ptr_(weak_pool) {}
105
106 StateTrackerDeleter() : valid_(false) {}
107
108 inline void operator()(StateTrackerUnit<T> * ptr) const {
109 if(!valid_ || !ptr) {
110 return;
111 }
112 if(!weak_pool_ptr_.expired()) {
113 weak_pool_ptr_.lock()->releaseToPool(ptr);
114 }
115 else {
116 delete ptr;
117 ptr = nullptr;
118 }
119 }
120
121 std::weak_ptr<StatePool<T>> weak_pool_ptr_;
122 bool valid_ {true};
123 };
124
126 // These unique pointers come with their own Deleters which
127 // is the StateTrackerDeleter Functor. Whenever a state tracker
128 // unit gets out of scope and is being destroyed, the Functor
129 // gets invoked. This is because, we need to know, whether we
130 // want to recycle the tracker unit or finally free it, if the
131 // simulation is over.
132 template<typename T>
133 using state_tracker_ptr =
134 std::unique_ptr<StateTrackerUnit<T>, StateTrackerDeleter<T>>;
135
137 // State Tracker Units. StatePool is a class template, templatized
138 // on the enum class type which we are tracking. Each StatePool class
139 // template contains its dedicated Tracker Queue, templatized on the
140 // same enum type. This Tracker Queue holds all the available,
141 // ready-to-go tracker units, ready for dispatching when the call comes.
142 // The State Tracker Units, upon deletion, come back to this queue during
143 // simulation and make themselves available again for tracking. Only when
144 // the Simulation class is torn down, this Tracker Queue gets destroyed and
145 // finally its custom deleter, which is TransferQueueData(), is called.
146 template<typename T>
147 using tracker_queue_ptr =
148 std::unique_ptr<std::deque<state_tracker_ptr<T>>,
149 std::function<void(std::deque<state_tracker_ptr<T>> *)>>;
150
152 // This class has been designed to solve the problem of storing StatePool
153 // instances in a single homogenous container. This is difficult because
154 // StatePool is a template class and cannot be grouped together in a single
155 // container. Hence, the StatePoolBase class is designed. Instead of storing
156 // the actual StatePool instances, we store base class pointers to those
157 // instances in a map, with each different template type having a unique ID.
159 public:
160 virtual ~StatePoolBase() {}
161
162 protected:
163 static size_t next() noexcept {
164 static size_t counter {0};
165 return counter++;
166 }
167 };
168
170 // The basic functionality of this class is to maintain a Ready-To-Go Queue of
171 // State Tracker Units of the same enum type. It also contains the running
172 // number of all the State Tracker Units it has dispatched and a shared_ptr to
173 // itself. The Pool class also contains the state tracking filename, passed
174 // on from the StatePoolManager.
175 template<typename T>
176 class StatePool : public StatePoolBase {
177 public:
178
180 // without a valid state tracking filename.
181 StatePool() = delete;
182
184 explicit StatePool(const std::string & tracking_filename) :
185
187 instance_count_(0),
188
190 // This is used later on when deleting Tracker Units.
191 pool_existence_reference_(this, [](void *){}),
192
194 tracking_filename_(tracking_filename),
195
197 // which is transferQueueData() API.
198 available_tracker_queue_(new std::deque<state_tracker_ptr<T>>(),
199 [this](std::deque<state_tracker_ptr<T>> * raw_queue){
200 transferQueueData(raw_queue);}) {}
201
203 // with a Unique ID. This is not a Thread Safe method.
204 static size_t id() noexcept {
205 static const size_t identifier {StatePoolBase::next()};
206 return identifier;
207 }
208
210 // State Tracker Unit is raised.
211 state_tracker_ptr<T> getNewStateTrackerUnit(Scheduler * scheduler) noexcept {
212
214 // increases by 1.
215 ++instance_count_;
216
218 // Tracker Unit on the fly and dispatch it.
219 if(available_tracker_queue_->empty()) {
220 state_tracker_ptr<T> state_tracker_unit(
221 new StateTrackerUnit<T>(scheduler),
222 StateTrackerDeleter<T>(pool_existence_reference_));
223 return state_tracker_unit;
224 }
225
227 // one from the front and dispatch it. This is where the recycling
228 // state tracker units happen.
229 state_tracker_ptr<T> state_tracker_unit {
230 std::move(available_tracker_queue_->front())};
231 available_tracker_queue_->pop_front();
232 return state_tracker_unit;
233 }
234
236 // need to be returned back to the pool during recycling.
238
240 // This makes the code safe from unexpected memory leaks.
241 state_tracker_ptr<T> tracker_unique_ptr(raw_ptr,
242 StateTrackerDeleter<T>(pool_existence_reference_));
243
245 sparta_assert(tracker_unique_ptr);
246
248 // from its last tracking run, we collect and process it
249 // before releasing it back in the pool.
250 tracker_unique_ptr->updateLastDeltas();
251
253 available_tracker_queue_->push_back(std::move(tracker_unique_ptr));
254 }
255
257 // This method gets called once and only once for each tracked enum type,
258 // when the StatePool instantiation is getting destroyed.
259 void transferQueueData(std::deque<state_tracker_ptr<T>> * raw_queue) {
260
262 std::unique_ptr<std::deque<state_tracker_ptr<T>>> queue_ptr(raw_queue);
263
265 sparta_assert(instance_count_);
266
268 std::fstream data_file(tracking_filename_, std::ios_base::app);
269
271 // weak_pointer in StateTrackerDeleter pointing here, expires,
272 // and stops tracker units to come back to the queue.
273 pool_existence_reference_.reset();
274
276 std::vector<sparta::Scheduler::Tick> stats_vector(
277 static_cast<uint64_t>(T::__LAST) + 1, 0);
278
280 std::for_each(queue_ptr->begin(), queue_ptr->end(),
281 [&stats_vector](const state_tracker_ptr<T> & item) {
282
284 const StateSet<T> & state_set {item->getStateSet()};
285 sparta_assert(state_set.state_delta_set.size() == stats_vector.size());
286
287 // Accumulate the data from this tracker unit into the result vector.
288 std::transform(stats_vector.begin(), stats_vector.end(),
289 state_set.state_delta_set.begin(), stats_vector.begin(),
290 std::plus<sparta::Scheduler::Tick>()); });
291
292 queue_ptr->clear();
293
295 // total instance count.
296 std::vector<double> avg_stats_vector(
297 static_cast<uint64_t>(T::__LAST) + 1, 0);
298
299 std::transform(stats_vector.begin(), stats_vector.end(),
300 avg_stats_vector.begin(),
301 [this](sparta::Scheduler::Tick & item) -> double {
302 return static_cast<double>(item) /
303 static_cast<double>(instance_count_); });
304
305
306 std::string name_string;
307
309 extractEnumTypeAsString_(__PRETTY_FUNCTION__, "[", "]", name_string);
310
312 data_file << "Enum Class Name : " << name_string << "\n"
313 << "Total State Tracker Units used : " << instance_count_ << "\n"
314 << "Aggregate Residency Stats: \n";
315
316 std::vector<std::string> enum_name_strings;
317
319 fillHistogramLabels_<T>(enum_name_strings);
320
322 // This is because the last enum state in every enum class is __LAST.
323 // This is not a real enum state but merely a placeholder or dummy state
324 // to calculate quickly the number of states in that particular state.
325 for(size_t i = 0; i < stats_vector.size() - 1; ++i) {
326 data_file << enum_name_strings[i] << " : " << stats_vector[i] << "\n";
327 }
328
329 data_file << "\n\nAverage Residency Stats: \n";
330
332 // This is because the last enum state in every enum class is __LAST.
333 // This is not a real enum state but merely a placeholder or dummy state
334 // to calculate quickly the number of states in that particular state.
335 for(size_t i = 0; i < avg_stats_vector.size() - 1; ++i) {
336 data_file << enum_name_strings[i] << " : " << avg_stats_vector[i] << "\n";
337 }
338
339 data_file << "\n\n";
340 }
341
342 private:
343
345 uint64_t instance_count_;
346
348 // If this self-reference is reset, the StateTrackerDeleter knows the
349 // pool is going through destruction and that State Tracker Units
350 // should not be recycled anymore. If the reference is still valid,
351 // the State Tracker Units need to com back to the queue, recycled.
352 std::shared_ptr<StatePool<T>> pool_existence_reference_;
353
355 // data will be written to.
356 std::string tracking_filename_;
357
359 // Units. It has a custom type with its own custom deleter.
360 tracker_queue_ptr<T> available_tracker_queue_;
361
363 // We put this string in our output text file to label the various
364 // histograms.
365 bool extractEnumTypeAsString_(const std::string & source,
366 const std::string & start,
367 const std::string & end,
368 std::string & result) {
369
371 // the enum class type that needs to be extracted as a std::string.
372 // The first 9 chars of this string is "with T = " which needs to be
373 // erased away before returning the rest of the string.
374 constexpr uint16_t redundant_char_length {9};
375
376 std::size_t start_index = source.find(start);
377 if(start_index == std::string::npos) {
378 return false;
379 }
380
381 start_index += start.length();
382
383 const std::string::size_type end_index =
384 source.find(end, start_index);
385
386 if(end_index == std::string::npos) {
387 return false;
388 }
389
390 result = source.substr(start_index, end_index - start_index);
391 result.erase(0, redundant_char_length);
392 return true;
393 }
394
396 // name equivalents of the different enum constants. For this,
397 // we need to invoke the << operator on individual enum constants.
398 // This template overload is SFINAEd out to enable if this enum
399 // type U has a << operator overloaded for it.
400 template<typename U>
401 typename std::enable_if<has_ostream_operator<U>::value, void>::type
402 fillHistogramLabels_(std::vector<std::string> & enum_name_strings) {
403 typedef typename std::underlying_type<U>::type enumType;
404 constexpr enumType last_index = static_cast<enumType>(U::__LAST);
405 enum_name_strings.reserve(last_index);
406 std::stringstream ss;
407 for(enumType e = 0; e < last_index; ++e) {
408 auto val = static_cast<U>(e);
409 ss << val;
410 enum_name_strings.emplace_back(ss.str());
411 ss.str("");
412 ss.clear();
413 }
414 }
415
417 // name equivalents of the different enum constants. For this,
418 // we need to invoke the << operator on individual enum constants.
419 // This template overload is SFINAEd out to disable if this enum
420 // type U does not have a << operator overloaded for it. We fill
421 // the resulting vector with empty strings.
422 template<typename U>
423 typename std::enable_if<!has_ostream_operator<U>::value, void>::type
424 fillHistogramLabels_(std::vector<std::string> & enum_name_strings) {
425 typedef typename std::underlying_type<U>::type enumType;
426 constexpr enumType last_index = static_cast<enumType>(U::__LAST);
427 enum_name_strings.resize(last_index);
428 }
429 };
430
432 // template instantiations. This class contains a map of unique integers
433 // mapped to StatePool template instantiations. Whenever a new State
434 // Tracker Unit is demanded, we first get a handle of the appropriate
435 // StatePool by using the Template type. Once we have the Pool, we query
436 // the internal Tracker Queue for an available Tracker Unit.
438 {
439 public:
440 StatePoolManager(const StatePoolManager &) = delete;
441 StatePoolManager & operator = (const StatePoolManager &) = delete;
443 StatePoolManager & operator = (StatePoolManager &&) = delete;
444
447 static StatePoolManager state_pool_manager;
448 return state_pool_manager;
449 }
450
452 // make tracking enabled in stand-alone
453 // tester.
454 inline void enableTracking() {
455 is_tracking_enabled_ = true;
456 }
457
459 // state pool template instantiations. This in turn,
460 // destroys all the individual state tracker units after
461 // processing them. This method must be called before the
462 // Simulation is done. This method is called from the
463 // destructor of the Special sparta::State.
464 void flushPool() {
465 unique_pool_type_map_.clear();
466 }
467
469 // This method is called during construction of individual
470 // State objects. This method encapsulates all the technicalities
471 // of State Pools, Tracker Queues and Deleter Functors.
472 template<typename T>
473 state_tracker_ptr<T> dispatchNewTracker() {
474
476 // only if user have turned on state tracking.
477 if(__builtin_expect(is_tracking_enabled_, 0)) {
478 return getStatePool_<T>()->getNewStateTrackerUnit(scheduler_);
479 }
480 return nullptr;
481 }
482
484 // provided by the user. This is passed down from the configure
485 // method of the Special sparta::State which is constructed during
486 // the Simulation construction phase. Pool Manager stores the
487 // filename and passes this name when constructing State Pools.
488 // State Pools use this filename to open file, write histogram
489 // data during the destruction of itself.
490 void setTrackingFilename(const std::string & filename) {
491
493 // This is important if the modeler uses the same filename
494 // for consecutive simulation runs. Else, we would be getting
495 // data from multiple simulation runs, all clobbered together.
496 is_tracking_enabled_ = true;
497 std::fstream data_file(filename, std::ios::out | std::ios_base::trunc);
498 data_file.close();
499 tracking_filename_ = filename;
500 }
501
503 void setScheduler(Scheduler * scheduler) {
504 scheduler_ = scheduler;
505 }
506
507 private:
508 StatePoolManager() = default;
509 bool is_tracking_enabled_ {false};
510 Scheduler * scheduler_ = nullptr;
511 using CachedBase = std::map<size_t, std::unique_ptr<StatePoolBase>>;
512 CachedBase unique_pool_type_map_;
513 std::string tracking_filename_;
514
516 // The process of dispatching a State Tracker Unit is
517 // two-stepped. The first step is to get the correct
518 // State Pool template instatiation. This method does the
519 // first step. Once we have a valid pool handle, we query
520 // internal Tracker queues of that pool and start issuing
521 // new or recycled State Tracker Units.
522 template<typename T>
523 inline StatePool<T> * getStatePool_() {
524 static StatePool<T> * state_pool {nullptr};
525
527 // have seen this enum type T before and hence,
528 // we do not need to create a new state pool to
529 // handle this enum type. We just return the
530 // state_pool pointer which is already pointing
531 // to the valid state pool for this enum type T.
532 if(__builtin_expect(state_pool != nullptr, 1)) {
533 return state_pool;
534 }
535
537 // this enum type T for the first time. So, we must
538 // instantiate a new, valid State Pool to handle
539 // enums of this type. We then make state_pool
540 // point to this pool and return it.
541 const size_t identifier = StatePool<T>::id();
542
543 unique_pool_type_map_[identifier].reset(
544 new StatePool<T>(tracking_filename_));
545
547 // dynamic_cast. This is done because we already know
548 // what the exact derived type of the resource being
549 // pointed by the base pointer is. So, it is much more
550 // efficient to not use the C++ runtime to perform the
551 // cast and let the compiler handle this.
552 state_pool = static_cast<StatePool<T> *>(
553 unique_pool_type_map_[identifier].get());
554 return state_pool;
555 }
556 };
557
559 // This struct tells us if there is a valid state which is being tracked
560 // right now or not, what is the timestamp when this state got active and
561 // a vector of accumulated ticks for all the different states of this
562 // particular enum type.
563 template<typename EnumT>
564 struct StateSet {
566 typename std::underlying_type<EnumT>::type> active_state_index;
567 sparta::Scheduler::Tick active_state_starting_time;
568 std::vector<sparta::Scheduler::Tick> state_delta_set;
569
570 explicit StateSet(const uint64_t num_states) :
571 active_state_starting_time(0),
572 state_delta_set(std::vector<sparta::Scheduler::Tick>(num_states, 0)) {}
573
574 StateSet(const StateSet & lval) = default;
575
576 StateSet(StateSet && rval) :
577 active_state_index(rval.active_state_index),
578 active_state_starting_time(rval.active_state_starting_time),
579 state_delta_set(std::move(rval.state_delta_set)) {}
580 };
581
583 // starting and stopping of tracking states. Instances of this class
584 // are contained inside sparta::State class which uses these tracker units
585 // to perform arithmetical calculations.
586 template<typename EnumT>
588 public:
589 StateTrackerUnit(Scheduler * scheduler) :
590 scheduler_instance_(scheduler),
591 time_assigned_(scheduler_instance_->getCurrentTick()),
592 state_set_(StateSet<EnumT>(
593 static_cast<uint64_t>(EnumT::__LAST) + 1)) {}
594
595 StateTrackerUnit(const StateTrackerUnit &) = default;
596
598 scheduler_instance_(rval.scheduler_instance_),
599 time_assigned_(rval.time_assigned_),
600 state_set_(rval.state_set_) {
601 rval.scheduler_instance_ = nullptr;
602 }
603
605 // the enum class type.
606 template<typename EnumType>
607 void startState(const EnumType& state_enum) {
608 sparta::Scheduler::Tick current_time =
609 scheduler_instance_->getCurrentTick();
610 typename std::underlying_type<EnumT>::type state_index =
611 static_cast<typename std::underlying_type<EnumT>::type>(state_enum);
612 if(__builtin_expect(state_set_.active_state_index.isValid(), 1)){
613 typename std::underlying_type<EnumT>::type active_state_in_set =
614 state_set_.active_state_index.getValue();
615 if(active_state_in_set == state_index){
616 return;
617 }
618 endTimerState_();
619 }
620 sparta_assert(static_cast<size_t>(state_index) <
621 state_set_.state_delta_set.size());
622 state_set_.active_state_index = state_index;
623 state_set_.active_state_starting_time = current_time;
624 }
625
627 // the enum class type.
628 template<typename EnumType>
629 void endState(const EnumType& state_enum) {
630 typename std::underlying_type<EnumT>::type state_index =
631 static_cast<typename std::underlying_type<EnumT>::type>(state_enum);
632 sparta_assert(state_set_.active_state_index.isValid());
633 typename std::underlying_type<EnumT>::type active_state_in_set =
634 state_set_.active_state_index.getValue();
635 sparta_assert(active_state_in_set == state_index);
636 endTimerState_();
637 state_set_.active_state_index.clearValid();
638 state_set_.active_state_starting_time = 0;
639 }
640
642 // tracker unit for recycling. This method basically does
643 // one last calculation and processes stats before
644 // sending the tracker unit back to the queue.
645 inline void updateLastDeltas() noexcept {
646 if(__builtin_expect(state_set_.active_state_index.isValid(), 1)) {
647 sparta::Scheduler::Tick current_time =
648 scheduler_instance_->getCurrentTick();
649 typename std::underlying_type<EnumT>::type active_state_in_set =
650 state_set_.active_state_index.getValue();
651 state_set_.state_delta_set[active_state_in_set] +=
652 current_time - state_set_.active_state_starting_time;
653 state_set_.active_state_index.clearValid();
654 state_set_.active_state_starting_time = 0;
655 }
656 }
657
661 return scheduler_instance_->getCurrentTick() -
662 state_set_.active_state_starting_time;
663 }
664
665 const StateSet<EnumT> & getStateSet() const {
666 return state_set_;
667 }
668
669 private:
670 inline void endTimerState_() noexcept {
671 sparta::Scheduler::Tick current_time =
672 scheduler_instance_->getCurrentTick();
673 typename std::underlying_type<EnumT>::type active_state_in_set =
674 state_set_.active_state_index.getValue();
675 state_set_.state_delta_set[active_state_in_set] +=
676 current_time - state_set_.active_state_starting_time;
677 }
678
679 const sparta::Scheduler * scheduler_instance_ {nullptr};
680 sparta::Scheduler::Tick time_assigned_ {0};
681 StateSet<EnumT> state_set_ {};
682 };
683 }
684
685}
#define sparta_assert(...)
Simple variadic assertion that will throw a sparta_exception if the condition fails.
File that defines a ValidValue.
A class that lets you schedule events now and in the future.
uint64_t Tick
Typedef for our unit of time.
This is the polymorphic base class for the StatePool template class.
This is a Singleton class which manages all the different StatePool.
static StatePoolManager & getInstance()
This is not a Thread Safe method.
void enableTracking()
This method needs to be public to.
void setTrackingFilename(const std::string &filename)
This filename is the name of the State Tracking File as.
state_tracker_ptr< T > dispatchNewTracker()
This is the public API to get new tracker units.
void setScheduler(Scheduler *scheduler)
Set the scheduler used by the Simulation class.
void flushPool()
This method starts the teardown of all the different.
StatePool class template is templatized on the enum type we are tracking.
StatePool(const std::string &tracking_filename)
A file name is a must when constructing StatePool.
static size_t id() noexcept
Method which associates this StatePool template instantiation.
void transferQueueData(std::deque< state_tracker_ptr< T > > *raw_queue)
This is the main method, the custom deleter of the Tracker Queues.
state_tracker_ptr< T > getNewStateTrackerUnit(Scheduler *scheduler) noexcept
Method which gets invoked whenever a demand for a new.
void releaseToPool(StateTrackerUnit< T > *&raw_ptr)
Method which is invoked when individual State Tracker Units.
StatePool()=delete
The Default Ctor is deleted because StatePool cannot be created.
This is the actual Lightweight Tracker class which does the.
sparta::Scheduler::Tick getActiveTime() const
void updateLastDeltas() noexcept
This method is called right before we send away a.
void startState(const EnumType &state_enum)
This method starts the timer on this particular value of.
void endState(const EnumType &state_enum)
This method stops the timer on this particular value of.
Provides a wrapper around a value to ensure that the value is assigned.
const value_type & getValue() const
Get the value - const version.
char no
Typedef a char array of size one.
char yes[2]
Typedef a char array of size two.
yes & test(std::ostream &)
If the class does have an << operator overloaded,.
no operator<<(std::ostream const &, fallback const &)
Declare a dummy << operator which operates on.
Macros for handling exponential backoff.
This is the Calculation Engine unit inside each State Tracker Unit.
Custom Deleter of individual State Tracker Units.
StateTrackerDeleter(const std::weak_ptr< StatePool< T > > &weak_pool)
Construct the internal weak_ptr from a shared_ptr.
A fallback struct which can create itself.