| #ifndef KMP_STATS_H | |
| #define KMP_STATS_H | |
| /** @file kmp_stats.h | |
| * Functions for collecting statistics. | |
| */ | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "kmp_config.h" | |
| #include "kmp_debug.h" | |
| #if KMP_STATS_ENABLED | |
| /* Statistics accumulator. | |
| Accumulates number of samples and computes min, max, mean, standard deviation | |
| on the fly. | |
| Online variance calculation algorithm from | |
| http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#On-line_algorithm | |
| */ | |
| #include "kmp_stats_timing.h" | |
| #include <limits> | |
| #include <math.h> | |
| #include <new> // placement new | |
| #include <stdint.h> | |
| #include <string> | |
| #include <vector> | |
| /* Enable developer statistics here if you want them. They are more detailed | |
| than is useful for application characterisation and are intended for the | |
| runtime library developer. */ | |
| #define KMP_DEVELOPER_STATS 0 | |
| /* Enable/Disable histogram output */ | |
| #define KMP_STATS_HIST 0 | |
| /*! | |
| * @ingroup STATS_GATHERING | |
| * \brief flags to describe the statistic (timer or counter) | |
| * | |
| */ | |
| enum stats_flags_e { | |
| noTotal = 1 << 0, //!< do not show a TOTAL_aggregation for this statistic | |
| onlyInMaster = 1 << 1, //!< statistic is valid only for primary thread | |
| noUnits = 1 << 2, //!< statistic doesn't need units printed next to it | |
| notInMaster = 1 << 3, //!< statistic is valid only for non-primary threads | |
| logEvent = 1 << 4 //!< statistic can be logged on the event timeline when | |
| //! KMP_STATS_EVENTS is on (valid only for timers) | |
| }; | |
| /*! | |
| * @ingroup STATS_GATHERING | |
| * \brief the states which a thread can be in | |
| * | |
| */ | |
| enum stats_state_e { | |
| IDLE, | |
| SERIAL_REGION, | |
| FORK_JOIN_BARRIER, | |
| PLAIN_BARRIER, | |
| TASKWAIT, | |
| TASKYIELD, | |
| TASKGROUP, | |
| IMPLICIT_TASK, | |
| EXPLICIT_TASK, | |
| TEAMS_REGION | |
| }; | |
| /*! | |
| * \brief Add new counters under KMP_FOREACH_COUNTER() macro in kmp_stats.h | |
| * | |
| * @param macro a user defined macro that takes three arguments - | |
| * macro(COUNTER_NAME, flags, arg) | |
| * @param arg a user defined argument to send to the user defined macro | |
| * | |
| * \details A counter counts the occurrence of some event. Each thread | |
| * accumulates its own count, at the end of execution the counts are aggregated | |
| * treating each thread as a separate measurement. (Unless onlyInMaster is set, | |
| * in which case there's only a single measurement). The min,mean,max are | |
| * therefore the values for the threads. Adding the counter here and then | |
| * putting a KMP_BLOCK_COUNTER(name) at the point you want to count is all you | |
| * need to do. All of the tables and printing is generated from this macro. | |
| * Format is "macro(name, flags, arg)" | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| // clang-format off | |
| #define KMP_FOREACH_COUNTER(macro, arg) \ | |
| macro(OMP_PARALLEL,stats_flags_e::onlyInMaster|stats_flags_e::noTotal,arg) \ | |
| macro(OMP_NESTED_PARALLEL, 0, arg) \ | |
| macro(OMP_LOOP_STATIC, 0, arg) \ | |
| macro(OMP_LOOP_STATIC_STEAL, 0, arg) \ | |
| macro(OMP_LOOP_DYNAMIC, 0, arg) \ | |
| macro(OMP_DISTRIBUTE, 0, arg) \ | |
| macro(OMP_BARRIER, 0, arg) \ | |
| macro(OMP_CRITICAL, 0, arg) \ | |
| macro(OMP_SINGLE, 0, arg) \ | |
| macro(OMP_SECTIONS, 0, arg) \ | |
| macro(OMP_MASTER, 0, arg) \ | |
| macro(OMP_MASKED, 0, arg) \ | |
| macro(OMP_TEAMS, 0, arg) \ | |
| macro(OMP_set_lock, 0, arg) \ | |
| macro(OMP_test_lock, 0, arg) \ | |
| macro(REDUCE_wait, 0, arg) \ | |
| macro(REDUCE_nowait, 0, arg) \ | |
| macro(OMP_TASKYIELD, 0, arg) \ | |
| macro(OMP_TASKLOOP, 0, arg) \ | |
| macro(TASK_executed, 0, arg) \ | |
| macro(TASK_cancelled, 0, arg) \ | |
| macro(TASK_stolen, 0, arg) | |
| // clang-format on | |
| /*! | |
| * \brief Add new timers under KMP_FOREACH_TIMER() macro in kmp_stats.h | |
| * | |
| * @param macro a user defined macro that takes three arguments - | |
| * macro(TIMER_NAME, flags, arg) | |
| * @param arg a user defined argument to send to the user defined macro | |
| * | |
| * \details A timer collects multiple samples of some count in each thread and | |
| * then finally aggregates all of the samples from all of the threads. For most | |
| * timers the printing code also provides an aggregation over the thread totals. | |
| * These are printed as TOTAL_foo. The count is normally a time (in ticks), | |
| * hence the name "timer". (But can be any value, so we use this for "number of | |
| * arguments passed to fork" as well). For timers the threads are not | |
| * significant, it's the individual observations that count, so the statistics | |
| * are at that level. Format is "macro(name, flags, arg)" | |
| * | |
| * @ingroup STATS_GATHERING2 | |
| */ | |
| // clang-format off | |
| #define KMP_FOREACH_TIMER(macro, arg) \ | |
| macro (OMP_worker_thread_life, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_parallel, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_parallel_overhead, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_teams, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_teams_overhead, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_loop_static, 0, arg) \ | |
| macro (OMP_loop_static_scheduling, 0, arg) \ | |
| macro (OMP_loop_dynamic, 0, arg) \ | |
| macro (OMP_loop_dynamic_scheduling, 0, arg) \ | |
| macro (OMP_distribute, 0, arg) \ | |
| macro (OMP_distribute_scheduling, 0, arg) \ | |
| macro (OMP_critical, 0, arg) \ | |
| macro (OMP_critical_wait, 0, arg) \ | |
| macro (OMP_single, 0, arg) \ | |
| macro (OMP_sections, 0, arg) \ | |
| macro (OMP_sections_overhead, 0, arg) \ | |
| macro (OMP_master, 0, arg) \ | |
| macro (OMP_masked, 0, arg) \ | |
| macro (OMP_task_immediate, 0, arg) \ | |
| macro (OMP_task_taskwait, 0, arg) \ | |
| macro (OMP_task_taskyield, 0, arg) \ | |
| macro (OMP_task_taskgroup, 0, arg) \ | |
| macro (OMP_task_join_bar, 0, arg) \ | |
| macro (OMP_task_plain_bar, 0, arg) \ | |
| macro (OMP_taskloop_scheduling, 0, arg) \ | |
| macro (OMP_plain_barrier, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_idle, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_fork_barrier, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_join_barrier, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_serial, stats_flags_e::logEvent, arg) \ | |
| macro (OMP_set_numthreads, stats_flags_e::noUnits | stats_flags_e::noTotal, \ | |
| arg) \ | |
| macro (OMP_PARALLEL_args, stats_flags_e::noUnits | stats_flags_e::noTotal, \ | |
| arg) \ | |
| macro (OMP_loop_static_iterations, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) \ | |
| macro (OMP_loop_static_total_iterations, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) \ | |
| macro (OMP_loop_dynamic_iterations, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) \ | |
| macro (OMP_loop_dynamic_total_iterations, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) \ | |
| macro (OMP_distribute_iterations, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) \ | |
| KMP_FOREACH_DEVELOPER_TIMER(macro, arg) | |
| // clang-format on | |
| // OMP_worker_thread_life -- Time from thread becoming an OpenMP thread (either | |
| // initializing OpenMP or being created by a primary | |
| // thread) until the thread is destroyed | |
| // OMP_parallel -- Time thread spends executing work directly | |
| // within a #pragma omp parallel | |
| // OMP_parallel_overhead -- Time thread spends setting up a parallel region | |
| // OMP_loop_static -- Time thread spends executing loop iterations from | |
| // a statically scheduled loop | |
| // OMP_loop_static_scheduling -- Time thread spends scheduling loop iterations | |
| // from a statically scheduled loop | |
| // OMP_loop_dynamic -- Time thread spends executing loop iterations from | |
| // a dynamically scheduled loop | |
| // OMP_loop_dynamic_scheduling -- Time thread spends scheduling loop iterations | |
| // from a dynamically scheduled loop | |
| // OMP_critical -- Time thread spends executing critical section | |
| // OMP_critical_wait -- Time thread spends waiting to enter | |
| // a critical section | |
| // OMP_single -- Time spent executing a "single" region | |
| // OMP_master -- Time spent executing a "master" region | |
| // OMP_masked -- Time spent executing a "masked" region | |
| // OMP_task_immediate -- Time spent executing non-deferred tasks | |
| // OMP_task_taskwait -- Time spent executing tasks inside a taskwait | |
| // construct | |
| // OMP_task_taskyield -- Time spent executing tasks inside a taskyield | |
| // construct | |
| // OMP_task_taskgroup -- Time spent executing tasks inside a taskygroup | |
| // construct | |
| // OMP_task_join_bar -- Time spent executing tasks inside a join barrier | |
| // OMP_task_plain_bar -- Time spent executing tasks inside a barrier | |
| // construct | |
| // OMP_taskloop_scheduling -- Time spent scheduling tasks inside a taskloop | |
| // construct | |
| // OMP_plain_barrier -- Time spent in a #pragma omp barrier construct or | |
| // inside implicit barrier at end of worksharing | |
| // construct | |
| // OMP_idle -- Time worker threads spend waiting for next | |
| // parallel region | |
| // OMP_fork_barrier -- Time spent in a the fork barrier surrounding a | |
| // parallel region | |
| // OMP_join_barrier -- Time spent in a the join barrier surrounding a | |
| // parallel region | |
| // OMP_serial -- Time thread zero spends executing serial code | |
| // OMP_set_numthreads -- Values passed to omp_set_num_threads | |
| // OMP_PARALLEL_args -- Number of arguments passed to a parallel region | |
| // OMP_loop_static_iterations -- Number of iterations thread is assigned for | |
| // statically scheduled loops | |
| // OMP_loop_dynamic_iterations -- Number of iterations thread is assigned for | |
| // dynamically scheduled loops | |
| #if (KMP_DEVELOPER_STATS) | |
| // Timers which are of interest to runtime library developers, not end users. | |
| // These have to be explicitly enabled in addition to the other stats. | |
| // KMP_fork_barrier -- time in __kmp_fork_barrier | |
| // KMP_join_barrier -- time in __kmp_join_barrier | |
| // KMP_barrier -- time in __kmp_barrier | |
| // KMP_end_split_barrier -- time in __kmp_end_split_barrier | |
| // KMP_setup_icv_copy -- time in __kmp_setup_icv_copy | |
| // KMP_icv_copy -- start/stop timer for any ICV copying | |
| // KMP_linear_gather -- time in __kmp_linear_barrier_gather | |
| // KMP_linear_release -- time in __kmp_linear_barrier_release | |
| // KMP_tree_gather -- time in __kmp_tree_barrier_gather | |
| // KMP_tree_release -- time in __kmp_tree_barrier_release | |
| // KMP_hyper_gather -- time in __kmp_hyper_barrier_gather | |
| // KMP_hyper_release -- time in __kmp_hyper_barrier_release | |
| // KMP_dist_gather -- time in __kmp_dist_barrier_gather | |
| // KMP_dist_release -- time in __kmp_dist_barrier_release | |
| // clang-format off | |
| #define KMP_FOREACH_DEVELOPER_TIMER(macro, arg) \ | |
| macro(KMP_fork_call, 0, arg) \ | |
| macro(KMP_join_call, 0, arg) \ | |
| macro(KMP_end_split_barrier, 0, arg) \ | |
| macro(KMP_hier_gather, 0, arg) \ | |
| macro(KMP_hier_release, 0, arg) \ | |
| macro(KMP_hyper_gather, 0, arg) \ | |
| macro(KMP_hyper_release, 0, arg) \ | |
| macro(KMP_dist_gather, 0, arg) \ | |
| macro(KMP_dist_release, 0, arg) \ | |
| macro(KMP_linear_gather, 0, arg) \ | |
| macro(KMP_linear_release, 0, arg) \ | |
| macro(KMP_tree_gather, 0, arg) \ | |
| macro(KMP_tree_release, 0, arg) \ | |
| macro(USER_resume, 0, arg) \ | |
| macro(USER_suspend, 0, arg) \ | |
| macro(USER_mwait, 0, arg) \ | |
| macro(KMP_allocate_team, 0, arg) \ | |
| macro(KMP_setup_icv_copy, 0, arg) \ | |
| macro(USER_icv_copy, 0, arg) \ | |
| macro (FOR_static_steal_stolen, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) \ | |
| macro (FOR_static_steal_chunks, \ | |
| stats_flags_e::noUnits | stats_flags_e::noTotal, arg) | |
| #else | |
| #define KMP_FOREACH_DEVELOPER_TIMER(macro, arg) | |
| #endif | |
| // clang-format on | |
| /*! | |
| * \brief Add new explicit timers under KMP_FOREACH_EXPLICIT_TIMER() macro. | |
| * | |
| * @param macro a user defined macro that takes three arguments - | |
| * macro(TIMER_NAME, flags, arg) | |
| * @param arg a user defined argument to send to the user defined macro | |
| * | |
| * \warning YOU MUST HAVE THE SAME NAMED TIMER UNDER KMP_FOREACH_TIMER() OR ELSE | |
| * BAD THINGS WILL HAPPEN! | |
| * | |
| * \details Explicit timers are ones where we need to allocate a timer itself | |
| * (as well as the accumulated timing statistics). We allocate these on a | |
| * per-thread basis, and explicitly start and stop them. Block timers just | |
| * allocate the timer itself on the stack, and use the destructor to notice | |
| * block exit; they don't need to be defined here. The name here should be the | |
| * same as that of a timer above. | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| #define KMP_FOREACH_EXPLICIT_TIMER(macro, arg) KMP_FOREACH_TIMER(macro, arg) | |
| #define ENUMERATE(name, ignore, prefix) prefix##name, | |
| enum timer_e { KMP_FOREACH_TIMER(ENUMERATE, TIMER_) TIMER_LAST }; | |
| enum explicit_timer_e { | |
| KMP_FOREACH_EXPLICIT_TIMER(ENUMERATE, EXPLICIT_TIMER_) EXPLICIT_TIMER_LAST | |
| }; | |
| enum counter_e { KMP_FOREACH_COUNTER(ENUMERATE, COUNTER_) COUNTER_LAST }; | |
| #undef ENUMERATE | |
| /* | |
| * A logarithmic histogram. It accumulates the number of values in each power of | |
| * ten bin. So 1<=x<10, 10<=x<100, ... | |
| * Mostly useful where we have some big outliers and want to see information | |
| * about them. | |
| */ | |
| class logHistogram { | |
| enum { | |
| numBins = 31, /* Number of powers of 10. If this changes you need to change | |
| * the initializer for binMax */ | |
| /* | |
| * If you want to use this to analyse values that may be less than 1, (for | |
| * instance times in s), then the logOffset gives you negative powers. | |
| * In our case here, we're just looking at times in ticks, or counts, so we | |
| * can never see values with magnitude < 1 (other than zero), so we can set | |
| * it to 0. As above change the initializer if you change this. | |
| */ | |
| logOffset = 0 | |
| }; | |
| uint32_t KMP_ALIGN_CACHE zeroCount; | |
| struct { | |
| uint32_t count; | |
| double total; | |
| } bins[numBins]; | |
| static double binMax[numBins]; | |
| #ifdef KMP_DEBUG | |
| uint64_t _total; | |
| void check() const { | |
| uint64_t t = zeroCount; | |
| for (int i = 0; i < numBins; i++) | |
| t += bins[i].count; | |
| KMP_DEBUG_ASSERT(t == _total); | |
| } | |
| #else | |
| void check() const {} | |
| #endif | |
| public: | |
| logHistogram() { reset(); } | |
| logHistogram(logHistogram const &o) { | |
| for (int i = 0; i < numBins; i++) | |
| bins[i] = o.bins[i]; | |
| #ifdef KMP_DEBUG | |
| _total = o._total; | |
| #endif | |
| } | |
| void reset() { | |
| zeroCount = 0; | |
| for (int i = 0; i < numBins; i++) { | |
| bins[i].count = 0; | |
| bins[i].total = 0; | |
| } | |
| #ifdef KMP_DEBUG | |
| _total = 0; | |
| #endif | |
| } | |
| uint32_t count(int b) const { return bins[b + logOffset].count; } | |
| double total(int b) const { return bins[b + logOffset].total; } | |
| static uint32_t findBin(double sample); | |
| logHistogram &operator+=(logHistogram const &o) { | |
| zeroCount += o.zeroCount; | |
| for (int i = 0; i < numBins; i++) { | |
| bins[i].count += o.bins[i].count; | |
| bins[i].total += o.bins[i].total; | |
| } | |
| #ifdef KMP_DEBUG | |
| _total += o._total; | |
| check(); | |
| #endif | |
| return *this; | |
| } | |
| void addSample(double sample); | |
| int minBin() const; | |
| int maxBin() const; | |
| std::string format(char) const; | |
| }; | |
| class statistic { | |
| double KMP_ALIGN_CACHE minVal; | |
| double maxVal; | |
| double meanVal; | |
| double m2; | |
| uint64_t sampleCount; | |
| double offset; | |
| bool collectingHist; | |
| logHistogram hist; | |
| public: | |
| statistic(bool doHist = bool(KMP_STATS_HIST)) { | |
| reset(); | |
| collectingHist = doHist; | |
| } | |
| statistic(statistic const &o) | |
| : minVal(o.minVal), maxVal(o.maxVal), meanVal(o.meanVal), m2(o.m2), | |
| sampleCount(o.sampleCount), offset(o.offset), | |
| collectingHist(o.collectingHist), hist(o.hist) {} | |
| statistic(double minv, double maxv, double meanv, uint64_t sc, double sd) | |
| : minVal(minv), maxVal(maxv), meanVal(meanv), m2(sd * sd * sc), | |
| sampleCount(sc), offset(0.0), collectingHist(false) {} | |
| bool haveHist() const { return collectingHist; } | |
| double getMin() const { return minVal; } | |
| double getMean() const { return meanVal; } | |
| double getMax() const { return maxVal; } | |
| uint64_t getCount() const { return sampleCount; } | |
| double getSD() const { return sqrt(m2 / sampleCount); } | |
| double getTotal() const { return sampleCount * meanVal; } | |
| logHistogram const *getHist() const { return &hist; } | |
| void setOffset(double d) { offset = d; } | |
| void reset() { | |
| minVal = (std::numeric_limits<double>::max)(); | |
| maxVal = -minVal; | |
| meanVal = 0.0; | |
| m2 = 0.0; | |
| sampleCount = 0; | |
| offset = 0.0; | |
| hist.reset(); | |
| } | |
| void addSample(double sample); | |
| void scale(double factor); | |
| void scaleDown(double f) { scale(1. / f); } | |
| void forceCount(uint64_t count) { sampleCount = count; } | |
| statistic &operator+=(statistic const &other); | |
| std::string format(char unit, bool total = false) const; | |
| std::string formatHist(char unit) const { return hist.format(unit); } | |
| }; | |
| struct statInfo { | |
| const char *name; | |
| uint32_t flags; | |
| }; | |
| class timeStat : public statistic { | |
| static statInfo timerInfo[]; | |
| public: | |
| timeStat() : statistic() {} | |
| static const char *name(timer_e e) { return timerInfo[e].name; } | |
| static bool noTotal(timer_e e) { | |
| return timerInfo[e].flags & stats_flags_e::noTotal; | |
| } | |
| static bool masterOnly(timer_e e) { | |
| return timerInfo[e].flags & stats_flags_e::onlyInMaster; | |
| } | |
| static bool workerOnly(timer_e e) { | |
| return timerInfo[e].flags & stats_flags_e::notInMaster; | |
| } | |
| static bool noUnits(timer_e e) { | |
| return timerInfo[e].flags & stats_flags_e::noUnits; | |
| } | |
| static bool logEvent(timer_e e) { | |
| return timerInfo[e].flags & stats_flags_e::logEvent; | |
| } | |
| static void clearEventFlags() { | |
| for (int i = 0; i < TIMER_LAST; i++) { | |
| timerInfo[i].flags &= (~(stats_flags_e::logEvent)); | |
| } | |
| } | |
| }; | |
| // Where we need explicitly to start and end the timer, this version can be used | |
| // Since these timers normally aren't nicely scoped, so don't have a good place | |
| // to live on the stack of the thread, they're more work to use. | |
| class explicitTimer { | |
| timeStat *stat; | |
| timer_e timerEnumValue; | |
| tsc_tick_count startTime; | |
| tsc_tick_count pauseStartTime; | |
| tsc_tick_count::tsc_interval_t totalPauseTime; | |
| public: | |
| explicitTimer(timeStat *s, timer_e te) | |
| : stat(s), timerEnumValue(te), startTime(), pauseStartTime(0), | |
| totalPauseTime() {} | |
| // void setStat(timeStat *s) { stat = s; } | |
| void start(tsc_tick_count tick); | |
| void pause(tsc_tick_count tick) { pauseStartTime = tick; } | |
| void resume(tsc_tick_count tick) { | |
| totalPauseTime += (tick - pauseStartTime); | |
| } | |
| void stop(tsc_tick_count tick, kmp_stats_list *stats_ptr = nullptr); | |
| void reset() { | |
| startTime = 0; | |
| pauseStartTime = 0; | |
| totalPauseTime = 0; | |
| } | |
| timer_e get_type() const { return timerEnumValue; } | |
| }; | |
| // Where you need to partition a threads clock ticks into separate states | |
| // e.g., a partitionedTimers class with two timers of EXECUTING_TASK, and | |
| // DOING_NOTHING would render these conditions: | |
| // time(EXECUTING_TASK) + time(DOING_NOTHING) = total time thread is alive | |
| // No clock tick in the EXECUTING_TASK is a member of DOING_NOTHING and vice | |
| // versa | |
| class partitionedTimers { | |
| private: | |
| std::vector<explicitTimer> timer_stack; | |
| public: | |
| partitionedTimers(); | |
| void init(explicitTimer timer); | |
| void exchange(explicitTimer timer); | |
| void push(explicitTimer timer); | |
| void pop(); | |
| void windup(); | |
| }; | |
| // Special wrapper around the partitioned timers to aid timing code blocks | |
| // It avoids the need to have an explicit end, leaving the scope suffices. | |
| class blockPartitionedTimer { | |
| partitionedTimers *part_timers; | |
| public: | |
| blockPartitionedTimer(partitionedTimers *pt, explicitTimer timer) | |
| : part_timers(pt) { | |
| part_timers->push(timer); | |
| } | |
| ~blockPartitionedTimer() { part_timers->pop(); } | |
| }; | |
| // Special wrapper around the thread state to aid in keeping state in code | |
| // blocks It avoids the need to have an explicit end, leaving the scope | |
| // suffices. | |
| class blockThreadState { | |
| stats_state_e *state_pointer; | |
| stats_state_e old_state; | |
| public: | |
| blockThreadState(stats_state_e *thread_state_pointer, stats_state_e new_state) | |
| : state_pointer(thread_state_pointer), old_state(*thread_state_pointer) { | |
| *state_pointer = new_state; | |
| } | |
| ~blockThreadState() { *state_pointer = old_state; } | |
| }; | |
| // If all you want is a count, then you can use this... | |
| // The individual per-thread counts will be aggregated into a statistic at | |
| // program exit. | |
| class counter { | |
| uint64_t value; | |
| static const statInfo counterInfo[]; | |
| public: | |
| counter() : value(0) {} | |
| void increment() { value++; } | |
| uint64_t getValue() const { return value; } | |
| void reset() { value = 0; } | |
| static const char *name(counter_e e) { return counterInfo[e].name; } | |
| static bool masterOnly(counter_e e) { | |
| return counterInfo[e].flags & stats_flags_e::onlyInMaster; | |
| } | |
| }; | |
| /* **************************************************************** | |
| Class to implement an event | |
| There are four components to an event: start time, stop time | |
| nest_level, and timer_name. | |
| The start and stop time should be obvious (recorded in clock ticks). | |
| The nest_level relates to the bar width in the timeline graph. | |
| The timer_name is used to determine which timer event triggered this event. | |
| the interface to this class is through four read-only operations: | |
| 1) getStart() -- returns the start time as 64 bit integer | |
| 2) getStop() -- returns the stop time as 64 bit integer | |
| 3) getNestLevel() -- returns the nest level of the event | |
| 4) getTimerName() -- returns the timer name that triggered event | |
| *MORE ON NEST_LEVEL* | |
| The nest level is used in the bar graph that represents the timeline. | |
| Its main purpose is for showing how events are nested inside each other. | |
| For example, say events, A, B, and C are recorded. If the timeline | |
| looks like this: | |
| Begin -------------------------------------------------------------> Time | |
| | | | | | | | |
| A B C C B A | |
| start start start end end end | |
| Then A, B, C will have a nest level of 1, 2, 3 respectively. | |
| These values are then used to calculate the barwidth so you can | |
| see that inside A, B has occurred, and inside B, C has occurred. | |
| Currently, this is shown with A's bar width being larger than B's | |
| bar width, and B's bar width being larger than C's bar width. | |
| **************************************************************** */ | |
| class kmp_stats_event { | |
| uint64_t start; | |
| uint64_t stop; | |
| int nest_level; | |
| timer_e timer_name; | |
| public: | |
| kmp_stats_event() | |
| : start(0), stop(0), nest_level(0), timer_name(TIMER_LAST) {} | |
| kmp_stats_event(uint64_t strt, uint64_t stp, int nst, timer_e nme) | |
| : start(strt), stop(stp), nest_level(nst), timer_name(nme) {} | |
| inline uint64_t getStart() const { return start; } | |
| inline uint64_t getStop() const { return stop; } | |
| inline int getNestLevel() const { return nest_level; } | |
| inline timer_e getTimerName() const { return timer_name; } | |
| }; | |
| /* **************************************************************** | |
| Class to implement a dynamically expandable array of events | |
| --------------------------------------------------------- | |
| | event 1 | event 2 | event 3 | event 4 | ... | event N | | |
| --------------------------------------------------------- | |
| An event is pushed onto the back of this array at every | |
| explicitTimer->stop() call. The event records the thread #, | |
| start time, stop time, and nest level related to the bar width. | |
| The event vector starts at size INIT_SIZE and grows (doubles in size) | |
| if needed. An implication of this behavior is that log(N) | |
| reallocations are needed (where N is number of events). If you want | |
| to avoid reallocations, then set INIT_SIZE to a large value. | |
| the interface to this class is through six operations: | |
| 1) reset() -- sets the internal_size back to 0 but does not deallocate any | |
| memory | |
| 2) size() -- returns the number of valid elements in the vector | |
| 3) push_back(start, stop, nest, timer_name) -- pushes an event onto | |
| the back of the array | |
| 4) deallocate() -- frees all memory associated with the vector | |
| 5) sort() -- sorts the vector by start time | |
| 6) operator[index] or at(index) -- returns event reference at that index | |
| **************************************************************** */ | |
| class kmp_stats_event_vector { | |
| kmp_stats_event *events; | |
| int internal_size; | |
| int allocated_size; | |
| static const int INIT_SIZE = 1024; | |
| public: | |
| kmp_stats_event_vector() { | |
| events = | |
| (kmp_stats_event *)__kmp_allocate(sizeof(kmp_stats_event) * INIT_SIZE); | |
| internal_size = 0; | |
| allocated_size = INIT_SIZE; | |
| } | |
| ~kmp_stats_event_vector() {} | |
| inline void reset() { internal_size = 0; } | |
| inline int size() const { return internal_size; } | |
| void push_back(uint64_t start_time, uint64_t stop_time, int nest_level, | |
| timer_e name) { | |
| int i; | |
| if (internal_size == allocated_size) { | |
| kmp_stats_event *tmp = (kmp_stats_event *)__kmp_allocate( | |
| sizeof(kmp_stats_event) * allocated_size * 2); | |
| for (i = 0; i < internal_size; i++) | |
| tmp[i] = events[i]; | |
| __kmp_free(events); | |
| events = tmp; | |
| allocated_size *= 2; | |
| } | |
| events[internal_size] = | |
| kmp_stats_event(start_time, stop_time, nest_level, name); | |
| internal_size++; | |
| return; | |
| } | |
| void deallocate(); | |
| void sort(); | |
| const kmp_stats_event &operator[](int index) const { return events[index]; } | |
| kmp_stats_event &operator[](int index) { return events[index]; } | |
| const kmp_stats_event &at(int index) const { return events[index]; } | |
| kmp_stats_event &at(int index) { return events[index]; } | |
| }; | |
| /* **************************************************************** | |
| Class to implement a doubly-linked, circular, statistics list | |
| |---| ---> |---| ---> |---| ---> |---| ---> ... next | |
| | | | | | | | | | |
| |---| <--- |---| <--- |---| <--- |---| <--- ... prev | |
| Sentinel first second third | |
| Node node node node | |
| The Sentinel Node is the user handle on the list. | |
| The first node corresponds to thread 0's statistics. | |
| The second node corresponds to thread 1's statistics and so on... | |
| Each node has a _timers, _counters, and _explicitTimers array to hold that | |
| thread's statistics. The _explicitTimers point to the correct _timer and | |
| update its statistics at every stop() call. The explicitTimers' pointers are | |
| set up in the constructor. Each node also has an event vector to hold that | |
| thread's timing events. The event vector expands as necessary and records | |
| the start-stop times for each timer. | |
| The nestLevel variable is for plotting events and is related | |
| to the bar width in the timeline graph. | |
| Every thread will have a thread local pointer to its node in | |
| the list. The sentinel node is used by the primary thread to | |
| store "dummy" statistics before __kmp_create_worker() is called. | |
| **************************************************************** */ | |
| class kmp_stats_list { | |
| int gtid; | |
| timeStat _timers[TIMER_LAST + 1]; | |
| counter _counters[COUNTER_LAST + 1]; | |
| explicitTimer thread_life_timer; | |
| partitionedTimers _partitionedTimers; | |
| int _nestLevel; // one per thread | |
| kmp_stats_event_vector _event_vector; | |
| kmp_stats_list *next; | |
| kmp_stats_list *prev; | |
| stats_state_e state; | |
| int thread_is_idle_flag; | |
| public: | |
| kmp_stats_list() | |
| : thread_life_timer(&_timers[TIMER_OMP_worker_thread_life], | |
| TIMER_OMP_worker_thread_life), | |
| _nestLevel(0), _event_vector(), next(this), prev(this), state(IDLE), | |
| thread_is_idle_flag(0) {} | |
| ~kmp_stats_list() {} | |
| inline timeStat *getTimer(timer_e idx) { return &_timers[idx]; } | |
| inline counter *getCounter(counter_e idx) { return &_counters[idx]; } | |
| inline partitionedTimers *getPartitionedTimers() { | |
| return &_partitionedTimers; | |
| } | |
| inline timeStat *getTimers() { return _timers; } | |
| inline counter *getCounters() { return _counters; } | |
| inline kmp_stats_event_vector &getEventVector() { return _event_vector; } | |
| inline void startLife() { thread_life_timer.start(tsc_tick_count::now()); } | |
| inline void endLife() { thread_life_timer.stop(tsc_tick_count::now(), this); } | |
| inline void resetEventVector() { _event_vector.reset(); } | |
| inline void incrementNestValue() { _nestLevel++; } | |
| inline int getNestValue() { return _nestLevel; } | |
| inline void decrementNestValue() { _nestLevel--; } | |
| inline int getGtid() const { return gtid; } | |
| inline void setGtid(int newgtid) { gtid = newgtid; } | |
| inline void setState(stats_state_e newstate) { state = newstate; } | |
| inline stats_state_e getState() const { return state; } | |
| inline stats_state_e *getStatePointer() { return &state; } | |
| inline bool isIdle() { return thread_is_idle_flag == 1; } | |
| inline void setIdleFlag() { thread_is_idle_flag = 1; } | |
| inline void resetIdleFlag() { thread_is_idle_flag = 0; } | |
| kmp_stats_list *push_back(int gtid); // returns newly created list node | |
| inline void push_event(uint64_t start_time, uint64_t stop_time, | |
| int nest_level, timer_e name) { | |
| _event_vector.push_back(start_time, stop_time, nest_level, name); | |
| } | |
| void deallocate(); | |
| class iterator; | |
| kmp_stats_list::iterator begin(); | |
| kmp_stats_list::iterator end(); | |
| int size(); | |
| class iterator { | |
| kmp_stats_list *ptr; | |
| friend kmp_stats_list::iterator kmp_stats_list::begin(); | |
| friend kmp_stats_list::iterator kmp_stats_list::end(); | |
| public: | |
| iterator(); | |
| ~iterator(); | |
| iterator operator++(); | |
| iterator operator++(int dummy); | |
| iterator operator--(); | |
| iterator operator--(int dummy); | |
| bool operator!=(const iterator &rhs); | |
| bool operator==(const iterator &rhs); | |
| kmp_stats_list *operator*() const; // dereference operator | |
| }; | |
| }; | |
| /* **************************************************************** | |
| Class to encapsulate all output functions and the environment variables | |
| This module holds filenames for various outputs (normal stats, events, plot | |
| file), as well as coloring information for the plot file. | |
| The filenames and flags variables are read from environment variables. | |
| These are read once by the constructor of the global variable | |
| __kmp_stats_output which calls init(). | |
| During this init() call, event flags for the timeStat::timerInfo[] global | |
| array are cleared if KMP_STATS_EVENTS is not true (on, 1, yes). | |
| The only interface function that is public is outputStats(heading). This | |
| function should print out everything it needs to, either to files or stderr, | |
| depending on the environment variables described below | |
| ENVIRONMENT VARIABLES: | |
| KMP_STATS_FILE -- if set, all statistics (not events) will be printed to this | |
| file, otherwise, print to stderr | |
| KMP_STATS_THREADS -- if set to "on", then will print per thread statistics to | |
| either KMP_STATS_FILE or stderr | |
| KMP_STATS_PLOT_FILE -- if set, print the ploticus plot file to this filename, | |
| otherwise, the plot file is sent to "events.plt" | |
| KMP_STATS_EVENTS -- if set to "on", then log events, otherwise, don't log | |
| events | |
| KMP_STATS_EVENTS_FILE -- if set, all events are outputted to this file, | |
| otherwise, output is sent to "events.dat" | |
| **************************************************************** */ | |
| class kmp_stats_output_module { | |
| public: | |
| struct rgb_color { | |
| float r; | |
| float g; | |
| float b; | |
| }; | |
| private: | |
| std::string outputFileName; | |
| static const char *eventsFileName; | |
| static const char *plotFileName; | |
| static int printPerThreadFlag; | |
| static int printPerThreadEventsFlag; | |
| static const rgb_color globalColorArray[]; | |
| static rgb_color timerColorInfo[]; | |
| void init(); | |
| static void setupEventColors(); | |
| static void printPloticusFile(); | |
| static void printHeaderInfo(FILE *statsOut); | |
| static void printTimerStats(FILE *statsOut, statistic const *theStats, | |
| statistic const *totalStats); | |
| static void printCounterStats(FILE *statsOut, statistic const *theStats); | |
| static void printCounters(FILE *statsOut, counter const *theCounters); | |
| static void printEvents(FILE *eventsOut, kmp_stats_event_vector *theEvents, | |
| int gtid); | |
| static rgb_color getEventColor(timer_e e) { return timerColorInfo[e]; } | |
| static void windupExplicitTimers(); | |
| bool eventPrintingEnabled() const { return printPerThreadEventsFlag; } | |
| public: | |
| kmp_stats_output_module() { init(); } | |
| void outputStats(const char *heading); | |
| }; | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| void __kmp_stats_init(); | |
| void __kmp_stats_fini(); | |
| void __kmp_reset_stats(); | |
| void __kmp_output_stats(const char *); | |
| void __kmp_accumulate_stats_at_exit(void); | |
| // thread local pointer to stats node within list | |
| extern KMP_THREAD_LOCAL kmp_stats_list *__kmp_stats_thread_ptr; | |
| // head to stats list. | |
| extern kmp_stats_list *__kmp_stats_list; | |
| // lock for __kmp_stats_list | |
| extern kmp_tas_lock_t __kmp_stats_lock; | |
| // reference start time | |
| extern tsc_tick_count __kmp_stats_start_time; | |
| // interface to output | |
| extern kmp_stats_output_module __kmp_stats_output; | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| // Simple, standard interfaces that drop out completely if stats aren't enabled | |
| /*! | |
| * \brief Adds value to specified timer (name). | |
| * | |
| * @param name timer name as specified under the KMP_FOREACH_TIMER() macro | |
| * @param value double precision sample value to add to statistics for the timer | |
| * | |
| * \details Use KMP_COUNT_VALUE(name, value) macro to add a particular value to | |
| * a timer statistics. | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| #define KMP_COUNT_VALUE(name, value) \ | |
| __kmp_stats_thread_ptr->getTimer(TIMER_##name)->addSample((double)value) | |
| /*! | |
| * \brief Increments specified counter (name). | |
| * | |
| * @param name counter name as specified under the KMP_FOREACH_COUNTER() macro | |
| * | |
| * \details Use KMP_COUNT_BLOCK(name, value) macro to increment a statistics | |
| * counter for the executing thread. | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| #define KMP_COUNT_BLOCK(name) \ | |
| __kmp_stats_thread_ptr->getCounter(COUNTER_##name)->increment() | |
| /*! | |
| * \brief Outputs the current thread statistics and reset them. | |
| * | |
| * @param heading_string heading put above the final stats output | |
| * | |
| * \details Explicitly stops all timers and outputs all stats. Environment | |
| * variable, `OMPTB_STATSFILE=filename`, can be used to output the stats to a | |
| * filename instead of stderr. Environment variable, | |
| * `OMPTB_STATSTHREADS=true|undefined`, can be used to output thread specific | |
| * stats. For now the `OMPTB_STATSTHREADS` environment variable can either be | |
| * defined with any value, which will print out thread specific stats, or it can | |
| * be undefined (not specified in the environment) and thread specific stats | |
| * won't be printed. It should be noted that all statistics are reset when this | |
| * macro is called. | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| #define KMP_OUTPUT_STATS(heading_string) __kmp_output_stats(heading_string) | |
| /*! | |
| * \brief Initializes the partitioned timers to begin with name. | |
| * | |
| * @param name timer which you want this thread to begin with | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| #define KMP_INIT_PARTITIONED_TIMERS(name) \ | |
| __kmp_stats_thread_ptr->getPartitionedTimers()->init(explicitTimer( \ | |
| __kmp_stats_thread_ptr->getTimer(TIMER_##name), TIMER_##name)) | |
| #define KMP_TIME_PARTITIONED_BLOCK(name) \ | |
| blockPartitionedTimer __PBLOCKTIME__( \ | |
| __kmp_stats_thread_ptr->getPartitionedTimers(), \ | |
| explicitTimer(__kmp_stats_thread_ptr->getTimer(TIMER_##name), \ | |
| TIMER_##name)) | |
| #define KMP_PUSH_PARTITIONED_TIMER(name) \ | |
| __kmp_stats_thread_ptr->getPartitionedTimers()->push(explicitTimer( \ | |
| __kmp_stats_thread_ptr->getTimer(TIMER_##name), TIMER_##name)) | |
| #define KMP_POP_PARTITIONED_TIMER() \ | |
| __kmp_stats_thread_ptr->getPartitionedTimers()->pop() | |
| #define KMP_EXCHANGE_PARTITIONED_TIMER(name) \ | |
| __kmp_stats_thread_ptr->getPartitionedTimers()->exchange(explicitTimer( \ | |
| __kmp_stats_thread_ptr->getTimer(TIMER_##name), TIMER_##name)) | |
| #define KMP_SET_THREAD_STATE(state_name) \ | |
| __kmp_stats_thread_ptr->setState(state_name) | |
| #define KMP_GET_THREAD_STATE() __kmp_stats_thread_ptr->getState() | |
| #define KMP_SET_THREAD_STATE_BLOCK(state_name) \ | |
| blockThreadState __BTHREADSTATE__(__kmp_stats_thread_ptr->getStatePointer(), \ | |
| state_name) | |
| /*! | |
| * \brief resets all stats (counters to 0, timers to 0 elapsed ticks) | |
| * | |
| * \details Reset all stats for all threads. | |
| * | |
| * @ingroup STATS_GATHERING | |
| */ | |
| #define KMP_RESET_STATS() __kmp_reset_stats() | |
| #if (KMP_DEVELOPER_STATS) | |
| #define KMP_COUNT_DEVELOPER_VALUE(n, v) KMP_COUNT_VALUE(n, v) | |
| #define KMP_COUNT_DEVELOPER_BLOCK(n) KMP_COUNT_BLOCK(n) | |
| #define KMP_TIME_DEVELOPER_PARTITIONED_BLOCK(n) KMP_TIME_PARTITIONED_BLOCK(n) | |
| #define KMP_PUSH_DEVELOPER_PARTITIONED_TIMER(n) KMP_PUSH_PARTITIONED_TIMER(n) | |
| #define KMP_POP_DEVELOPER_PARTITIONED_TIMER(n) KMP_POP_PARTITIONED_TIMER(n) | |
| #define KMP_EXCHANGE_DEVELOPER_PARTITIONED_TIMER(n) \ | |
| KMP_EXCHANGE_PARTITIONED_TIMER(n) | |
| #else | |
| // Null definitions | |
| #define KMP_COUNT_DEVELOPER_VALUE(n, v) ((void)0) | |
| #define KMP_COUNT_DEVELOPER_BLOCK(n) ((void)0) | |
| #define KMP_TIME_DEVELOPER_PARTITIONED_BLOCK(n) ((void)0) | |
| #define KMP_PUSH_DEVELOPER_PARTITIONED_TIMER(n) ((void)0) | |
| #define KMP_POP_DEVELOPER_PARTITIONED_TIMER(n) ((void)0) | |
| #define KMP_EXCHANGE_DEVELOPER_PARTITIONED_TIMER(n) ((void)0) | |
| #endif | |
| #else // KMP_STATS_ENABLED | |
| // Null definitions | |
| #define KMP_COUNT_VALUE(n, v) ((void)0) | |
| #define KMP_COUNT_BLOCK(n) ((void)0) | |
| #define KMP_OUTPUT_STATS(heading_string) ((void)0) | |
| #define KMP_RESET_STATS() ((void)0) | |
| #define KMP_COUNT_DEVELOPER_VALUE(n, v) ((void)0) | |
| #define KMP_COUNT_DEVELOPER_BLOCK(n) ((void)0) | |
| #define KMP_TIME_DEVELOPER_PARTITIONED_BLOCK(n) ((void)0) | |
| #define KMP_PUSH_DEVELOPER_PARTITIONED_TIMER(n) ((void)0) | |
| #define KMP_POP_DEVELOPER_PARTITIONED_TIMER(n) ((void)0) | |
| #define KMP_EXCHANGE_DEVELOPER_PARTITIONED_TIMER(n) ((void)0) | |
| #define KMP_INIT_PARTITIONED_TIMERS(name) ((void)0) | |
| #define KMP_TIME_PARTITIONED_BLOCK(name) ((void)0) | |
| #define KMP_PUSH_PARTITIONED_TIMER(name) ((void)0) | |
| #define KMP_POP_PARTITIONED_TIMER() ((void)0) | |
| #define KMP_SET_THREAD_STATE(state_name) ((void)0) | |
| #define KMP_GET_THREAD_STATE() ((void)0) | |
| #define KMP_SET_THREAD_STATE_BLOCK(state_name) ((void)0) | |
| #endif // KMP_STATS_ENABLED | |
| #endif // KMP_STATS_H |