33const assert = require ( 'node:assert' )
44const { setup } = require ( './utils' )
55
6- const tolerance = 5
6+ const DEFAULT_MAX_COLLECTION_SIZE = 100
7+ const COLLECTION_SIZE_THRESHOLD = 500
78
89describe ( 'Dynamic Instrumentation' , function ( ) {
910 describe ( 'input messages' , function ( ) {
@@ -18,51 +19,146 @@ describe('Dynamic Instrumentation', function () {
1819
1920 it (
2021 'should include partial snapshot marked with notCapturedReason: timeout' ,
21- test ( { t, maxPausedTime : target + tolerance , breakpointIndex : 0 , maxReferenceDepth : 5 } )
22+ // A tolerance of 10ms is used to avoid flakiness
23+ test ( { t, maxPausedTime : target + 10 , breakpointIndex : 0 , maxReferenceDepth : 5 } , ( locals ) => {
24+ assert . strictEqual (
25+ containsTimeBudget ( locals ) ,
26+ true ,
27+ 'expected at least one field/element to be marked with notCapturedReason: "timeout"'
28+ )
29+ } )
2230 )
2331 } )
2432
2533 context ( 'default time budget' , function ( ) {
2634 const target = 10 // default time budget in ms
2735 const t = setup ( { dependencies : [ 'fastify' ] } )
2836
29- // TODO: Make this pass
30- // eslint-disable-next-line mocha/no-pending-tests
31- it . skip (
32- 'should keep budget when state includes an object with 1 million properties' ,
33- test ( { t, maxPausedTime : target + tolerance , breakpointIndex : 1 , maxReferenceDepth : 1 } )
37+ it (
38+ 'should timeout first, then disable subsequent snapshots and emit error diagnostics' ,
39+ async function ( ) {
40+ const breakpoint = t . breakpoints [ 1 ]
41+
42+ // Listen for the first snapshot payload (should contain notCapturedReason: "timeout")
43+ const firstPayloadReceived = new Promise ( ( resolve ) => {
44+ t . agent . once ( 'debugger-input' , ( { payload : [ { debugger : { snapshot : { captures } } } ] } ) => {
45+ const { locals } = captures . lines [ breakpoint . line ]
46+ resolve ( locals )
47+ } )
48+ } )
49+
50+ // Prepare to assert that an ERROR diagnostics event with exception details is emitted
51+ const errorDiagnosticsReceived = new Promise ( ( /** @type {(value?: unknown) => void } */ resolve , reject ) => {
52+ const handler = ( { payload } ) => {
53+ payload . forEach ( ( { debugger : { diagnostics } } ) => {
54+ if ( diagnostics . status !== 'ERROR' ) return
55+ try {
56+ assert . strictEqual (
57+ diagnostics . exception . message ,
58+ 'An object with more than 500 properties was detected while collecting a snapshot. Future ' +
59+ 'snapshots for exising probes in this location will be skipped until the Node.js process is ' +
60+ 'restarted'
61+ )
62+ resolve ( )
63+ } catch ( e ) {
64+ reject ( e )
65+ } finally {
66+ t . agent . off ( 'debugger-diagnostics' , handler )
67+ }
68+ } )
69+ }
70+ t . agent . on ( 'debugger-diagnostics' , handler )
71+ } )
72+
73+ // Install probe with snapshot capture enabled
74+ t . agent . addRemoteConfig ( breakpoint . generateRemoteConfig ( {
75+ captureSnapshot : true ,
76+ capture : { maxReferenceDepth : 1 }
77+ } ) )
78+
79+ // Trigger once; this run is expected to be slow and mark fields with "timeout"
80+ const result1 = await breakpoint . triggerBreakpoint ( )
81+ assert . ok (
82+ result1 . data . paused >= 1_000 ,
83+ `expected thread to be paused for at least 1 second, but was paused for ~${ result1 . data . paused } ms`
84+ )
85+ const locals = await firstPayloadReceived
86+ assert . strictEqual (
87+ containsTimeBudget ( locals ) ,
88+ true ,
89+ 'expected at least one field/element to be marked with notCapturedReason: "timeout"'
90+ )
91+ await errorDiagnosticsReceived
92+
93+ // Prepare to assert that no snapshot is produced on a subsequent trigger
94+ const noSnapshotAfterSecondTrigger = new Promise ( ( /** @type {(value?: unknown) => void } */ resolve ) => {
95+ t . agent . once ( 'debugger-input' , ( { payload : [ { debugger : { snapshot : { captures } } } ] } ) => {
96+ assert . strictEqual ( captures , undefined )
97+ resolve ( )
98+ } )
99+ } )
100+
101+ // Trigger the same breakpoint again directly
102+ const result2 = await t . axios . get ( breakpoint . url )
103+ assert . ok (
104+ result2 . data . paused <= 5 ,
105+ `expected thread to be paused <=5ms, but was paused for ~${ result2 . data . paused } ms`
106+ )
107+
108+ await noSnapshotAfterSecondTrigger
109+ }
34110 )
35111
36- // TODO: Make this pass
37- // eslint-disable-next-line mocha/no-pending-tests
38- it . skip (
39- 'should keep budget when state includes an array of 1 million primitives' ,
40- test ( { t, maxPausedTime : target + tolerance , breakpointIndex : 2 , maxReferenceDepth : 1 } )
112+ it (
113+ 'should keep budget when state includes collections with 1 million elements' ,
114+ // A tolerance of 5ms is used to avoid flakiness
115+ test ( { t, maxPausedTime : target + 5 , breakpointIndex : 2 , maxReferenceDepth : 1 } , ( locals ) => {
116+ assert . strictEqual ( locals . arrOfPrimitives . notCapturedReason , 'collectionSize' )
117+ assert . strictEqual ( locals . arrOfPrimitives . size , 1_000_000 )
118+ assert . strictEqual ( locals . arrOfPrimitives . elements . length , 0 )
119+ assert . strictEqual ( locals . arrOfObjects . notCapturedReason , 'collectionSize' )
120+ assert . strictEqual ( locals . arrOfObjects . size , 1_000_000 )
121+ assert . strictEqual ( locals . arrOfObjects . elements . length , 0 )
122+ assert . strictEqual ( locals . map . notCapturedReason , 'collectionSize' )
123+ assert . strictEqual ( locals . map . size , 1_000_000 )
124+ assert . strictEqual ( locals . map . entries . length , 0 )
125+ assert . strictEqual ( locals . set . notCapturedReason , 'collectionSize' )
126+ assert . strictEqual ( locals . set . size , 1_000_000 )
127+ assert . strictEqual ( locals . set . elements . length , 0 )
128+ } )
41129 )
42130
43- // TODO: Make this pass
44- // eslint-disable-next-line mocha/no-pending-tests
45- it . skip (
46- 'should keep budget when state includes an array of 1 million objects' ,
47- test ( { t, maxPausedTime : target + tolerance , breakpointIndex : 3 , maxReferenceDepth : 1 } )
131+ it (
132+ 'should keep budget when state includes collections with less than the size threshold' ,
133+ // A tolerance of 30ms is used to avoid flakiness
134+ test ( { t, maxPausedTime : target + 30 , breakpointIndex : 3 , maxReferenceDepth : 1 } , ( locals ) => {
135+ assert . strictEqual ( locals . arrOfPrimitives . notCapturedReason , 'collectionSize' )
136+ assert . strictEqual ( locals . arrOfPrimitives . size , COLLECTION_SIZE_THRESHOLD - 1 )
137+ assert . strictEqual ( locals . arrOfPrimitives . elements . length , DEFAULT_MAX_COLLECTION_SIZE )
138+ assert . strictEqual ( locals . arrOfObjects . notCapturedReason , 'collectionSize' )
139+ assert . strictEqual ( locals . arrOfObjects . size , COLLECTION_SIZE_THRESHOLD - 1 )
140+ assert . strictEqual ( locals . arrOfObjects . elements . length , DEFAULT_MAX_COLLECTION_SIZE )
141+ assert . strictEqual ( locals . map . notCapturedReason , 'collectionSize' )
142+ assert . strictEqual ( locals . map . size , COLLECTION_SIZE_THRESHOLD - 1 )
143+ assert . strictEqual ( locals . map . entries . length , DEFAULT_MAX_COLLECTION_SIZE )
144+ assert . strictEqual ( locals . set . notCapturedReason , 'collectionSize' )
145+ assert . strictEqual ( locals . set . size , COLLECTION_SIZE_THRESHOLD - 1 )
146+ assert . strictEqual ( locals . set . elements . length , DEFAULT_MAX_COLLECTION_SIZE )
147+ } )
48148 )
49149 } )
50150 } )
51151 } )
52152} )
53153
54- function test ( { t, maxPausedTime, breakpointIndex, maxReferenceDepth } ) {
154+ function test ( { t, maxPausedTime = 0 , breakpointIndex, maxReferenceDepth } , assertFn ) {
55155 const breakpoint = t . breakpoints [ breakpointIndex ]
56156
57157 return async function ( ) {
58158 const payloadReceived = new Promise ( ( /** @type {(value?: unknown) => void } */ resolve ) => {
59159 t . agent . on ( 'debugger-input' , ( { payload : [ { debugger : { snapshot : { captures } } } ] } ) => {
60160 const { locals } = captures . lines [ breakpoint . line ]
61- assert . strictEqual (
62- containsTimeBudget ( locals ) ,
63- true ,
64- 'expected at least one field/element to be marked with notCapturedReason: "timeout"'
65- )
161+ assertFn ?. ( locals )
66162 resolve ( )
67163 } )
68164 } )
0 commit comments