@platformatic/wattpm-pprof-capture 3.24.0 → 3.25.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/index.js +7 -1
- package/package.json +3 -3
- package/test/watt-pprof-capture.test.js +121 -0
package/index.js
CHANGED
|
@@ -31,6 +31,7 @@ const profilingState = {
|
|
|
31
31
|
cpu: {
|
|
32
32
|
isCapturing: false,
|
|
33
33
|
latestProfile: null,
|
|
34
|
+
latestProfileTimestamp: null,
|
|
34
35
|
captureInterval: null,
|
|
35
36
|
durationMillis: null,
|
|
36
37
|
eluThreshold: null,
|
|
@@ -42,6 +43,7 @@ const profilingState = {
|
|
|
42
43
|
heap: {
|
|
43
44
|
isCapturing: false,
|
|
44
45
|
latestProfile: null,
|
|
46
|
+
latestProfileTimestamp: null,
|
|
45
47
|
captureInterval: null,
|
|
46
48
|
durationMillis: null,
|
|
47
49
|
eluThreshold: null,
|
|
@@ -76,6 +78,7 @@ function scheduleLastProfileCleanup (state) {
|
|
|
76
78
|
if (state.options?.durationMillis) {
|
|
77
79
|
state.clearProfileTimeout = setTimeout(() => {
|
|
78
80
|
state.latestProfile = undefined
|
|
81
|
+
state.latestProfileTimestamp = null
|
|
79
82
|
state.clearProfileTimeout = null
|
|
80
83
|
}, state.options.durationMillis)
|
|
81
84
|
state.clearProfileTimeout.unref()
|
|
@@ -148,6 +151,7 @@ function stopProfiler (type, state) {
|
|
|
148
151
|
scheduleLastProfileCleanup(state)
|
|
149
152
|
unscheduleProfileRotation(state)
|
|
150
153
|
state.profilerStarted = false
|
|
154
|
+
state.latestProfileTimestamp = Date.now()
|
|
151
155
|
|
|
152
156
|
if (type === 'heap') {
|
|
153
157
|
// Get the profile before stopping
|
|
@@ -330,6 +334,7 @@ export function getLastProfile (options = {}) {
|
|
|
330
334
|
const profiler = getProfiler(type)
|
|
331
335
|
// Get heap profile with sourceMapper if enabled and available
|
|
332
336
|
state.latestProfile = (state.sourceMapsEnabled && sourceMapper) ? profiler.profile(undefined, sourceMapper) : profiler.profile()
|
|
337
|
+
state.latestProfileTimestamp = Date.now()
|
|
333
338
|
}
|
|
334
339
|
|
|
335
340
|
// Check if we have a profile
|
|
@@ -357,7 +362,8 @@ export function getProfilingState (options = {}) {
|
|
|
357
362
|
isProfilerRunning: state.profilerStarted,
|
|
358
363
|
isPausedBelowThreshold: state.eluThreshold != null && !state.profilerStarted,
|
|
359
364
|
lastELU: lastELU?.utilization,
|
|
360
|
-
eluThreshold: state.eluThreshold
|
|
365
|
+
eluThreshold: state.eluThreshold,
|
|
366
|
+
latestProfileTimestamp: state.latestProfileTimestamp
|
|
361
367
|
}
|
|
362
368
|
}
|
|
363
369
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/wattpm-pprof-capture",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.0",
|
|
4
4
|
"description": "pprof profiling capture for wattpm",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"neostandard": "^0.12.0",
|
|
27
27
|
"pprof-format": "^2.1.0",
|
|
28
28
|
"typescript": "^5.0.0",
|
|
29
|
-
"@platformatic/
|
|
30
|
-
"@platformatic/
|
|
29
|
+
"@platformatic/service": "3.25.0",
|
|
30
|
+
"@platformatic/foundation": "3.25.0"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=22.19.0"
|
|
@@ -647,3 +647,124 @@ test('profiling with eluThreshold should continue rotating while above threshold
|
|
|
647
647
|
await request(`${url}/cpu-intensive/stop`, { method: 'POST' })
|
|
648
648
|
await app.sendCommandToApplication('service', 'stopProfiling')
|
|
649
649
|
})
|
|
650
|
+
|
|
651
|
+
test('latestProfileTimestamp should be set after profile rotation', async t => {
|
|
652
|
+
const { app } = await createApp(t)
|
|
653
|
+
|
|
654
|
+
// Check initial state - timestamp should be null
|
|
655
|
+
const initialState = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
656
|
+
assert.strictEqual(initialState.latestProfileTimestamp, null, 'Timestamp should be null before profiling')
|
|
657
|
+
|
|
658
|
+
// Start profiling with rotation
|
|
659
|
+
await app.sendCommandToApplication('service', 'startProfiling', { durationMillis: 200 })
|
|
660
|
+
|
|
661
|
+
// Wait for first rotation
|
|
662
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
663
|
+
|
|
664
|
+
// Get state and verify timestamp is set
|
|
665
|
+
const stateAfterRotation = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
666
|
+
assert.ok(stateAfterRotation.latestProfileTimestamp != null, 'Timestamp should be set after rotation')
|
|
667
|
+
assert.ok(typeof stateAfterRotation.latestProfileTimestamp === 'number', 'Timestamp should be a number')
|
|
668
|
+
assert.ok(stateAfterRotation.latestProfileTimestamp <= Date.now(), 'Timestamp should not be in the future')
|
|
669
|
+
assert.ok(stateAfterRotation.latestProfileTimestamp > Date.now() - 5000, 'Timestamp should be recent')
|
|
670
|
+
|
|
671
|
+
await app.sendCommandToApplication('service', 'stopProfiling')
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
test('latestProfileTimestamp should be set after stopProfiling', async t => {
|
|
675
|
+
const { app } = await createApp(t)
|
|
676
|
+
|
|
677
|
+
// Start profiling without rotation
|
|
678
|
+
await app.sendCommandToApplication('service', 'startProfiling', {})
|
|
679
|
+
|
|
680
|
+
// Get state before stop - timestamp should be null (no rotation occurred)
|
|
681
|
+
const stateBeforeStop = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
682
|
+
assert.strictEqual(stateBeforeStop.latestProfileTimestamp, null, 'Timestamp should be null before stop')
|
|
683
|
+
|
|
684
|
+
// Stop profiling
|
|
685
|
+
const beforeStopTime = Date.now()
|
|
686
|
+
await app.sendCommandToApplication('service', 'stopProfiling')
|
|
687
|
+
const afterStopTime = Date.now()
|
|
688
|
+
|
|
689
|
+
// Get state after stop - timestamp should be set
|
|
690
|
+
const stateAfterStop = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
691
|
+
assert.ok(stateAfterStop.latestProfileTimestamp != null, 'Timestamp should be set after stopProfiling')
|
|
692
|
+
assert.ok(stateAfterStop.latestProfileTimestamp >= beforeStopTime, 'Timestamp should be >= time before stop')
|
|
693
|
+
assert.ok(stateAfterStop.latestProfileTimestamp <= afterStopTime, 'Timestamp should be <= time after stop')
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
test('latestProfileTimestamp should be cleared after profile cleanup timeout', async t => {
|
|
697
|
+
const { app } = await createApp(t)
|
|
698
|
+
|
|
699
|
+
// Start profiling with short rotation interval
|
|
700
|
+
await app.sendCommandToApplication('service', 'startProfiling', { durationMillis: 200 })
|
|
701
|
+
|
|
702
|
+
// Wait for first rotation
|
|
703
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
704
|
+
|
|
705
|
+
// Verify timestamp is set
|
|
706
|
+
const stateWithProfile = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
707
|
+
assert.ok(stateWithProfile.latestProfileTimestamp != null, 'Timestamp should be set after rotation')
|
|
708
|
+
assert.ok(stateWithProfile.hasProfile, 'Should have profile')
|
|
709
|
+
|
|
710
|
+
// Stop profiling - this schedules cleanup after durationMillis (200ms)
|
|
711
|
+
await app.sendCommandToApplication('service', 'stopProfiling')
|
|
712
|
+
|
|
713
|
+
// Wait for cleanup timeout (durationMillis after stop)
|
|
714
|
+
await new Promise(resolve => setTimeout(resolve, 300))
|
|
715
|
+
|
|
716
|
+
// Verify timestamp is cleared after cleanup
|
|
717
|
+
const stateAfterCleanup = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
718
|
+
assert.strictEqual(stateAfterCleanup.latestProfileTimestamp, null, 'Timestamp should be cleared after cleanup')
|
|
719
|
+
assert.ok(!stateAfterCleanup.hasProfile, 'Profile should be cleared')
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
test('latestProfileTimestamp should be set for heap profiling', async t => {
|
|
723
|
+
const { app } = await createApp(t)
|
|
724
|
+
|
|
725
|
+
// Check initial state for heap - timestamp should be null
|
|
726
|
+
const initialState = await app.sendCommandToApplication('service', 'getProfilingState', { type: 'heap' })
|
|
727
|
+
assert.strictEqual(initialState.latestProfileTimestamp, null, 'Heap timestamp should be null before profiling')
|
|
728
|
+
|
|
729
|
+
// Start heap profiling
|
|
730
|
+
await app.sendCommandToApplication('service', 'startProfiling', { type: 'heap' })
|
|
731
|
+
|
|
732
|
+
// Wait a bit
|
|
733
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
734
|
+
|
|
735
|
+
// Get last profile (for heap, this captures a snapshot)
|
|
736
|
+
const beforeGetProfile = Date.now()
|
|
737
|
+
await app.sendCommandToApplication('service', 'getLastProfile', { type: 'heap' })
|
|
738
|
+
const afterGetProfile = Date.now()
|
|
739
|
+
|
|
740
|
+
// Verify timestamp is set after getLastProfile for heap
|
|
741
|
+
const stateAfterGet = await app.sendCommandToApplication('service', 'getProfilingState', { type: 'heap' })
|
|
742
|
+
assert.ok(stateAfterGet.latestProfileTimestamp != null, 'Heap timestamp should be set after getLastProfile')
|
|
743
|
+
assert.ok(stateAfterGet.latestProfileTimestamp >= beforeGetProfile, 'Timestamp should be >= time before get')
|
|
744
|
+
assert.ok(stateAfterGet.latestProfileTimestamp <= afterGetProfile, 'Timestamp should be <= time after get')
|
|
745
|
+
|
|
746
|
+
await app.sendCommandToApplication('service', 'stopProfiling', { type: 'heap' })
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
test('latestProfileTimestamp should update with each rotation', async t => {
|
|
750
|
+
const { app } = await createApp(t)
|
|
751
|
+
|
|
752
|
+
// Start profiling with short rotation interval
|
|
753
|
+
await app.sendCommandToApplication('service', 'startProfiling', { durationMillis: 200 })
|
|
754
|
+
|
|
755
|
+
// Wait for first rotation
|
|
756
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
757
|
+
const stateAfterFirst = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
758
|
+
const firstTimestamp = stateAfterFirst.latestProfileTimestamp
|
|
759
|
+
assert.ok(firstTimestamp != null, 'Timestamp should be set after first rotation')
|
|
760
|
+
|
|
761
|
+
// Wait for second rotation
|
|
762
|
+
await new Promise(resolve => setTimeout(resolve, 250))
|
|
763
|
+
const stateAfterSecond = await app.sendCommandToApplication('service', 'getProfilingState')
|
|
764
|
+
const secondTimestamp = stateAfterSecond.latestProfileTimestamp
|
|
765
|
+
|
|
766
|
+
// Second timestamp should be greater than first
|
|
767
|
+
assert.ok(secondTimestamp > firstTimestamp, 'Timestamp should update with each rotation')
|
|
768
|
+
|
|
769
|
+
await app.sendCommandToApplication('service', 'stopProfiling')
|
|
770
|
+
})
|