@platformatic/watt-extra 1.6.3-alpha.5 → 1.7.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 +8 -5
- package/package.json +1 -1
- package/plugins/alerts.js +25 -1
- package/plugins/env.js +2 -1
- package/plugins/flamegraphs.js +210 -244
- package/plugins/health-signals.js +3 -5
- package/plugins/update.js +2 -2
- package/test/alerts.test.js +179 -7
- package/test/health-signals.test.js +5 -2
- package/test/helper.js +1 -0
- package/test/trigger-flamegraphs.test.js +439 -187
- package/test/profiler.test.js +0 -443
package/test/profiler.test.js
DELETED
|
@@ -1,443 +0,0 @@
|
|
|
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
|
-
})
|