@platformatic/watt-extra 1.4.0-alpha.3 → 1.4.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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//work/workspaces/workspace-platformatic/platformatic/**)",
5
+ "Bash(npx borp:*)",
6
+ "Bash(timeout 30 npx borp -c 1 --timeout=20000 ./test/trigger-flamegraphs.test.js)"
7
+ ],
8
+ "deny": [],
9
+ "ask": []
10
+ }
11
+ }
package/app.js CHANGED
@@ -112,6 +112,9 @@ async function buildApp (logger) {
112
112
 
113
113
  app.close = async function close () {
114
114
  app.log.info('Closing runtime')
115
+ if (app.cleanupFlamegraphs) {
116
+ await app.cleanupFlamegraphs()
117
+ }
115
118
  if (app.watt.runtime) {
116
119
  await app.watt.close()
117
120
  }
package/lib/watt.js CHANGED
@@ -151,7 +151,17 @@ class Watt {
151
151
  config.server = {
152
152
  ...serverConfig,
153
153
  hostname: this.#env.PLT_APP_HOSTNAME || serverConfig.hostname,
154
- port: this.#env.PLT_APP_PORT || serverConfig.port,
154
+ port: this.#env.PLT_APP_PORT || serverConfig.port
155
+ }
156
+
157
+ const labels = {
158
+ serviceId: 'main',
159
+ instanceId: this.#instanceId
160
+ }
161
+
162
+ const applicationId = this.#instanceConfig?.applicationId
163
+ if (applicationId) {
164
+ labels.applicationId = applicationId
155
165
  }
156
166
 
157
167
  config.hotReload = false
@@ -159,15 +169,11 @@ class Watt {
159
169
  config.metrics = {
160
170
  server: 'hide',
161
171
  defaultMetrics: {
162
- enabled: true,
172
+ enabled: true
163
173
  },
164
174
  hostname: this.#env.PLT_APP_HOSTNAME || '0.0.0.0',
165
175
  port: this.#env.PLT_METRICS_PORT || 9090,
166
- labels: {
167
- serviceId: 'main',
168
- applicationId: this.#instanceConfig?.applicationId,
169
- instanceId: this.#instanceId,
170
- },
176
+ labels,
171
177
  applicationLabel: this.#instanceConfig?.applicationMetricsLabel ?? 'serviceId'
172
178
  }
173
179
 
@@ -231,20 +237,20 @@ class Watt {
231
237
  ),
232
238
  options: {
233
239
  labels: {
234
- applicationId: this.#instanceConfig.applicationId,
240
+ applicationId: this.#instanceConfig.applicationId
235
241
  },
236
242
  bloomFilter: {
237
243
  size: 100000,
238
- errorRate: 0.01,
244
+ errorRate: 0.01
239
245
  },
240
246
  maxResponseSize: 5 * 1024 * 1024, // 5MB
241
247
  trafficInspectorOptions: {
242
248
  url: trafficInspectorOrigin,
243
249
  pathSendBody: join(trafficInspectorPath, '/requests'),
244
- pathSendMeta: join(trafficInspectorPath, '/requests/hash'),
250
+ pathSendMeta: join(trafficInspectorPath, '/requests/hash')
245
251
  },
246
- matchingDomains: [this.#env.PLT_APP_INTERNAL_SUB_DOMAIN],
247
- },
252
+ matchingDomains: [this.#env.PLT_APP_INTERNAL_SUB_DOMAIN]
253
+ }
248
254
  }
249
255
  }
250
256
 
@@ -255,9 +261,9 @@ class Watt {
255
261
  rules: [
256
262
  {
257
263
  routeToMatch: 'http://plt.slicer.default/',
258
- headers: {},
259
- },
260
- ],
264
+ headers: {}
265
+ }
266
+ ]
261
267
  }
262
268
 
263
269
  // This is the cache config from ICC
@@ -309,7 +315,7 @@ class Watt {
309
315
 
310
316
  return {
311
317
  module: require.resolve('undici-slicer-interceptor'),
312
- options: cacheConfig,
318
+ options: cacheConfig
313
319
  }
314
320
  }
315
321
 
@@ -341,7 +347,7 @@ class Watt {
341
347
  applicationName: `${this.#applicationName}`,
342
348
  skip: [
343
349
  { method: 'GET', path: '/documentation' },
344
- { method: 'GET', path: '/documentation/json' },
350
+ { method: 'GET', path: '/documentation/json' }
345
351
  ],
346
352
  exporter: {
347
353
  type: 'otlp',
@@ -349,14 +355,14 @@ class Watt {
349
355
  url:
350
356
  this.#instanceConfig?.iccServices?.riskEngine?.url + '/v1/traces',
351
357
  headers: {
352
- 'x-platformatic-application-id': this.#instanceConfig?.applicationId,
358
+ 'x-platformatic-application-id': this.#instanceConfig?.applicationId
353
359
  },
354
360
  keepAlive: true,
355
361
  httpAgentOptions: {
356
- rejectUnauthorized: false,
357
- },
358
- },
359
- },
362
+ rejectUnauthorized: false
363
+ }
364
+ }
365
+ }
360
366
  }
361
367
  }
362
368
 
@@ -375,7 +381,7 @@ class Watt {
375
381
  ...config.httpCache,
376
382
  cacheTagsHeader,
377
383
  store: require.resolve('undici-cache-redis'),
378
- clientOpts: httpCache,
384
+ clientOpts: httpCache
379
385
  }
380
386
  }
381
387
 
@@ -384,7 +390,7 @@ class Watt {
384
390
  ...config.health,
385
391
  enabled: true,
386
392
  interval: 1000,
387
- maxUnhealthyChecks: 30,
393
+ maxUnhealthyChecks: 30
388
394
  }
389
395
  }
390
396
 
@@ -394,7 +400,7 @@ class Watt {
394
400
  if (config.scheduler) {
395
401
  config.scheduler = config.scheduler.map((scheduler) => ({
396
402
  ...scheduler,
397
- enabled: false,
403
+ enabled: false
398
404
  }))
399
405
  }
400
406
  }
@@ -413,7 +419,7 @@ class Watt {
413
419
  [
414
420
  '@platformatic/service',
415
421
  '@platformatic/composer',
416
- '@platformatic/db',
422
+ '@platformatic/db'
417
423
  ].includes(app.type)
418
424
  ) {
419
425
  await this.#configurePlatformaticServices(runtime, app)
@@ -457,8 +463,8 @@ class Watt {
457
463
  adapter: 'valkey',
458
464
  url: `valkey://${username}:${password}@${host}:${port}`,
459
465
  prefix: keyPrefix,
460
- maxTTL: 604800, // 86400 * 7
461
- },
466
+ maxTTL: 604800 // 86400 * 7
467
+ }
462
468
  })
463
469
  }
464
470
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/watt-extra",
3
- "version": "1.4.0-alpha.3",
3
+ "version": "1.4.0",
4
4
  "description": "The Platformatic runtime manager",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -80,11 +80,45 @@ async function flamegraphs (app, _opts) {
80
80
  runtime.on('application:worker:started', workerStartedListener)
81
81
  }
82
82
 
