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