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