1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_METRICS_H_
6#define V8_METRICS_H_
7
8#include <stddef.h>
9#include <stdint.h>
10
11#include <vector>
12
13#include "v8-internal.h"      // NOLINT(build/include_directory)
14#include "v8-local-handle.h"  // NOLINT(build/include_directory)
15#include "v8config.h"         // NOLINT(build/include_directory)
16
17namespace v8 {
18
19class Context;
20class Isolate;
21
22namespace metrics {
23
24struct GarbageCollectionPhases {
25  int64_t total_wall_clock_duration_in_us = -1;
26  int64_t compact_wall_clock_duration_in_us = -1;
27  int64_t mark_wall_clock_duration_in_us = -1;
28  int64_t sweep_wall_clock_duration_in_us = -1;
29  int64_t weak_wall_clock_duration_in_us = -1;
30};
31
32struct GarbageCollectionSizes {
33  int64_t bytes_before = -1;
34  int64_t bytes_after = -1;
35  int64_t bytes_freed = -1;
36};
37
38struct GarbageCollectionFullCycle {
39  int reason = -1;
40  GarbageCollectionPhases total;
41  GarbageCollectionPhases total_cpp;
42  GarbageCollectionPhases main_thread;
43  GarbageCollectionPhases main_thread_cpp;
44  GarbageCollectionPhases main_thread_atomic;
45  GarbageCollectionPhases main_thread_atomic_cpp;
46  GarbageCollectionPhases main_thread_incremental;
47  GarbageCollectionPhases main_thread_incremental_cpp;
48  GarbageCollectionSizes objects;
49  GarbageCollectionSizes objects_cpp;
50  GarbageCollectionSizes memory;
51  GarbageCollectionSizes memory_cpp;
52  double collection_rate_in_percent = -1.0;
53  double collection_rate_cpp_in_percent = -1.0;
54  double efficiency_in_bytes_per_us = -1.0;
55  double efficiency_cpp_in_bytes_per_us = -1.0;
56  double main_thread_efficiency_in_bytes_per_us = -1.0;
57  double main_thread_efficiency_cpp_in_bytes_per_us = -1.0;
58};
59
60struct GarbageCollectionFullMainThreadIncrementalMark {
61  int64_t wall_clock_duration_in_us = -1;
62  int64_t cpp_wall_clock_duration_in_us = -1;
63};
64
65struct GarbageCollectionFullMainThreadIncrementalSweep {
66  int64_t wall_clock_duration_in_us = -1;
67  int64_t cpp_wall_clock_duration_in_us = -1;
68};
69
70template <typename EventType>
71struct GarbageCollectionBatchedEvents {
72  std::vector<EventType> events;
73};
74
75using GarbageCollectionFullMainThreadBatchedIncrementalMark =
76    GarbageCollectionBatchedEvents<
77        GarbageCollectionFullMainThreadIncrementalMark>;
78using GarbageCollectionFullMainThreadBatchedIncrementalSweep =
79    GarbageCollectionBatchedEvents<
80        GarbageCollectionFullMainThreadIncrementalSweep>;
81
82struct GarbageCollectionYoungCycle {
83  int reason = -1;
84  int64_t total_wall_clock_duration_in_us = -1;
85  int64_t main_thread_wall_clock_duration_in_us = -1;
86  double collection_rate_in_percent = -1.0;
87  double efficiency_in_bytes_per_us = -1.0;
88  double main_thread_efficiency_in_bytes_per_us = -1.0;
89#if defined(CPPGC_YOUNG_GENERATION)
90  GarbageCollectionPhases total_cpp;
91  GarbageCollectionSizes objects_cpp;
92  GarbageCollectionSizes memory_cpp;
93  double collection_rate_cpp_in_percent = -1.0;
94  double efficiency_cpp_in_bytes_per_us = -1.0;
95  double main_thread_efficiency_cpp_in_bytes_per_us = -1.0;
96#endif  // defined(CPPGC_YOUNG_GENERATION)
97};
98
99struct WasmModuleDecoded {
100  WasmModuleDecoded() = default;
101  WasmModuleDecoded(bool async, bool streamed, bool success,
102                    size_t module_size_in_bytes, size_t function_count,
103                    int64_t wall_clock_duration_in_us)
104      : async(async),
105        streamed(streamed),
106        success(success),
107        module_size_in_bytes(module_size_in_bytes),
108        function_count(function_count),
109        wall_clock_duration_in_us(wall_clock_duration_in_us) {}
110
111  bool async = false;
112  bool streamed = false;
113  bool success = false;
114  size_t module_size_in_bytes = 0;
115  size_t function_count = 0;
116  int64_t wall_clock_duration_in_us = -1;
117};
118
119struct WasmModuleCompiled {
120  WasmModuleCompiled() = default;
121
122  WasmModuleCompiled(bool async, bool streamed, bool cached, bool deserialized,
123                     bool lazy, bool success, size_t code_size_in_bytes,
124                     size_t liftoff_bailout_count,
125                     int64_t wall_clock_duration_in_us)
126      : async(async),
127        streamed(streamed),
128        cached(cached),
129        deserialized(deserialized),
130        lazy(lazy),
131        success(success),
132        code_size_in_bytes(code_size_in_bytes),
133        liftoff_bailout_count(liftoff_bailout_count),
134        wall_clock_duration_in_us(wall_clock_duration_in_us) {}
135
136  bool async = false;
137  bool streamed = false;
138  bool cached = false;
139  bool deserialized = false;
140  bool lazy = false;
141  bool success = false;
142  size_t code_size_in_bytes = 0;
143  size_t liftoff_bailout_count = 0;
144  int64_t wall_clock_duration_in_us = -1;
145};
146
147struct WasmModuleInstantiated {
148  bool async = false;
149  bool success = false;
150  size_t imported_function_count = 0;
151  int64_t wall_clock_duration_in_us = -1;
152};
153
154struct WasmModulesPerIsolate {
155  size_t count = 0;
156};
157
158/**
159 * This class serves as a base class for recording event-based metrics in V8.
160 * There a two kinds of metrics, those which are expected to be thread-safe and
161 * whose implementation is required to fulfill this requirement and those whose
162 * implementation does not have that requirement and only needs to be
163 * executable on the main thread. If such an event is triggered from a
164 * background thread, it will be delayed and executed by the foreground task
165 * runner.
166 *
167 * The embedder is expected to call v8::Isolate::SetMetricsRecorder()
168 * providing its implementation and have the virtual methods overwritten
169 * for the events it cares about.
170 */
171class V8_EXPORT Recorder {
172 public:
173  // A unique identifier for a context in this Isolate.
174  // It is guaranteed to not be reused throughout the lifetime of the Isolate.
175  class ContextId {
176   public:
177    ContextId() : id_(kEmptyId) {}
178
179    bool IsEmpty() const { return id_ == kEmptyId; }
180    static const ContextId Empty() { return ContextId{kEmptyId}; }
181
182    bool operator==(const ContextId& other) const { return id_ == other.id_; }
183    bool operator!=(const ContextId& other) const { return id_ != other.id_; }
184
185   private:
186    friend class ::v8::Context;
187    friend class ::v8::internal::Isolate;
188
189    explicit ContextId(uintptr_t id) : id_(id) {}
190
191    static constexpr uintptr_t kEmptyId = 0;
192    uintptr_t id_;
193  };
194
195  virtual ~Recorder() = default;
196
197  // Main thread events. Those are only triggered on the main thread, and hence
198  // can access the context.
199#define ADD_MAIN_THREAD_EVENT(E) \
200  virtual void AddMainThreadEvent(const E&, ContextId) {}
201  ADD_MAIN_THREAD_EVENT(GarbageCollectionFullCycle)
202  ADD_MAIN_THREAD_EVENT(GarbageCollectionFullMainThreadIncrementalMark)
203  ADD_MAIN_THREAD_EVENT(GarbageCollectionFullMainThreadBatchedIncrementalMark)
204  ADD_MAIN_THREAD_EVENT(GarbageCollectionFullMainThreadIncrementalSweep)
205  ADD_MAIN_THREAD_EVENT(GarbageCollectionFullMainThreadBatchedIncrementalSweep)
206  ADD_MAIN_THREAD_EVENT(GarbageCollectionYoungCycle)
207  ADD_MAIN_THREAD_EVENT(WasmModuleDecoded)
208  ADD_MAIN_THREAD_EVENT(WasmModuleCompiled)
209  ADD_MAIN_THREAD_EVENT(WasmModuleInstantiated)
210#undef ADD_MAIN_THREAD_EVENT
211
212  // Thread-safe events are not allowed to access the context and therefore do
213  // not carry a context ID with them. These IDs can be generated using
214  // Recorder::GetContextId() and the ID will be valid throughout the lifetime
215  // of the isolate. It is not guaranteed that the ID will still resolve to
216  // a valid context using Recorder::GetContext() at the time the metric is
217  // recorded. In this case, an empty handle will be returned.
218#define ADD_THREAD_SAFE_EVENT(E) \
219  virtual void AddThreadSafeEvent(const E&) {}
220  ADD_THREAD_SAFE_EVENT(WasmModulesPerIsolate)
221#undef ADD_THREAD_SAFE_EVENT
222
223  virtual void NotifyIsolateDisposal() {}
224
225  // Return the context with the given id or an empty handle if the context
226  // was already garbage collected.
227  static MaybeLocal<Context> GetContext(Isolate* isolate, ContextId id);
228  // Return the unique id corresponding to the given context.
229  static ContextId GetContextId(Local<Context> context);
230};
231
232/**
233 * Experimental API intended for the LongTasks UKM (crbug.com/1173527).
234 * The Reset() method should be called at the start of a potential
235 * long task. The Get() method returns durations of V8 work that
236 * happened during the task.
237 *
238 * This API is experimental and may be removed/changed in the future.
239 */
240struct V8_EXPORT LongTaskStats {
241  /**
242   * Resets durations of V8 work for the new task.
243   */
244  V8_INLINE static void Reset(Isolate* isolate) {
245    v8::internal::Internals::IncrementLongTasksStatsCounter(isolate);
246  }
247
248  /**
249   * Returns durations of V8 work that happened since the last Reset().
250   */
251  static LongTaskStats Get(Isolate* isolate);
252
253  int64_t gc_full_atomic_wall_clock_duration_us = 0;
254  int64_t gc_full_incremental_wall_clock_duration_us = 0;
255  int64_t gc_young_wall_clock_duration_us = 0;
256  // Only collected with --slow-histograms
257  int64_t v8_execute_us = 0;
258};
259
260}  // namespace metrics
261}  // namespace v8
262
263#endif  // V8_METRICS_H_
264