1 /** 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 #ifndef PANDA_RUNTIME_COROUTINES_COROUTINE_STATS_H 16 #define PANDA_RUNTIME_COROUTINES_COROUTINE_STATS_H 17 18 #include "runtime/include/histogram-inl.h" 19 #include "runtime/include/mem/panda_containers.h" 20 #include "runtime/coroutines/utils.h" 21 #include <array> 22 23 namespace ark { 24 25 template <class T> 26 constexpr size_t COROUTINE_STATS_ENUM_SIZE = static_cast<size_t>(T::LAST_ID); 27 28 /* 29 Metrics we are potentially interested in (in terms of both time and memory): 30 - initialization and finalization overhead of the coroutine manager (coros, workers, system coros, sch, etc.) 31 - coroutine (+ctx) instance creation, deletion, existance, including stacks 32 - scheduler: locks, queues management and traversal, migration, stealing, etc. 33 - scheduler: ctx switch separately 34 - synchronization: await, locks in promises/events 35 - data sharing 36 - various js emulation code: JS promises support, event loop, etc. 37 */ 38 39 /// Coroutine time statistic counter types 40 enum class CoroutineTimeStats { 41 INIT, ///< coroutine manager initialization, including workers creation 42 LAUNCH, ///< launching a single corotutine 43 SCH_ALL, ///< cumulative scheduler overhead 44 // NOTE(konstanting, #IAD5MH): need a separate event for CTX_SWITCH count for payload coros? 45 CTX_SWITCH, ///< context switch, separately 46 LOCKS, ///< time spent locking and unlocking mutexes (precision is questionable...) 47 48 LAST_ID ///< should be the last one 49 }; 50 51 /// Coroutine memory statistic counter types 52 enum class CoroutineMemStats { 53 INSTANCE_SIZE, ///< coroutine instance size 54 55 LAST_ID ///< should be the last one 56 }; 57 58 /** 59 * @brief The basic functionality of coroutine statistic counters implementation. 60 * 61 * Contains the basic profiling tools functionality: 62 * - measuring instant and scoped metrics 63 * - accessing the measured values and their aggregates 64 * - enable/disable controls for the statistics engine 65 * - obtaining the information about metrics 66 */ 67 class CoroutineStatsBase { 68 public: 69 NO_COPY_SEMANTIC(CoroutineStatsBase); 70 DEFAULT_MOVE_SEMANTIC(CoroutineStatsBase); 71 72 /* supplementary types */ 73 /// possible aggregate types for the metrics 74 enum class AggregateType { MAX, MIN, AVG, COUNT, SUM, LAST_ID }; 75 /// a single metric description: name, type and suggested meaningful aggregates 76 struct MetricDescription { 77 const char *prettyName; 78 std::vector<AggregateType> neededAggregates; 79 /// means that this metric should be measured on per-worker basis 80 bool perWorker; 81 }; 82 /// time metric value: expected to be in nanoseconds 83 using TimeMetricVal = uint64_t; 84 static constexpr TimeMetricVal INVALID_TIME_METRIC_VAL = std::numeric_limits<TimeMetricVal>::max(); 85 /// memory metric value: expected to be in bytes 86 using MemMetricVal = uint64_t; 87 using TimeStatsDescriptionMap = std::map<CoroutineTimeStats, MetricDescription>; 88 using MemStatsDescriptionMap = std::map<CoroutineMemStats, MetricDescription>; 89 90 CoroutineStatsBase(); 91 virtual ~CoroutineStatsBase() = default; 92 93 /* enable/disable controls */ 94 /// enable the gathering of statistics (until that, all profiling methods will be no-ops) Enable()95 void Enable() 96 { 97 enabled_ = true; 98 } 99 /// disable the gathering of statistics (all profiling methods become no-ops) Disable()100 void Disable() 101 { 102 enabled_ = false; 103 } IsEnabled()104 bool IsEnabled() const 105 { 106 return enabled_; 107 } 108 109 /* profiling controls */ 110 /// Scoped profiling: open an interval for the given metric id 111 void StartInterval(CoroutineTimeStats id); 112 /// Scoped profiling: close an interval for the given metric id 113 void FinishInterval(CoroutineTimeStats id); 114 /// Scoped profiling: check that an interval for the given metric id is active 115 bool IsInInterval(CoroutineTimeStats id) const; 116 /// Instant profiling: record the value for the given time-based metric 117 void RecordTimeStatsValue(CoroutineTimeStats id, TimeMetricVal value); 118 /// Instant profiling: record the value for the given memory-based metric 119 void RecordMemStatsValue(CoroutineMemStats id, MemMetricVal value); 120 121 /* accessors */ 122 const SimpleHistogram<TimeMetricVal> &GetTimeStatValue(CoroutineTimeStats id) const; 123 /// obtain a summary string for the given time metric id 124 PandaString GetTimeStatString(CoroutineTimeStats id) const; 125 const SimpleHistogram<MemMetricVal> &GetMemStatValue(CoroutineMemStats id) const; 126 /// obtain a summary string for the given memory metric id 127 PandaString GetMemStatString(CoroutineMemStats id) const; 128 129 /// @return metrics description 130 static TimeStatsDescriptionMap &GetTimeStatsDescription(); 131 132 private: 133 bool enabled_ = false; 134 std::array<TimeMetricVal, COROUTINE_STATS_ENUM_SIZE<CoroutineTimeStats>> intervalStarts_; 135 std::array<SimpleHistogram<TimeMetricVal>, COROUTINE_STATS_ENUM_SIZE<CoroutineTimeStats>> timeStats_; 136 std::array<SimpleHistogram<MemMetricVal>, COROUTINE_STATS_ENUM_SIZE<CoroutineMemStats>> memStats_; 137 }; 138 139 std::ostream &operator<<(std::ostream &os, CoroutineStatsBase::AggregateType id); 140 141 /** 142 * @brief Per-worker statistics. 143 * 144 * Should be used for gathering per worker metrics only. 145 */ 146 class CoroutineWorkerStats : public CoroutineStatsBase { 147 public: 148 NO_COPY_SEMANTIC(CoroutineWorkerStats); 149 DEFAULT_MOVE_SEMANTIC(CoroutineWorkerStats); 150 CoroutineWorkerStats(PandaString name)151 explicit CoroutineWorkerStats(PandaString name) : workerName_(std::move(name)) {} 152 ~CoroutineWorkerStats() override = default; 153 154 /// @return name of the worker being profiled GetName()155 PandaString GetName() const 156 { 157 return workerName_; 158 } 159 160 private: 161 PandaString workerName_; 162 }; 163 164 /// @brief The helper RAII-style class that facilitates the gathering of scoped time metrics for some arbitrary scope 165 class ScopedCoroutineStats { 166 public: 167 NO_COPY_SEMANTIC(ScopedCoroutineStats); 168 NO_MOVE_SEMANTIC(ScopedCoroutineStats); 169 170 /** 171 * @brief The constructor 172 * 173 * @param stats The statistics instance to use 174 * @param id The time metric id to measure 175 * @param avoidNesting Iff this parameter is set to true, do not count anything in case if the measuring 176 * scope for the given metric is already open. 177 */ 178 explicit ScopedCoroutineStats(CoroutineStatsBase *stats, CoroutineTimeStats id, bool avoidNesting = false) id_(id)179 : id_(id), stats_(stats) 180 { 181 ASSERT(stats != nullptr); 182 if (avoidNesting && stats_->IsInInterval(id)) { 183 doNothing_ = true; 184 return; 185 } 186 stats_->StartInterval(id_); 187 } 188 ~ScopedCoroutineStats()189 ~ScopedCoroutineStats() 190 { 191 if (doNothing_) { 192 return; 193 } 194 stats_->FinishInterval(id_); 195 } 196 197 private: 198 CoroutineTimeStats id_ = CoroutineTimeStats::LAST_ID; 199 CoroutineStatsBase *stats_ = nullptr; 200 bool doNothing_ = false; 201 }; 202 203 /** 204 * @brief Global statistics. 205 * 206 * Should be used for measuring global (not per-worker) concurrency statistics. 207 * Suppors statistics report generation. 208 */ 209 class CoroutineStats : public CoroutineStatsBase { 210 public: 211 NO_COPY_SEMANTIC(CoroutineStats); 212 NO_MOVE_SEMANTIC(CoroutineStats); 213 214 /* supplementary types */ 215 /// all the aggregates for one metric 216 using TimeStatsAggregateArray = std::array<TimeMetricVal, COROUTINE_STATS_ENUM_SIZE<AggregateType>>; 217 /// all the aggregates for all the metrics for all the workers (does not include global metrics) 218 using TimeStatsDataArray = std::array<TimeStatsAggregateArray, COROUTINE_STATS_ENUM_SIZE<CoroutineTimeStats>>; 219 220 CoroutineStats() = default; 221 ~CoroutineStats() override = default; 222 223 /// generate the summary of global metrics 224 PandaString GetBriefStatistics() const; 225 /** 226 * @brief Generate the full statistics report 227 * 228 * This method generates the full report that includes the global metrics, the per-worker metrics and 229 * the summary, which includes the aggregated data from all workers. 230 * 231 * @param workerStats a vector that holds per-worker statistic instance pointers 232 */ 233 PandaString GetFullStatistics(const PandaVector<CoroutineWorkerStats> &workerStats) const; 234 235 protected: 236 /// Aggregates data for the workers. Does not include global (not per-worker) metrics! 237 static TimeStatsDataArray GenerateTimeStatsDataArray(const PandaVector<CoroutineWorkerStats> &workerStats); 238 }; 239 240 } // namespace ark 241 242 #endif /* PANDA_RUNTIME_COROUTINES_COROUTINE_STATS_H */