83
- app.cleanupFlamegraphs = () => {
83
+ app.cleanupFlamegraphs = async () => {
84
84
  if (workerStartedListener && app.watt?.runtime) {
85
85
  app.watt.runtime.removeListener('application:worker:started', workerStartedListener)
86
86
  workerStartedListener = null
87
87
  }
88
+
89
+ // Explicitly stop all active profiling sessions to avoid memory corruption
90
+ if (!isFlamegraphsDisabled && app.watt?.runtime) {
91
+ try {
92
+ const workers = await app.watt.runtime.getWorkers()
93
+ const stopPromises = []
94
+ for (const workerFullId of Object.keys(workers)) {
95
+ // Stop both CPU and heap profiling on each worker
96
+ stopPromises.push(
97
+ app.watt.runtime.sendCommandToApplication(workerFullId, 'stopProfiling', { type: 'cpu' })
98
+ .catch(err => {
99
+ // Ignore errors if profiling wasn't running
100
+ if (err.code !== 'PLT_PPROF_PROFILING_NOT_STARTED') {
101
+ app.log.warn({ err, workerFullId }, 'Failed to stop CPU profiling')
102
+ }
103
+ })
104
+ )
105
+ stopPromises.push(
106
+ app.watt.runtime.sendCommandToApplication(workerFullId, 'stopProfiling', { type: 'heap' })
107
+ .catch(err => {
108
+ // Ignore errors if profiling wasn't running
109
+ if (err.code !== 'PLT_PPROF_PROFILING_NOT_STARTED') {
110
+ app.log.warn({ err, workerFullId }, 'Failed to stop heap profiling')
111
+ }
112
+ })
113
+ )
114
+ }
115
+ await Promise.all(stopPromises)
116
+ // Small delay to ensure native cleanup completes
117
+ await sleep(100)
118
+ } catch (err) {
119
+ app.log.warn({ err }, 'Failed to stop profiling during cleanup')
120
+ }
121
+ }
88
122
  }
89
123
 
90
124
  app.sendFlamegraphs = async (options = {}) => {
@@ -52,8 +52,6 @@ test('should send alert when service becomes unhealthy', async (t) => {
52
52
  const app = await start()
53
53
  app.getAuthorizationHeader = getAuthorizationHeader
54
54
 
55
- await app.setupAlerts()
56
-
57
55
  t.after(async () => {
58
56
  await app.close()
59
57
  await icc.close()
@@ -131,8 +129,6 @@ test('should not send alert when service is healthy', async (t) => {
131
129
  const app = await start()
132
130
  app.getAuthorizationHeader = getAuthorizationHeader
133
131
 
134
- await app.setupAlerts()
135
-
136
132
  t.after(async () => {
137
133
  await app.close()
138
134
  await icc.close()
@@ -199,8 +195,6 @@ test('should cache health data and include it in alerts', async (t) => {
199
195
  const app = await start()
200
196
  app.getAuthorizationHeader = getAuthorizationHeader
201
197
 
202
- await app.setupAlerts()
203
-
204
198
  t.after(async () => {
205
199
  await app.close()
206
200
  await icc.close()
@@ -314,8 +308,6 @@ test('should not fail when health info is missing', async (t) => {
314
308
  const app = await start()
315
309
  app.getAuthorizationHeader = getAuthorizationHeader
316
310
 
317
- await app.setupAlerts()
318
-
319
311
  t.after(async () => {
320
312
  await app.close()
321
313
  await icc.close()
@@ -365,8 +357,6 @@ test('should respect alert retention window', async (t) => {
365
357
 
366
358
  app.getAuthorizationHeader = getAuthorizationHeader
367
359
 
368
- await app.setupAlerts()
369
-
370
360
  t.after(async () => {
371
361
  await app.close()
372
362
  await icc.close()
@@ -489,8 +479,6 @@ test('should send alert when flamegraphs are disabled', async (t) => {
489
479
  const app = await start()
490
480
  app.getAuthorizationHeader = getAuthorizationHeader
491
481
 
492
- await app.setupAlerts()
493
-
494
482
  t.after(async () => {
495
483
  await app.close()
496
484
  await icc.close()
@@ -568,8 +556,6 @@ test('should send alert when failed to send a flamegraph', async (t) => {
568
556
  const app = await start()
569
557
  app.getAuthorizationHeader = getAuthorizationHeader
570
558
 
571
- await app.setupAlerts()
572
-
573
559
  t.after(async () => {
574
560
  await app.close()
575
561
  await icc.close()
@@ -28,7 +28,7 @@ test('should generate metrics with a correct labels', async (t) => {
28
28
  setUpEnvironment({
29
29
  PLT_APP_NAME: applicationName,
30
30
  PLT_APP_DIR: applicationPath,
31
- PLT_ICC_URL: 'http://127.0.0.1:3000',
31
+ PLT_ICC_URL: 'http://127.0.0.1:3000'
32
32
  })
33
33
 
34
34
  const app = await start()
@@ -40,7 +40,7 @@ test('should generate metrics with a correct labels', async (t) => {
40
40
 
41
41
  const { statusCode, body } = await request('http://127.0.0.1:9090/metrics', {
42
42
  headers: {
43
- accept: 'application/json',
43
+ accept: 'application/json'
44
44
  }
45
45
  })
46
46
  assert.strictEqual(statusCode, 200)
@@ -77,7 +77,7 @@ test('should generate metrics with a custom metrics label', async (t) => {
77
77
  setUpEnvironment({
78
78
  PLT_APP_NAME: applicationName,
79
79
  PLT_APP_DIR: applicationPath,
80
- PLT_ICC_URL: 'http://127.0.0.1:3000',
80
+ PLT_ICC_URL: 'http://127.0.0.1:3000'
81
81
  })
82
82
 
83
83
  const app = await start()
@@ -89,7 +89,7 @@ test('should generate metrics with a custom metrics label', async (t) => {
89
89
 
90
90
  const { statusCode, body } = await request('http://127.0.0.1:9090/metrics', {
91
91
  headers: {
92
- accept: 'application/json',
92
+ accept: 'application/json'
93
93
  }
94
94
  })
95
95
  assert.strictEqual(statusCode, 200)
@@ -105,3 +105,36 @@ test('should generate metrics with a custom metrics label', async (t) => {
105
105
  assert.strictEqual(labels[applicationMetricsLabel], 'main')
106
106
  }
107
107
  })
108
+
109
+ test('should not set an applicationId label if it is undefined', async (t) => {
110
+ const applicationName = 'test-app'
111
+ const applicationPath = join(__dirname, 'fixtures', 'service-1')
112
+
113
+ delete process.env.PLT_ICC_URL
114
+
115
+ process.env.PLT_TEST_APP_1_URL = 'http://test-app-1:3042'
116
+ t.after(() => {
117
+ delete process.env.PLT_TEST_APP_1_URL
118
+ })
119
+
120
+ setUpEnvironment({
121
+ PLT_APP_NAME: applicationName,
122
+ PLT_APP_DIR: applicationPath
123
+ })
124
+
125
+ const app = await start()
126
+
127
+ t.after(async () => {
128
+ await app.close()
129
+ })
130
+
131
+ const { statusCode, body } = await request('http://127.0.0.1:9090/metrics')
132
+ assert.strictEqual(statusCode, 200)
133
+
134
+ const metrics = await body.text()
135
+ const lines = metrics.split('\n')
136
+
137
+ for (const line of lines) {
138
+ assert.ok(!line.includes('applicationId'), 'applicationId label should not be set:' + line)
139
+ }
140
+ })