@platformatic/runtime 2.0.0-alpha.5 → 2.0.0-alpha.7

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/config.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and run json-schema-to-typescript to regenerate this file.
6
6
  */
7
7
 
8
- export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha5Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime200Alpha7Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
2
+ "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.5.json",
3
3
  "entrypoint": "service-1",
4
4
  "watch": false,
5
5
  "autoload": {
@@ -13,5 +13,10 @@
13
13
  "logs": {
14
14
  "maxSize": 6
15
15
  }
16
+ },
17
+ "metrics": {
18
+ "labels": {
19
+ "custom_label": "custom-value"
20
+ }
16
21
  }
17
22
  }
package/index.js CHANGED
@@ -6,11 +6,12 @@ const errors = require('./lib/errors')
6
6
  const { platformaticRuntime, wrapConfigInRuntimeConfig } = require('./lib/config')
7
7
  const RuntimeGenerator = require('./lib/generator/runtime-generator')
8
8
  const { Runtime } = require('./lib/runtime')
9
- const { start, startCommand } = require('./lib/start')
9
+ const { buildRuntime, start, startCommand } = require('./lib/start')
10
10
  const symbols = require('./lib/worker/symbols')
11
11
  const { loadConfig, getRuntimeLogsDir } = require('./lib/utils')
12
12
 
13
13
  module.exports.buildServer = buildServer
14
+ module.exports.buildRuntime = buildRuntime
14
15
  module.exports.compile = compile
15
16
  module.exports.errors = errors
16
17
  module.exports.Generator = RuntimeGenerator
package/lib/errors.js CHANGED
@@ -14,6 +14,7 @@ module.exports = {
14
14
  FailedToRetrieveOpenAPISchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_OPENAPI_SCHEMA`, 'Failed to retrieve OpenAPI schema for service with id "%s": %s'),
15
15
  FailedToRetrieveGraphQLSchemaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_GRAPHQL_SCHEMA`, 'Failed to retrieve GraphQL schema for service with id "%s": %s'),
