• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */