@sentry/profiling-node 8.54.0 → 9.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -3
- package/lib/cjs/index.js +45 -260
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/index.js +46 -255
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/package.json +1 -1
- package/lib/types/integration.d.ts.map +1 -1
- package/lib/types/spanProfileUtils.d.ts.map +1 -1
- package/lib/types/utils.d.ts.map +1 -1
- package/package.json +13 -32
- package/binding.gyp +0 -20
- package/bindings/cpu_profiler.cc +0 -1226
- package/lib/sentry_cpu_profiler-darwin-arm64-108.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-115.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-127.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-arm64-93.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-108.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-115.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-127.node +0 -0
- package/lib/sentry_cpu_profiler-darwin-x64-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-glibc-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-arm64-musl-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-glibc-93.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-108.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-115.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-127.node +0 -0
- package/lib/sentry_cpu_profiler-linux-x64-musl-93.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-108.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-115.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-127.node +0 -0
- package/lib/sentry_cpu_profiler-win32-x64-93.node +0 -0
- package/lib/types/cpu_profiler.d.ts +0 -16
- package/lib/types/cpu_profiler.d.ts.map +0 -1
- package/lib/types-ts3.8/cpu_profiler.d.ts +0 -16
- package/scripts/binaries.js +0 -27
- package/scripts/check-build.js +0 -56
- package/scripts/copy-target.js +0 -27
package/bindings/cpu_profiler.cc
DELETED
|
@@ -1,1226 +0,0 @@
|
|
|
1
|
-
#ifndef NOMINMAX
|
|
2
|
-
#define NOMINMAX
|
|
3
|
-
#endif
|
|
4
|
-
|
|
5
|
-
#include <assert.h>
|
|
6
|
-
#include <math.h>
|
|
7
|
-
#include <node_api.h>
|
|
8
|
-
#include <string.h>
|
|
9
|
-
#include <uv.h>
|
|
10
|
-
#include <v8-profiler.h>
|
|
11
|
-
#include <v8.h>
|
|
12
|
-
|
|
13
|
-
#include <chrono>
|
|
14
|
-
#include <functional>
|
|
15
|
-
#include <string>
|
|
16
|
-
#include <unordered_map>
|
|
17
|
-
|
|
18
|
-
static const uint8_t kMaxStackDepth(128);
|
|
19
|
-
static const float kSamplingFrequency(99.0); // 99 to avoid lockstep sampling
|
|
20
|
-
static const float kSamplingHz(1 / kSamplingFrequency);
|
|
21
|
-
static const int kSamplingInterval(kSamplingHz * 1e6);
|
|
22
|
-
static const v8::CpuProfilingNamingMode
|
|
23
|
-
kNamingMode(v8::CpuProfilingNamingMode::kDebugNaming);
|
|
24
|
-
static const v8::CpuProfilingLoggingMode
|
|
25
|
-
kDefaultLoggingMode(v8::CpuProfilingLoggingMode::kEagerLogging);
|
|
26
|
-
|
|
27
|
-
enum ProfileFormat {
|
|
28
|
-
kFormatThread = 0,
|
|
29
|
-
kFormatChunk = 1,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Allow users to override the default logging mode via env variable. This is
|
|
33
|
-
// useful because sometimes the flow of the profiled program can be to execute
|
|
34
|
-
// many sequential transaction - in that case, it may be preferable to set eager
|
|
35
|
-
// logging to avoid paying the high cost of profiling for each individual
|
|
36
|
-
// transaction (one example for this are jest tests when run with --runInBand
|
|
37
|
-
// option).
|
|
38
|
-
static const char *kEagerLoggingMode = "eager";
|
|
39
|
-
static const char *kLazyLoggingMode = "lazy";
|
|
40
|
-
|
|
41
|
-
v8::CpuProfilingLoggingMode GetLoggingMode() {
|
|
42
|
-
static const char *logging_mode(getenv("SENTRY_PROFILER_LOGGING_MODE"));
|
|
43
|
-
|
|
44
|
-
// most times this wont be set so just bail early
|
|
45
|
-
if (!logging_mode) {
|
|
46
|
-
return kDefaultLoggingMode;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// other times it'll likely be set to lazy as eager is the default
|
|
50
|
-
if (strcmp(logging_mode, kLazyLoggingMode) == 0) {
|
|
51
|
-
return v8::CpuProfilingLoggingMode::kLazyLogging;
|
|
52
|
-
} else if (strcmp(logging_mode, kEagerLoggingMode) == 0) {
|
|
53
|
-
return v8::CpuProfilingLoggingMode::kEagerLogging;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return kDefaultLoggingMode;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
uint64_t timestamp_milliseconds() {
|
|
60
|
-
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
61
|
-
std::chrono::system_clock::now().time_since_epoch())
|
|
62
|
-
.count();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
class SentryProfile;
|
|
66
|
-
class Profiler;
|
|
67
|
-
|
|
68
|
-
enum class ProfileStatus {
|
|
69
|
-
kNotStarted,
|
|
70
|
-
kStarted,
|
|
71
|
-
kStopped,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
class MeasurementsTicker {
|
|
75
|
-
private:
|
|
76
|
-
uv_timer_t *timer;
|
|
77
|
-
uint64_t period_ms;
|
|
78
|
-
std::unordered_map<std::string,
|
|
79
|
-
const std::function<bool(uint64_t, v8::HeapStatistics &)>>
|
|
80
|
-
heap_listeners;
|
|
81
|
-
std::unordered_map<std::string, const std::function<bool(uint64_t, double)>>
|
|
82
|
-
cpu_listeners;
|
|
83
|
-
v8::Isolate *isolate;
|
|
84
|
-
v8::HeapStatistics heap_stats;
|
|
85
|
-
uv_cpu_info_t cpu_stats;
|
|
86
|
-
|
|
87
|
-
public:
|
|
88
|
-
MeasurementsTicker(uv_loop_t *loop)
|
|
89
|
-
: period_ms(100), isolate(v8::Isolate::GetCurrent()) {
|
|
90
|
-
timer = new uv_timer_t;
|
|
91
|
-
uv_timer_init(loop, timer);
|
|
92
|
-
uv_handle_set_data((uv_handle_t *)timer, this);
|
|
93
|
-
uv_ref((uv_handle_t *)timer);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
static void ticker(uv_timer_t *);
|
|
97
|
-
// Memory listeners
|
|
98
|
-
void heap_callback();
|
|
99
|
-
void add_heap_listener(
|
|
100
|
-
std::string &profile_id,
|
|
101
|
-
const std::function<bool(uint64_t, v8::HeapStatistics &)> cb);
|
|
102
|
-
void remove_heap_listener(
|
|
103
|
-
std::string &profile_id,
|
|
104
|
-
const std::function<bool(uint64_t, v8::HeapStatistics &)> &cb);
|
|
105
|
-
|
|
106
|
-
// CPU listeners
|
|
107
|
-
void cpu_callback();
|
|
108
|
-
void add_cpu_listener(std::string &profile_id,
|
|
109
|
-
const std::function<bool(uint64_t, double)> cb);
|
|
110
|
-
void remove_cpu_listener(std::string &profile_id,
|
|
111
|
-
const std::function<bool(uint64_t, double)> &cb);
|
|
112
|
-
|
|
113
|
-
size_t listener_count();
|
|
114
|
-
|
|
115
|
-
~MeasurementsTicker() {
|
|
116
|
-
uv_handle_t *handle = (uv_handle_t *)timer;
|
|
117
|
-
|
|
118
|
-
uv_timer_stop(timer);
|
|
119
|
-
uv_unref(handle);
|
|
120
|
-
|
|
121
|
-
if (!uv_is_closing(handle)) {
|
|
122
|
-
uv_close(handle, [](uv_handle_t *handle) { delete handle; });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
size_t MeasurementsTicker::listener_count() {
|
|
128
|
-
return heap_listeners.size() + cpu_listeners.size();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Heap tickers
|
|
132
|
-
void MeasurementsTicker::heap_callback() {
|
|
133
|
-
isolate->GetHeapStatistics(&heap_stats);
|
|
134
|
-
uint64_t ts = uv_hrtime();
|
|
135
|
-
|
|
136
|
-
for (auto cb : heap_listeners) {
|
|
137
|
-
cb.second(ts, heap_stats);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
void MeasurementsTicker::add_heap_listener(
|
|
142
|
-
std::string &profile_id,
|
|
143
|
-
const std::function<bool(uint64_t, v8::HeapStatistics &)> cb) {
|
|
144
|
-
heap_listeners.emplace(profile_id, cb);
|
|
145
|
-
|
|
146
|
-
if (listener_count() == 1) {
|
|
147
|
-
uv_timer_set_repeat(timer, period_ms);
|
|
148
|
-
uv_timer_start(timer, ticker, 0, period_ms);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
void MeasurementsTicker::remove_heap_listener(
|
|
153
|
-
std::string &profile_id,
|
|
154
|
-
const std::function<bool(uint64_t, v8::HeapStatistics &)> &cb) {
|
|
155
|
-
heap_listeners.erase(profile_id);
|
|
156
|
-
|
|
157
|
-
if (listener_count() == 0) {
|
|
158
|
-
uv_timer_stop(timer);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// CPU tickers
|
|
163
|
-
void MeasurementsTicker::cpu_callback() {
|
|
164
|
-
uv_cpu_info_t *cpu = &cpu_stats;
|
|
165
|
-
int count;
|
|
166
|
-
int err = uv_cpu_info(&cpu, &count);
|
|
167
|
-
|
|
168
|
-
if (err) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (count < 1) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
uint64_t ts = uv_hrtime();
|
|
177
|
-
uint64_t total = 0;
|
|
178
|
-
uint64_t idle_total = 0;
|
|
179
|
-
|
|
180
|
-
for (int i = 0; i < count; i++) {
|
|
181
|
-
uv_cpu_info_t *core = cpu + i;
|
|
182
|
-
|
|
183
|
-
total += core->cpu_times.user;
|
|
184
|
-
total += core->cpu_times.nice;
|
|
185
|
-
total += core->cpu_times.sys;
|
|
186
|
-
total += core->cpu_times.idle;
|
|
187
|
-
total += core->cpu_times.irq;
|
|
188
|
-
|
|
189
|
-
idle_total += core->cpu_times.idle;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
double idle_avg = idle_total / count;
|
|
193
|
-
double total_avg = total / count;
|
|
194
|
-
double rate = 1.0 - idle_avg / total_avg;
|
|
195
|
-
|
|
196
|
-
if (rate < 0.0 || isinf(rate) || isnan(rate)) {
|
|
197
|
-
rate = 0.0;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
auto it = cpu_listeners.begin();
|
|
201
|
-
while (it != cpu_listeners.end()) {
|
|
202
|
-
if (it->second(ts, rate)) {
|
|
203
|
-
it = cpu_listeners.erase(it);
|
|
204
|
-
} else {
|
|
205
|
-
++it;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
uv_free_cpu_info(cpu, count);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
void MeasurementsTicker::ticker(uv_timer_t *handle) {
|
|
213
|
-
if (handle == nullptr) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
MeasurementsTicker *self = static_cast<MeasurementsTicker *>(handle->data);
|
|
218
|
-
self->heap_callback();
|
|
219
|
-
self->cpu_callback();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
void MeasurementsTicker::add_cpu_listener(
|
|
223
|
-
std::string &profile_id, const std::function<bool(uint64_t, double)> cb) {
|
|
224
|
-
cpu_listeners.emplace(profile_id, cb);
|
|
225
|
-
|
|
226
|
-
if (listener_count() == 1) {
|
|
227
|
-
uv_timer_set_repeat(timer, period_ms);
|
|
228
|
-
uv_timer_start(timer, ticker, 0, period_ms);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
void MeasurementsTicker::remove_cpu_listener(
|
|
233
|
-
std::string &profile_id, const std::function<bool(uint64_t, double)> &cb) {
|
|
234
|
-
cpu_listeners.erase(profile_id);
|
|
235
|
-
|
|
236
|
-
if (listener_count() == 0) {
|
|
237
|
-
uv_timer_stop(timer);
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
class Profiler {
|
|
242
|
-
public:
|
|
243
|
-
std::unordered_map<std::string, SentryProfile *> active_profiles;
|
|
244
|
-
|
|
245
|
-
MeasurementsTicker measurements_ticker;
|
|
246
|
-
v8::CpuProfiler *cpu_profiler;
|
|
247
|
-
|
|
248
|
-
explicit Profiler(const napi_env &env, v8::Isolate *isolate)
|
|
249
|
-
: measurements_ticker(uv_default_loop()),
|
|
250
|
-
cpu_profiler(
|
|
251
|
-
v8::CpuProfiler::New(isolate, kNamingMode, GetLoggingMode())) {}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
class SentryProfile {
|
|
255
|
-
private:
|
|
256
|
-
uint64_t started_at;
|
|
257
|
-
uint64_t timestamp;
|
|
258
|
-
uint16_t heap_write_index = 0;
|
|
259
|
-
uint16_t cpu_write_index = 0;
|
|
260
|
-
|
|
261
|
-
std::vector<uint64_t> heap_stats_ts;
|
|
262
|
-
std::vector<uint64_t> heap_stats_usage;
|
|
263
|
-
|
|
264
|
-
std::vector<uint64_t> cpu_stats_ts;
|
|
265
|
-
std::vector<double> cpu_stats_usage;
|
|
266
|
-
|
|
267
|
-
const std::function<bool(uint64_t, v8::HeapStatistics &)> memory_sampler_cb;
|
|
268
|
-
const std::function<bool(uint64_t, double)> cpu_sampler_cb;
|
|
269
|
-
|
|
270
|
-
ProfileStatus status = ProfileStatus::kNotStarted;
|
|
271
|
-
std::string id;
|
|
272
|
-
|
|
273
|
-
public:
|
|
274
|
-
explicit SentryProfile(const char *id)
|
|
275
|
-
: started_at(uv_hrtime()), timestamp(timestamp_milliseconds()),
|
|
276
|
-
memory_sampler_cb([this](uint64_t ts, v8::HeapStatistics &stats) {
|
|
277
|
-
if ((heap_write_index >= heap_stats_ts.capacity()) ||
|
|
278
|
-
heap_write_index >= heap_stats_usage.capacity()) {
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
heap_stats_ts.insert(heap_stats_ts.begin() + heap_write_index,
|
|
283
|
-
ts - started_at);
|
|
284
|
-
heap_stats_usage.insert(
|
|
285
|
-
heap_stats_usage.begin() + heap_write_index,
|
|
286
|
-
static_cast<uint64_t>(stats.used_heap_size()));
|
|
287
|
-
++heap_write_index;
|
|
288
|
-
|
|
289
|
-
return false;
|
|
290
|
-
}),
|
|
291
|
-
|
|
292
|
-
cpu_sampler_cb([this](uint64_t ts, double rate) {
|
|
293
|
-
if (cpu_write_index >= cpu_stats_ts.capacity() ||
|
|
294
|
-
cpu_write_index >= cpu_stats_usage.capacity()) {
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
cpu_stats_ts.insert(cpu_stats_ts.begin() + cpu_write_index,
|
|
298
|
-
ts - started_at);
|
|
299
|
-
cpu_stats_usage.insert(cpu_stats_usage.begin() + cpu_write_index,
|
|
300
|
-
rate);
|
|
301
|
-
++cpu_write_index;
|
|
302
|
-
return false;
|
|
303
|
-
}),
|
|
304
|
-
|
|
305
|
-
status(ProfileStatus::kNotStarted), id(id) {
|
|
306
|
-
heap_stats_ts.reserve(300);
|
|
307
|
-
heap_stats_usage.reserve(300);
|
|
308
|
-
cpu_stats_ts.reserve(300);
|
|
309
|
-
cpu_stats_usage.reserve(300);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const std::vector<uint64_t> &heap_usage_timestamps() const;
|
|
313
|
-
const std::vector<uint64_t> &heap_usage_values() const;
|
|
314
|
-
const uint16_t &heap_usage_write_index() const;
|
|
315
|
-
|
|
316
|
-
const std::vector<uint64_t> &cpu_usage_timestamps() const;
|
|
317
|
-
const std::vector<double> &cpu_usage_values() const;
|
|
318
|
-
const uint16_t &cpu_usage_write_index() const;
|
|
319
|
-
const uint64_t &profile_start_timestamp() const;
|
|
320
|
-
|
|
321
|
-
void Start(Profiler *profiler);
|
|
322
|
-
v8::CpuProfile *Stop(Profiler *profiler);
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
void SentryProfile::Start(Profiler *profiler) {
|
|
326
|
-
v8::Local<v8::String> profile_title =
|
|
327
|
-
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(),
|
|
328
|
-
v8::NewStringType::kNormal)
|
|
329
|
-
.ToLocalChecked();
|
|
330
|
-
|
|
331
|
-
started_at = uv_hrtime();
|
|
332
|
-
timestamp = timestamp_milliseconds();
|
|
333
|
-
|
|
334
|
-
// Initialize the CPU Profiler
|
|
335
|
-
profiler->cpu_profiler->StartProfiling(
|
|
336
|
-
profile_title, v8::CpuProfilingMode::kCallerLineNumbers, true,
|
|
337
|
-
v8::CpuProfilingOptions::kNoSampleLimit);
|
|
338
|
-
|
|
339
|
-
// listen for memory sample ticks
|
|
340
|
-
profiler->measurements_ticker.add_cpu_listener(id, cpu_sampler_cb);
|
|
341
|
-
profiler->measurements_ticker.add_heap_listener(id, memory_sampler_cb);
|
|
342
|
-
|
|
343
|
-
status = ProfileStatus::kStarted;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
v8::CpuProfile *SentryProfile::Stop(Profiler *profiler) {
|
|
347
|
-
// Stop the CPU Profiler
|
|
348
|
-
v8::CpuProfile *profile = profiler->cpu_profiler->StopProfiling(
|
|
349
|
-
v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(),
|
|
350
|
-
v8::NewStringType::kNormal)
|
|
351
|
-
.ToLocalChecked());
|
|
352
|
-
|
|
353
|
-
// Remove the memory sampler
|
|
354
|
-
profiler->measurements_ticker.remove_heap_listener(id, memory_sampler_cb);
|
|
355
|
-
profiler->measurements_ticker.remove_cpu_listener(id, cpu_sampler_cb);
|
|
356
|
-
// If for some reason stopProfiling was called with an invalid profile title
|
|
357
|
-
// or if that title had somehow been stopped already, profile will be null.
|
|
358
|
-
status = ProfileStatus::kStopped;
|
|
359
|
-
return profile;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Memory getters
|
|
363
|
-
const std::vector<uint64_t> &SentryProfile::heap_usage_timestamps() const {
|
|
364
|
-
return heap_stats_ts;
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const std::vector<uint64_t> &SentryProfile::heap_usage_values() const {
|
|
368
|
-
return heap_stats_usage;
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const uint16_t &SentryProfile::heap_usage_write_index() const {
|
|
372
|
-
return heap_write_index;
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
// CPU getters
|
|
376
|
-
const std::vector<uint64_t> &SentryProfile::cpu_usage_timestamps() const {
|
|
377
|
-
return cpu_stats_ts;
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const std::vector<double> &SentryProfile::cpu_usage_values() const {
|
|
381
|
-
return cpu_stats_usage;
|
|
382
|
-
};
|
|
383
|
-
const uint16_t &SentryProfile::cpu_usage_write_index() const {
|
|
384
|
-
return cpu_write_index;
|
|
385
|
-
};
|
|
386
|
-
const uint64_t &SentryProfile::profile_start_timestamp() const {
|
|
387
|
-
return timestamp;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
static void CleanupSentryProfile(Profiler *profiler,
|
|
391
|
-
SentryProfile *sentry_profile,
|
|
392
|
-
const std::string &profile_id) {
|
|
393
|
-
if (sentry_profile == nullptr) {
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
sentry_profile->Stop(profiler);
|
|
398
|
-
profiler->active_profiles.erase(profile_id);
|
|
399
|
-
delete sentry_profile;
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
#ifdef _WIN32
|
|
403
|
-
static const char kPlatformSeparator = '\\';
|
|
404
|
-
static const char kWinDiskPrefix = ':';
|
|
405
|
-
#else
|
|
406
|
-
static const char kPlatformSeparator = '/';
|
|
407
|
-
#endif
|
|
408
|
-
|
|
409
|
-
static const char kSentryPathDelimiter = '.';
|
|
410
|
-
static const char kSentryFileDelimiter = ':';
|
|
411
|
-
static const std::string kNodeModulesPath =
|
|
412
|
-
std::string("node_modules") + kPlatformSeparator;
|
|
413
|
-
|
|
414
|
-
static void GetFrameModule(const std::string &abs_path, std::string &module) {
|
|
415
|
-
if (abs_path.empty()) {
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
module = abs_path;
|
|
420
|
-
|
|
421
|
-
// Drop .js extension
|
|
422
|
-
size_t module_len = module.length();
|
|
423
|
-
if (module.compare(module_len - 3, 3, ".js") == 0) {
|
|
424
|
-
module = module.substr(0, module_len - 3);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Drop anything before and including node_modules/
|
|
428
|
-
size_t node_modules_pos = module.rfind(kNodeModulesPath);
|
|
429
|
-
if (node_modules_pos != std::string::npos) {
|
|
430
|
-
module = module.substr(node_modules_pos + 13);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Replace all path separators with dots except the last one, that one is
|
|
434
|
-
// replaced with a colon
|
|
435
|
-
int match_count = 0;
|
|
436
|
-
for (int pos = module.length() - 1; pos >= 0; pos--) {
|
|
437
|
-
// if there is a match and it's not the first character, replace it
|
|
438
|
-
if (module[pos] == kPlatformSeparator) {
|
|
439
|
-
module[pos] =
|
|
440
|
-
match_count == 0 ? kSentryFileDelimiter : kSentryPathDelimiter;
|
|
441
|
-
match_count++;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
#ifdef _WIN32
|
|
446
|
-
// Strip out C: prefix. On Windows, the drive letter is not part of the module
|
|
447
|
-
// name
|
|
448
|
-
if (module[1] == kWinDiskPrefix) {
|
|
449
|
-
// We will try and strip our the disk prefix.
|
|
450
|
-
module = module.substr(2, std::string::npos);
|
|
451
|
-
}
|
|
452
|
-
#endif
|
|
453
|
-
|
|
454
|
-
if (module[0] == '.') {
|
|
455
|
-
module = module.substr(1, std::string::npos);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
static napi_value GetFrameModuleWrapped(napi_env env, napi_callback_info info) {
|
|
460
|
-
size_t argc = 2;
|
|
461
|
-
napi_value argv[2];
|
|
462
|
-
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
|
|
463
|
-
|
|
464
|
-
size_t len;
|
|
465
|
-
assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok);
|
|
466
|
-
|
|
467
|
-
char *abs_path = (char *)malloc(len + 1);
|
|
468
|
-
assert(napi_get_value_string_utf8(env, argv[0], abs_path, len + 1, &len) ==
|
|
469
|
-
napi_ok);
|
|
470
|
-
|
|
471
|
-
std::string module;
|
|
472
|
-
napi_value napi_module;
|
|
473
|
-
|
|
474
|
-
GetFrameModule(abs_path, module);
|
|
475
|
-
|
|
476
|
-
assert(napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH,
|
|
477
|
-
&napi_module) == napi_ok);
|
|
478
|
-
return napi_module;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
napi_value
|
|
482
|
-
CreateFrameNode(const napi_env &env, const v8::CpuProfileNode &node,
|
|
483
|
-
std::unordered_map<std::string, std::string> &module_cache,
|
|
484
|
-
napi_value &resources) {
|
|
485
|
-
napi_value js_node;
|
|
486
|
-
napi_create_object(env, &js_node);
|
|
487
|
-
|
|
488
|
-
napi_value lineno_prop;
|
|
489
|
-
napi_create_int32(env, node.GetLineNumber(), &lineno_prop);
|
|
490
|
-
napi_set_named_property(env, js_node, "lineno", lineno_prop);
|
|
491
|
-
|
|
492
|
-
napi_value colno_prop;
|
|
493
|
-
napi_create_int32(env, node.GetColumnNumber(), &colno_prop);
|
|
494
|
-
napi_set_named_property(env, js_node, "colno", colno_prop);
|
|
495
|
-
|
|
496
|
-
if (node.GetSourceType() != v8::CpuProfileNode::SourceType::kScript) {
|
|
497
|
-
napi_value system_frame_prop;
|
|
498
|
-
napi_get_boolean(env, false, &system_frame_prop);
|
|
499
|
-
napi_set_named_property(env, js_node, "in_app", system_frame_prop);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
napi_value function;
|
|
503
|
-
napi_create_string_utf8(env, node.GetFunctionNameStr(), NAPI_AUTO_LENGTH,
|
|
504
|
-
&function);
|
|
505
|
-
napi_set_named_property(env, js_node, "function", function);
|
|
506
|
-
|
|
507
|
-
const char *resource = node.GetScriptResourceNameStr();
|
|
508
|
-
|
|
509
|
-
if (resource != nullptr) {
|
|
510
|
-
// resource is absolute path, set it on the abs_path property
|
|
511
|
-
napi_value abs_path_prop;
|
|
512
|
-
napi_create_string_utf8(env, resource, NAPI_AUTO_LENGTH, &abs_path_prop);
|
|
513
|
-
napi_set_named_property(env, js_node, "abs_path", abs_path_prop);
|
|
514
|
-
// Error stack traces are not relative to root dir, doing our own path
|
|
515
|
-
// normalization breaks people's code mapping configs so we need to leave it
|
|
516
|
-
// as is.
|
|
517
|
-
napi_set_named_property(env, js_node, "filename", abs_path_prop);
|
|
518
|
-
|
|
519
|
-
std::string module;
|
|
520
|
-
std::string resource_str = std::string(resource);
|
|
521
|
-
|
|
522
|
-
if (resource_str.empty()) {
|
|
523
|
-
return js_node;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (module_cache.find(resource_str) != module_cache.end()) {
|
|
527
|
-
module = module_cache[resource_str];
|
|
528
|
-
} else {
|
|
529
|
-
napi_value resource;
|
|
530
|
-
napi_create_string_utf8(env, resource_str.c_str(), NAPI_AUTO_LENGTH,
|
|
531
|
-
&resource);
|
|
532
|
-
napi_set_element(env, resources, module_cache.size(), resource);
|
|
533
|
-
|
|
534
|
-
GetFrameModule(resource_str, module);
|
|
535
|
-
module_cache.emplace(resource_str, module);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (!module.empty()) {
|
|
539
|
-
napi_value filename_prop;
|
|
540
|
-
napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH,
|
|
541
|
-
&filename_prop);
|
|
542
|
-
napi_set_named_property(env, js_node, "module", filename_prop);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
return js_node;
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
napi_value CreateSample(const napi_env &env, const enum ProfileFormat format,
|
|
550
|
-
const uint32_t stack_id,
|
|
551
|
-
const int64_t sample_timestamp_ns,
|
|
552
|
-
const double chunk_timestamp,
|
|
553
|
-
const uint32_t thread_id) {
|
|
554
|
-
napi_value js_node;
|
|
555
|
-
napi_create_object(env, &js_node);
|
|
556
|
-
|
|
557
|
-
napi_value stack_id_prop;
|
|
558
|
-
napi_create_uint32(env, stack_id, &stack_id_prop);
|
|
559
|
-
napi_set_named_property(env, js_node, "stack_id", stack_id_prop);
|
|
560
|
-
|
|
561
|
-
napi_value thread_id_prop;
|
|
562
|
-
napi_create_string_utf8(env, std::to_string(thread_id).c_str(),
|
|
563
|
-
NAPI_AUTO_LENGTH, &thread_id_prop);
|
|
564
|
-
napi_set_named_property(env, js_node, "thread_id", thread_id_prop);
|
|
565
|
-
|
|
566
|
-
switch (format) {
|
|
567
|
-
case ProfileFormat::kFormatThread: {
|
|
568
|
-
napi_value timestamp;
|
|
569
|
-
napi_create_int64(env, sample_timestamp_ns, ×tamp);
|
|
570
|
-
napi_set_named_property(env, js_node, "elapsed_since_start_ns", timestamp);
|
|
571
|
-
} break;
|
|
572
|
-
case ProfileFormat::kFormatChunk: {
|
|
573
|
-
napi_value timestamp;
|
|
574
|
-
napi_create_double(env, chunk_timestamp, ×tamp);
|
|
575
|
-
napi_set_named_property(env, js_node, "timestamp", timestamp);
|
|
576
|
-
} break;
|
|
577
|
-
default:
|
|
578
|
-
break;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
return js_node;
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
std::string kDelimiter = std::string(";");
|
|
585
|
-
std::string hashCpuProfilerNodeByPath(const v8::CpuProfileNode *node,
|
|
586
|
-
std::string &path) {
|
|
587
|
-
path.clear();
|
|
588
|
-
|
|
589
|
-
while (node != nullptr) {
|
|
590
|
-
path.append(std::to_string(node->GetNodeId()));
|
|
591
|
-
node = node->GetParent();
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return path;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
static void GetSamples(const napi_env &env, const v8::CpuProfile *profile,
|
|
598
|
-
ProfileFormat format,
|
|
599
|
-
const uint64_t profile_start_timestamp_ms,
|
|
600
|
-
const uint32_t thread_id, napi_value &samples,
|
|
601
|
-
napi_value &stacks, napi_value &frames,
|
|
602
|
-
napi_value &resources) {
|
|
603
|
-
const int64_t profile_start_time_us = profile->GetStartTime();
|
|
604
|
-
const int64_t sampleCount = profile->GetSamplesCount();
|
|
605
|
-
|
|
606
|
-
uint32_t unique_stack_id = 0;
|
|
607
|
-
uint32_t unique_frame_id = 0;
|
|
608
|
-
|
|
609
|
-
// Initialize the lookup tables for stacks and frames, both of these are
|
|
610
|
-
// indexed in the sample format we are using to optimize for size.
|
|
611
|
-
std::unordered_map<uint32_t, uint32_t> frame_lookup_table;
|
|
612
|
-
std::unordered_map<std::string, uint32_t> stack_lookup_table;
|
|
613
|
-
std::unordered_map<std::string, std::string> module_cache;
|
|
614
|
-
|
|
615
|
-
// At worst, all stacks are unique so reserve the maximum amount of space
|
|
616
|
-
stack_lookup_table.reserve(sampleCount);
|
|
617
|
-
|
|
618
|
-
std::string node_hash = "";
|
|
619
|
-
|
|
620
|
-
for (int i = 0; i < sampleCount; i++) {
|
|
621
|
-
uint32_t stack_index = unique_stack_id;
|
|
622
|
-
|
|
623
|
-
const v8::CpuProfileNode *node = profile->GetSample(i);
|
|
624
|
-
const int64_t sample_timestamp_us = profile->GetSampleTimestamp(i);
|
|
625
|
-
|
|
626
|
-
// If a node was only on top of the stack once, then it will only ever
|
|
627
|
-
// be inserted once and there is no need for hashing.
|
|
628
|
-
if (node->GetHitCount() > 1) {
|
|
629
|
-
hashCpuProfilerNodeByPath(node, node_hash);
|
|
630
|
-
|
|
631
|
-
std::unordered_map<std::string, uint32_t>::iterator
|
|
632
|
-
stack_index_cache_hit = stack_lookup_table.find(node_hash);
|
|
633
|
-
|
|
634
|
-
// If we have a hit, update the stack index, otherwise
|
|
635
|
-
// insert it into the hash table and continue.
|
|
636
|
-
if (stack_index_cache_hit == stack_lookup_table.end()) {
|
|
637
|
-
stack_lookup_table.emplace(node_hash, stack_index);
|
|
638
|
-
} else {
|
|
639
|
-
stack_index = stack_index_cache_hit->second;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
uint64_t sample_delta_us = sample_timestamp_us - profile_start_time_us;
|
|
644
|
-
uint64_t sample_timestamp_ns = sample_delta_us * 1e3;
|
|
645
|
-
uint64_t sample_offset_from_profile_start_ms =
|
|
646
|
-
(sample_timestamp_us - profile_start_time_us) * 1e-3;
|
|
647
|
-
double seconds_since_start =
|
|
648
|
-
(profile_start_timestamp_ms + sample_offset_from_profile_start_ms) *
|
|
649
|
-
1e-3;
|
|
650
|
-
|
|
651
|
-
napi_value sample = nullptr;
|
|
652
|
-
sample = CreateSample(env, format, stack_index, sample_timestamp_ns,
|
|
653
|
-
seconds_since_start, thread_id);
|
|
654
|
-
|
|
655
|
-
if (stack_index != unique_stack_id) {
|
|
656
|
-
napi_value index;
|
|
657
|
-
napi_create_uint32(env, i, &index);
|
|
658
|
-
napi_set_property(env, samples, index, sample);
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// A stack is a list of frames ordered from outermost (top) to innermost
|
|
663
|
-
// frame (bottom)
|
|
664
|
-
napi_value stack;
|
|
665
|
-
napi_create_array(env, &stack);
|
|
666
|
-
|
|
667
|
-
uint32_t stack_depth = 0;
|
|
668
|
-
|
|
669
|
-
while (node != nullptr && stack_depth < kMaxStackDepth) {
|
|
670
|
-
auto nodeId = node->GetNodeId();
|
|
671
|
-
auto frame_index = frame_lookup_table.find(nodeId);
|
|
672
|
-
|
|
673
|
-
// If the frame does not exist in the index
|
|
674
|
-
if (frame_index == frame_lookup_table.end()) {
|
|
675
|
-
frame_lookup_table.emplace(nodeId, unique_frame_id);
|
|
676
|
-
|
|
677
|
-
napi_value frame_id;
|
|
678
|
-
napi_create_uint32(env, unique_frame_id, &frame_id);
|
|
679
|
-
|
|
680
|
-
napi_value depth;
|
|
681
|
-
napi_create_uint32(env, stack_depth, &depth);
|
|
682
|
-
napi_set_property(env, stack, depth, frame_id);
|
|
683
|
-
napi_set_property(env, frames, frame_id,
|
|
684
|
-
CreateFrameNode(env, *node, module_cache, resources));
|
|
685
|
-
|
|
686
|
-
unique_frame_id++;
|
|
687
|
-
} else {
|
|
688
|
-
// If it was already indexed, just add it's id to the stack
|
|
689
|
-
napi_value depth;
|
|
690
|
-
napi_create_uint32(env, stack_depth, &depth);
|
|
691
|
-
|
|
692
|
-
napi_value frame;
|
|
693
|
-
napi_create_uint32(env, frame_index->second, &frame);
|
|
694
|
-
napi_set_property(env, stack, depth, frame);
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
// Continue walking down the stack
|
|
698
|
-
node = node->GetParent();
|
|
699
|
-
stack_depth++;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
napi_value napi_sample_index;
|
|
703
|
-
napi_value napi_stack_index;
|
|
704
|
-
|
|
705
|
-
napi_create_uint32(env, i, &napi_sample_index);
|
|
706
|
-
napi_set_property(env, samples, napi_sample_index, sample);
|
|
707
|
-
napi_create_uint32(env, stack_index, &napi_stack_index);
|
|
708
|
-
napi_set_property(env, stacks, napi_stack_index, stack);
|
|
709
|
-
|
|
710
|
-
unique_stack_id++;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
static napi_value TranslateMeasurementsDouble(
|
|
715
|
-
const napi_env &env, const enum ProfileFormat format, const char *unit,
|
|
716
|
-
const uint64_t profile_start_timestamp_ms, const uint16_t size,
|
|
717
|
-
const std::vector<double> &values,
|
|
718
|
-
const std::vector<uint64_t> ×tamps_ns) {
|
|
719
|
-
if (size > values.size() || size > timestamps_ns.size()) {
|
|
720
|
-
napi_throw_range_error(env, "NAPI_ERROR",
|
|
721
|
-
"CPU measurement size is larger than the number of "
|
|
722
|
-
"values or timestamps");
|
|
723
|
-
return nullptr;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
if (values.size() != timestamps_ns.size()) {
|
|
727
|
-
napi_throw_range_error(env, "NAPI_ERROR",
|
|
728
|
-
"CPU measurement entries are corrupt, expected "
|
|
729
|
-
"values and timestamps to be of equal length");
|
|
730
|
-
return nullptr;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
napi_value measurement;
|
|
734
|
-
napi_create_object(env, &measurement);
|
|
735
|
-
|
|
736
|
-
napi_value unit_string;
|
|
737
|
-
napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string);
|
|
738
|
-
napi_set_named_property(env, measurement, "unit", unit_string);
|
|
739
|
-
|
|
740
|
-
napi_value values_array;
|
|
741
|
-
napi_create_array(env, &values_array);
|
|
742
|
-
|
|
743
|
-
uint16_t idx = size;
|
|
744
|
-
|
|
745
|
-
for (size_t i = 0; i < idx; i++) {
|
|
746
|
-
napi_value entry;
|
|
747
|
-
napi_create_object(env, &entry);
|
|
748
|
-
|
|
749
|
-
napi_value value;
|
|
750
|
-
if (napi_create_double(env, values[i], &value) != napi_ok) {
|
|
751
|
-
if (napi_create_double(env, 0.0, &value) != napi_ok) {
|
|
752
|
-
continue;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
napi_set_named_property(env, entry, "value", value);
|
|
757
|
-
|
|
758
|
-
if (format == ProfileFormat::kFormatThread) {
|
|
759
|
-
napi_value ts;
|
|
760
|
-
napi_create_int64(env, timestamps_ns[i], &ts);
|
|
761
|
-
napi_set_named_property(env, entry, "elapsed_since_start_ns", ts);
|
|
762
|
-
} else if (format == ProfileFormat::kFormatChunk) {
|
|
763
|
-
napi_value ts;
|
|
764
|
-
napi_create_double(
|
|
765
|
-
env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts);
|
|
766
|
-
napi_set_named_property(env, entry, "timestamp", ts);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
napi_set_element(env, values_array, i, entry);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
napi_set_named_property(env, measurement, "values", values_array);
|
|
773
|
-
|
|
774
|
-
return measurement;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
static napi_value
|
|
778
|
-
TranslateMeasurements(const napi_env &env, const enum ProfileFormat format,
|
|
779
|
-
const char *unit,
|
|
780
|
-
const uint64_t profile_start_timestamp_ms,
|
|
781
|
-
const uint16_t size, const std::vector<uint64_t> &values,
|
|
782
|
-
const std::vector<uint64_t> ×tamps_ns) {
|
|
783
|
-
if (size > values.size() || size > timestamps_ns.size()) {
|
|
784
|
-
napi_throw_range_error(env, "NAPI_ERROR",
|
|
785
|
-
"Memory measurement size is larger than the number "
|
|
786
|
-
"of values or timestamps");
|
|
787
|
-
return nullptr;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
if (values.size() != timestamps_ns.size()) {
|
|
791
|
-
napi_throw_range_error(env, "NAPI_ERROR",
|
|
792
|
-
"Memory measurement entries are corrupt, expected "
|
|
793
|
-
"values and timestamps to be of equal length");
|
|
794
|
-
return nullptr;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
napi_value measurement;
|
|
798
|
-
napi_create_object(env, &measurement);
|
|
799
|
-
|
|
800
|
-
napi_value unit_string;
|
|
801
|
-
napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string);
|
|
802
|
-
napi_set_named_property(env, measurement, "unit", unit_string);
|
|
803
|
-
|
|
804
|
-
napi_value values_array;
|
|
805
|
-
napi_create_array(env, &values_array);
|
|
806
|
-
|
|
807
|
-
for (size_t i = 0; i < size; i++) {
|
|
808
|
-
napi_value entry;
|
|
809
|
-
napi_create_object(env, &entry);
|
|
810
|
-
|
|
811
|
-
napi_value value;
|
|
812
|
-
napi_create_int64(env, values[i], &value);
|
|
813
|
-
|
|
814
|
-
napi_set_named_property(env, entry, "value", value);
|
|
815
|
-
switch (format) {
|
|
816
|
-
case ProfileFormat::kFormatThread: {
|
|
817
|
-
napi_value ts;
|
|
818
|
-
napi_create_int64(env, timestamps_ns[i], &ts);
|
|
819
|
-
napi_set_named_property(env, entry, "elapsed_since_start_ns", ts);
|
|
820
|
-
} break;
|
|
821
|
-
case ProfileFormat::kFormatChunk: {
|
|
822
|
-
napi_value ts;
|
|
823
|
-
napi_create_double(
|
|
824
|
-
env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts);
|
|
825
|
-
napi_set_named_property(env, entry, "timestamp", ts);
|
|
826
|
-
} break;
|
|
827
|
-
default:
|
|
828
|
-
break;
|
|
829
|
-
}
|
|
830
|
-
napi_set_element(env, values_array, i, entry);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
napi_set_named_property(env, measurement, "values", values_array);
|
|
834
|
-
|
|
835
|
-
return measurement;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
static napi_value TranslateProfile(const napi_env &env,
|
|
839
|
-
const v8::CpuProfile *profile,
|
|
840
|
-
const enum ProfileFormat format,
|
|
841
|
-
const uint64_t profile_start_timestamp_ms,
|
|
842
|
-
const uint32_t thread_id,
|
|
843
|
-
bool collect_resources) {
|
|
844
|
-
napi_value js_profile;
|
|
845
|
-
|
|
846
|
-
napi_create_object(env, &js_profile);
|
|
847
|
-
|
|
848
|
-
napi_value logging_mode;
|
|
849
|
-
napi_value samples;
|
|
850
|
-
napi_value stacks;
|
|
851
|
-
napi_value frames;
|
|
852
|
-
napi_value resources;
|
|
853
|
-
|
|
854
|
-
napi_create_string_utf8(
|
|
855
|
-
env,
|
|
856
|
-
GetLoggingMode() == v8::CpuProfilingLoggingMode::kEagerLogging ? "eager"
|
|
857
|
-
: "lazy",
|
|
858
|
-
NAPI_AUTO_LENGTH, &logging_mode);
|
|
859
|
-
|
|
860
|
-
napi_create_array(env, &samples);
|
|
861
|
-
napi_create_array(env, &stacks);
|
|
862
|
-
napi_create_array(env, &frames);
|
|
863
|
-
napi_create_array(env, &resources);
|
|
864
|
-
|
|
865
|
-
napi_set_named_property(env, js_profile, "samples", samples);
|
|
866
|
-
napi_set_named_property(env, js_profile, "stacks", stacks);
|
|
867
|
-
napi_set_named_property(env, js_profile, "frames", frames);
|
|
868
|
-
napi_set_named_property(env, js_profile, "profiler_logging_mode",
|
|
869
|
-
logging_mode);
|
|
870
|
-
|
|
871
|
-
GetSamples(env, profile, format, profile_start_timestamp_ms, thread_id,
|
|
872
|
-
samples, stacks, frames, resources);
|
|
873
|
-
|
|
874
|
-
if (collect_resources) {
|
|
875
|
-
napi_set_named_property(env, js_profile, "resources", resources);
|
|
876
|
-
} else {
|
|
877
|
-
napi_create_array(env, &resources);
|
|
878
|
-
napi_set_named_property(env, js_profile, "resources", resources);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
return js_profile;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
static napi_value StartProfiling(napi_env env, napi_callback_info info) {
|
|
885
|
-
size_t argc = 1;
|
|
886
|
-
napi_value argv[1];
|
|
887
|
-
|
|
888
|
-
assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok);
|
|
889
|
-
|
|
890
|
-
napi_valuetype callbacktype0;
|
|
891
|
-
assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok);
|
|
892
|
-
|
|
893
|
-
if (callbacktype0 != napi_string) {
|
|
894
|
-
napi_throw_error(
|
|
895
|
-
env, "NAPI_ERROR",
|
|
896
|
-
"TypeError: StartProfiling expects a string as first argument.");
|
|
897
|
-
napi_value napi_null;
|
|
898
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
899
|
-
|
|
900
|
-
return napi_null;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
size_t len;
|
|
904
|
-
assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok);
|
|
905
|
-
|
|
906
|
-
char *title = (char *)malloc(len + 1);
|
|
907
|
-
assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) ==
|
|
908
|
-
napi_ok);
|
|
909
|
-
|
|
910
|
-
if (len < 1) {
|
|
911
|
-
napi_throw_error(env, "NAPI_ERROR",
|
|
912
|
-
"StartProfiling expects a non-empty string as first "
|
|
913
|
-
"argument, got an empty string.");
|
|
914
|
-
|
|
915
|
-
napi_value napi_null;
|
|
916
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
917
|
-
|
|
918
|
-
return napi_null;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
v8::Isolate *isolate = v8::Isolate::GetCurrent();
|
|
922
|
-
assert(isolate != 0);
|
|
923
|
-
|
|
924
|
-
Profiler *profiler;
|
|
925
|
-
assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok);
|
|
926
|
-
|
|
927
|
-
if (!profiler) {
|
|
928
|
-
napi_throw_error(env, "NAPI_ERROR",
|
|
929
|
-
"StartProfiling: Profiler is not initialized.");
|
|
930
|
-
|
|
931
|
-
napi_value napi_null;
|
|
932
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
933
|
-
|
|
934
|
-
return napi_null;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const std::string profile_id(title);
|
|
938
|
-
// In case we have a collision, cleanup the old profile first
|
|
939
|
-
auto existing_profile = profiler->active_profiles.find(profile_id);
|
|
940
|
-
if (existing_profile != profiler->active_profiles.end()) {
|
|
941
|
-
existing_profile->second->Stop(profiler);
|
|
942
|
-
CleanupSentryProfile(profiler, existing_profile->second, profile_id);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
SentryProfile *sentry_profile = new SentryProfile(title);
|
|
946
|
-
sentry_profile->Start(profiler);
|
|
947
|
-
|
|
948
|
-
profiler->active_profiles.emplace(profile_id, sentry_profile);
|
|
949
|
-
|
|
950
|
-
napi_value napi_null;
|
|
951
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
952
|
-
|
|
953
|
-
return napi_null;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// StopProfiling(string title)
|
|
957
|
-
// https://v8docs.nodesource.com/node-18.2/d2/d34/classv8_1_1_cpu_profiler.html#a40ca4c8a8aa4c9233aa2a2706457cc80
|
|
958
|
-
static napi_value StopProfiling(napi_env env, napi_callback_info info) {
|
|
959
|
-
size_t argc = 4;
|
|
960
|
-
napi_value argv[4];
|
|
961
|
-
|
|
962
|
-
assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok);
|
|
963
|
-
|
|
964
|
-
if (argc < 3) {
|
|
965
|
-
napi_throw_error(env, "NAPI_ERROR",
|
|
966
|
-
"StopProfiling expects at least three arguments.");
|
|
967
|
-
|
|
968
|
-
napi_value napi_null;
|
|
969
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
970
|
-
|
|
971
|
-
return napi_null;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// Verify the first argument is a string
|
|
975
|
-
napi_valuetype callbacktype0;
|
|
976
|
-
assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok);
|
|
977
|
-
|
|
978
|
-
if (callbacktype0 != napi_string) {
|
|
979
|
-
napi_throw_error(env, "NAPI_ERROR",
|
|
980
|
-
"StopProfiling expects a string as first argument.");
|
|
981
|
-
|
|
982
|
-
napi_value napi_null;
|
|
983
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
984
|
-
|
|
985
|
-
return napi_null;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
size_t len;
|
|
989
|
-
assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok);
|
|
990
|
-
|
|
991
|
-
char *title = (char *)malloc(len + 1);
|
|
992
|
-
assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) ==
|
|
993
|
-
napi_ok);
|
|
994
|
-
|
|
995
|
-
if (len < 1) {
|
|
996
|
-
napi_throw_error(
|
|
997
|
-
env, "NAPI_ERROR",
|
|
998
|
-
"StopProfiling expects a non empty string as first argument.");
|
|
999
|
-
|
|
1000
|
-
napi_value napi_null;
|
|
1001
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1002
|
-
|
|
1003
|
-
return napi_null;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// Verify the second argument is a number
|
|
1007
|
-
napi_valuetype callbacktype1;
|
|
1008
|
-
assert(napi_typeof(env, argv[1], &callbacktype1) == napi_ok);
|
|
1009
|
-
|
|
1010
|
-
if (callbacktype1 != napi_number) {
|
|
1011
|
-
napi_throw_error(env, "NAPI_ERROR",
|
|
1012
|
-
"StopProfiling expects a format type as second argument.");
|
|
1013
|
-
|
|
1014
|
-
napi_value napi_null;
|
|
1015
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1016
|
-
|
|
1017
|
-
return napi_null;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
// Verify the second argument is a number
|
|
1021
|
-
napi_valuetype callbacktype2;
|
|
1022
|
-
assert(napi_typeof(env, argv[2], &callbacktype2) == napi_ok);
|
|
1023
|
-
|
|
1024
|
-
if (callbacktype2 != napi_number) {
|
|
1025
|
-
napi_throw_error(
|
|
1026
|
-
env, "NAPI_ERROR",
|
|
1027
|
-
"StopProfiling expects a thread_id integer as third argument.");
|
|
1028
|
-
|
|
1029
|
-
napi_value napi_null;
|
|
1030
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1031
|
-
return napi_null;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
// Get the value of the second argument and convert it to uint8
|
|
1035
|
-
int32_t format;
|
|
1036
|
-
assert(napi_get_value_int32(env, argv[1], &format) == napi_ok);
|
|
1037
|
-
|
|
1038
|
-
// Get the value of the second argument and convert it to uint64
|
|
1039
|
-
int64_t thread_id;
|
|
1040
|
-
assert(napi_get_value_int64(env, argv[2], &thread_id) == napi_ok);
|
|
1041
|
-
|
|
1042
|
-
// Get profiler from instance data
|
|
1043
|
-
Profiler *profiler;
|
|
1044
|
-
assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok);
|
|
1045
|
-
|
|
1046
|
-
if (!profiler) {
|
|
1047
|
-
napi_throw_error(env, "NAPI_ERROR",
|
|
1048
|
-
"StopProfiling: Profiler is not initialized.");
|
|
1049
|
-
|
|
1050
|
-
napi_value napi_null;
|
|
1051
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1052
|
-
return napi_null;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
const std::string profile_id(title);
|
|
1056
|
-
auto profile = profiler->active_profiles.find(profile_id);
|
|
1057
|
-
|
|
1058
|
-
// If the profile was never started, silently ignore the call and return null
|
|
1059
|
-
if (profile == profiler->active_profiles.end()) {
|
|
1060
|
-
napi_value napi_null;
|
|
1061
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1062
|
-
return napi_null;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
v8::CpuProfile *cpu_profile = profile->second->Stop(profiler);
|
|
1066
|
-
|
|
1067
|
-
// If for some reason stopProfiling was called with an invalid profile title
|
|
1068
|
-
// or if that title had somehow been stopped already, profile will be null.
|
|
1069
|
-
if (!cpu_profile) {
|
|
1070
|
-
CleanupSentryProfile(profiler, profile->second, profile_id);
|
|
1071
|
-
|
|
1072
|
-
napi_value napi_null;
|
|
1073
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1074
|
-
return napi_null;
|
|
1075
|
-
};
|
|
1076
|
-
|
|
1077
|
-
napi_valuetype callbacktype3;
|
|
1078
|
-
assert(napi_typeof(env, argv[3], &callbacktype3) == napi_ok);
|
|
1079
|
-
|
|
1080
|
-
bool collect_resources;
|
|
1081
|
-
napi_get_value_bool(env, argv[3], &collect_resources);
|
|
1082
|
-
|
|
1083
|
-
const ProfileFormat format_type = static_cast<ProfileFormat>(format);
|
|
1084
|
-
|
|
1085
|
-
if (format_type != ProfileFormat::kFormatThread &&
|
|
1086
|
-
format_type != ProfileFormat::kFormatChunk) {
|
|
1087
|
-
napi_throw_error(
|
|
1088
|
-
env, "NAPI_ERROR",
|
|
1089
|
-
"StopProfiling expects a valid format type as second argument.");
|
|
1090
|
-
|
|
1091
|
-
napi_value napi_null;
|
|
1092
|
-
assert(napi_get_null(env, &napi_null) == napi_ok);
|
|
1093
|
-
return napi_null;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
napi_value js_profile = TranslateProfile(
|
|
1097
|
-
env, cpu_profile, format_type, profile->second->profile_start_timestamp(),
|
|
1098
|
-
thread_id, collect_resources);
|
|
1099
|
-
|
|
1100
|
-
napi_value measurements;
|
|
1101
|
-
napi_create_object(env, &measurements);
|
|
1102
|
-
|
|
1103
|
-
if (profile->second->heap_usage_write_index() > 0) {
|
|
1104
|
-
static const char *memory_unit = "byte";
|
|
1105
|
-
napi_value heap_usage_measurements =
|
|
1106
|
-
TranslateMeasurements(env, format_type, memory_unit,
|
|
1107
|
-
profile->second->profile_start_timestamp(),
|
|
1108
|
-
profile->second->heap_usage_write_index(),
|
|
1109
|
-
profile->second->heap_usage_values(),
|
|
1110
|
-
profile->second->heap_usage_timestamps());
|
|
1111
|
-
|
|
1112
|
-
if (heap_usage_measurements != nullptr) {
|
|
1113
|
-
napi_set_named_property(env, measurements, "memory_footprint",
|
|
1114
|
-
heap_usage_measurements);
|
|
1115
|
-
};
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
if (profile->second->cpu_usage_write_index() > 0) {
|
|
1119
|
-
static const char *cpu_unit = "percent";
|
|
1120
|
-
napi_value cpu_usage_measurements = TranslateMeasurementsDouble(
|
|
1121
|
-
env, format_type, cpu_unit, profile->second->profile_start_timestamp(),
|
|
1122
|
-
profile->second->cpu_usage_write_index(),
|
|
1123
|
-
profile->second->cpu_usage_values(),
|
|
1124
|
-
profile->second->cpu_usage_timestamps());
|
|
1125
|
-
|
|
1126
|
-
if (cpu_usage_measurements != nullptr) {
|
|
1127
|
-
napi_set_named_property(env, measurements, "cpu_usage",
|
|
1128
|
-
cpu_usage_measurements);
|
|
1129
|
-
};
|
|
1130
|
-
};
|
|
1131
|
-
|
|
1132
|
-
napi_set_named_property(env, js_profile, "measurements", measurements);
|
|
1133
|
-
|
|
1134
|
-
CleanupSentryProfile(profiler, profile->second, profile_id);
|
|
1135
|
-
cpu_profile->Delete();
|
|
1136
|
-
|
|
1137
|
-
return js_profile;
|
|
1138
|
-
};
|
|
1139
|
-
|
|
1140
|
-
void FreeAddonData(napi_env env, void *data, void *hint) {
|
|
1141
|
-
Profiler *profiler = static_cast<Profiler *>(data);
|
|
1142
|
-
|
|
1143
|
-
if (profiler == nullptr) {
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
if (!profiler->active_profiles.empty()) {
|
|
1148
|
-
for (auto &profile : profiler->active_profiles) {
|
|
1149
|
-
CleanupSentryProfile(profiler, profile.second, profile.first);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
if (profiler->cpu_profiler != nullptr) {
|
|
1154
|
-
profiler->cpu_profiler->Dispose();
|
|
1155
|
-
profiler->cpu_profiler = nullptr;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
delete profiler;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
napi_value Init(napi_env env, napi_value exports) {
|
|
1162
|
-
v8::Isolate *isolate = v8::Isolate::GetCurrent();
|
|
1163
|
-
|
|
1164
|
-
if (isolate == nullptr) {
|
|
1165
|
-
napi_throw_error(env, nullptr,
|
|
1166
|
-
"Failed to initialize Sentry profiler: isolate is null.");
|
|
1167
|
-
return NULL;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
Profiler *profiler = new Profiler(env, isolate);
|
|
1171
|
-
profiler->cpu_profiler->SetSamplingInterval(kSamplingInterval);
|
|
1172
|
-
|
|
1173
|
-
if (napi_set_instance_data(env, profiler, FreeAddonData, NULL) != napi_ok) {
|
|
1174
|
-
napi_throw_error(env, nullptr, "Failed to set instance data for profiler.");
|
|
1175
|
-
return NULL;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
napi_value start_profiling;
|
|
1179
|
-
if (napi_create_function(env, "startProfiling", NAPI_AUTO_LENGTH,
|
|
1180
|
-
StartProfiling, exports,
|
|
1181
|
-
&start_profiling) != napi_ok) {
|
|
1182
|
-
napi_throw_error(env, nullptr, "Failed to create startProfiling function.");
|
|
1183
|
-
return NULL;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
if (napi_set_named_property(env, exports, "startProfiling",
|
|
1187
|
-
start_profiling) != napi_ok) {
|
|
1188
|
-
napi_throw_error(env, nullptr,
|
|
1189
|
-
"Failed to set startProfiling property on exports.");
|
|
1190
|
-
return NULL;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
napi_value stop_profiling;
|
|
1194
|
-
if (napi_create_function(env, "stopProfiling", NAPI_AUTO_LENGTH,
|
|
1195
|
-
StopProfiling, exports,
|
|
1196
|
-
&stop_profiling) != napi_ok) {
|
|
1197
|
-
napi_throw_error(env, nullptr, "Failed to create stopProfiling function.");
|
|
1198
|
-
return NULL;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
if (napi_set_named_property(env, exports, "stopProfiling", stop_profiling) !=
|
|
1202
|
-
napi_ok) {
|
|
1203
|
-
napi_throw_error(env, nullptr,
|
|
1204
|
-
"Failed to set stopProfiling property on exports.");
|
|
1205
|
-
return NULL;
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
napi_value get_frame_module;
|
|
1209
|
-
if (napi_create_function(env, "getFrameModule", NAPI_AUTO_LENGTH,
|
|
1210
|
-
GetFrameModuleWrapped, exports,
|
|
1211
|
-
&get_frame_module) != napi_ok) {
|
|
1212
|
-
napi_throw_error(env, nullptr, "Failed to create getFrameModule function.");
|
|
1213
|
-
return NULL;
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
if (napi_set_named_property(env, exports, "getFrameModule",
|
|
1217
|
-
get_frame_module) != napi_ok) {
|
|
1218
|
-
napi_throw_error(env, nullptr,
|
|
1219
|
-
"Failed to set getFrameModule property on exports.");
|
|
1220
|
-
return NULL;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
return exports;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|