16
16
  FailedToRetrieveMetaError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_META`, 'Failed to retrieve metadata for service with id "%s": %s'),
17
+ FailedToRetrieveMetricsError: createError(`${ERROR_PREFIX}_FAILED_TO_RETRIEVE_METRICS`, 'Failed to retrieve metrics for service with id "%s": %s'),
17
18
  ApplicationAlreadyStartedError: createError(`${ERROR_PREFIX}_APPLICATION_ALREADY_STARTED`, 'Application is already started'),
18
19
  ApplicationNotStartedError: createError(`${ERROR_PREFIX}_APPLICATION_NOT_STARTED`, 'Application has not been started'),
19
20
  ConfigPathMustBeStringError: createError(`${ERROR_PREFIX}_CONFIG_PATH_MUST_BE_STRING`, 'Config path must be a string'),
@@ -72,20 +72,6 @@ async function managementApiPlugin (app, opts) {
72
72
  return runtime.getServiceGraphqlSchema(id)
73
73
  })
74
74
 
75
- app.get('/services/:id/connection-strings', async request => {
76
- const { id } = request.params
77
- try {
78
- app.log.debug('get connection strings', { id })
79
- const meta = await runtime.getServiceMeta(id)
80
- if (meta.db) {
81
- return meta.db
82
- }
83
- return null
84
- } catch (err) {
85
- throw new errors.FailedToRetrieveMetaError(id, err.message)
86
- }
87
- })
88
-
89
75
  app.post('/services/:id/start', async request => {
90
76
  const { id } = request.params
91
77
  app.log.debug('start service', { id })
package/lib/runtime.js CHANGED
@@ -9,6 +9,7 @@ const { setTimeout: sleep } = require('node:timers/promises')
9
9
  const { Worker } = require('node:worker_threads')
10
10
 
11
11
  const { ITC } = require('@platformatic/itc')
12
+ const { Unpromise } = require('@watchable/unpromise')
12
13
  const ts = require('tail-file-stream')
13
14
  const { createThreadInterceptor } = require('undici-thread-interceptor')
14
15
 
@@ -150,7 +151,7 @@ class Runtime extends EventEmitter {
150
151
  return this.#url
151
152
  }
152
153
 
153
- async stop () {
154
+ async stop (silent = false) {
154
155
  if (this.#status === 'starting') {
155
156
  await once(this, 'started')
156
157
  }
@@ -158,7 +159,7 @@ class Runtime extends EventEmitter {
158
159
  this.#updateStatus('stopping')
159
160
  this.#startedServices.clear()
160
161
 
161
- await Promise.all(this.#servicesIds.map(service => this._stopService(service)))
162
+ await Promise.all(this.#servicesIds.map(service => this._stopService(service, silent)))
162
163
 
163
164
  this.#updateStatus('stopped')
164
165
  }
@@ -174,12 +175,12 @@ class Runtime extends EventEmitter {
174
175
  return this.#url
175
176
  }
176
177
 
177
- async close (fromManagementApi) {
178
+ async close (fromManagementApi = false, silent = false) {
178
179
  this.#updateStatus('closing')
179
180
 
180
181
  clearInterval(this.#metricsTimeout)
181
182
 
182
- await this.stop()
183
+ await this.stop(silent)
183
184
 
184
185
  if (this.#managementApi) {
185
186
  if (fromManagementApi) {
@@ -262,7 +263,7 @@ class Runtime extends EventEmitter {
262
263
  }
263
264
 
264
265
  // Do not rename to #stopService as this is used in tests
265
- async _stopService (id) {
266
+ async _stopService (id, silent) {
266
267
  const service = await this.#getServiceById(id, false, false)
267
268
 
268
269
  if (!service) {
@@ -271,17 +272,19 @@ class Runtime extends EventEmitter {
271
272
 
272
273
  this.#startedServices.set(id, false)
273
274
 
274
- this.logger?.info(`Stopping service "${id}"...`)
275
+ if (!silent) {
276
+ this.logger?.info(`Stopping service "${id}"...`)
277
+ }
275
278
 
276
279
  // Always send the stop message, it will shut down workers that only had ITC and interceptors setup
277
280
  try {
278
- await Promise.race([sendViaITC(service, 'stop'), sleep(10000, 'timeout', { ref: false })])
281
+ await Unpromise.race([sendViaITC(service, 'stop'), sleep(10000, 'timeout', { ref: false })])
279
282
  } catch (error) {
280
283
  this.logger?.info(`Failed to stop service "${id}". Killing a worker thread.`, error)
281
284
  }
282
285
 
283
286
  // Wait for the worker thread to finish, we're going to create a new one if the service is ever restarted
284
- const res = await Promise.race([once(service, 'exit'), sleep(10000, 'timeout', { ref: false })])
287
+ const res = await Unpromise.race([once(service, 'exit'), sleep(10000, 'timeout', { ref: false })])
285
288
 
286
289
  // If the worker didn't exit in time, kill it
287
290
  if (res === 'timeout') {
@@ -422,7 +425,7 @@ class Runtime extends EventEmitter {
422
425
  packageName: packageJson.name ?? null,
423
426
  packageVersion: packageJson.version ?? null,
424
427
  url: entrypointDetails?.url ?? null,
425
- platformaticVersion,
428
+ platformaticVersion
426
429
  }
427
430
  }
428
431
 
@@ -449,7 +452,7 @@ class Runtime extends EventEmitter {
449
452
  async getServices () {
450
453
  return {
451
454
  entrypoint: this.#entrypointId,
452
- services: await Promise.all(this.#servicesIds.map(id => this.getServiceDetails(id))),
455
+ services: await Promise.all(this.#servicesIds.map(id => this.getServiceDetails(id)))
453
456
  }
454
457
  }
455
458
 
@@ -478,7 +481,7 @@ class Runtime extends EventEmitter {
478
481
  version,
479
482
  localUrl,
480
483
  entrypoint,
481
- dependencies,
484
+ dependencies
482
485
  }
483
486
 
484
487
  if (entrypoint) {
@@ -523,7 +526,6 @@ class Runtime extends EventEmitter {
523
526
  }
524
527
 
525
528
  const serviceMetrics = await sendViaITC(service, 'getMetrics', format)
526
-
527
529
  if (serviceMetrics) {
528
530
  if (metrics === null) {
529
531
  metrics = format === 'json' ? [] : ''
@@ -575,17 +577,26 @@ class Runtime extends EventEmitter {
575
577
  let p99Value = 0
576
578
 
577
579
  const metricName = 'http_request_all_summary_seconds'
578
- const httpLatencyMetrics = metrics.find(metric => metric.name === metricName)
579
-
580
- p50Value = httpLatencyMetrics.values.find(value => value.labels.quantile === 0.5).value || 0
581
- p90Value = httpLatencyMetrics.values.find(value => value.labels.quantile === 0.9).value || 0
582
- p95Value = httpLatencyMetrics.values.find(value => value.labels.quantile === 0.95).value || 0
583
- p99Value = httpLatencyMetrics.values.find(value => value.labels.quantile === 0.99).value || 0
580
+ const httpLatencyMetrics = metrics.filter(
581
+ metric => metric.name === metricName
582
+ )
584
583
 
585
- p50Value = Math.round(p50Value * 1000)
586
- p90Value = Math.round(p90Value * 1000)
587
- p95Value = Math.round(p95Value * 1000)
588
- p99Value = Math.round(p99Value * 1000)
584
+ if (httpLatencyMetrics) {
585
+ const entrypointMetrics = httpLatencyMetrics.find(
586
+ metric => metric.values?.[0]?.labels?.serviceId === this.#entrypointId
587
+ )
588
+ if (entrypointMetrics) {
589
+ p50Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.5)?.value || 0
590
+ p90Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.9)?.value || 0
591
+ p95Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.95)?.value || 0
592
+ p99Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.99)?.value || 0
593
+
594
+ p50Value = Math.round(p50Value * 1000)
595
+ p90Value = Math.round(p90Value * 1000)
596
+ p95Value = Math.round(p95Value * 1000)
597
+ p99Value = Math.round(p99Value * 1000)
598
+ }
599
+ }
589
600
 
590
601
  const cpu = cpuMetric.values[0].value
591
602
  const rss = rssMetric.values[0].value
@@ -610,9 +621,9 @@ class Runtime extends EventEmitter {
610
621
  p50: p50Value,
611
622
  p90: p90Value,
612
623
  p95: p95Value,
613
- p99: p99Value,
614
- },
615
- },
624
+ p99: p99Value
625
+ }
626
+ }
616
627
  }
617
628
 
618
629
  return formattedMetrics
@@ -649,7 +660,7 @@ class Runtime extends EventEmitter {
649
660
  }
650
661
  runtimesLogsIds.push({
651
662
  pid: runtime.runtimePID,
652
- indexes: runtimeLogIds,
663
+ indexes: runtimeLogIds
653
664
  })
654
665
  }
655
666
 
@@ -687,7 +698,7 @@ class Runtime extends EventEmitter {
687
698
  serviceConfig,
688
699
  dirname: this.#configManager.dirname,
689
700
  runtimeLogsDir: this.#runtimeLogsDir,
690
- loggingPort,
701
+ loggingPort
691
702
  },
692
703
  execArgv: [], // Avoid side effects
693
704
  env: this.#env,
@@ -700,7 +711,7 @@ class Runtime extends EventEmitter {
700
711
  The author of this (Paolo and Matteo) are not proud of the solution. Forgive us.
701
712
  */
702
713
  stdout: true,
703
- stderr: true,
714
+ stderr: true
704
715
  })
705
716
 
706
717
  // Make sure the listener can handle a lot of API requests at once before raising a warning
@@ -737,9 +748,13 @@ class Runtime extends EventEmitter {
737
748
  service[kConfig] = serviceConfig
738
749
 
739
750
  // Setup ITC
740
- service[kITC] = new ITC({ port: service })
751
+ service[kITC] = new ITC({
752
+ port: service,
753
+ handlers: {
754
+ getServiceMeta: this.getServiceMeta.bind(this)
755
+ }
756
+ })
741
757
  service[kITC].listen()
742
- service[kITC].handle('getServiceMeta', this.getServiceMeta.bind(this))
743
758
 
744
759
  // Handle services changes
745
760
  // This is not purposely activated on when this.#configManager.current.watch === true
@@ -904,7 +919,7 @@ class Runtime extends EventEmitter {
904
919
  runtimesLogFiles.push({
905
920
  runtimePID: parseInt(runtimePID),
906
921
  runtimeLogFiles,
907
- lastModified,
922
+ lastModified
908
923
  })
909
924
  }
910
925
 
package/lib/worker/app.js CHANGED
@@ -7,6 +7,7 @@ const debounce = require('debounce')
7
7
 
8
8
  const errors = require('../errors')
9
9
  const defaultStackable = require('./default-stackable')
10
+ const { collectMetrics } = require('./metrics')
10
11
  const { getServiceUrl, loadConfig, loadEmptyConfig } = require('../utils')
11
12
 
12
13
  class PlatformaticApp extends EventEmitter {
@@ -15,6 +16,7 @@ class PlatformaticApp extends EventEmitter {
15
16
  #listening
16
17
  #watch
17
18
  #fileWatcher
19
+ #metricsRegistry
18
20
  #debouncedRestart
19
21
  #context
20
22
 
@@ -27,6 +29,7 @@ class PlatformaticApp extends EventEmitter {
27
29
  this.#listening = false
28
30
  this.stackable = null
29
31
  this.#fileWatcher = null
32
+ this.#metricsRegistry = null
30
33
 
31
34
  this.#context = {
32
35
  serviceId: this.appConfig.id,
@@ -93,6 +96,15 @@ class PlatformaticApp extends EventEmitter {
93
96
  })
94
97
  this.stackable = this.#wrapStackable(stackable)
95
98
 
99
+ const metricsConfig = this.#context.metricsConfig
100
+ if (metricsConfig !== false) {
101
+ this.#metricsRegistry = await collectMetrics(
102
+ this.stackable,
103
+ this.appConfig.id,
104
+ metricsConfig
105
+ )
106
+ }
107
+
96
108
  this.#updateDispatcher()
97
109
  } catch (err) {
98
110
  this.#logAndExit(err)
@@ -164,6 +176,14 @@ class PlatformaticApp extends EventEmitter {
164
176
  await this.stackable.start({ listen: true })
165
177
  }
166
178
 
179
+ async getMetrics ({ format }) {
180
+ if (!this.#metricsRegistry) return null
181
+
182
+ return format === 'json'
183
+ ? this.#metricsRegistry.getMetricsAsJSON()
184
+ : this.#metricsRegistry.metrics()
185
+ }
186
+
167
187
  #fetchServiceUrl (key, { parent, context: service }) {
168
188
  if (service.localServiceEnvVars.has(key)) {
169
189
  return service.localServiceEnvVars.get(key)
@@ -14,7 +14,10 @@ const defaultStackable = {
14
14
  getOpenapiSchema: () => null,
15
15
  getGraphqlSchema: () => null,
16
16
  getMeta: () => ({}),
17
- getMetrics: () => null,
17
+ collectMetrics: () => ({
18
+ defaultMetrics: true,
19
+ httpMetrics: true,
20
+ }),
18
21
  inject: () => {
19
22
  throw new Error('Stackable inject not implemented')
20
23
  },
package/lib/worker/itc.js CHANGED
@@ -4,6 +4,7 @@ const { once } = require('node:events')
4
4
  const { parentPort } = require('node:worker_threads')
5
5
 
6
6
  const { ITC } = require('@platformatic/itc')
7
+ const { Unpromise } = require('@watchable/unpromise')
7
8
 
8
9
  const errors = require('../errors')
9
10
  const { kITC, kId } = require('./symbols')
@@ -14,11 +15,11 @@ async function sendViaITC (worker, name, message) {
14
15
  const ac = new AbortController()
15
16
  let exitCode
16
17
 
17
- const response = await Promise.race([
18
+ const response = await Unpromise.race([
18
19
  worker[kITC].send(name, message),
19
20
  once(worker, 'exit', { signal: ac.signal }).then(([code]) => {
20
21
  exitCode = code
21
- }),
22
+ })
22
23
  ])
23
24
 
24
25
  if (typeof exitCode === 'number') {
@@ -42,90 +43,97 @@ async function sendViaITC (worker, name, message) {
42
43
  }
43
44
 
44
45
  function setupITC (app, service, dispatcher) {
45
- const itc = new ITC({ port: parentPort })
46
-
47
- itc.handle('start', async () => {
48
- const status = app.getStatus()
49
-
50
- if (status === 'starting') {
51
- await once(app, 'start')
52
- } else {
53
- await app.start()
54
- }
55
-
56
- if (service.entrypoint) {
57
- await app.listen()
58
- }
59
-
60
- const url = app.stackable.getUrl()
61
-
62
- const dispatchFunc = await app.stackable.getDispatchFunc()
63
- dispatcher.replaceServer(url ?? dispatchFunc)
64
-
65
- return service.entrypoint ? url : null
66
- })
67
-
68
- itc.handle('stop', async () => {
69
- const status = app.getStatus()
70
-
71
- if (status === 'starting') {
72
- await once(app, 'start')
73
- }
74
-
75
- if (status !== 'stopped') {
76
- await app.stop()
77
- }
78
-
79
- dispatcher.interceptor.close()
80
- itc.close()
81
- })
82
-
83
- itc.handle('getStatus', async () => {
84
- return app.getStatus()
85
- })
86
-
87
- itc.handle('getServiceInfo', async () => {
88
- return app.stackable.getInfo()
89
- })
90
-
91
- itc.handle('getServiceConfig', async () => {
92
- const current = await app.stackable.getConfig()
93
- // Remove all undefined keys from the config
94
- return JSON.parse(JSON.stringify(current))
95
- })
96
-
97
- itc.handle('getServiceOpenAPISchema', async () => {
98
- try {
99
- return app.stackable.getOpenapiSchema()
100
- } catch (err) {
101
- throw new errors.FailedToRetrieveOpenAPISchemaError(service.id, err.message)
46
+ const itc = new ITC({
47
+ port: parentPort,
48
+ handlers: {
49
+ async start () {
50
+ const status = app.getStatus()
51
+
52
+ if (status === 'starting') {
53
+ await once(app, 'start')
54
+ } else {
55
+ await app.start()
56
+ }
57
+
58
+ if (service.entrypoint) {
59
+ await app.listen()
60
+ }
61
+
62
+ const url = app.stackable.getUrl()
63
+
64
+ const dispatchFunc = await app.stackable.getDispatchFunc()
65
+ dispatcher.replaceServer(url ?? dispatchFunc)
66
+
67
+ return service.entrypoint ? url : null
68
+ },
69
+
70
+ async stop () {
71
+ const status = app.getStatus()
72
+
73
+ if (status === 'starting') {
74
+ await once(app, 'start')
75
+ }
76
+
77
+ if (status !== 'stopped') {
78
+ await app.stop()
79
+ }
80
+
81
+ dispatcher.interceptor.close()
82
+ itc.close()
83
+ },
84
+
85
+ getStatus () {
86
+ return app.getStatus()
87
+ },
88
+
89
+ getServiceInfo () {
90
+ return app.stackable.getInfo()
91
+ },
92
+
93
+ async getServiceConfig () {
94
+ const current = await app.stackable.getConfig()
95
+ // Remove all undefined keys from the config
96
+ return JSON.parse(JSON.stringify(current))
97
+ },
98
+
99
+ async getServiceOpenAPISchema () {
100
+ try {
101
+ return await app.stackable.getOpenapiSchema()
102
+ } catch (err) {
103
+ throw new errors.FailedToRetrieveOpenAPISchemaError(service.id, err.message)
104
+ }
105
+ },
106
+
107
+ async getServiceGraphQLSchema () {
108
+ try {
109
+ return await app.stackable.getGraphqlSchema()
110
+ } catch (err) {
111
+ throw new errors.FailedToRetrieveGraphQLSchemaError(service.id, err.message)
112
+ }
113
+ },
114
+
115
+ async getServiceMeta () {
116
+ try {
117
+ return await app.stackable.getMeta()
118
+ } catch (err) {
119
+ throw new errors.FailedToRetrieveMetaError(service.id, err.message)
120
+ }
121
+ },
122
+
123
+ async getMetrics (format) {
124
+ try {
125
+ return await app.getMetrics({ format })
126
+ } catch (err) {
127
+ throw new errors.FailedToRetrieveMetricsError(service.id, err.message)
128
+ }
129
+ },
130
+
131
+ inject (injectParams) {
132
+ return app.stackable.inject(injectParams)
133
+ }
102
134
  }
103
135
  })
104
136
 
105
- itc.handle('getServiceGraphQLSchema', async () => {
106
- try {
107
- return app.stackable.getGraphqlSchema()
108
- } catch (err) {
109
- throw new errors.FailedToRetrieveGraphQLSchemaError(service.id, err.message)
110
- }
111
- })
112
-
113
- itc.handle('getServiceMeta', async () => {
114
- try {
115
- return app.stackable.getMeta()
116
- } catch (err) {
117
- throw new errors.FailedToRetrieveMetaError(service.id, err.message)
118
- }
119
- })
120
-
121
- itc.handle('getMetrics', async format => {
122
- return app.stackable.getMetrics({ format })
123
- })
124
-
125
- itc.handle('inject', async injectParams => {
126
- return app.stackable.inject(injectParams)
127
- })
128
-
129
137
  app.on('changed', () => {
130
138
  itc.notify('changed')
131
139
  })
@@ -6,6 +6,7 @@ const { setTimeout: sleep } = require('node:timers/promises')
6
6
  const { parentPort, workerData, threadId } = require('node:worker_threads')
7
7
  const { pathToFileURL } = require('node:url')
8
8
 
9
+ const { Unpromise } = require('@watchable/unpromise')
9
10
  const pino = require('pino')
10
11
  const { fetch, setGlobalDispatcher, Agent } = require('undici')
11
12
  const { wire } = require('undici-thread-interceptor')
@@ -29,7 +30,7 @@ const logger = createLogger()
29
30
  function handleUnhandled (type, err) {
30
31
  logger.error({ err }, `application ${type}`)
31
32
 
32
- Promise.race([app?.stop(), sleep(1000, 'timeout', { ref: false })])
33
+ Unpromise.race([app?.stop(), sleep(1000, 'timeout', { ref: false })])
33
34
  .catch()
34
35
  .finally(() => {
35
36
  process.exit(1)
@@ -0,0 +1,106 @@
1
+ 'use strict'
2
+
3
+ const os = require('node:os')
4
+ const { eventLoopUtilization } = require('node:perf_hooks').performance
5
+ const { Registry, Gauge, collectDefaultMetrics } = require('prom-client')
6
+ const collectHttpMetrics = require('@platformatic/http-metrics')
7
+
8
+ async function collectMetrics (stackable, serviceId, opts = {}) {
9
+ const registry = new Registry()
10
+ const metricsConfig = await stackable.collectMetrics({ registry })
11
+
12
+ const labels = opts.labels ?? {}
13
+ registry.setDefaultLabels({ ...labels, serviceId })
14
+
15
+ if (metricsConfig.defaultMetrics) {
16
+ collectDefaultMetrics({ register: registry })
17
+ collectEluMetric(registry)
18
+ }
19
+
20
+ if (metricsConfig.httpMetrics) {
21
+ collectHttpMetrics(registry, {
22
+ customLabels: ['telemetry_id'],
23
+ getCustomLabels: (req) => {
24
+ const telemetryId = req.headers['x-plt-telemetry-id'] ?? 'unknown'
25
+ return { telemetry_id: telemetryId }
26
+ }
27
+ })
28
+
29
+ // TODO: check if it's a nodejs environment
30
+ // Needed for the Meraki metrics
31
+ collectHttpMetrics(registry, {
32
+ customLabels: ['telemetry_id'],
33
+ getCustomLabels: (req) => {
34
+ const telemetryId = req.headers['x-plt-telemetry-id'] ?? 'unknown'
35
+ return { telemetry_id: telemetryId }
36
+ },
37
+ histogram: {
38
+ name: 'http_request_all_duration_seconds',
39
+ help: 'request duration in seconds summary for all requests',
40
+ collect: function () {
41
+ process.nextTick(() => this.reset())
42
+ },
43
+ },
44
+ summary: {
45
+ name: 'http_request_all_summary_seconds',
46
+ help: 'request duration in seconds histogram for all requests',
47
+ collect: function () {
48
+ process.nextTick(() => this.reset())
49
+ },
50
+ },
51
+ })
52
+ }
53
+
54
+ return registry
55
+ }
56
+
57
+ function collectEluMetric (register) {
58
+ let startELU = eventLoopUtilization()
59
+ const eluMetric = new Gauge({
60
+ name: 'nodejs_eventloop_utilization',
61
+ help: 'The event loop utilization as a fraction of the loop time. 1 is fully utilized, 0 is fully idle.',
62
+ collect: () => {
63
+ const endELU = eventLoopUtilization()
64
+ const result = eventLoopUtilization(endELU, startELU).utilization
65
+ eluMetric.set(result)
66
+ startELU = endELU
67
+ },
68
+ registers: [register],
69
+ })
70
+ register.registerMetric(eluMetric)
71
+
72
+ let previousIdleTime = 0
73
+ let previousTotalTime = 0
74
+ const cpuMetric = new Gauge({
75
+ name: 'process_cpu_percent_usage',
76
+ help: 'The process CPU percent usage.',
77
+ collect: () => {
78
+ const cpus = os.cpus()
79
+ let idleTime = 0
80
+ let totalTime = 0
81
+
82
+ cpus.forEach(cpu => {
83
+ for (const type in cpu.times) {
84
+ totalTime += cpu.times[type]
85
+ if (type === 'idle') {
86
+ idleTime += cpu.times[type]
87
+ }
88
+ }
89
+ })
90
+
91
+ const idleDiff = idleTime - previousIdleTime
92
+ const totalDiff = totalTime - previousTotalTime
93
+
94
+ const usagePercent = 100 - ((100 * idleDiff) / totalDiff)
95
+ const roundedUsage = Math.round(usagePercent * 100) / 100
96
+ cpuMetric.set(roundedUsage)
97
+
98
+ previousIdleTime = idleTime
99
+ previousTotalTime = totalTime
100
+ },
101
+ registers: [register],
102
+ })
103
+ register.registerMetric(cpuMetric)
104
+ }
105
+
106
+ module.exports = { collectMetrics }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.0.0-alpha.5",
3
+ "version": "2.0.0-alpha.7",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -34,16 +34,18 @@
34
34
  "typescript": "^5.5.4",
35
35
  "undici-oidc-interceptor": "^0.5.0",
36
36
  "why-is-node-running": "^2.2.2",
37
- "@platformatic/composer": "2.0.0-alpha.5",
38
- "@platformatic/db": "2.0.0-alpha.5",
39
- "@platformatic/sql-graphql": "2.0.0-alpha.5",
40
- "@platformatic/sql-mapper": "2.0.0-alpha.5",
41
- "@platformatic/service": "2.0.0-alpha.5"
37
+ "@platformatic/composer": "2.0.0-alpha.7",
38
+ "@platformatic/db": "2.0.0-alpha.7",
39
+ "@platformatic/service": "2.0.0-alpha.7",
40
+ "@platformatic/sql-graphql": "2.0.0-alpha.7",
41
+ "@platformatic/sql-mapper": "2.0.0-alpha.7"
42
42
  },
43
43
  "dependencies": {
44
44
  "@fastify/error": "^3.4.1",
45
45
  "@fastify/websocket": "^10.0.0",
46
46
  "@hapi/topo": "^6.0.2",
47
+ "@platformatic/http-metrics": "^0.1.0",
48
+ "@watchable/unpromise": "^1.0.2",
47
49
  "boring-name-generator": "^1.0.3",
48
50
  "change-case-all": "^2.1.0",
49
51
  "close-with-grace": "^2.0.0",
@@ -61,18 +63,19 @@
61
63
  "pino": "^8.19.0",
62
64
  "pino-pretty": "^11.0.0",
63
65
  "pino-roll": "^1.0.0",
66
+ "prom-client": "^15.1.2",
64
67
  "semgrator": "^0.3.0",
65
68
  "tail-file-stream": "^0.2.0",
66
69
  "undici": "^6.9.0",
67
70
  "undici-thread-interceptor": "^0.5.1",
68
71
  "ws": "^8.16.0",
69
- "@platformatic/basic": "2.0.0-alpha.5",
70
- "@platformatic/itc": "2.0.0-alpha.5",
71
- "@platformatic/generators": "2.0.0-alpha.5",
72
- "@platformatic/telemetry": "2.0.0-alpha.5",
73
- "@platformatic/ts-compiler": "2.0.0-alpha.5",
74
- "@platformatic/utils": "2.0.0-alpha.5",
75
- "@platformatic/config": "2.0.0-alpha.5"
72
+ "@platformatic/basic": "2.0.0-alpha.7",
73
+ "@platformatic/config": "2.0.0-alpha.7",
74
+ "@platformatic/generators": "2.0.0-alpha.7",
75
+ "@platformatic/itc": "2.0.0-alpha.7",
76
+ "@platformatic/telemetry": "2.0.0-alpha.7",
77
+ "@platformatic/ts-compiler": "2.0.0-alpha.7",
78
+ "@platformatic/utils": "2.0.0-alpha.7"
76
79
  },
77
80
  "scripts": {
78
81
  "test": "npm run lint && borp --concurrency=1 --timeout=180000 && tsd",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.5.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.0.0-alpha.7.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {