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