@sentry/profiling-node 8.9.2 → 8.11.0

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 (41) hide show
  1. package/bindings/cpu_profiler.cc +7 -5
  2. package/lib/cjs/index.js +83 -33
  3. package/lib/cjs/index.js.map +1 -1
  4. package/lib/esm/index.js +85 -35
  5. package/lib/esm/index.js.map +1 -1
  6. package/lib/sentry_cpu_profiler-darwin-arm64-108.node +0 -0
  7. package/lib/sentry_cpu_profiler-darwin-arm64-115.node +0 -0
  8. package/lib/sentry_cpu_profiler-darwin-arm64-127.node +0 -0
  9. package/lib/sentry_cpu_profiler-darwin-arm64-93.node +0 -0
  10. package/lib/sentry_cpu_profiler-darwin-x64-108.node +0 -0
  11. package/lib/sentry_cpu_profiler-darwin-x64-115.node +0 -0
  12. package/lib/sentry_cpu_profiler-darwin-x64-127.node +0 -0
  13. package/lib/sentry_cpu_profiler-darwin-x64-93.node +0 -0
  14. package/lib/sentry_cpu_profiler-linux-arm64-glibc-108.node +0 -0
  15. package/lib/sentry_cpu_profiler-linux-arm64-glibc-115.node +0 -0
  16. package/lib/sentry_cpu_profiler-linux-arm64-glibc-127.node +0 -0
  17. package/lib/sentry_cpu_profiler-linux-arm64-glibc-93.node +0 -0
  18. package/lib/sentry_cpu_profiler-linux-arm64-musl-108.node +0 -0
  19. package/lib/sentry_cpu_profiler-linux-arm64-musl-115.node +0 -0
  20. package/lib/sentry_cpu_profiler-linux-arm64-musl-127.node +0 -0
  21. package/lib/sentry_cpu_profiler-linux-arm64-musl-93.node +0 -0
  22. package/lib/sentry_cpu_profiler-linux-x64-glibc-108.node +0 -0
  23. package/lib/sentry_cpu_profiler-linux-x64-glibc-115.node +0 -0
  24. package/lib/sentry_cpu_profiler-linux-x64-glibc-127.node +0 -0
  25. package/lib/sentry_cpu_profiler-linux-x64-glibc-93.node +0 -0
  26. package/lib/sentry_cpu_profiler-linux-x64-musl-108.node +0 -0
  27. package/lib/sentry_cpu_profiler-linux-x64-musl-115.node +0 -0
  28. package/lib/sentry_cpu_profiler-linux-x64-musl-127.node +0 -0
  29. package/lib/sentry_cpu_profiler-linux-x64-musl-93.node +0 -0
  30. package/lib/sentry_cpu_profiler-win32-x64-108.node +0 -0
  31. package/lib/sentry_cpu_profiler-win32-x64-115.node +0 -0
  32. package/lib/sentry_cpu_profiler-win32-x64-127.node +0 -0
  33. package/lib/sentry_cpu_profiler-win32-x64-93.node +0 -0
  34. package/lib/types/integration.d.ts +18 -0
  35. package/lib/types/integration.d.ts.map +1 -1
  36. package/lib/types/spanProfileUtils.d.ts.map +1 -1
  37. package/lib/types/utils.d.ts +3 -1
  38. package/lib/types/utils.d.ts.map +1 -1
  39. package/lib/types-ts3.8/integration.d.ts +18 -0
  40. package/lib/types-ts3.8/utils.d.ts +3 -1
  41. package/package.json +5 -5
@@ -546,7 +546,8 @@ CreateFrameNode(const napi_env &env, const v8::CpuProfileNode &node,
546
546
  };
547
547
 
