@sentry/profiling-node 9.7.0-alpha.0 → 9.8.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.
- package/lib/cjs/index.js +354 -150
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/index.js +355 -151
- 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 +1 -1
- package/lib/types/utils.d.ts.map +1 -1
- package/lib/types-ts3.8/utils.d.ts +1 -1
- package/package.json +3 -3
package/lib/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CpuProfilerBindings, ProfileFormat } from '@sentry-internal/node-cpu-profiler';
|
|
2
|
-
import { parseSemver,
|
|
2
|
+
import { parseSemver, forEachEnvelopeItem, createEnvelope, logger, dsnToString, uuid4, getDebugImagesForResources, spanIsSampled, spanToJSON, LRUMap, defineIntegration, consoleSandbox, getRootSpan, getIsolationScope, getCurrentScope, getGlobalScope } from '@sentry/core';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import { env, versions } from 'process';
|
|
5
5
|
import { isMainThread, threadId } from 'worker_threads';
|
|
@@ -373,6 +373,7 @@ function createEventEnvelopeHeaders(
|
|
|
373
373
|
* Creates a standalone profile_chunk envelope.
|
|
374
374
|
*/
|
|
375
375
|
function makeProfileChunkEnvelope(
|
|
376
|
+
platform,
|
|
376
377
|
chunk,
|
|
377
378
|
sdkInfo,
|
|
378
379
|
tunnel,
|
|
@@ -380,6 +381,7 @@ function makeProfileChunkEnvelope(
|
|
|
380
381
|
) {
|
|
381
382
|
const profileChunkHeader = {
|
|
382
383
|
type: 'profile_chunk',
|
|
384
|
+
platform,
|
|
383
385
|
};
|
|
384
386
|
|
|
385
387
|
return createEnvelope(createEventEnvelopeHeaders(sdkInfo, tunnel, dsn), [
|
|
@@ -402,6 +404,8 @@ function applyDebugMetadata(client, resource_paths) {
|
|
|
402
404
|
return getDebugImagesForResources(options.stackParser, resource_paths);
|
|
403
405
|
}
|
|
404
406
|
|
|
407
|
+
/* eslint-disable deprecation/deprecation */
|
|
408
|
+
|
|
405
409
|
const MAX_PROFILE_DURATION_MS = 30 * 1000;
|
|
406
410
|
|
|
407
411
|
/**
|
|
@@ -538,174 +542,361 @@ function takeFromProfileQueue(profile_id) {
|
|
|
538
542
|
return profile;
|
|
539
543
|
}
|
|
540
544
|
|
|
541
|
-
|
|
542
|
-
* Instruments the client to automatically invoke the profiler on span start and stop events.
|
|
543
|
-
* @param client
|
|
544
|
-
*/
|
|
545
|
-
function setupAutomatedSpanProfiling(client) {
|
|
546
|
-
const spanToProfileIdMap = new WeakMap();
|
|
547
|
-
|
|
548
|
-
client.on('spanStart', span => {
|
|
549
|
-
if (span !== getRootSpan(span)) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const profile_id = maybeProfileSpan(client, span);
|
|
545
|
+
class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.call(this);ContinuousProfiler.prototype.__init2.call(this);ContinuousProfiler.prototype.__init3.call(this);ContinuousProfiler.prototype.__init4.call(this);ContinuousProfiler.prototype.__init5.call(this);ContinuousProfiler.prototype.__init6.call(this);ContinuousProfiler.prototype.__init7.call(this); }
|
|
554
546
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
547
|
+
__init() {this._client = undefined;}
|
|
548
|
+
__init2() {this._chunkData = undefined;}
|
|
549
|
+
__init3() {this._mode = undefined;}
|
|
550
|
+
__init4() {this._legacyProfilerMode = undefined;}
|
|
551
|
+
__init5() {this._profileLifecycle = undefined;}
|
|
552
|
+
__init6() {this._sampled = undefined;}
|
|
553
|
+
__init7() {this._sessionSamplingRate = undefined;}
|
|
554
|
+
/**
|
|
555
|
+
* Called when the profiler is attached to the client (continuous mode is enabled). If of the profiler
|
|
556
|
+
* methods called before the profiler is initialized will result in a noop action with debug logs.
|
|
557
|
+
* @param client
|
|
558
|
+
*/
|
|
559
|
+
initialize(client) {
|
|
560
|
+
this._client = client;
|
|
561
|
+
const options = client.getOptions();
|
|
562
|
+
|
|
563
|
+
this._mode = getProfilingMode(options);
|
|
564
|
+
this._sessionSamplingRate = Math.random();
|
|
565
|
+
this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0);
|
|
566
|
+
this._profileLifecycle = options.profileLifecycle ?? 'manual';
|
|
567
|
+
|
|
568
|
+
switch (this._mode) {
|
|
569
|
+
case 'legacy': {
|
|
570
|
+
this._legacyProfilerMode =
|
|
571
|
+
'profilesSampleRate' in options || 'profilesSampler' in options ? 'span' : 'continuous';
|
|
572
|
+
|
|
573
|
+
DEBUG_BUILD && logger.log(`[Profiling] Profiling mode is ${this._legacyProfilerMode}.`);
|
|
574
|
+
|
|
575
|
+
switch (this._legacyProfilerMode) {
|
|
576
|
+
case 'span': {
|
|
577
|
+
this._setupAutomaticSpanProfiling();
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
case 'continuous': {
|
|
581
|
+
// Continous mode requires manual calls to profiler.start() and profiler.stop()
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
default: {
|
|
585
|
+
DEBUG_BUILD &&
|
|
586
|
+
logger.warn(
|
|
587
|
+
`[Profiling] Unknown profiler mode: ${this._legacyProfilerMode}, profiler was not initialized`,
|
|
588
|
+
);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
560
594
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
595
|
+
case 'current': {
|
|
596
|
+
this._setupSpanChunkInstrumentation();
|
|
597
|
+
|
|
598
|
+
DEBUG_BUILD && logger.log(`[Profiling] Profiling mode is ${this._profileLifecycle}.`);
|
|
599
|
+
|
|
600
|
+
switch (this._profileLifecycle) {
|
|
601
|
+
case 'trace': {
|
|
602
|
+
this._startTraceLifecycleProfiling();
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case 'manual': {
|
|
606
|
+
// Manual mode requires manual calls to profiler.startProfiler() and profiler.stopProfiler()
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
default: {
|
|
610
|
+
DEBUG_BUILD &&
|
|
611
|
+
logger.warn(`[Profiling] Unknown profiler mode: ${this._profileLifecycle}, profiler was not initialized`);
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
565
616
|
}
|
|
617
|
+
default: {
|
|
618
|
+
DEBUG_BUILD && logger.warn(`[Profiling] Unknown profiler mode: ${this._mode}, profiler was not initialized`);
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Attaches a listener to beforeSend which will add the threadId data to the event being sent.
|
|
624
|
+
// This adds a constant overhead to all events being sent which could be improved to only attach
|
|
625
|
+
// and detach the listener during a profiler session
|
|
626
|
+
this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this));
|
|
627
|
+
}
|
|
566
628
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
629
|
+
/**
|
|
630
|
+
* Initializes a new profilerId session and schedules chunk profiling.
|
|
631
|
+
* @returns void
|
|
632
|
+
*/
|
|
633
|
+
start() {
|
|
634
|
+
if (this._mode === 'current') {
|
|
635
|
+
this._startProfiler();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
571
638
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}, maxProfileDurationMs);
|
|
639
|
+
if (!this._client) {
|
|
640
|
+
DEBUG_BUILD && logger.log('[Profiling] Failed to start, sentry client was never attached to the profiler.');
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
577
643
|
|
|
578
|
-
|
|
579
|
-
|
|
644
|
+
if (this._mode !== 'legacy') {
|
|
645
|
+
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.');
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
580
648
|
|
|
581
|
-
|
|
582
|
-
|
|
649
|
+
if (this._legacyProfilerMode === 'span') {
|
|
650
|
+
DEBUG_BUILD && logger.log('[Profiling] Calls to profiler.start() are not supported in span profiling mode.');
|
|
651
|
+
return;
|
|
583
652
|
}
|
|
584
|
-
});
|
|
585
653
|
|
|
586
|
-
|
|
587
|
-
const profile_id = spanToProfileIdMap.get(span);
|
|
654
|
+
// Flush any existing chunks before starting a new one.
|
|
588
655
|
|
|
589
|
-
|
|
590
|
-
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
591
|
-
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
592
|
-
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
593
|
-
delete PROFILE_TIMEOUTS[profile_id];
|
|
594
|
-
}
|
|
595
|
-
const profile = stopSpanProfile(span, profile_id);
|
|
656
|
+
this._stopChunkProfiling();
|
|
596
657
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
658
|
+
// Restart the profiler session
|
|
659
|
+
this._setupSpanChunkInstrumentation();
|
|
660
|
+
this._restartChunkProfiling();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Stops the current chunk and flushes the profile to Sentry.
|
|
665
|
+
* @returns void
|
|
666
|
+
*/
|
|
667
|
+
stop() {
|
|
668
|
+
if (this._mode === 'current') {
|
|
669
|
+
this._stopProfiler();
|
|
670
|
+
return;
|
|
600
671
|
}
|
|
601
|
-
});
|
|
602
672
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (!PROFILE_MAP.size) {
|
|
673
|
+
if (!this._client) {
|
|
674
|
+
DEBUG_BUILD && logger.log('[Profiling] Failed to stop, sentry client was never attached to the profiler.');
|
|
606
675
|
return;
|
|
607
676
|
}
|
|
608
677
|
|
|
609
|
-
|
|
610
|
-
|
|
678
|
+
if (this._mode !== 'legacy') {
|
|
679
|
+
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.');
|
|
611
680
|
return;
|
|
612
681
|
}
|
|
613
682
|
|
|
614
|
-
|
|
683
|
+
if (this._legacyProfilerMode === 'span') {
|
|
684
|
+
DEBUG_BUILD && logger.log('[Profiling] Calls to profiler.stop() are not supported in span profiling mode.');
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
615
687
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
688
|
+
this._stopChunkProfiling();
|
|
689
|
+
this._teardownSpanChunkInstrumentation();
|
|
690
|
+
}
|
|
619
691
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
692
|
+
_startProfiler() {
|
|
693
|
+
if (this._mode !== 'current') {
|
|
694
|
+
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
623
697
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
698
|
+
if (this._chunkData !== undefined) {
|
|
699
|
+
DEBUG_BUILD && logger.log('[Profiling] Profile session already running, no-op.');
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
628
702
|
|
|
629
|
-
|
|
630
|
-
if (!
|
|
631
|
-
DEBUG_BUILD && logger.log(
|
|
632
|
-
|
|
703
|
+
if (this._mode === 'current') {
|
|
704
|
+
if (!this._sampled) {
|
|
705
|
+
DEBUG_BUILD && logger.log('[Profiling] Profile session not sampled, no-op.');
|
|
706
|
+
return;
|
|
633
707
|
}
|
|
708
|
+
}
|
|
634
709
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
client.emit('preprocessEvent', profile, {
|
|
642
|
-
event_id: profiledTransaction.event_id,
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
// @ts-expect-error profile does not inherit from Event
|
|
646
|
-
client.emit('postprocessEvent', profile, {
|
|
647
|
-
event_id: profiledTransaction.event_id,
|
|
648
|
-
});
|
|
710
|
+
if (this._profileLifecycle === 'trace') {
|
|
711
|
+
DEBUG_BUILD &&
|
|
712
|
+
logger.log(
|
|
713
|
+
'[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.',
|
|
714
|
+
);
|
|
715
|
+
return;
|
|
649
716
|
}
|
|
650
717
|
|
|
651
|
-
|
|
652
|
-
}
|
|
653
|
-
}
|
|
718
|
+
this._startChunkProfiling();
|
|
719
|
+
}
|
|
654
720
|
|
|
655
|
-
|
|
721
|
+
_stopProfiler() {
|
|
722
|
+
if (this._mode !== 'current') {
|
|
723
|
+
DEBUG_BUILD && logger.log('[Profiling] Continuous profiling is not supported in the current mode.');
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
656
726
|
|
|
657
|
-
|
|
658
|
-
|
|
727
|
+
if (this._profileLifecycle === 'trace') {
|
|
728
|
+
DEBUG_BUILD &&
|
|
729
|
+
logger.log(
|
|
730
|
+
'[Profiling] You are using the trace profile lifecycle, manual calls to profiler.startProfiler() and profiler.stopProfiler() will be ignored.',
|
|
731
|
+
);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
659
734
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
*/
|
|
665
|
-
initialize(client) {
|
|
666
|
-
this._client = client;
|
|
735
|
+
if (!this._chunkData) {
|
|
736
|
+
DEBUG_BUILD && logger.log('[Profiling] No profile session running, no-op.');
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
667
739
|
|
|
668
|
-
|
|
669
|
-
// This adds a constant overhead to all events being sent which could be improved to only attach
|
|
670
|
-
// and detach the listener during a profiler session
|
|
671
|
-
this._client.on('beforeSendEvent', this._onBeforeSendThreadContextAssignment.bind(this));
|
|
740
|
+
this._stopChunkProfiling();
|
|
672
741
|
}
|
|
673
742
|
|
|
674
743
|
/**
|
|
675
|
-
*
|
|
676
|
-
* @returns void
|
|
744
|
+
* Starts trace lifecycle profiling. Profiling will remain active as long as there is an active span.
|
|
677
745
|
*/
|
|
678
|
-
|
|
746
|
+
_startTraceLifecycleProfiling() {
|
|
679
747
|
if (!this._client) {
|
|
680
|
-
DEBUG_BUILD &&
|
|
748
|
+
DEBUG_BUILD &&
|
|
749
|
+
logger.log(
|
|
750
|
+
'[Profiling] Failed to start trace lifecycle profiling, sentry client was never attached to the profiler.',
|
|
751
|
+
);
|
|
681
752
|
return;
|
|
682
753
|
}
|
|
683
754
|
|
|
684
|
-
|
|
685
|
-
this.
|
|
755
|
+
let activeSpanCounter = 0;
|
|
756
|
+
this._client.on('spanStart', _span => {
|
|
757
|
+
if (activeSpanCounter === 0) {
|
|
758
|
+
this._startChunkProfiling();
|
|
759
|
+
}
|
|
760
|
+
activeSpanCounter++;
|
|
761
|
+
});
|
|
686
762
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
763
|
+
this._client.on('spanEnd', _span => {
|
|
764
|
+
if (activeSpanCounter === 1) {
|
|
765
|
+
this._stopChunkProfiling();
|
|
766
|
+
}
|
|
767
|
+
activeSpanCounter--;
|
|
768
|
+
});
|
|
690
769
|
}
|
|
691
770
|
|
|
692
|
-
|
|
693
|
-
* Stops the current chunk and flushes the profile to Sentry.
|
|
694
|
-
* @returns void
|
|
695
|
-
*/
|
|
696
|
-
stop() {
|
|
771
|
+
_setupAutomaticSpanProfiling() {
|
|
697
772
|
if (!this._client) {
|
|
698
|
-
DEBUG_BUILD &&
|
|
773
|
+
DEBUG_BUILD &&
|
|
774
|
+
logger.log(
|
|
775
|
+
'[Profiling] Failed to setup automatic span profiling, sentry client was never attached to the profiler.',
|
|
776
|
+
);
|
|
699
777
|
return;
|
|
700
778
|
}
|
|
701
|
-
|
|
702
|
-
|
|
779
|
+
|
|
780
|
+
const spanToProfileIdMap = new WeakMap();
|
|
781
|
+
|
|
782
|
+
this._client.on('spanStart', span => {
|
|
783
|
+
if (span !== getRootSpan(span)) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const profile_id = maybeProfileSpan(this._client, span);
|
|
788
|
+
|
|
789
|
+
if (profile_id) {
|
|
790
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
791
|
+
const options = this._client.getOptions();
|
|
792
|
+
// Not intended for external use, hence missing types, but we want to profile a couple of things at Sentry that
|
|
793
|
+
// currently exceed the default timeout set by the SDKs.
|
|
794
|
+
const maxProfileDurationMs = options._experiments?.maxProfileDurationMs || MAX_PROFILE_DURATION_MS;
|
|
795
|
+
|
|
796
|
+
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
797
|
+
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
798
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
799
|
+
delete PROFILE_TIMEOUTS[profile_id];
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Enqueue a timeout to prevent profiles from running over max duration.
|
|
803
|
+
const timeout = global.setTimeout(() => {
|
|
804
|
+
DEBUG_BUILD &&
|
|
805
|
+
logger.log(
|
|
806
|
+
'[Profiling] max profile duration elapsed, stopping profiling for:',
|
|
807
|
+
spanToJSON(span).description,
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
811
|
+
if (profile) {
|
|
812
|
+
addToProfileQueue(profile_id, profile);
|
|
813
|
+
}
|
|
814
|
+
}, maxProfileDurationMs);
|
|
815
|
+
|
|
816
|
+
// Unref timeout so it doesn't keep the process alive.
|
|
817
|
+
timeout.unref();
|
|
818
|
+
|
|
819
|
+
getIsolationScope().setContext('profile', { profile_id });
|
|
820
|
+
spanToProfileIdMap.set(span, profile_id);
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
this._client.on('spanEnd', span => {
|
|
825
|
+
const profile_id = spanToProfileIdMap.get(span);
|
|
826
|
+
|
|
827
|
+
if (profile_id) {
|
|
828
|
+
if (PROFILE_TIMEOUTS[profile_id]) {
|
|
829
|
+
global.clearTimeout(PROFILE_TIMEOUTS[profile_id]);
|
|
830
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
831
|
+
delete PROFILE_TIMEOUTS[profile_id];
|
|
832
|
+
}
|
|
833
|
+
const profile = stopSpanProfile(span, profile_id);
|
|
834
|
+
|
|
835
|
+
if (profile) {
|
|
836
|
+
addToProfileQueue(profile_id, profile);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
this._client.on('beforeEnvelope', (envelope) => {
|
|
842
|
+
// if not profiles are in queue, there is nothing to add to the envelope.
|
|
843
|
+
if (!PROFILE_MAP.size) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const profiledTransactionEvents = findProfiledTransactionsFromEnvelope(envelope);
|
|
848
|
+
if (!profiledTransactionEvents.length) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const profilesToAddToEnvelope = [];
|
|
853
|
+
|
|
854
|
+
for (const profiledTransaction of profiledTransactionEvents) {
|
|
855
|
+
const profileContext = profiledTransaction.contexts?.profile;
|
|
856
|
+
const profile_id = profileContext?.profile_id;
|
|
857
|
+
|
|
858
|
+
if (!profile_id) {
|
|
859
|
+
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context');
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Remove the profile from the transaction context before sending, relay will take care of the rest.
|
|
863
|
+
if (profileContext) {
|
|
864
|
+
delete profiledTransaction.contexts?.profile;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const cpuProfile = takeFromProfileQueue(profile_id);
|
|
868
|
+
if (!cpuProfile) {
|
|
869
|
+
DEBUG_BUILD && logger.log(`[Profiling] Could not retrieve profile for transaction: ${profile_id}`);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
874
|
+
const profile = createProfilingEvent(this._client, cpuProfile, profiledTransaction);
|
|
875
|
+
if (!profile) return;
|
|
876
|
+
|
|
877
|
+
profilesToAddToEnvelope.push(profile);
|
|
878
|
+
|
|
879
|
+
// @ts-expect-error profile does not inherit from Event
|
|
880
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
881
|
+
this._client.emit('preprocessEvent', profile, {
|
|
882
|
+
event_id: profiledTransaction.event_id,
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
// @ts-expect-error profile does not inherit from Event
|
|
886
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
887
|
+
this._client.emit('postprocessEvent', profile, {
|
|
888
|
+
event_id: profiledTransaction.event_id,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
addProfilesToEnvelope(envelope, profilesToAddToEnvelope);
|
|
893
|
+
});
|
|
703
894
|
}
|
|
704
895
|
|
|
705
896
|
/**
|
|
706
897
|
* Stop profiler and initializes profiling of the next chunk
|
|
707
898
|
*/
|
|
708
|
-
|
|
899
|
+
_restartChunkProfiling() {
|
|
709
900
|
if (!this._client) {
|
|
710
901
|
// The client is not attached to the profiler if the user has not enabled continuous profiling.
|
|
711
902
|
// In this case, calling start() and stop() is a noop action.The reason this exists is because
|
|
@@ -713,12 +904,13 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
|
|
|
713
904
|
DEBUG_BUILD && logger.log('[Profiling] Profiler was never attached to the client.');
|
|
714
905
|
return;
|
|
715
906
|
}
|
|
907
|
+
|
|
716
908
|
if (this._chunkData) {
|
|
717
909
|
DEBUG_BUILD &&
|
|
718
910
|
logger.log(
|
|
719
911
|
`[Profiling] Chunk with chunk_id ${this._chunkData.id} is still running, current chunk will be stopped a new chunk will be started.`,
|
|
720
912
|
);
|
|
721
|
-
this.
|
|
913
|
+
this._stopChunkProfiling();
|
|
722
914
|
}
|
|
723
915
|
|
|
724
916
|
this._startChunkProfiling();
|
|
@@ -727,32 +919,44 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
|
|
|
727
919
|
/**
|
|
728
920
|
* Stops profiling of the current chunks and flushes the profile to Sentry
|
|
729
921
|
*/
|
|
730
|
-
|
|
922
|
+
_stopChunkProfiling() {
|
|
923
|
+
if (!this._chunkData) {
|
|
924
|
+
DEBUG_BUILD && logger.log('[Profiling] No chunk data found, no-op.');
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
|
|
731
928
|
if (this._chunkData?.timer) {
|
|
732
929
|
global.clearTimeout(this._chunkData.timer);
|
|
733
930
|
this._chunkData.timer = undefined;
|
|
734
931
|
DEBUG_BUILD && logger.log(`[Profiling] Stopping profiling chunk: ${this._chunkData.id}`);
|
|
735
932
|
}
|
|
933
|
+
|
|
736
934
|
if (!this._client) {
|
|
737
935
|
DEBUG_BUILD &&
|
|
738
936
|
logger.log('[Profiling] Failed to collect profile, sentry client was never attached to the profiler.');
|
|
937
|
+
this._resetChunkData();
|
|
739
938
|
return;
|
|
740
939
|
}
|
|
940
|
+
|
|
741
941
|
if (!this._chunkData?.id) {
|
|
742
942
|
DEBUG_BUILD &&
|
|
743
943
|
logger.log(`[Profiling] Failed to collect profile for: ${this._chunkData?.id}, the chunk_id is missing.`);
|
|
944
|
+
this._resetChunkData();
|
|
744
945
|
return;
|
|
745
946
|
}
|
|
746
947
|
|
|
747
948
|
const profile = CpuProfilerBindings.stopProfiling(this._chunkData.id, ProfileFormat.CHUNK);
|
|
748
949
|
|
|
749
950
|
if (!profile) {
|
|
750
|
-
DEBUG_BUILD && logger.log(`[Profiling]
|
|
951
|
+
DEBUG_BUILD && logger.log(`[Profiling] Failed to collect profile for: ${this._chunkData.id}`);
|
|
952
|
+
this._resetChunkData();
|
|
751
953
|
return;
|
|
752
954
|
}
|
|
955
|
+
|
|
753
956
|
if (!this._profilerId) {
|
|
754
957
|
DEBUG_BUILD &&
|
|
755
958
|
logger.log('[Profiling] Profile chunk does not contain a valid profiler_id, this is a bug in the SDK');
|
|
959
|
+
this._resetChunkData();
|
|
756
960
|
return;
|
|
757
961
|
}
|
|
758
962
|
if (profile) {
|
|
@@ -807,7 +1011,7 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
|
|
|
807
1011
|
const metadata = this._client.getSdkMetadata();
|
|
808
1012
|
const tunnel = this._client.getOptions().tunnel;
|
|
809
1013
|
|
|
810
|
-
const envelope = makeProfileChunkEnvelope(chunk, metadata?.sdk, tunnel, dsn);
|
|
1014
|
+
const envelope = makeProfileChunkEnvelope('node', chunk, metadata?.sdk, tunnel, dsn);
|
|
811
1015
|
transport.send(envelope).then(null, reason => {
|
|
812
1016
|
DEBUG_BUILD && logger.error('Error while sending profile chunk envelope:', reason);
|
|
813
1017
|
});
|
|
@@ -818,6 +1022,11 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
|
|
|
818
1022
|
* @param chunk
|
|
819
1023
|
*/
|
|
820
1024
|
_startChunkProfiling() {
|
|
1025
|
+
if (this._chunkData) {
|
|
1026
|
+
DEBUG_BUILD && logger.log('[Profiling] Chunk is already running, no-op.');
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
821
1030
|
const traceId =
|
|
822
1031
|
getCurrentScope().getPropagationContext().traceId || getIsolationScope().getPropagationContext().traceId;
|
|
823
1032
|
const chunk = this._initializeChunk(traceId);
|
|
@@ -827,9 +1036,9 @@ class ContinuousProfiler {constructor() { ContinuousProfiler.prototype.__init.ca
|
|
|
827
1036
|
|
|
828
1037
|
chunk.timer = global.setTimeout(() => {
|
|
829
1038
|
DEBUG_BUILD && logger.log(`[Profiling] Stopping profiling chunk: ${chunk.id}`);
|
|
830
|
-
this.
|
|
1039
|
+
this._stopChunkProfiling();
|
|
831
1040
|
DEBUG_BUILD && logger.log('[Profiling] Starting new profiling chunk.');
|
|
832
|
-
setImmediate(this.
|
|
1041
|
+
setImmediate(this._restartChunkProfiling.bind(this));
|
|
833
1042
|
}, CHUNK_INTERVAL_MS);
|
|
834
1043
|
|
|
835
1044
|
// Unref timeout so it doesn't keep the process alive.
|
|
@@ -935,36 +1144,31 @@ const _nodeProfilingIntegration = (() => {
|
|
|
935
1144
|
_profiler: new ContinuousProfiler(),
|
|
936
1145
|
setup(client) {
|
|
937
1146
|
DEBUG_BUILD && logger.log('[Profiling] Profiling integration setup.');
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const mode =
|
|
941
|
-
(options.profilesSampleRate === undefined ||
|
|
942
|
-
options.profilesSampleRate === null ||
|
|
943
|
-
options.profilesSampleRate === 0) &&
|
|
944
|
-
!options.profilesSampler
|
|
945
|
-
? 'continuous'
|
|
946
|
-
: 'span';
|
|
947
|
-
switch (mode) {
|
|
948
|
-
case 'continuous': {
|
|
949
|
-
DEBUG_BUILD && logger.log('[Profiling] Continuous profiler mode enabled.');
|
|
950
|
-
this._profiler.initialize(client);
|
|
951
|
-
break;
|
|
952
|
-
}
|
|
953
|
-
// Default to span profiling when no mode profiler mode is set
|
|
954
|
-
case 'span':
|
|
955
|
-
case undefined: {
|
|
956
|
-
DEBUG_BUILD && logger.log('[Profiling] Span profiler mode enabled.');
|
|
957
|
-
setupAutomatedSpanProfiling(client);
|
|
958
|
-
break;
|
|
959
|
-
}
|
|
960
|
-
default: {
|
|
961
|
-
DEBUG_BUILD && logger.warn(`[Profiling] Unknown profiler mode: ${mode}, profiler was not initialized`);
|
|
962
|
-
}
|
|
963
|
-
}
|
|
1147
|
+
this._profiler.initialize(client);
|
|
1148
|
+
return;
|
|
964
1149
|
},
|
|
965
1150
|
};
|
|
966
1151
|
}) ;
|
|
967
1152
|
|
|
1153
|
+
/**
|
|
1154
|
+
* Determines the profiling mode based on the options.
|
|
1155
|
+
* @param options
|
|
1156
|
+
* @returns 'legacy' if the options are using the legacy profiling API, 'current' if the options are using the current profiling API
|
|
1157
|
+
*/
|
|
1158
|
+
function getProfilingMode(options) {
|
|
1159
|
+
// Legacy mode takes precedence over current mode
|
|
1160
|
+
if ('profilesSampleRate' in options || 'profilesSampler' in options) {
|
|
1161
|
+
return 'legacy';
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
if ('profileSessionSampleRate' in options || 'profileLifecycle' in options) {
|
|
1165
|
+
return 'current';
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// If neither are set, we are in the legacy continuous profiling mode
|
|
1169
|
+
return 'legacy';
|
|
1170
|
+
}
|
|
1171
|
+
|
|
968
1172
|
/**
|
|
969
1173
|
* We need this integration in order to send data to Sentry. We hook into the event processor
|
|
970
1174
|
* and inspect each event to see if it is a transaction event and if that transaction event
|