@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.
Files changed (47) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -3
  3. package/lib/cjs/index.js +45 -260
  4. package/lib/cjs/index.js.map +1 -1
  5. package/lib/esm/index.js +46 -255
  6. package/lib/esm/index.js.map +1 -1
  7. package/lib/esm/package.json +1 -1
  8. package/lib/types/integration.d.ts.map +1 -1
  9. package/lib/types/spanProfileUtils.d.ts.map +1 -1
  10. package/lib/types/utils.d.ts.map +1 -1
  11. package/package.json +13 -32
  12. package/binding.gyp +0 -20
  13. package/bindings/cpu_profiler.cc +0 -1226
  14. package/lib/sentry_cpu_profiler-darwin-arm64-108.node +0 -0
  15. package/lib/sentry_cpu_profiler-darwin-arm64-115.node +0 -0
  16. package/lib/sentry_cpu_profiler-darwin-arm64-127.node +0 -0
  17. package/lib/sentry_cpu_profiler-darwin-arm64-93.node +0 -0
  18. package/lib/sentry_cpu_profiler-darwin-x64-108.node +0 -0
  19. package/lib/sentry_cpu_profiler-darwin-x64-115.node +0 -0
  20. package/lib/sentry_cpu_profiler-darwin-x64-127.node +0 -0
  21. package/lib/sentry_cpu_profiler-darwin-x64-93.node +0 -0
  22. package/lib/sentry_cpu_profiler-linux-arm64-glibc-108.node +0 -0
  23. package/lib/sentry_cpu_profiler-linux-arm64-glibc-115.node +0 -0
  24. package/lib/sentry_cpu_profiler-linux-arm64-glibc-127.node +0 -0
  25. package/lib/sentry_cpu_profiler-linux-arm64-glibc-93.node +0 -0
  26. package/lib/sentry_cpu_profiler-linux-arm64-musl-108.node +0 -0
  27. package/lib/sentry_cpu_profiler-linux-arm64-musl-115.node +0 -0
  28. package/lib/sentry_cpu_profiler-linux-arm64-musl-127.node +0 -0
  29. package/lib/sentry_cpu_profiler-linux-arm64-musl-93.node +0 -0
  30. package/lib/sentry_cpu_profiler-linux-x64-glibc-108.node +0 -0
  31. package/lib/sentry_cpu_profiler-linux-x64-glibc-115.node +0 -0
  32. package/lib/sentry_cpu_profiler-linux-x64-glibc-127.node +0 -0
  33. package/lib/sentry_cpu_profiler-linux-x64-glibc-93.node +0 -0
  34. package/lib/sentry_cpu_profiler-linux-x64-musl-108.node +0 -0
  35. package/lib/sentry_cpu_profiler-linux-x64-musl-115.node +0 -0
  36. package/lib/sentry_cpu_profiler-linux-x64-musl-127.node +0 -0
  37. package/lib/sentry_cpu_profiler-linux-x64-musl-93.node +0 -0
  38. package/lib/sentry_cpu_profiler-win32-x64-108.node +0 -0
  39. package/lib/sentry_cpu_profiler-win32-x64-115.node +0 -0
  40. package/lib/sentry_cpu_profiler-win32-x64-127.node +0 -0
  41. package/lib/sentry_cpu_profiler-win32-x64-93.node +0 -0
  42. package/lib/types/cpu_profiler.d.ts +0 -16
  43. package/lib/types/cpu_profiler.d.ts.map +0 -1
  44. package/lib/types-ts3.8/cpu_profiler.d.ts +0 -16
  45. package/scripts/binaries.js +0 -27
  46. package/scripts/check-build.js +0 -56
  47. package/scripts/copy-target.js +0 -27
@@ -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, &timestamp);
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, &timestamp);
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> &timestamps_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> &timestamps_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)