548
548
  napi_value CreateSample(const napi_env &env, const enum ProfileFormat format,
549
- const uint32_t stack_id, const int64_t sample_timestamp,
549
+ const uint32_t stack_id,
550
+ const int64_t sample_timestamp_ns,
550
551
  const double chunk_timestamp,
551
552
  const uint32_t thread_id) {
552
553
  napi_value js_node;
@@ -564,7 +565,7 @@ napi_value CreateSample(const napi_env &env, const enum ProfileFormat format,
564
565
  switch (format) {
565
566
  case ProfileFormat::kFormatThread: {
566
567
  napi_value timestamp;
567
- napi_create_int64(env, sample_timestamp, &timestamp);
568
+ napi_create_int64(env, sample_timestamp_ns, &timestamp);
568
569
  napi_set_named_property(env, js_node, "elapsed_since_start_ns", timestamp);
569
570
  } break;
570
571
  case ProfileFormat::kFormatChunk: {
@@ -643,7 +644,8 @@ static void GetSamples(const napi_env &env, const v8::CpuProfile *profile,
643
644
  uint64_t sample_offset_from_profile_start_ms =
644
645
  (sample_timestamp_us - profile_start_time_us) * 1e-3;
645
646
  double seconds_since_start =
646
- profile_start_timestamp_ms + sample_offset_from_profile_start_ms;
647
+ (profile_start_timestamp_ms + sample_offset_from_profile_start_ms) *
648
+ 1e-3;
647
649
 
648
650
  napi_value sample = nullptr;
649
651
  sample = CreateSample(env, format, stack_index, sample_timestamp_ns,
@@ -759,7 +761,7 @@ static napi_value TranslateMeasurementsDouble(
759
761
  } else if (format == ProfileFormat::kFormatChunk) {
760
762
  napi_value ts;
761
763
  napi_create_double(
762
- env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-6), &ts);
764
+ env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts);
763
765
  napi_set_named_property(env, entry, "timestamp", ts);
764
766
  }
765
767
 
@@ -818,7 +820,7 @@ TranslateMeasurements(const napi_env &env, const enum ProfileFormat format,
818
820
  case ProfileFormat::kFormatChunk: {
819
821
  napi_value ts;
820
822
  napi_create_double(
821
- env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-6), &ts);
823
+ env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts);
822
824
  napi_set_named_property(env, entry, "timestamp", ts);
823
825
  } break;
824
826
  default:
package/lib/cjs/index.js CHANGED
@@ -202,12 +202,12 @@ const CpuProfilerBindings = new Bindings();
202
202
  const NODE_VERSION = utils.parseSemver(process.versions.node) ;
203
203
  const NODE_MAJOR = NODE_VERSION.major;
204
204
 
205
- function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/* eslint-disable max-lines */
205
+ function _nullishCoalesce$1(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/* eslint-disable max-lines */
206
206
 
207
207
  // We require the file because if we import it, it will be included in the bundle.
208
208
  // I guess tsc does not check file contents when it's imported.
209
- const THREAD_ID_STRING = String(worker_threads.threadId);
210
- const THREAD_NAME = worker_threads.isMainThread ? 'main' : 'worker';
209
+ const PROFILER_THREAD_ID_STRING = String(worker_threads.threadId);
210
+ const PROFILER_THREAD_NAME = worker_threads.isMainThread ? 'main' : 'worker';
211
211
  const FORMAT_VERSION = '1';
212
212
  const CONTINUOUS_FORMAT_VERSION = '2';
213
213
 
@@ -253,8 +253,8 @@ function enrichWithThreadInformation(
253
253
  frames: profile.frames,
254
254
  stacks: profile.stacks,
255
255
  thread_metadata: {
256
- [THREAD_ID_STRING]: {
257
- name: THREAD_NAME,
256
+ [PROFILER_THREAD_ID_STRING]: {
257
+ name: PROFILER_THREAD_NAME,
258
258
  },
259
259
  },
260
260
  } ;
@@ -272,12 +272,12 @@ function createProfilingEvent(client, profile, event) {
272
272
  }
273
273
 
274
274
  return createProfilePayload(client, profile, {
275
- release: _nullishCoalesce(event.release, () => ( '')),
276
- environment: _nullishCoalesce(event.environment, () => ( '')),
277
- event_id: _nullishCoalesce(event.event_id, () => ( '')),
278
- transaction: _nullishCoalesce(event.transaction, () => ( '')),
275
+ release: _nullishCoalesce$1(event.release, () => ( '')),
276
+ environment: _nullishCoalesce$1(event.environment, () => ( '')),
277
+ event_id: _nullishCoalesce$1(event.event_id, () => ( '')),
278
+ transaction: _nullishCoalesce$1(event.transaction, () => ( '')),
279
279
  start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),
280
- trace_id: _nullishCoalesce(_optionalChain$1([event, 'access', _ => _.contexts, 'optionalAccess', _2 => _2['trace'], 'optionalAccess', _3 => _3['trace_id']]), () => ( '')),
280
+ trace_id: _nullishCoalesce$1(_optionalChain$1([event, 'access', _ => _.contexts, 'optionalAccess', _2 => _2['trace'], 'optionalAccess', _3 => _3['trace_id']]), () => ( '')),
281
281
  profile_id: profile.profile_id,
282
282
  });
283
283
  }
@@ -344,7 +344,7 @@ function createProfilePayload(
344
344
  name: transaction,
345
345
  id: event_id,
346
346
  trace_id: trace_id || '',
347
- active_thread_id: THREAD_ID_STRING,
347
+ active_thread_id: PROFILER_THREAD_ID_STRING,
348
348
  },
349
349
  };
350
350
 
@@ -363,7 +363,6 @@ function createProfileChunkPayload(
363
363
  {
364
364
  release,
365
365
  environment,
366
- start_timestamp,
367
366
  trace_id,
368
367
  profiler_id,
369
368
  chunk_id,
@@ -383,7 +382,6 @@ function createProfileChunkPayload(
383
382
  const profile = {
384
383
  chunk_id: chunk_id,
385
384
  profiler_id: profiler_id,
386
- timestamp: new Date(start_timestamp).toISOString(),
387
385
  platform: 'node',
388
386
  version: CONTINUOUS_FORMAT_VERSION,
389
387
  release: release,
@@ -402,7 +400,6 @@ function createProfileChunkPayload(
402
400
  * Creates a profiling chunk envelope item, if the profile does not pass validation, returns null.
403
401
  */
404
402
  function createProfilingChunkEvent(
405
- start_timestamp,
406
403
  client,
407
404
  options,
408
405
  profile,
@@ -413,10 +410,9 @@ function createProfilingChunkEvent(
413
410
  }
414
411
 
415
412
  return createProfileChunkPayload(client, profile, {
416
- release: _nullishCoalesce(options.release, () => ( '')),
417
- environment: _nullishCoalesce(options.environment, () => ( '')),
418
- start_timestamp: start_timestamp,
419
- trace_id: _nullishCoalesce(identifiers.trace_id, () => ( '')),
413
+ release: _nullishCoalesce$1(options.release, () => ( '')),
414
+ environment: _nullishCoalesce$1(options.environment, () => ( '')),
415
+ trace_id: _nullishCoalesce$1(identifiers.trace_id, () => ( '')),
420
416
  chunk_id: identifiers.chunk_id,
421
417
  profiler_id: identifiers.profiler_id,
422
418
  });
@@ -753,7 +749,6 @@ function stopSpanProfile(span, profile_id) {
753
749
  }
754
750
 
755
751
  const profile = CpuProfilerBindings.stopProfiling(profile_id, 0);
756
-
757
752
  DEBUG_BUILD && utils.logger.log(`[Profiling] stopped profiling of transaction: ${core.spanToJSON(span).description}`);
758
753
 
759
754
  // In case of an overlapping span, stopProfiling may return null and silently ignore the overlapping profile.
@@ -962,8 +957,10 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
962
957
  utils.logger.log(`[Profiling] Failed to collect profile for: ${_optionalChain([this, 'access', _10 => _10._chunkData, 'optionalAccess', _11 => _11.id])}, the chunk_id is missing.`);
963
958
  return;
964
959
  }
965
- const profile = CpuProfilerBindings.stopProfiling(this._chunkData.id, ProfileFormat.CHUNK);
966
- if (!profile || !this._chunkData.startTimestampMS) {
960
+
961
+ const profile = this._stopChunkProfiling(this._chunkData);
962
+
963
+ if (!profile) {
967
964
  DEBUG_BUILD && utils.logger.log(`[Profiling] _chunkiledStartTraceID to collect profile for: ${this._chunkData.id}`);
968
965
  return;
969
966
  }
@@ -972,17 +969,11 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
972
969
  }
973
970
 
974
971
  DEBUG_BUILD && utils.logger.log(`[Profiling] Profile chunk ${this._chunkData.id} sent to Sentry.`);
975
- const chunk = createProfilingChunkEvent(
976
- this._chunkData.startTimestampMS,
977
- this._client,
978
- this._client.getOptions(),
979
- profile,
980
- {
981
- chunk_id: this._chunkData.id,
982
- trace_id: this._chunkData.startTraceID,
983
- profiler_id: this._profilerId,
984
- },
985
- );
972
+ const chunk = createProfilingChunkEvent(this._client, this._client.getOptions(), profile, {
973
+ chunk_id: this._chunkData.id,
974
+ trace_id: this._chunkData.startTraceID,
975
+ profiler_id: this._profilerId,
976
+ });
986
977
 
987
978
  if (!chunk) {
988
979
  DEBUG_BUILD && utils.logger.log(`[Profiling] Failed to create profile chunk for: ${this._chunkData.id}`);
@@ -1025,11 +1016,21 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
1025
1016
  });
1026
1017
  }
1027
1018
 
1019
+ /**
1020
+ * Stops the profile and clears chunk instrumentation from global scope
1021
+ * @returns void
1022
+ */
1023
+ _stopChunkProfiling(chunk) {
1024
+ this._teardownSpanChunkInstrumentation();
1025
+ return CpuProfilerBindings.stopProfiling(chunk.id, ProfileFormat.CHUNK);
1026
+ }
1027
+
1028
1028
  /**
1029
1029
  * Starts the profiler and registers the flush timer for a given chunk.
1030
1030
  * @param chunk
1031
1031
  */
1032
1032
  _startChunkProfiling(chunk) {
1033
+ this._setupSpanChunkInstrumentation();
1033
1034
  CpuProfilerBindings.startProfiling(chunk.id);
1034
1035
  DEBUG_BUILD && utils.logger.log(`[Profiling] starting profiling chunk: ${chunk.id}`);
1035
1036
 
@@ -1044,6 +1045,32 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
1044
1045
  chunk.timer.unref();
1045
1046
  }
1046
1047
 
1048
+ /**
1049
+ * Attaches profiling information to spans that were started
1050
+ * during a profiling session.
1051
+ */
1052
+ _setupSpanChunkInstrumentation() {
1053
+ if (!this._client) {
1054
+ DEBUG_BUILD &&
1055
+ utils.logger.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.');
1056
+ return;
1057
+ }
1058
+
1059
+ core.getGlobalScope().setContext('profile', {
1060
+ profiler_id: this._profilerId,
1061
+ });
1062
+
1063
+ this._client.on('beforeSendEvent', e => this._assignThreadIdContext(e));
1064
+ }
1065
+
1066
+ /**
1067
+ * Clear profiling information from global context when a profile is not running.
1068
+ */
1069
+ _teardownSpanChunkInstrumentation() {
1070
+ const globalScope = core.getGlobalScope();
1071
+ globalScope.setContext('profile', {});
1072
+ }
1073
+
1047
1074
  /**
1048
1075
  * Initializes new profile chunk metadata
1049
1076
  */
@@ -1051,11 +1078,34 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
1051
1078
  this._chunkData = {
1052
1079
  id: utils.uuid4(),
1053
1080
  startTraceID: traceId,
1054
- startTimestampMS: utils.timestampInSeconds(),
1055
1081
  timer: undefined,
1056
1082
  };
1057
1083
  }
1058
1084
 
1085
+ /**
1086
+ * Assigns thread_id and thread name context to a profiled event.
1087
+ */
1088
+ _assignThreadIdContext(event) {
1089
+ if (!_optionalChain([event, 'optionalAccess', _13 => _13['contexts'], 'optionalAccess', _14 => _14['profile']])) {
1090
+ return;
1091
+ }
1092
+
1093
+ if (!event.contexts) {
1094
+ return;
1095
+ }
1096
+
1097
+ // @ts-expect-error the trace fallback value is wrong, though it should never happen
1098
+ // and in case it does, we dont want to override whatever was passed initially.
1099
+ event.contexts['trace'] = {
1100
+ ...(_nullishCoalesce(_optionalChain([event, 'access', _15 => _15.contexts, 'optionalAccess', _16 => _16['trace']]), () => ( {}))),
1101
+ data: {
1102
+ ...(_nullishCoalesce(_optionalChain([event, 'access', _17 => _17.contexts, 'optionalAccess', _18 => _18['trace'], 'optionalAccess', _19 => _19['data']]), () => ( {}))),
1103
+ ['thread.id']: PROFILER_THREAD_ID_STRING,
1104
+ ['thread.name']: PROFILER_THREAD_NAME,
1105
+ },
1106
+ };
1107
+ }
1108
+
1059
1109
  /**
1060
1110
  * Resets the current chunk state.
1061
1111
  */