@platformatic/runtime 2.25.0 → 2.26.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/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 HttpsSchemasPlatformaticDevPlatformaticRuntime2250Json = {
8
+ export type HttpsSchemasPlatformaticDevPlatformaticRuntime2260Json = {
9
9
  [k: string]: unknown;
10
10
  } & {
11
11
  $schema?: string;
package/lib/config.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { readdir } = require('node:fs/promises')
4
4
  const { join, resolve: pathResolve, isAbsolute } = require('node:path')
5
5
 
6
+ const { createRequire, loadModule } = require('@platformatic/utils')
6
7
  const ConfigManager = require('@platformatic/config')
7
8
  const { Store } = require('@platformatic/config')
8
9
 
@@ -103,6 +104,15 @@ async function _transformConfig (configManager, args) {
103
104
  const serviceConfig = await store.loadConfig(service)
104
105
  service.isPLTService = !!serviceConfig.app.isPLTService
105
106
  service.type = serviceConfig.app.configType
107
+ const _require = createRequire(service.path)
108
+ // This is needed to work around Rust bug on dylibs:
109
+ // https://github.com/rust-lang/rust/issues/91979
110
+ // https://github.com/rollup/rollup/issues/5761
111
+ // TODO(mcollina): we should expose this inside every stackable configuration.
112
+ serviceConfig.app.modulesToLoad?.forEach((m) => {
113
+ const toLoad = _require.resolve(m)
114
+ loadModule(_require, toLoad).catch(() => {})
115
+ })
106
116
  } catch (err) {
107
117
  // Fallback if for any reason a dependency is not found
108
118
  try {
@@ -120,6 +130,25 @@ async function _transformConfig (configManager, args) {
120
130
  service.isPLTService = false
121
131
  }
122
132
  }
133
+ } else {
134
+ // We need to identify the service type
135
+ const basic = await import('@platformatic/basic')
136
+ service.isPLTService = false
137
+ try {
138
+ const imported = await basic.importStackableAndConfig(service.path)
139
+ service.type = imported.stackable.default.configType
140
+ const _require = createRequire(service.path)
141
+ // This is needed to work around Rust bug on dylibs:
142
+ // https://github.com/rust-lang/rust/issues/91979
143
+ // https://github.com/rollup/rollup/issues/5761
144
+ // TODO(mcollina): we should expose this inside every stackable configuration.
145
+ imported.stackable.default.modulesToLoad?.forEach((m) => {
146
+ const toLoad = _require.resolve(m)
147
+ loadModule(_require, toLoad).catch(() => {})
148
+ })
149
+ } catch {
150
+ // Nothing to do here
151
+ }
123
152
  }
124
153
 
125
154
  service.entrypoint = service.id === config.entrypoint
@@ -13,6 +13,8 @@ const { getRuntimeLogsDir } = require('./utils')
13
13
  const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
14
14
 
15
15
  async function managementApiPlugin (app, opts) {
16
+ app.register(require('@fastify/accepts'))
17
+
16
18
  const runtime = opts.runtime
17
19
 
18
20
  app.get('/status', async () => {
@@ -113,6 +115,19 @@ async function managementApiPlugin (app, opts) {
113
115
  reply.code(res.statusCode).headers(res.headers).send(res.body)
114
116
  })
115
117
 
118
+ app.get('/metrics', { logLevel: 'debug' }, async (req, reply) => {
119
+ const accepts = req.accepts()
120
+
121
+ if (!accepts.type('text/plain') && accepts.type('application/json')) {
122
+ const { metrics } = await runtime.getMetrics('json')
123
+ return metrics
124
+ }
125
+
126
+ reply.type('text/plain')
127
+ const { metrics } = await runtime.getMetrics('text')
128
+ return metrics
129
+ })
130
+
116
131
  app.get('/metrics/live', { websocket: true }, async socket => {
117
132
  const cachedMetrics = runtime.getCachedMetrics()
118
133
  if (cachedMetrics.length > 0) {
package/lib/runtime.js CHANGED
@@ -687,7 +687,10 @@ class Runtime extends EventEmitter {
687
687
  }
688
688
  } catch (e) {
689
689
  // The service exited while we were sending the ITC, skip it
690
- if (e.code === 'PLT_RUNTIME_SERVICE_NOT_STARTED' || e.code === 'PLT_RUNTIME_SERVICE_EXIT') {
690
+ if (
691
+ e.code === 'PLT_RUNTIME_SERVICE_NOT_STARTED' ||
692
+ e.code === 'PLT_RUNTIME_SERVICE_EXIT'
693
+ ) {
691
694
  continue
692
695
  }
693
696
 
@@ -706,73 +709,104 @@ class Runtime extends EventEmitter {
706
709
  try {
707
710
  const { metrics } = await this.getMetrics()
708
711
 
709
- if (metrics === null) {
712
+ if (metrics === null || metrics.length === 0) {
710
713
  return null
711
714
  }
712
715
 
713
- const cpuMetric = metrics.find(metric => metric.name === 'process_cpu_percent_usage')
714
- const rssMetric = metrics.find(metric => metric.name === 'process_resident_memory_bytes')
715
- const totalHeapSizeMetric = metrics.find(metric => metric.name === 'nodejs_heap_size_total_bytes')
716
- const usedHeapSizeMetric = metrics.find(metric => metric.name === 'nodejs_heap_size_used_bytes')
717
- const heapSpaceSizeTotalMetric = metrics.find(metric => metric.name === 'nodejs_heap_space_size_total_bytes')
718
- const newSpaceSizeTotalMetric = heapSpaceSizeTotalMetric.values.find(value => value.labels.space === 'new')
719
- const oldSpaceSizeTotalMetric = heapSpaceSizeTotalMetric.values.find(value => value.labels.space === 'old')
720
- const eventLoopUtilizationMetric = metrics.find(metric => metric.name === 'nodejs_eventloop_utilization')
721
-
722
- let p50Value = 0
723
- let p90Value = 0
724
- let p95Value = 0
725
- let p99Value = 0
726
-
727
- const metricName = 'http_request_all_summary_seconds'
728
- const httpLatencyMetrics = metrics.filter(metric => metric.name === metricName)
729
-
730
- if (httpLatencyMetrics) {
731
- const entrypointMetrics = httpLatencyMetrics.find(
732
- metric => metric.values?.[0]?.labels?.serviceId === this.#entrypointId
733
- )
734
- if (entrypointMetrics) {
735
- p50Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.5)?.value || 0
736
- p90Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.9)?.value || 0
737
- p95Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.95)?.value || 0
738
- p99Value = entrypointMetrics.values.find(value => value.labels.quantile === 0.99)?.value || 0
739
-
740
- p50Value = Math.round(p50Value * 1000)
741
- p90Value = Math.round(p90Value * 1000)
742
- p95Value = Math.round(p95Value * 1000)
743
- p99Value = Math.round(p99Value * 1000)
716
+ const metricsNames = [
717
+ 'process_cpu_percent_usage',
718
+ 'process_resident_memory_bytes',
719
+ 'nodejs_heap_size_total_bytes',
720
+ 'nodejs_heap_size_used_bytes',
721
+ 'nodejs_heap_space_size_total_bytes',
722
+ 'nodejs_eventloop_utilization',
723
+ 'http_request_all_summary_seconds'
724
+ ]
725
+
726
+ const servicesMetrics = {}
727
+
728
+ for (const metric of metrics) {
729
+ const { name, values } = metric
730
+
731
+ if (!metricsNames.includes(name)) continue
732
+ if (!values || values.length === 0) continue
733
+
734
+ const labels = values[0].labels
735
+ const serviceId = labels?.serviceId
736
+
737
+ if (!serviceId) {
738
+ throw new Error('Missing serviceId label in metrics')
744
739
  }
740
+
741
+ let serviceMetrics = servicesMetrics[serviceId]
742
+ if (!serviceMetrics) {
743
+ serviceMetrics = {
744
+ cpu: 0,
745
+ rss: 0,
746
+ totalHeapSize: 0,
747
+ usedHeapSize: 0,
748
+ newSpaceSize: 0,
749
+ oldSpaceSize: 0,
750
+ elu: 0,
751
+ latency: {
752
+ p50: 0,
753
+ p90: 0,
754
+ p95: 0,
755
+ p99: 0
756
+ }
757
+ }
758
+ servicesMetrics[serviceId] = serviceMetrics
759
+ }
760
+
761
+ parsePromMetric(serviceMetrics, metric)
745
762
  }
746
763
 
747
- const cpu = cpuMetric.values[0].value
748
- const rss = rssMetric.values[0].value
749
- const elu = eventLoopUtilizationMetric.values[0].value
750
- const totalHeapSize = totalHeapSizeMetric.values[0].value
751
- const usedHeapSize = usedHeapSizeMetric.values[0].value
752
- const newSpaceSize = newSpaceSizeTotalMetric.value
753
- const oldSpaceSize = oldSpaceSizeTotalMetric.value
764
+ function parsePromMetric (serviceMetrics, promMetric) {
765
+ const { name } = promMetric
754
766
 
755
- const formattedMetrics = {
756
- version: 1,
757
- date: new Date().toISOString(),
758
- cpu,
759
- elu,
760
- rss,
761
- totalHeapSize,
762
- usedHeapSize,
763
- newSpaceSize,
764
- oldSpaceSize,
765
- entrypoint: {
766
- latency: {
767
- p50: p50Value,
768
- p90: p90Value,
769
- p95: p95Value,
770
- p99: p99Value
767
+ if (name === 'process_cpu_percent_usage') {
768
+ serviceMetrics.cpu = promMetric.values[0].value
769
+ return
770
+ }
771
+ if (name === 'process_resident_memory_bytes') {
772
+ serviceMetrics.rss = promMetric.values[0].value
773
+ return
774
+ }
775
+ if (name === 'nodejs_heap_size_total_bytes') {
776
+ serviceMetrics.totalHeapSize = promMetric.values[0].value
777
+ return
778
+ }
779
+ if (name === 'nodejs_heap_size_used_bytes') {
780
+ serviceMetrics.usedHeapSize = promMetric.values[0].value
781
+ return
782
+ }
783
+ if (name === 'nodejs_heap_space_size_total_bytes') {
784
+ const newSpaceSize = promMetric.values.find(value => value.labels.space === 'new')
785
+ const oldSpaceSize = promMetric.values.find(value => value.labels.space === 'old')
786
+
787
+ serviceMetrics.newSpaceSize = newSpaceSize.value
788
+ serviceMetrics.oldSpaceSize = oldSpaceSize.value
789
+ return
790
+ }
791
+ if (name === 'nodejs_eventloop_utilization') {
792
+ serviceMetrics.elu = promMetric.values[0].value
793
+ return
794
+ }
795
+ if (name === 'http_request_all_summary_seconds') {
796
+ serviceMetrics.latency = {
797
+ p50: promMetric.values.find(value => value.labels.quantile === 0.5)?.value || 0,
798
+ p90: promMetric.values.find(value => value.labels.quantile === 0.9)?.value || 0,
799
+ p95: promMetric.values.find(value => value.labels.quantile === 0.95)?.value || 0,
800
+ p99: promMetric.values.find(value => value.labels.quantile === 0.99)?.value || 0
771
801
  }
772
802
  }
773
803
  }
774
804
 
775
- return formattedMetrics
805
+ return {
806
+ version: 1,
807
+ date: new Date().toISOString(),
808
+ services: servicesMetrics
809
+ }
776
810
  } catch (err) {
777
811
  // If any metric is missing, return nothing
778
812
  this.logger.warn({ err }, 'Cannot fetch metrics')
@@ -3,27 +3,53 @@
3
3
  const { Readable, Writable } = require('node:stream')
4
4
  const { kITC } = require('./symbols')
5
5
 
6
+ const noop = () => {}
7
+
6
8
  class RemoteCacheStore {
9
+ #onRequest
10
+ #onCacheHit
11
+ #onCacheMiss
12
+ #logger
13
+
14
+ constructor (opts = {}) {
15
+ this.#onRequest = opts.onRequest ?? noop
16
+ this.#onCacheHit = opts.onCacheHit ?? noop
17
+ this.#onCacheMiss = opts.onCacheMiss ?? noop
18
+ this.#logger = opts.logger
19
+ }
20
+
7
21
  async get (request) {
22
+ try {
23
+ this.#onRequest(request)
24
+ } catch (err) {
25
+ this.#logger.error(err, 'Error in onRequest http cache hook')
26
+ }
27
+
8
28
  const itc = globalThis[kITC]
9
29
  if (!itc) return
10
30
 
11
31
  const cachedValue = await itc.send('getHttpCacheValue', {
12
32
  request: this.#sanitizeRequest(request)
13
33
  })
14
- if (!cachedValue) return
15
-
16
- const readable = new Readable({
17
- read () {}
18
- })
19
-
20
- Object.defineProperty(readable, 'value', {
21
- get () { return cachedValue.response }
22
- })
34
+ if (!cachedValue) {
35
+ try {
36
+ this.#onCacheMiss(request)
37
+ } catch (err) {
38
+ this.#logger.error(err, 'Error in onCacheMiss http cache hook')
39
+ }
40
+ return
41
+ }
23
42
 
43
+ const readable = new Readable({ read () {} })
24
44
  readable.push(cachedValue.payload)
25
45
  readable.push(null)
26
46
 
47
+ try {
48
+ this.#onCacheHit(request, cachedValue.response)
49
+ } catch (err) {
50
+ this.#logger.error(err, 'Error in onCacheHit http cache hook')
51
+ }
52
+
27
53
  return {
28
54
  ...cachedValue.response,
29
55
  body: readable
@@ -159,7 +159,18 @@ async function main () {
159
159
  setGlobalDispatcher(
160
160
  getGlobalDispatcher().compose(
161
161
  undici.interceptors.cache({
162
- store: new RemoteCacheStore(),
162
+ store: new RemoteCacheStore({
163
+ onRequest: (opts) => {
164
+ globalThis.platformatic?.onHttpCacheRequest?.(opts)
165
+ },
166
+ onCacheHit: (opts) => {
167
+ globalThis.platformatic?.onHttpCacheHit?.(opts)
168
+ },
169
+ onCacheMiss: (opts) => {
170
+ globalThis.platformatic?.onHttpCacheMiss?.(opts)
171
+ },
172
+ logger: globalThis.platformatic.logger
173
+ }),
163
174
  methods: config.httpCache.methods ?? ['GET', 'HEAD']
164
175
  })
165
176
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "2.25.0",
3
+ "version": "2.26.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -35,12 +35,12 @@
35
35
  "typescript": "^5.5.4",
36
36
  "undici-oidc-interceptor": "^0.5.0",
37
37
  "why-is-node-running": "^2.2.2",
38
- "@platformatic/composer": "2.25.0",
39
- "@platformatic/db": "2.25.0",
40
- "@platformatic/node": "2.25.0",
41
- "@platformatic/service": "2.25.0",
42
- "@platformatic/sql-graphql": "2.25.0",
43
- "@platformatic/sql-mapper": "2.25.0"
38
+ "@platformatic/composer": "2.26.0",
39
+ "@platformatic/db": "2.26.0",
40
+ "@platformatic/node": "2.26.0",
41
+ "@platformatic/service": "2.26.0",
42
+ "@platformatic/sql-graphql": "2.26.0",
43
+ "@platformatic/sql-mapper": "2.26.0"
44
44
  },
45
45
  "dependencies": {
46
46
  "@fastify/error": "^4.0.0",
@@ -72,13 +72,13 @@
72
72
  "undici": "^7.0.0",
73
73
  "undici-thread-interceptor": "^0.10.0",
74
74
  "ws": "^8.16.0",
75
- "@platformatic/basic": "2.25.0",
76
- "@platformatic/generators": "2.25.0",
77
- "@platformatic/config": "2.25.0",
78
- "@platformatic/ts-compiler": "2.25.0",
79
- "@platformatic/telemetry": "2.25.0",
80
- "@platformatic/itc": "2.25.0",
81
- "@platformatic/utils": "2.25.0"
75
+ "@platformatic/basic": "2.26.0",
76
+ "@platformatic/generators": "2.26.0",
77
+ "@platformatic/itc": "2.26.0",
78
+ "@platformatic/telemetry": "2.26.0",
79
+ "@platformatic/ts-compiler": "2.26.0",
80
+ "@platformatic/config": "2.26.0",
81
+ "@platformatic/utils": "2.26.0"
82
82
  },
83
83
  "scripts": {
84
84
  "test": "npm run lint && borp --concurrency=1 --timeout=300000 && tsd",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.25.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/2.26.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "type": "object",
5
5
  "properties": {