@platformatic/watt-extra 1.7.0 → 1.7.1-alpha.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/.claude/settings.local.json +5 -8
- package/package.json +1 -1
- package/plugins/alerts.js +2 -7
- package/plugins/flamegraphs.js +274 -205
- package/plugins/health-signals.js +4 -9
- package/plugins/update.js +2 -2
- package/test/alerts.test.js +7 -17
- package/test/health-signals.test.js +3 -6
- package/test/profiler.test.js +443 -0
- package/test/trigger-flamegraphs.test.js +257 -414
package/test/alerts.test.js
CHANGED
|
@@ -90,9 +90,6 @@ test('should send alert when service becomes unhealthy', async (t) => {
|
|
|
90
90
|
await icc.close()
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
// Wait for the first flamegraph to be generated
|
|
94
|
-
await sleep(5000)
|
|
95
|
-
|
|
96
93
|
// Manually trigger health event with unhealthy state
|
|
97
94
|
const healthInfo = {
|
|
98
95
|
id: 'main:0',
|
|
@@ -133,6 +130,9 @@ test('should send alert when service becomes unhealthy', async (t) => {
|
|
|
133
130
|
assert.strictEqual(alertReceived.healthHistory[0].application, 'main')
|
|
134
131
|
assert.strictEqual(alertReceived.healthHistory[0].service, 'main')
|
|
135
132
|
|
|
133
|
+
// Wait for flamegraph to be generated (duration is 2 seconds)
|
|
134
|
+
await sleep(2500)
|
|
135
|
+
|
|
136
136
|
assert.ok(flamegraphReceived, 'Flamegraph should have been received')
|
|
137
137
|
|
|
138
138
|
const profile = Profile.decode(flamegraphReceived)
|
|
@@ -526,8 +526,6 @@ test('should send alert when flamegraphs are disabled', async (t) => {
|
|
|
526
526
|
await icc.close()
|
|
527
527
|
})
|
|
528
528
|
|
|
529
|
-
await sleep(5000)
|
|
530
|
-
|
|
531
529
|
// Manually trigger health event with unhealthy state
|
|
532
530
|
const healthInfo = {
|
|
533
531
|
id: 'main:0',
|
|
@@ -611,8 +609,6 @@ test('should send alert when failed to send a flamegraph', async (t) => {
|
|
|
611
609
|
await icc.close()
|
|
612
610
|
})
|
|
613
611
|
|
|
614
|
-
await sleep(5000)
|
|
615
|
-
|
|
616
612
|
// Manually trigger health event with unhealthy state
|
|
617
613
|
const healthInfo = {
|
|
618
614
|
id: 'main:0',
|
|
@@ -799,9 +795,6 @@ test('should attach one flamegraph to multiple alerts', async (t) => {
|
|
|
799
795
|
await icc.close()
|
|
800
796
|
})
|
|
801
797
|
|
|
802
|
-
// Wait for the first flamegraph to be generated
|
|
803
|
-
await sleep(5000)
|
|
804
|
-
|
|
805
798
|
// Manually trigger health event with unhealthy state
|
|
806
799
|
const healthInfo = {
|
|
807
800
|
id: 'main:0',
|
|
@@ -827,8 +820,8 @@ test('should attach one flamegraph to multiple alerts', async (t) => {
|
|
|
827
820
|
await sleep(1000)
|
|
828
821
|
emitHealthEvent(app, healthInfo)
|
|
829
822
|
|
|
830
|
-
// Wait for
|
|
831
|
-
await sleep(
|
|
823
|
+
// Wait for flamegraph to be generated (duration is 5 seconds) and sent
|
|
824
|
+
await sleep(5500)
|
|
832
825
|
|
|
833
826
|
assert.strictEqual(receivedAlerts.length, 2)
|
|
834
827
|
const alert1 = receivedAlerts[0]
|
|
@@ -902,9 +895,6 @@ test('should send flamegraphs if attaching fails', async (t) => {
|
|
|
902
895
|
await icc.close()
|
|
903
896
|
})
|
|
904
897
|
|
|
905
|
-
// Wait for the first flamegraph to be generated
|
|
906
|
-
await sleep(5000)
|
|
907
|
-
|
|
908
898
|
// Manually trigger health event with unhealthy state
|
|
909
899
|
const healthInfo = {
|
|
910
900
|
id: 'main:0',
|
|
@@ -930,8 +920,8 @@ test('should send flamegraphs if attaching fails', async (t) => {
|
|
|
930
920
|
await sleep(1000)
|
|
931
921
|
emitHealthEvent(app, healthInfo)
|
|
932
922
|
|
|
933
|
-
// Wait for
|
|
934
|
-
await sleep(
|
|
923
|
+
// Wait for flamegraph to be generated (duration is 5 seconds) and sent
|
|
924
|
+
await sleep(5500)
|
|
935
925
|
|
|
936
926
|
assert.strictEqual(receivedAlerts.length, 2)
|
|
937
927
|
const alert1 = receivedAlerts[0]
|
|
@@ -31,7 +31,7 @@ test('should send health signals when service becomes unhealthy', async (t) => {
|
|
|
31
31
|
processSignals: (req) => {
|
|
32
32
|
assert.equal(req.headers.authorization, 'Bearer test-token')
|
|
33
33
|
receivedSignalReqs.push(req.body)
|
|
34
|
-
return {
|
|
34
|
+
return { alertId: 'test-alert-id' }
|
|
35
35
|
},
|
|
36
36
|
processFlamegraphs: (req) => {
|
|
37
37
|
const alertId = req.query.alertId
|
|
@@ -58,9 +58,6 @@ test('should send health signals when service becomes unhealthy', async (t) => {
|
|
|
58
58
|
await icc.close()
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
// Wait for the first flamegraph to be generated
|
|
62
|
-
await sleep(5000)
|
|
63
|
-
|
|
64
61
|
{
|
|
65
62
|
const { statusCode } = await request('http://127.0.0.1:3042/custom-health-signal', {
|
|
66
63
|
method: 'POST',
|
|
@@ -119,8 +116,8 @@ test('should send health signals when service becomes unhealthy', async (t) => {
|
|
|
119
116
|
assert.ok(receivedSignal.timestamp > 0)
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
// Wait for
|
|
123
|
-
await sleep(
|
|
119
|
+
// Wait for flamegraph to be generated (duration is 2 seconds)
|
|
120
|
+
await sleep(2500)
|
|
124
121
|
|
|
125
122
|
// assert.strictEqual(receivedFlamegraphReqs.length, 1)
|
|
126
123
|
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { randomUUID } from 'node:crypto'
|
|
4
|
+
import { join, dirname } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
7
|
+
import { Profile } from 'pprof-format'
|
|
8
|
+
import { setUpEnvironment, startICC } from './helper.js'
|
|
9
|
+
import { start } from '../index.js'
|
|
10
|
+
import { Profiler } from '../plugins/flamegraphs.js'
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
13
|
+
const __dirname = dirname(__filename)
|
|
14
|
+
|
|
15
|
+
async function setupApp (t) {
|
|
16
|
+
const applicationName = 'test-app'
|
|
17
|
+
const applicationId = randomUUID()
|
|
18
|
+
const applicationPath = join(__dirname, 'fixtures', 'service-1')
|
|
19
|
+
|
|
20
|
+
const icc = await startICC(t, {
|
|
21
|
+
applicationId,
|
|
22
|
+
applicationName
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
setUpEnvironment({
|
|
26
|
+
PLT_APP_NAME: applicationName,
|
|
27
|
+
PLT_APP_DIR: applicationPath,
|
|
28
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000'
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const app = await start()
|
|
32
|
+
|
|
33
|
+
t.after(async () => {
|
|
34
|
+
await app.close()
|
|
35
|
+
await icc.close()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return app
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test('Profiler should start profiling and generate profile on first request', async (t) => {
|
|
42
|
+
const app = await setupApp(t)
|
|
43
|
+
|
|
44
|
+
let profileReceived = false
|
|
45
|
+
const profiler = new Profiler({
|
|
46
|
+
app,
|
|
47
|
+
workerId: 'main:0',
|
|
48
|
+
type: 'cpu',
|
|
49
|
+
duration: 1000,
|
|
50
|
+
sourceMaps: false,
|
|
51
|
+
onProfile: (err, profile, requests) => {
|
|
52
|
+
assert.strictEqual(err, null)
|
|
53
|
+
assert.ok(profile)
|
|
54
|
+
assert.ok(profile.data)
|
|
55
|
+
assert.ok(profile.timestamp)
|
|
56
|
+
assert.strictEqual(requests.length, 1)
|
|
57
|
+
assert.strictEqual(requests[0].alertId, 'test-alert')
|
|
58
|
+
profileReceived = true
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
t.after(async () => {
|
|
63
|
+
await profiler.stop()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Request a profile
|
|
67
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
68
|
+
|
|
69
|
+
// Wait for profile to be generated (duration is 1 second)
|
|
70
|
+
await sleep(1500)
|
|
71
|
+
|
|
72
|
+
assert.ok(profileReceived, 'Profile should have been generated')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('Profiler should queue multiple requests and associate them with the profile', async (t) => {
|
|
76
|
+
const app = await setupApp(t)
|
|
77
|
+
|
|
78
|
+
let profileReceived = false
|
|
79
|
+
const profiler = new Profiler({
|
|
80
|
+
app,
|
|
81
|
+
workerId: 'main:0',
|
|
82
|
+
type: 'cpu',
|
|
83
|
+
duration: 1000,
|
|
84
|
+
sourceMaps: false,
|
|
85
|
+
onProfile: (err, profile, requests) => {
|
|
86
|
+
assert.strictEqual(err, null)
|
|
87
|
+
assert.ok(profile)
|
|
88
|
+
assert.strictEqual(requests.length, 3)
|
|
89
|
+
assert.strictEqual(requests[0].alertId, 'alert-1')
|
|
90
|
+
assert.strictEqual(requests[1].alertId, 'alert-2')
|
|
91
|
+
assert.strictEqual(requests[2].alertId, 'alert-3')
|
|
92
|
+
profileReceived = true
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
t.after(async () => {
|
|
97
|
+
await profiler.stop()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Queue multiple requests
|
|
101
|
+
await profiler.requestProfile({ alertId: 'alert-1' })
|
|
102
|
+
await profiler.requestProfile({ alertId: 'alert-2' })
|
|
103
|
+
await profiler.requestProfile({ alertId: 'alert-3' })
|
|
104
|
+
|
|
105
|
+
// Wait for profile to be generated
|
|
106
|
+
await sleep(1500)
|
|
107
|
+
|
|
108
|
+
assert.ok(profileReceived, 'Profile should have been generated with all requests')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('Profiler should filter requests by timestamp', async (t) => {
|
|
112
|
+
const app = await setupApp(t)
|
|
113
|
+
|
|
114
|
+
const profilesReceived = []
|
|
115
|
+
const profiler = new Profiler({
|
|
116
|
+
app,
|
|
117
|
+
workerId: 'main:0',
|
|
118
|
+
type: 'cpu',
|
|
119
|
+
duration: 1000,
|
|
120
|
+
sourceMaps: false,
|
|
121
|
+
onProfile: (err, profile, requests) => {
|
|
122
|
+
if (err) return
|
|
123
|
+
profilesReceived.push({ profile, requests })
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
t.after(async () => {
|
|
128
|
+
await profiler.stop()
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// Request first profile
|
|
132
|
+
await profiler.requestProfile({ alertId: 'alert-1' })
|
|
133
|
+
|
|
134
|
+
// Wait for first profile
|
|
135
|
+
await sleep(1500)
|
|
136
|
+
|
|
137
|
+
// Request second profile after some delay
|
|
138
|
+
await profiler.requestProfile({ alertId: 'alert-2' })
|
|
139
|
+
|
|
140
|
+
// Wait for second profile
|
|
141
|
+
await sleep(1500)
|
|
142
|
+
|
|
143
|
+
assert.strictEqual(profilesReceived.length, 2)
|
|
144
|
+
assert.strictEqual(profilesReceived[0].requests.length, 1)
|
|
145
|
+
assert.strictEqual(profilesReceived[0].requests[0].alertId, 'alert-1')
|
|
146
|
+
assert.strictEqual(profilesReceived[1].requests.length, 1)
|
|
147
|
+
assert.strictEqual(profilesReceived[1].requests[0].alertId, 'alert-2')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('Profiler should auto-stop after idle period', async (t) => {
|
|
151
|
+
const app = await setupApp(t)
|
|
152
|
+
|
|
153
|
+
let stopCalled = false
|
|
154
|
+
const originalSendCommand = app.watt.runtime.sendCommandToApplication
|
|
155
|
+
app.watt.runtime.sendCommandToApplication = async (workerId, command, options) => {
|
|
156
|
+
if (command === 'stopProfiling') {
|
|
157
|
+
stopCalled = true
|
|
158
|
+
}
|
|
159
|
+
return originalSendCommand.call(app.watt.runtime, workerId, command, options)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const profiler = new Profiler({
|
|
163
|
+
app,
|
|
164
|
+
workerId: 'main:0',
|
|
165
|
+
type: 'cpu',
|
|
166
|
+
duration: 1000,
|
|
167
|
+
sourceMaps: false,
|
|
168
|
+
onProfile: () => {}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
t.after(async () => {
|
|
172
|
+
await profiler.stop()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// Request a profile
|
|
176
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
177
|
+
|
|
178
|
+
// Wait for profile generation + idle timeout (duration + duration/2)
|
|
179
|
+
await sleep(2000)
|
|
180
|
+
|
|
181
|
+
assert.ok(stopCalled, 'Profiler should have stopped after idle period')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('Profiler should not stop if new requests arrive', async (t) => {
|
|
185
|
+
const app = await setupApp(t)
|
|
186
|
+
|
|
187
|
+
let stopCalled = false
|
|
188
|
+
const originalSendCommand = app.watt.runtime.sendCommandToApplication
|
|
189
|
+
app.watt.runtime.sendCommandToApplication = async (workerId, command, options) => {
|
|
190
|
+
if (command === 'stopProfiling') {
|
|
191
|
+
stopCalled = true
|
|
192
|
+
}
|
|
193
|
+
return originalSendCommand.call(app.watt.runtime, workerId, command, options)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const profilesReceived = []
|
|
197
|
+
const profiler = new Profiler({
|
|
198
|
+
app,
|
|
199
|
+
workerId: 'main:0',
|
|
200
|
+
type: 'cpu',
|
|
201
|
+
duration: 1000,
|
|
202
|
+
sourceMaps: false,
|
|
203
|
+
onProfile: (err, profile, requests) => {
|
|
204
|
+
if (err) return
|
|
205
|
+
profilesReceived.push({ profile, requests })
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
t.after(async () => {
|
|
210
|
+
await profiler.stop()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Request first profile
|
|
214
|
+
await profiler.requestProfile({ alertId: 'alert-1' })
|
|
215
|
+
|
|
216
|
+
// Wait for first profile
|
|
217
|
+
await sleep(1200)
|
|
218
|
+
|
|
219
|
+
// Request second profile before idle timeout
|
|
220
|
+
await profiler.requestProfile({ alertId: 'alert-2' })
|
|
221
|
+
|
|
222
|
+
// Wait for second profile
|
|
223
|
+
await sleep(1200)
|
|
224
|
+
|
|
225
|
+
assert.strictEqual(profilesReceived.length, 2)
|
|
226
|
+
assert.strictEqual(stopCalled, false, 'Profiler should not have stopped yet')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test('Profiler should handle errors when starting profiling', async (t) => {
|
|
230
|
+
const app = await setupApp(t)
|
|
231
|
+
|
|
232
|
+
// Mock sendCommandToApplication to throw error on startProfiling
|
|
233
|
+
const originalSendCommand = app.watt.runtime.sendCommandToApplication
|
|
234
|
+
app.watt.runtime.sendCommandToApplication = async (workerId, command, options) => {
|
|
235
|
+
if (command === 'startProfiling') {
|
|
236
|
+
throw new Error('Failed to start profiling')
|
|
237
|
+
}
|
|
238
|
+
return originalSendCommand.call(app.watt.runtime, workerId, command, options)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let errorReceived = false
|
|
242
|
+
const profiler = new Profiler({
|
|
243
|
+
app,
|
|
244
|
+
workerId: 'main:0',
|
|
245
|
+
type: 'cpu',
|
|
246
|
+
duration: 1000,
|
|
247
|
+
sourceMaps: false,
|
|
248
|
+
onProfile: (err, profile, requests) => {
|
|
249
|
+
assert.ok(err)
|
|
250
|
+
assert.strictEqual(err.message, 'Failed to start profiling')
|
|
251
|
+
assert.strictEqual(profile, null)
|
|
252
|
+
assert.strictEqual(requests.length, 1)
|
|
253
|
+
errorReceived = true
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
t.after(async () => {
|
|
258
|
+
await profiler.stop()
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Request a profile
|
|
262
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
263
|
+
|
|
264
|
+
// Wait for error callback
|
|
265
|
+
await sleep(200)
|
|
266
|
+
|
|
267
|
+
assert.ok(errorReceived, 'Error should have been handled')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('Profiler should handle errors when getting profile', async (t) => {
|
|
271
|
+
const app = await setupApp(t)
|
|
272
|
+
|
|
273
|
+
// Mock sendCommandToApplication to throw error on getLastProfile
|
|
274
|
+
const originalSendCommand = app.watt.runtime.sendCommandToApplication
|
|
275
|
+
app.watt.runtime.sendCommandToApplication = async (workerId, command, options) => {
|
|
276
|
+
if (command === 'getLastProfile') {
|
|
277
|
+
throw new Error('Failed to get profile')
|
|
278
|
+
}
|
|
279
|
+
return originalSendCommand.call(app.watt.runtime, workerId, command, options)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let errorReceived = false
|
|
283
|
+
const profiler = new Profiler({
|
|
284
|
+
app,
|
|
285
|
+
workerId: 'main:0',
|
|
286
|
+
type: 'cpu',
|
|
287
|
+
duration: 1000,
|
|
288
|
+
sourceMaps: false,
|
|
289
|
+
onProfile: (err, profile, requests) => {
|
|
290
|
+
assert.ok(err)
|
|
291
|
+
assert.strictEqual(err.message, 'Failed to get profile')
|
|
292
|
+
assert.strictEqual(profile, null)
|
|
293
|
+
assert.strictEqual(requests.length, 1)
|
|
294
|
+
errorReceived = true
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
t.after(async () => {
|
|
299
|
+
await profiler.stop()
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
// Request a profile
|
|
303
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
304
|
+
|
|
305
|
+
// Wait for profile generation attempt
|
|
306
|
+
await sleep(1500)
|
|
307
|
+
|
|
308
|
+
assert.ok(errorReceived, 'Error should have been handled')
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('Profiler should pass sourceMaps option correctly', async (t) => {
|
|
312
|
+
const app = await setupApp(t)
|
|
313
|
+
|
|
314
|
+
let sourceMapsOptionReceived = null
|
|
315
|
+
const originalSendCommand = app.watt.runtime.sendCommandToApplication
|
|
316
|
+
app.watt.runtime.sendCommandToApplication = async (workerId, command, options) => {
|
|
317
|
+
if (command === 'startProfiling') {
|
|
318
|
+
sourceMapsOptionReceived = options.sourceMaps
|
|
319
|
+
}
|
|
320
|
+
return originalSendCommand.call(app.watt.runtime, workerId, command, options)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const profiler = new Profiler({
|
|
324
|
+
app,
|
|
325
|
+
workerId: 'main:0',
|
|
326
|
+
type: 'cpu',
|
|
327
|
+
duration: 1000,
|
|
328
|
+
sourceMaps: true,
|
|
329
|
+
onProfile: () => {}
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
t.after(async () => {
|
|
333
|
+
await profiler.stop()
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// Request a profile
|
|
337
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
338
|
+
|
|
339
|
+
// Wait for profiling to start
|
|
340
|
+
await sleep(200)
|
|
341
|
+
|
|
342
|
+
assert.strictEqual(sourceMapsOptionReceived, true, 'sourceMaps option should be passed correctly')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('Profiler should manually stop profiling when stop() is called', async (t) => {
|
|
346
|
+
const app = await setupApp(t)
|
|
347
|
+
|
|
348
|
+
let stopCalled = false
|
|
349
|
+
const originalSendCommand = app.watt.runtime.sendCommandToApplication
|
|
350
|
+
app.watt.runtime.sendCommandToApplication = async (workerId, command, options) => {
|
|
351
|
+
if (command === 'stopProfiling') {
|
|
352
|
+
stopCalled = true
|
|
353
|
+
}
|
|
354
|
+
return originalSendCommand.call(app.watt.runtime, workerId, command, options)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const profiler = new Profiler({
|
|
358
|
+
app,
|
|
359
|
+
workerId: 'main:0',
|
|
360
|
+
type: 'cpu',
|
|
361
|
+
duration: 1000,
|
|
362
|
+
sourceMaps: false,
|
|
363
|
+
onProfile: () => {}
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// Request a profile
|
|
367
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
368
|
+
|
|
369
|
+
// Wait a bit
|
|
370
|
+
await sleep(200)
|
|
371
|
+
|
|
372
|
+
// Manually stop
|
|
373
|
+
await profiler.stop()
|
|
374
|
+
|
|
375
|
+
assert.ok(stopCalled, 'stopProfiling should have been called')
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('Profiler should handle requests with custom timestamps', async (t) => {
|
|
379
|
+
const app = await setupApp(t)
|
|
380
|
+
|
|
381
|
+
let profileReceived = false
|
|
382
|
+
const profiler = new Profiler({
|
|
383
|
+
app,
|
|
384
|
+
workerId: 'main:0',
|
|
385
|
+
type: 'cpu',
|
|
386
|
+
duration: 1000,
|
|
387
|
+
sourceMaps: false,
|
|
388
|
+
onProfile: (err, profile, requests) => {
|
|
389
|
+
assert.strictEqual(err, null)
|
|
390
|
+
assert.ok(profile)
|
|
391
|
+
assert.strictEqual(requests.length, 1)
|
|
392
|
+
assert.strictEqual(requests[0].timestamp, 123456)
|
|
393
|
+
profileReceived = true
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
t.after(async () => {
|
|
398
|
+
await profiler.stop()
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// Request with custom timestamp
|
|
402
|
+
await profiler.requestProfile({ alertId: 'test-alert', timestamp: 123456 })
|
|
403
|
+
|
|
404
|
+
// Wait for profile to be generated
|
|
405
|
+
await sleep(1500)
|
|
406
|
+
|
|
407
|
+
assert.ok(profileReceived, 'Profile should have been generated with custom timestamp')
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
test('Profiler should generate valid pprof profile', async (t) => {
|
|
411
|
+
const app = await setupApp(t)
|
|
412
|
+
|
|
413
|
+
let profileData = null
|
|
414
|
+
const profiler = new Profiler({
|
|
415
|
+
app,
|
|
416
|
+
workerId: 'main:0',
|
|
417
|
+
type: 'cpu',
|
|
418
|
+
duration: 1000,
|
|
419
|
+
sourceMaps: false,
|
|
420
|
+
onProfile: (err, profile, requests) => {
|
|
421
|
+
if (!err && profile) {
|
|
422
|
+
profileData = profile.data
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
t.after(async () => {
|
|
428
|
+
await profiler.stop()
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
// Request a profile
|
|
432
|
+
await profiler.requestProfile({ alertId: 'test-alert' })
|
|
433
|
+
|
|
434
|
+
// Wait for profile to be generated
|
|
435
|
+
await sleep(1500)
|
|
436
|
+
|
|
437
|
+
assert.ok(profileData, 'Profile data should exist')
|
|
438
|
+
|
|
439
|
+
// Verify it's a valid pprof format
|
|
440
|
+
const profile = Profile.decode(profileData)
|
|
441
|
+
assert.ok(profile, 'Profile should be decodable')
|
|
442
|
+
assert.ok(profile.sample, 'Profile should have samples')
|
|
443
|
+
})
|