@platformatic/watt-extra 1.5.3 → 1.6.0-alpha.1
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 -1
- package/lib/watt.js +23 -5
- package/package.json +9 -9
- package/plugins/alerts.js +41 -2
- package/plugins/flamegraphs.js +8 -2
- package/test/alerts.test.js +151 -19
- package/test/fixtures/runtime-health-custom/package.json +20 -0
- package/test/fixtures/runtime-health-custom/platformatic.json +21 -0
- package/test/fixtures/runtime-health-custom/services/service-1/package.json +17 -0
- package/test/fixtures/runtime-health-custom/services/service-1/platformatic.json +16 -0
- package/test/fixtures/runtime-health-custom/services/service-1/plugins/example.js +6 -0
- package/test/fixtures/runtime-health-custom/services/service-1/routes/root.cjs +8 -0
- package/test/fixtures/runtime-health-custom/services/service-2/package.json +17 -0
- package/test/fixtures/runtime-health-custom/services/service-2/platformatic.json +16 -0
- package/test/fixtures/runtime-health-custom/services/service-2/plugins/example.js +6 -0
- package/test/fixtures/runtime-health-custom/services/service-2/routes/root.cjs +8 -0
- package/test/fixtures/runtime-health-disabled/package.json +20 -0
- package/test/fixtures/runtime-health-disabled/platformatic.json +20 -0
- package/test/fixtures/runtime-health-disabled/services/service-1/package.json +17 -0
- package/test/fixtures/runtime-health-disabled/services/service-1/platformatic.json +16 -0
- package/test/fixtures/runtime-health-disabled/services/service-1/plugins/example.js +6 -0
- package/test/fixtures/runtime-health-disabled/services/service-1/routes/root.cjs +8 -0
- package/test/fixtures/runtime-health-disabled/services/service-2/package.json +17 -0
- package/test/fixtures/runtime-health-disabled/services/service-2/platformatic.json +16 -0
- package/test/fixtures/runtime-health-disabled/services/service-2/plugins/example.js +6 -0
- package/test/fixtures/runtime-health-disabled/services/service-2/routes/root.cjs +8 -0
- package/test/health.test.js +85 -2
- package/test/patch-config.test.js +117 -2
- package/test/trigger-flamegraphs.test.js +431 -9
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.platformatic.dev/@platformatic/runtime/2.62.1.json",
|
|
3
|
+
"entrypoint": "service-1",
|
|
4
|
+
"watch": true,
|
|
5
|
+
"restartOnError": true,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "services",
|
|
8
|
+
"exclude": ["docs"]
|
|
9
|
+
},
|
|
10
|
+
"logger": {
|
|
11
|
+
"level": "info"
|
|
12
|
+
},
|
|
13
|
+
"server": {
|
|
14
|
+
"hostname": "127.0.0.1",
|
|
15
|
+
"port": "3042"
|
|
16
|
+
},
|
|
17
|
+
"health": {
|
|
18
|
+
"enabled": false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "service-1",
|
|
3
|
+
"scripts": {
|
|
4
|
+
"start": "platformatic start",
|
|
5
|
+
"test": "borp"
|
|
6
|
+
},
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"fastify": "^5.0.0",
|
|
9
|
+
"borp": "^0.19.0"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@platformatic/service": "^2.70.0"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^18.8.0 || >=20.6.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.platformatic.dev/@platformatic/service/2.62.1.json",
|
|
3
|
+
"service": {
|
|
4
|
+
"openapi": true
|
|
5
|
+
},
|
|
6
|
+
"watch": true,
|
|
7
|
+
"plugins": {
|
|
8
|
+
"paths": [
|
|
9
|
+
{
|
|
10
|
+
"path": "./plugins",
|
|
11
|
+
"encapsulate": false
|
|
12
|
+
},
|
|
13
|
+
"./routes"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "service-2",
|
|
3
|
+
"scripts": {
|
|
4
|
+
"start": "platformatic start",
|
|
5
|
+
"test": "borp"
|
|
6
|
+
},
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"fastify": "^5.0.0",
|
|
9
|
+
"borp": "^0.19.0"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@platformatic/service": "^2.70.0"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^18.8.0 || >=20.6.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schemas.platformatic.dev/@platformatic/service/2.62.1.json",
|
|
3
|
+
"service": {
|
|
4
|
+
"openapi": true
|
|
5
|
+
},
|
|
6
|
+
"watch": true,
|
|
7
|
+
"plugins": {
|
|
8
|
+
"paths": [
|
|
9
|
+
{
|
|
10
|
+
"path": "./plugins",
|
|
11
|
+
"encapsulate": false
|
|
12
|
+
},
|
|
13
|
+
"./routes"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
package/test/health.test.js
CHANGED
|
@@ -3,6 +3,7 @@ import { test } from 'node:test'
|
|
|
3
3
|
import { randomUUID } from 'node:crypto'
|
|
4
4
|
import { join, dirname } from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { setTimeout } from 'node:timers/promises'
|
|
6
7
|
|
|
7
8
|
import {
|
|
8
9
|
setUpEnvironment,
|
|
@@ -37,8 +38,90 @@ test('check that health is configured in runtime', async (t) => {
|
|
|
37
38
|
|
|
38
39
|
const runtimeConfig = await app.watt.runtime.getRuntimeConfig()
|
|
39
40
|
|
|
41
|
+
// Runtime applies its own defaults on top of ours, so we just verify health is enabled
|
|
42
|
+
// and has expected properties
|
|
40
43
|
assert.ok(runtimeConfig.health, 'Health configuration should be present')
|
|
41
44
|
assert.strictEqual(runtimeConfig.health.enabled, true, 'Health monitoring should be enabled')
|
|
42
|
-
assert.
|
|
43
|
-
assert.
|
|
45
|
+
assert.ok(runtimeConfig.health.interval, 'Health interval should be set')
|
|
46
|
+
assert.ok(runtimeConfig.health.maxUnhealthyChecks, 'Health maxUnhealthyChecks should be set')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('check that custom health configuration is not overridden', async (t) => {
|
|
50
|
+
const appName = 'test-health-custom'
|
|
51
|
+
const applicationId = randomUUID()
|
|
52
|
+
const applicationPath = join(__dirname, 'fixtures', 'runtime-health-custom')
|
|
53
|
+
|
|
54
|
+
const icc = await startICC(t, {
|
|
55
|
+
applicationId
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
setUpEnvironment({
|
|
59
|
+
PLT_APP_NAME: appName,
|
|
60
|
+
PLT_APP_DIR: applicationPath,
|
|
61
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000'
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const app = await start()
|
|
65
|
+
|
|
66
|
+
t.after(async () => {
|
|
67
|
+
await app.close()
|
|
68
|
+
await icc.close()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const runtimeConfig = await app.watt.runtime.getRuntimeConfig()
|
|
72
|
+
|
|
73
|
+
assert.ok(runtimeConfig.health, 'Health configuration should be present')
|
|
74
|
+
assert.strictEqual(runtimeConfig.health.enabled, true, 'Health monitoring should be enabled')
|
|
75
|
+
assert.strictEqual(runtimeConfig.health.interval, 2500, 'Custom interval should be preserved')
|
|
76
|
+
assert.strictEqual(runtimeConfig.health.maxUnhealthyChecks, 50, 'Custom maxUnhealthyChecks should be preserved')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should force health enabled and set defaults when app tries to disable it', async (t) => {
|
|
80
|
+
const appName = 'test-health-disabled'
|
|
81
|
+
const applicationId = randomUUID()
|
|
82
|
+
const applicationPath = join(__dirname, 'fixtures', 'runtime-health-disabled')
|
|
83
|
+
|
|
84
|
+
const icc = await startICC(t, {
|
|
85
|
+
applicationId
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
setUpEnvironment({
|
|
89
|
+
PLT_APP_NAME: appName,
|
|
90
|
+
PLT_APP_DIR: applicationPath,
|
|
91
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const app = await start()
|
|
95
|
+
|
|
96
|
+
t.after(async () => {
|
|
97
|
+
await app.close()
|
|
98
|
+
await icc.close()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const runtimeConfig = await app.watt.runtime.getRuntimeConfig()
|
|
102
|
+
|
|
103
|
+
assert.ok(runtimeConfig.health, 'Health configuration should be present')
|
|
104
|
+
assert.strictEqual(runtimeConfig.health.enabled, true, 'Health should be forced to enabled even if app config says false')
|
|
105
|
+
assert.ok(runtimeConfig.health.interval, 'Health interval should be set')
|
|
106
|
+
assert.ok(runtimeConfig.health.maxUnhealthyChecks, 'Health maxUnhealthyChecks should be set')
|
|
107
|
+
|
|
108
|
+
// Wait for health events
|
|
109
|
+
let healthEventReceived = false
|
|
110
|
+
const eventName = app.watt.runtimeSupportsNewHealthMetrics()
|
|
111
|
+
? 'application:worker:health:metrics'
|
|
112
|
+
: 'application:worker:health'
|
|
113
|
+
|
|
114
|
+
app.watt.runtime.on(eventName, (healthInfo) => {
|
|
115
|
+
healthEventReceived = true
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Wait up to 5 seconds for a health event
|
|
119
|
+
const startTime = Date.now()
|
|
120
|
+
/* eslint-disable no-unmodified-loop-condition */
|
|
121
|
+
while (!healthEventReceived && (Date.now() - startTime) < 5000) {
|
|
122
|
+
await setTimeout(100)
|
|
123
|
+
}
|
|
124
|
+
/* eslint-enable no-unmodified-loop-condition */
|
|
125
|
+
|
|
126
|
+
assert.ok(healthEventReceived, 'Should receive health events since enabled is forced to true')
|
|
44
127
|
})
|
|
@@ -151,9 +151,13 @@ test('should configure health options', async (t) => {
|
|
|
151
151
|
const runtimeConfig = await app.watt.runtime.getRuntimeConfig(true)
|
|
152
152
|
|
|
153
153
|
const { health } = runtimeConfig
|
|
154
|
+
// Runtime applies its own defaults on top of ours, so we just verify health is configured
|
|
154
155
|
assert.strictEqual(health.enabled, true)
|
|
155
|
-
assert.
|
|
156
|
-
assert.strictEqual(health.maxUnhealthyChecks,
|
|
156
|
+
assert.ok(health.interval, 'Health interval should be set')
|
|
157
|
+
assert.strictEqual(health.maxUnhealthyChecks, 10, 'Health maxUnhealthyChecks should have default value')
|
|
158
|
+
assert.strictEqual(health.maxELU, 0.99, 'Health maxELU should have default value')
|
|
159
|
+
assert.strictEqual(health.maxHeapUsed, 0.99, 'Health maxHeapUsed should have default value')
|
|
160
|
+
assert.strictEqual(health.gracePeriod, 30000, 'Health gracePeriod should have default value')
|
|
157
161
|
})
|
|
158
162
|
|
|
159
163
|
test('should not set opentelemetry if it is disabled', async (t) => {
|
|
@@ -214,3 +218,114 @@ test('should not set opentelemetry if it is disabled', async (t) => {
|
|
|
214
218
|
const mainConfig = await body.json()
|
|
215
219
|
assert.deepStrictEqual(mainConfig.telemetry, expectedTelemetry)
|
|
216
220
|
})
|
|
221
|
+
test('should expose runtimeSupportsNewHealthMetrics method', async (t) => {
|
|
222
|
+
const applicationName = 'test-application'
|
|
223
|
+
const applicationId = randomUUID()
|
|
224
|
+
const applicationPath = join(__dirname, 'fixtures', 'service-1')
|
|
225
|
+
|
|
226
|
+
const icc = await startICC(t, {
|
|
227
|
+
applicationId,
|
|
228
|
+
applicationName
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
setUpEnvironment({
|
|
232
|
+
PLT_APP_NAME: applicationName,
|
|
233
|
+
PLT_APP_DIR: applicationPath,
|
|
234
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000'
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const app = await start()
|
|
238
|
+
|
|
239
|
+
t.after(async () => {
|
|
240
|
+
await app.close()
|
|
241
|
+
await icc.close()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
assert.strictEqual(typeof app.watt.runtimeSupportsNewHealthMetrics, 'function', 'runtimeSupportsNewHealthMetrics should be a function')
|
|
245
|
+
|
|
246
|
+
const supportsNewMetrics = app.watt.runtimeSupportsNewHealthMetrics()
|
|
247
|
+
assert.strictEqual(typeof supportsNewMetrics, 'boolean', 'runtimeSupportsNewHealthMetrics should return a boolean')
|
|
248
|
+
|
|
249
|
+
const runtimeVersion = app.watt.getRuntimeVersion()
|
|
250
|
+
assert.ok(runtimeVersion, 'Runtime version should be available')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('should expose getHealthConfig method', async (t) => {
|
|
254
|
+
const applicationName = 'test-application'
|
|
255
|
+
const applicationId = randomUUID()
|
|
256
|
+
const applicationPath = join(__dirname, 'fixtures', 'service-1')
|
|
257
|
+
|
|
258
|
+
const icc = await startICC(t, {
|
|
259
|
+
applicationId,
|
|
260
|
+
applicationName
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
setUpEnvironment({
|
|
264
|
+
PLT_APP_NAME: applicationName,
|
|
265
|
+
PLT_APP_DIR: applicationPath,
|
|
266
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000'
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const app = await start()
|
|
270
|
+
|
|
271
|
+
t.after(async () => {
|
|
272
|
+
await app.close()
|
|
273
|
+
await icc.close()
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
assert.strictEqual(typeof app.watt.getHealthConfig, 'function', 'getHealthConfig should be a function')
|
|
277
|
+
|
|
278
|
+
const healthConfig = app.watt.getHealthConfig()
|
|
279
|
+
assert.ok(healthConfig, 'Health config should be returned')
|
|
280
|
+
assert.strictEqual(typeof healthConfig, 'object', 'Health config should be an object')
|
|
281
|
+
assert.strictEqual(healthConfig.enabled, true, 'Health should be enabled')
|
|
282
|
+
assert.ok(healthConfig.interval, 'Health config should have interval')
|
|
283
|
+
assert.strictEqual(healthConfig.maxUnhealthyChecks, 10, 'Health config should have maxUnhealthyChecks default')
|
|
284
|
+
assert.strictEqual(healthConfig.maxELU, 0.99, 'Health config should have maxELU default')
|
|
285
|
+
assert.strictEqual(healthConfig.maxHeapUsed, 0.99, 'Health config should have maxHeapUsed default')
|
|
286
|
+
assert.strictEqual(healthConfig.gracePeriod, 30000, 'Health config should have gracePeriod default')
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test('should configure health based on runtime version', async (t) => {
|
|
290
|
+
const appName = 'test-app'
|
|
291
|
+
const applicationId = randomUUID()
|
|
292
|
+
const applicationPath = join(__dirname, 'fixtures', 'runtime-service')
|
|
293
|
+
|
|
294
|
+
await installDeps(t, applicationPath)
|
|
295
|
+
|
|
296
|
+
const icc = await startICC(t, {
|
|
297
|
+
applicationId
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
setUpEnvironment({
|
|
301
|
+
PLT_APP_NAME: appName,
|
|
302
|
+
PLT_APP_DIR: applicationPath,
|
|
303
|
+
PLT_ICC_URL: 'http://127.0.0.1:3000'
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const app = await start()
|
|
307
|
+
|
|
308
|
+
t.after(async () => {
|
|
309
|
+
await app.close()
|
|
310
|
+
await icc.close()
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
const runtimeConfig = app.watt.runtime.getRuntimeConfig(true)
|
|
314
|
+
const { health } = runtimeConfig
|
|
315
|
+
|
|
316
|
+
// Health should always be enabled regardless of runtime version
|
|
317
|
+
assert.strictEqual(health.enabled, true, 'Health should be enabled')
|
|
318
|
+
|
|
319
|
+
const supportsNewMetrics = app.watt.runtimeSupportsNewHealthMetrics()
|
|
320
|
+
|
|
321
|
+
if (supportsNewMetrics) {
|
|
322
|
+
// For new runtime (>= 3.18.0), we only force enabled: true
|
|
323
|
+
// Other values come from app config or runtime defaults
|
|
324
|
+
assert.ok(health.interval, 'Health interval should be set')
|
|
325
|
+
assert.ok(health.maxUnhealthyChecks, 'Health maxUnhealthyChecks should be set')
|
|
326
|
+
} else {
|
|
327
|
+
// For old runtime (< 3.18.0), we set specific defaults
|
|
328
|
+
assert.ok(health.interval, 'Health interval should be set')
|
|
329
|
+
assert.ok(health.maxUnhealthyChecks, 'Health maxUnhealthyChecks should be set')
|
|
330
|
+
}
|
|
331
|
+
})
|