@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.
@@ -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
- })