@platformatic/runtime 3.9.0 → 3.11.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 +34 -2
- package/lib/config.js +15 -9
- package/lib/metrics.js +73 -0
- package/lib/runtime.js +161 -30
- package/lib/scaling-algorithm.js +158 -84
- package/lib/worker/symbols.js +1 -0
- package/package.json +17 -16
- package/schema.json +187 -17
package/config.d.ts
CHANGED
|
@@ -31,10 +31,39 @@ export type PlatformaticRuntimeConfig = {
|
|
|
31
31
|
maxHeapTotal?: number | string;
|
|
32
32
|
maxYoungGeneration?: number | string;
|
|
33
33
|
};
|
|
34
|
-
preload?: string | string[];
|
|
35
34
|
dependencies?: string[];
|
|
36
35
|
arguments?: string[];
|
|
36
|
+
env?: {
|
|
37
|
+
[k: string]: string;
|
|
38
|
+
};
|
|
39
|
+
envfile?: string;
|
|
40
|
+
sourceMaps?: boolean;
|
|
41
|
+
packageManager?: "npm" | "pnpm" | "yarn";
|
|
42
|
+
preload?: string | string[];
|
|
37
43
|
nodeOptions?: string;
|
|
44
|
+
permissions?: {
|
|
45
|
+
fs?: {
|
|
46
|
+
read?: string[];
|
|
47
|
+
write?: string[];
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
telemetry?: {
|
|
51
|
+
/**
|
|
52
|
+
* An array of instrumentations loaded if telemetry is enabled
|
|
53
|
+
*/
|
|
54
|
+
instrumentations?: (
|
|
55
|
+
| string
|
|
56
|
+
| {
|
|
57
|
+
package: string;
|
|
58
|
+
exportName?: string;
|
|
59
|
+
options?: {
|
|
60
|
+
[k: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
[k: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
)[];
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
};
|
|
38
67
|
};
|
|
39
68
|
};
|
|
40
69
|
};
|
|
@@ -255,6 +284,7 @@ export type PlatformaticRuntimeConfig = {
|
|
|
255
284
|
};
|
|
256
285
|
};
|
|
257
286
|
plugins?: string[];
|
|
287
|
+
timeout?: number | string;
|
|
258
288
|
};
|
|
259
289
|
telemetry?: {
|
|
260
290
|
enabled?: boolean | string;
|
|
@@ -335,14 +365,16 @@ export type PlatformaticRuntimeConfig = {
|
|
|
335
365
|
verticalScaler?: {
|
|
336
366
|
enabled?: boolean;
|
|
337
367
|
maxTotalWorkers?: number;
|
|
368
|
+
maxTotalMemory?: number;
|
|
338
369
|
minWorkers?: number;
|
|
339
370
|
maxWorkers?: number;
|
|
340
371
|
scaleUpELU?: number;
|
|
341
372
|
scaleDownELU?: number;
|
|
342
|
-
minELUDiff?: number;
|
|
343
373
|
timeWindowSec?: number;
|
|
374
|
+
scaleDownTimeWindowSec?: number;
|
|
344
375
|
cooldownSec?: number;
|
|
345
376
|
scaleIntervalSec?: number;
|
|
377
|
+
gracePeriod?: number;
|
|
346
378
|
applications?: {
|
|
347
379
|
[k: string]: {
|
|
348
380
|
minWorkers?: number;
|
package/lib/config.js
CHANGED
|
@@ -45,7 +45,7 @@ function raiseInvalidWorkersError (location, received, hint) {
|
|
|
45
45
|
throw new InvalidArgumentError(`${location} workers must be a positive integer; received "${received}"${extra}`)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function
|
|
48
|
+
export function pprofCapturePreloadPath () {
|
|
49
49
|
const require = createRequire(import.meta.url)
|
|
50
50
|
|
|
51
51
|
let pprofCapturePath
|
|
@@ -55,6 +55,12 @@ export function autoDetectPprofCapture (config) {
|
|
|
55
55
|
// No-op
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
return pprofCapturePath
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function autoDetectPprofCapture (config) {
|
|
62
|
+
const pprofCapturePath = pprofCapturePreloadPath()
|
|
63
|
+
|
|
58
64
|
// Add to preload if not already present
|
|
59
65
|
if (!config.preload) {
|
|
60
66
|
config.preload = []
|
|
@@ -192,14 +198,7 @@ export async function transform (config, _, context) {
|
|
|
192
198
|
config = join(entryPath, configFilename)
|
|
193
199
|
}
|
|
194
200
|
|
|
195
|
-
const application = {
|
|
196
|
-
id,
|
|
197
|
-
config,
|
|
198
|
-
path: entryPath,
|
|
199
|
-
useHttp: !!mapping.useHttp,
|
|
200
|
-
health: mapping.health,
|
|
201
|
-
dependencies: mapping.dependencies
|
|
202
|
-
}
|
|
201
|
+
const application = { id, config, path: entryPath, ...mapping }
|
|
203
202
|
const existingApplicationId = applications.findIndex(application => application.id === id)
|
|
204
203
|
|
|
205
204
|
if (existingApplicationId !== -1) {
|
|
@@ -336,6 +335,13 @@ export async function transform (config, _, context) {
|
|
|
336
335
|
// like adding other applications.
|
|
337
336
|
}
|
|
338
337
|
|
|
338
|
+
if (config.metrics === true) {
|
|
339
|
+
config.metrics = {
|
|
340
|
+
enabled: true,
|
|
341
|
+
timeout: 1000
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
339
345
|
config.applications = applications
|
|
340
346
|
config.web = undefined
|
|
341
347
|
config.services = undefined
|
package/lib/metrics.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import si from 'systeminformation'
|
|
3
|
+
|
|
4
|
+
async function readNumberFromCgroupFile (path) {
|
|
5
|
+
try {
|
|
6
|
+
const raw = (await readFile(path, 'utf8')).trim()
|
|
7
|
+
if (raw === 'max') return null
|
|
8
|
+
return Number(raw)
|
|
9
|
+
} catch {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function getCgroupV2MemoryInfo () {
|
|
15
|
+
let [total, used] = await Promise.all([
|
|
16
|
+
readNumberFromCgroupFile('/sys/fs/cgroup/memory.max'),
|
|
17
|
+
readNumberFromCgroupFile('/sys/fs/cgroup/memory.current')
|
|
18
|
+
])
|
|
19
|
+
if (total == null && used == null) return null
|
|
20
|
+
|
|
21
|
+
if (total === null) {
|
|
22
|
+
const mem = await si.mem()
|
|
23
|
+
total = mem.total
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { scope: 'cgroup-v2', used, total }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getCgroupV1MemoryInfo () {
|
|
30
|
+
let [total, used] = await Promise.all([
|
|
31
|
+
readNumberFromCgroupFile('/sys/fs/cgroup/memory/memory.limit_in_bytes'),
|
|
32
|
+
readNumberFromCgroupFile('/sys/fs/cgroup/memory/memory.usage_in_bytes')
|
|
33
|
+
])
|
|
34
|
+
if (total == null && used == null) return null
|
|
35
|
+
|
|
36
|
+
// Some v1 setups report 9.22e18 (≈unlimited)
|
|
37
|
+
if (total === null || total > 1e18) {
|
|
38
|
+
const mem = await si.mem()
|
|
39
|
+
total = mem.total
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { scope: 'cgroup-v1', used, total }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function readHostMemoryInfo () {
|
|
46
|
+
const mem = await si.mem()
|
|
47
|
+
return { scope: 'host', used: mem.active, total: mem.total }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getMemoryInfo (options = {}) {
|
|
51
|
+
const scope = options.scope
|
|
52
|
+
|
|
53
|
+
if (scope === 'cgroup-v2') {
|
|
54
|
+
return getCgroupV2MemoryInfo()
|
|
55
|
+
}
|
|
56
|
+
if (scope === 'cgroup-v1') {
|
|
57
|
+
return getCgroupV1MemoryInfo()
|
|
58
|
+
}
|
|
59
|
+
if (scope === 'host') {
|
|
60
|
+
return readHostMemoryInfo()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let memInfo = await getCgroupV2MemoryInfo()
|
|
64
|
+
|
|
65
|
+
if (!memInfo) {
|
|
66
|
+
memInfo = await getCgroupV1MemoryInfo()
|
|
67
|
+
}
|
|
68
|
+
if (!memInfo) {
|
|
69
|
+
memInfo = await readHostMemoryInfo()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return memInfo
|
|
73
|
+
}
|
package/lib/runtime.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
kTimeout,
|
|
10
10
|
parseMemorySize
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
|
-
import os from 'node:os'
|
|
13
12
|
import { ITC } from '@platformatic/itc'
|
|
14
13
|
import fastify from 'fastify'
|
|
15
14
|
import { EventEmitter, once } from 'node:events'
|
|
@@ -17,13 +16,15 @@ import { existsSync } from 'node:fs'
|
|
|
17
16
|
import { readFile } from 'node:fs/promises'
|
|
18
17
|
import { STATUS_CODES } from 'node:http'
|
|
19
18
|
import { createRequire } from 'node:module'
|
|
20
|
-
import
|
|
19
|
+
import os from 'node:os'
|
|
20
|
+
import { dirname, isAbsolute, join } from 'node:path'
|
|
21
21
|
import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
|
|
22
22
|
import { pathToFileURL } from 'node:url'
|
|
23
23
|
import { Worker } from 'node:worker_threads'
|
|
24
24
|
import SonicBoom from 'sonic-boom'
|
|
25
25
|
import { Agent, request, interceptors as undiciInterceptors } from 'undici'
|
|
26
26
|
import { createThreadInterceptor } from 'undici-thread-interceptor'
|
|
27
|
+
import { pprofCapturePreloadPath } from './config.js'
|
|
27
28
|
import {
|
|
28
29
|
ApplicationAlreadyStartedError,
|
|
29
30
|
ApplicationNotFoundError,
|
|
@@ -40,12 +41,13 @@ import {
|
|
|
40
41
|
import { abstractLogger, createLogger } from './logger.js'
|
|
41
42
|
import { startManagementApi } from './management-api.js'
|
|
42
43
|
import { startPrometheusServer } from './prom-server.js'
|
|
44
|
+
import ScalingAlgorithm from './scaling-algorithm.js'
|
|
43
45
|
import { startScheduler } from './scheduler.js'
|
|
44
46
|
import { createSharedStore } from './shared-http-cache.js'
|
|
45
47
|
import { version } from './version.js'
|
|
46
48
|
import { sendViaITC, waitEventFromITC } from './worker/itc.js'
|
|
47
49
|
import { RoundRobinMap } from './worker/round-robin-map.js'
|
|
48
|
-
import
|
|
50
|
+
import { getMemoryInfo } from './metrics.js'
|
|
49
51
|
import {
|
|
50
52
|
kApplicationId,
|
|
51
53
|
kConfig,
|
|
@@ -57,7 +59,8 @@ import {
|
|
|
57
59
|
kStderrMarker,
|
|
58
60
|
kWorkerId,
|
|
59
61
|
kWorkersBroadcast,
|
|
60
|
-
kWorkerStatus
|
|
62
|
+
kWorkerStatus,
|
|
63
|
+
kWorkerStartTime
|
|
61
64
|
} from './worker/symbols.js'
|
|
62
65
|
|
|
63
66
|
const kWorkerFile = join(import.meta.dirname, 'worker/main.js')
|
|
@@ -277,7 +280,7 @@ export class Runtime extends EventEmitter {
|
|
|
277
280
|
}
|
|
278
281
|
|
|
279
282
|
if (this.#config.verticalScaler?.enabled) {
|
|
280
|
-
this.#setupVerticalScaler()
|
|
283
|
+
await this.#setupVerticalScaler()
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
this.#showUrl()
|
|
@@ -891,8 +894,12 @@ export class Runtime extends EventEmitter {
|
|
|
891
894
|
continue
|
|
892
895
|
}
|
|
893
896
|
|
|
894
|
-
const applicationMetrics = await
|
|
895
|
-
|
|
897
|
+
const applicationMetrics = await executeWithTimeout(
|
|
898
|
+
sendViaITC(worker, 'getMetrics', format),
|
|
899
|
+
this.#config.metrics?.timeout ?? 10000
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
if (applicationMetrics && applicationMetrics !== kTimeout) {
|
|
896
903
|
if (metrics === null) {
|
|
897
904
|
metrics = format === 'json' ? [] : ''
|
|
898
905
|
}
|
|
@@ -1315,6 +1322,17 @@ export class Runtime extends EventEmitter {
|
|
|
1315
1322
|
execArgv.push('--enable-source-maps')
|
|
1316
1323
|
}
|
|
1317
1324
|
|
|
1325
|
+
if (applicationConfig.permissions?.fs) {
|
|
1326
|
+
execArgv.push(...this.#setupPermissions(applicationConfig))
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
let preload = config.preload
|
|
1330
|
+
if (execArgv.includes('--permission')) {
|
|
1331
|
+
// Remove wattpm-pprof-capture from preload since it is not supported
|
|
1332
|
+
const pprofCapturePath = pprofCapturePreloadPath()
|
|
1333
|
+
preload = preload.filter(p => p !== pprofCapturePath)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1318
1336
|
const workerEnv = structuredClone(this.#env)
|
|
1319
1337
|
|
|
1320
1338
|
if (applicationConfig.nodeOptions?.trim().length > 0) {
|
|
@@ -1337,7 +1355,10 @@ export class Runtime extends EventEmitter {
|
|
|
1337
1355
|
|
|
1338
1356
|
const worker = new Worker(kWorkerFile, {
|
|
1339
1357
|
workerData: {
|
|
1340
|
-
config
|
|
1358
|
+
config: {
|
|
1359
|
+
...config,
|
|
1360
|
+
preload
|
|
1361
|
+
},
|
|
1341
1362
|
applicationConfig: {
|
|
1342
1363
|
...applicationConfig,
|
|
1343
1364
|
isProduction: this.#isProduction,
|
|
@@ -1642,6 +1663,8 @@ export class Runtime extends EventEmitter {
|
|
|
1642
1663
|
}
|
|
1643
1664
|
|
|
1644
1665
|
worker[kWorkerStatus] = 'started'
|
|
1666
|
+
worker[kWorkerStartTime] = Date.now()
|
|
1667
|
+
|
|
1645
1668
|
this.emitAndNotify('application:worker:started', eventPayload)
|
|
1646
1669
|
this.#broadcastWorkers()
|
|
1647
1670
|
|
|
@@ -2441,36 +2464,55 @@ export class Runtime extends EventEmitter {
|
|
|
2441
2464
|
}
|
|
2442
2465
|
}
|
|
2443
2466
|
|
|
2444
|
-
#setupVerticalScaler () {
|
|
2445
|
-
const
|
|
2446
|
-
if (
|
|
2467
|
+
async #setupVerticalScaler () {
|
|
2468
|
+
const fixedWorkersCount = this.#config.workers
|
|
2469
|
+
if (fixedWorkersCount !== undefined) {
|
|
2470
|
+
this.logger.warn(
|
|
2471
|
+
`Vertical scaler disabled because the "workers" configuration is set to ${fixedWorkersCount}`
|
|
2472
|
+
)
|
|
2473
|
+
return
|
|
2474
|
+
}
|
|
2447
2475
|
|
|
2448
2476
|
const scalerConfig = this.#config.verticalScaler
|
|
2477
|
+
const memInfo = await getMemoryInfo()
|
|
2478
|
+
const memScope = memInfo.scope
|
|
2449
2479
|
|
|
2450
2480
|
scalerConfig.maxTotalWorkers ??= os.availableParallelism()
|
|
2481
|
+
scalerConfig.maxTotalMemory ??= memInfo.total * 0.9
|
|
2451
2482
|
scalerConfig.maxWorkers ??= scalerConfig.maxTotalWorkers
|
|
2452
2483
|
scalerConfig.minWorkers ??= 1
|
|
2453
2484
|
scalerConfig.cooldownSec ??= 60
|
|
2454
2485
|
scalerConfig.scaleUpELU ??= 0.8
|
|
2455
2486
|
scalerConfig.scaleDownELU ??= 0.2
|
|
2456
|
-
scalerConfig.minELUDiff ??= 0.2
|
|
2457
2487
|
scalerConfig.scaleIntervalSec ??= 60
|
|
2458
|
-
scalerConfig.timeWindowSec ??=
|
|
2488
|
+
scalerConfig.timeWindowSec ??= 10
|
|
2489
|
+
scalerConfig.scaleDownTimeWindowSec ??= 60
|
|
2490
|
+
scalerConfig.gracePeriod ??= 30 * 1000
|
|
2459
2491
|
scalerConfig.applications ??= {}
|
|
2460
2492
|
|
|
2461
2493
|
const maxTotalWorkers = scalerConfig.maxTotalWorkers
|
|
2494
|
+
const maxTotalMemory = scalerConfig.maxTotalMemory
|
|
2462
2495
|
const maxWorkers = scalerConfig.maxWorkers
|
|
2463
2496
|
const minWorkers = scalerConfig.minWorkers
|
|
2464
2497
|
const cooldown = scalerConfig.cooldownSec
|
|
2465
2498
|
const scaleUpELU = scalerConfig.scaleUpELU
|
|
2466
2499
|
const scaleDownELU = scalerConfig.scaleDownELU
|
|
2467
|
-
const minELUDiff = scalerConfig.minELUDiff
|
|
2468
2500
|
const scaleIntervalSec = scalerConfig.scaleIntervalSec
|
|
2469
2501
|
const timeWindowSec = scalerConfig.timeWindowSec
|
|
2502
|
+
const scaleDownTimeWindowSec = scalerConfig.scaleDownTimeWindowSec
|
|
2470
2503
|
const applicationsConfigs = scalerConfig.applications
|
|
2504
|
+
const gracePeriod = scalerConfig.gracePeriod
|
|
2505
|
+
const healthCheckInterval = 1000
|
|
2506
|
+
|
|
2507
|
+
const initialResourcesUpdates = []
|
|
2471
2508
|
|
|
2472
2509
|
for (const application of this.#config.applications) {
|
|
2473
2510
|
if (application.entrypoint && !features.node.reusePort) {
|
|
2511
|
+
this.logger.warn(
|
|
2512
|
+
`The "${application.id}" application cannot be scaled because it is an entrypoint` +
|
|
2513
|
+
' and the "reusePort" feature is not available in your OS.'
|
|
2514
|
+
)
|
|
2515
|
+
|
|
2474
2516
|
applicationsConfigs[application.id] = {
|
|
2475
2517
|
minWorkers: 1,
|
|
2476
2518
|
maxWorkers: 1
|
|
@@ -2478,6 +2520,10 @@ export class Runtime extends EventEmitter {
|
|
|
2478
2520
|
continue
|
|
2479
2521
|
}
|
|
2480
2522
|
if (application.workers !== undefined) {
|
|
2523
|
+
this.logger.warn(
|
|
2524
|
+
`The "${application.id}" application cannot be scaled because` +
|
|
2525
|
+
` it has a fixed number of workers (${application.workers}).`
|
|
2526
|
+
)
|
|
2481
2527
|
applicationsConfigs[application.id] = {
|
|
2482
2528
|
minWorkers: application.workers,
|
|
2483
2529
|
maxWorkers: application.workers
|
|
@@ -2488,12 +2534,22 @@ export class Runtime extends EventEmitter {
|
|
|
2488
2534
|
applicationsConfigs[application.id] ??= {}
|
|
2489
2535
|
applicationsConfigs[application.id].minWorkers ??= minWorkers
|
|
2490
2536
|
applicationsConfigs[application.id].maxWorkers ??= maxWorkers
|
|
2537
|
+
|
|
2538
|
+
const appMinWorkers = applicationsConfigs[application.id].minWorkers
|
|
2539
|
+
if (appMinWorkers > 1) {
|
|
2540
|
+
initialResourcesUpdates.push({
|
|
2541
|
+
application: application.id,
|
|
2542
|
+
workers: minWorkers
|
|
2543
|
+
})
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
if (initialResourcesUpdates.length > 0) {
|
|
2548
|
+
await this.updateApplicationsResources(initialResourcesUpdates)
|
|
2491
2549
|
}
|
|
2492
2550
|
|
|
2493
2551
|
for (const applicationId in applicationsConfigs) {
|
|
2494
|
-
const application = this.#config.applications.find(
|
|
2495
|
-
app => app.id === applicationId
|
|
2496
|
-
)
|
|
2552
|
+
const application = this.#config.applications.find(app => app.id === applicationId)
|
|
2497
2553
|
if (!application) {
|
|
2498
2554
|
delete applicationsConfigs[applicationId]
|
|
2499
2555
|
|
|
@@ -2507,23 +2563,50 @@ export class Runtime extends EventEmitter {
|
|
|
2507
2563
|
maxTotalWorkers,
|
|
2508
2564
|
scaleUpELU,
|
|
2509
2565
|
scaleDownELU,
|
|
2510
|
-
|
|
2511
|
-
|
|
2566
|
+
scaleUpTimeWindowSec: timeWindowSec,
|
|
2567
|
+
scaleDownTimeWindowSec,
|
|
2512
2568
|
applications: applicationsConfigs
|
|
2513
2569
|
})
|
|
2514
2570
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
}
|
|
2571
|
+
const healthCheckTimeout = setTimeout(async () => {
|
|
2572
|
+
let shouldCheckForScaling = false
|
|
2573
|
+
|
|
2574
|
+
const now = Date.now()
|
|
2520
2575
|
|
|
2521
|
-
|
|
2576
|
+
for (const worker of this.#workers.values()) {
|
|
2577
|
+
if (
|
|
2578
|
+
worker[kWorkerStatus] !== 'started' ||
|
|
2579
|
+
worker[kWorkerStartTime] + gracePeriod > now
|
|
2580
|
+
) {
|
|
2581
|
+
continue
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
try {
|
|
2585
|
+
const health = await this.#getHealth(worker)
|
|
2586
|
+
if (!health) continue
|
|
2587
|
+
|
|
2588
|
+
scalingAlgorithm.addWorkerHealthInfo({
|
|
2589
|
+
workerId: worker[kId],
|
|
2590
|
+
applicationId: worker[kApplicationId],
|
|
2591
|
+
elu: health.elu,
|
|
2592
|
+
heapUsed: health.heapUsed,
|
|
2593
|
+
heapTotal: health.heapTotal
|
|
2594
|
+
})
|
|
2595
|
+
|
|
2596
|
+
if (health.elu > scaleUpELU) {
|
|
2597
|
+
shouldCheckForScaling = true
|
|
2598
|
+
}
|
|
2599
|
+
} catch (err) {
|
|
2600
|
+
this.logger.error({ err }, 'Failed to get health for worker')
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2522
2603
|
|
|
2523
|
-
if (
|
|
2604
|
+
if (shouldCheckForScaling) {
|
|
2524
2605
|
await checkForScaling()
|
|
2525
2606
|
}
|
|
2526
|
-
|
|
2607
|
+
|
|
2608
|
+
healthCheckTimeout.refresh()
|
|
2609
|
+
}, healthCheckInterval).unref()
|
|
2527
2610
|
|
|
2528
2611
|
let isScaling = false
|
|
2529
2612
|
let lastScaling = 0
|
|
@@ -2535,6 +2618,7 @@ export class Runtime extends EventEmitter {
|
|
|
2535
2618
|
|
|
2536
2619
|
try {
|
|
2537
2620
|
const workersInfo = await this.getWorkers()
|
|
2621
|
+
const mem = await getMemoryInfo({ scope: memScope })
|
|
2538
2622
|
|
|
2539
2623
|
const appsWorkersInfo = {}
|
|
2540
2624
|
for (const worker of Object.values(workersInfo)) {
|
|
@@ -2545,19 +2629,22 @@ export class Runtime extends EventEmitter {
|
|
|
2545
2629
|
appsWorkersInfo[applicationId]++
|
|
2546
2630
|
}
|
|
2547
2631
|
|
|
2548
|
-
const
|
|
2632
|
+
const availableMemory = maxTotalMemory - mem.used
|
|
2633
|
+
const recommendations = scalingAlgorithm.getRecommendations(appsWorkersInfo, {
|
|
2634
|
+
availableMemory
|
|
2635
|
+
})
|
|
2549
2636
|
if (recommendations.length > 0) {
|
|
2550
2637
|
await applyRecommendations(recommendations)
|
|
2638
|
+
lastScaling = Date.now()
|
|
2551
2639
|
}
|
|
2552
2640
|
} catch (err) {
|
|
2553
2641
|
this.logger.error({ err }, 'Failed to scale applications')
|
|
2554
2642
|
} finally {
|
|
2555
2643
|
isScaling = false
|
|
2556
|
-
lastScaling = Date.now()
|
|
2557
2644
|
}
|
|
2558
2645
|
}
|
|
2559
2646
|
|
|
2560
|
-
const applyRecommendations = async
|
|
2647
|
+
const applyRecommendations = async recommendations => {
|
|
2561
2648
|
const resourcesUpdates = []
|
|
2562
2649
|
for (const recommendation of recommendations) {
|
|
2563
2650
|
const { applicationId, workersCount, direction } = recommendation
|
|
@@ -2574,4 +2661,48 @@ export class Runtime extends EventEmitter {
|
|
|
2574
2661
|
// Interval for periodic scaling checks
|
|
2575
2662
|
setInterval(checkForScaling, scaleIntervalSec * 1000).unref()
|
|
2576
2663
|
}
|
|
2664
|
+
|
|
2665
|
+
#setupPermissions (applicationConfig) {
|
|
2666
|
+
const argv = []
|
|
2667
|
+
const allows = new Set()
|
|
2668
|
+
const { read, write } = applicationConfig.permissions.fs
|
|
2669
|
+
|
|
2670
|
+
if (read?.length) {
|
|
2671
|
+
for (const p of read) {
|
|
2672
|
+
allows.add(`--allow-fs-read=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
if (write?.length) {
|
|
2677
|
+
for (const p of write) {
|
|
2678
|
+
allows.add(`--allow-fs-write=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
if (allows.size === 0) {
|
|
2683
|
+
return argv
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
// We need to allow read access to the node_modules folder both at the runtime level and at the application level
|
|
2687
|
+
allows.add(`--allow-fs-read=${join(this.#root, 'node_modules', '*')}`)
|
|
2688
|
+
allows.add(`--allow-fs-read=${join(applicationConfig.path, 'node_modules', '*')}`)
|
|
2689
|
+
|
|
2690
|
+
// Since we can't really predict how dependencies are installed (symlinks, pnpm store, and so forth), we also
|
|
2691
|
+
// add any node_modules folder found in the ancestors of the current file
|
|
2692
|
+
let lastPath = import.meta.dirname
|
|
2693
|
+
let currentPath = import.meta.dirname
|
|
2694
|
+
|
|
2695
|
+
do {
|
|
2696
|
+
lastPath = currentPath
|
|
2697
|
+
const nodeModules = join(currentPath, 'node_modules')
|
|
2698
|
+
if (existsSync(nodeModules)) {
|
|
2699
|
+
allows.add(`--allow-fs-read=${join(nodeModules, '*')}`)
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
currentPath = dirname(currentPath)
|
|
2703
|
+
} while (lastPath !== currentPath)
|
|
2704
|
+
|
|
2705
|
+
argv.push('--permission', ...allows)
|
|
2706
|
+
return argv
|
|
2707
|
+
}
|
|
2577
2708
|
}
|
package/lib/scaling-algorithm.js
CHANGED
|
@@ -2,151 +2,201 @@ class ScalingAlgorithm {
|
|
|
2
2
|
#scaleUpELU
|
|
3
3
|
#scaleDownELU
|
|
4
4
|
#maxTotalWorkers
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
#scaleUpTimeWindowSec
|
|
6
|
+
#scaleDownTimeWindowSec
|
|
7
|
+
#appsMetrics
|
|
8
8
|
#appsConfigs
|
|
9
9
|
|
|
10
10
|
constructor (options = {}) {
|
|
11
11
|
this.#scaleUpELU = options.scaleUpELU ?? 0.8
|
|
12
12
|
this.#scaleDownELU = options.scaleDownELU ?? 0.2
|
|
13
|
-
this.#maxTotalWorkers = options.maxTotalWorkers
|
|
14
|
-
this.#
|
|
15
|
-
this.#
|
|
13
|
+
this.#maxTotalWorkers = options.maxTotalWorkers ?? Infinity
|
|
14
|
+
this.#scaleUpTimeWindowSec = options.scaleUpTimeWindowSec ?? 10
|
|
15
|
+
this.#scaleDownTimeWindowSec = options.scaleDownTimeWindowSec ?? 60
|
|
16
16
|
this.#appsConfigs = options.applications ?? {}
|
|
17
17
|
|
|
18
|
-
this.#
|
|
18
|
+
this.#appsMetrics = {}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
addWorkerHealthInfo (healthInfo) {
|
|
22
|
-
const workerId = healthInfo
|
|
23
|
-
const applicationId = healthInfo.application
|
|
24
|
-
const elu = healthInfo.currentHealth.elu
|
|
22
|
+
const { workerId, applicationId, elu, heapUsed } = healthInfo
|
|
25
23
|
const timestamp = Date.now()
|
|
26
24
|
|
|
27
|
-
if (!this.#
|
|
28
|
-
this.#
|
|
25
|
+
if (!this.#appsMetrics[applicationId]) {
|
|
26
|
+
this.#appsMetrics[applicationId] = {}
|
|
29
27
|
}
|
|
30
|
-
if (!this.#
|
|
31
|
-
this.#
|
|
28
|
+
if (!this.#appsMetrics[applicationId][workerId]) {
|
|
29
|
+
this.#appsMetrics[applicationId][workerId] = []
|
|
32
30
|
}
|
|
33
|
-
this.#
|
|
31
|
+
this.#appsMetrics[applicationId][workerId].push({
|
|
32
|
+
elu,
|
|
33
|
+
timestamp,
|
|
34
|
+
heapUsed
|
|
35
|
+
})
|
|
34
36
|
this.#removeOutdatedAppELUs(applicationId)
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
getRecommendations (appsWorkersInfo) {
|
|
39
|
+
getRecommendations (appsWorkersInfo, options = {}) {
|
|
38
40
|
let totalWorkersCount = 0
|
|
39
|
-
let
|
|
41
|
+
let totalAvailableMemory = options.availableMemory ?? Infinity
|
|
42
|
+
|
|
43
|
+
const appsInfo = []
|
|
40
44
|
|
|
41
45
|
for (const applicationId in appsWorkersInfo) {
|
|
42
46
|
const workersCount = appsWorkersInfo[applicationId]
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
|
|
48
|
+
const { heapUsed } = this.#calculateAppAvgMetrics(applicationId)
|
|
49
|
+
|
|
50
|
+
appsInfo.push({
|
|
51
|
+
applicationId,
|
|
52
|
+
workersCount,
|
|
53
|
+
avgHeapUsed: heapUsed,
|
|
54
|
+
})
|
|
55
|
+
|
|
45
56
|
totalWorkersCount += workersCount
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
appsInfo = appsInfo.sort(
|
|
49
|
-
(app1, app2) => {
|
|
50
|
-
if (app1.elu > app2.elu) return 1
|
|
51
|
-
if (app1.elu < app2.elu) return -1
|
|
52
|
-
if (app1.workersCount < app2.workersCount) return 1
|
|
53
|
-
if (app1.workersCount > app2.workersCount) return -1
|
|
54
|
-
return 0
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
|
|
58
59
|
const recommendations = []
|
|
59
60
|
|
|
60
|
-
for (const { applicationId,
|
|
61
|
+
for (const { applicationId, workersCount, avgHeapUsed } of appsInfo) {
|
|
61
62
|
const appMinWorkers = this.#appsConfigs[applicationId]?.minWorkers ?? 1
|
|
63
|
+
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
62
64
|
|
|
63
|
-
if (
|
|
65
|
+
if (workersCount < appMinWorkers) {
|
|
64
66
|
recommendations.push({
|
|
65
67
|
applicationId,
|
|
66
|
-
workersCount:
|
|
67
|
-
direction: '
|
|
68
|
+
workersCount: appMinWorkers,
|
|
69
|
+
direction: 'up'
|
|
68
70
|
})
|
|
69
|
-
|
|
71
|
+
|
|
72
|
+
const newWorkersCount = appMinWorkers - workersCount
|
|
73
|
+
totalWorkersCount += newWorkersCount
|
|
74
|
+
totalAvailableMemory += newWorkersCount * avgHeapUsed
|
|
75
|
+
continue
|
|
70
76
|
}
|
|
71
|
-
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
if (workersCount > appMaxWorkers) {
|
|
79
|
+
recommendations.push({
|
|
80
|
+
applicationId,
|
|
81
|
+
workersCount: appMaxWorkers,
|
|
82
|
+
direction: 'down'
|
|
83
|
+
})
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
const removedWorkersCount = workersCount - appMaxWorkers
|
|
86
|
+
totalWorkersCount -= removedWorkersCount
|
|
87
|
+
totalAvailableMemory -= removedWorkersCount * avgHeapUsed
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
77
90
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
if (workersCount > appMinWorkers) {
|
|
92
|
+
const recommendation = this.#getApplicationScaleRecommendation(applicationId)
|
|
93
|
+
if (recommendation.recommendation === 'scaleDown') {
|
|
94
|
+
recommendations.push({
|
|
95
|
+
applicationId,
|
|
96
|
+
workersCount: workersCount - 1,
|
|
97
|
+
direction: 'down'
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const removedWorkersCount = 1
|
|
101
|
+
totalWorkersCount -= removedWorkersCount
|
|
102
|
+
totalAvailableMemory -= removedWorkersCount * avgHeapUsed
|
|
89
103
|
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
if (totalWorkersCount < this.#maxTotalWorkers) {
|
|
108
|
+
let scaleUpCandidate = null
|
|
109
|
+
|
|
110
|
+
for (const { applicationId, workersCount, avgHeapUsed } of appsInfo) {
|
|
111
|
+
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
112
|
+
if (workersCount >= appMaxWorkers) continue
|
|
113
|
+
if (avgHeapUsed >= totalAvailableMemory) continue
|
|
114
|
+
|
|
115
|
+
const isScaled = recommendations.some(
|
|
116
|
+
r => r.applicationId === applicationId
|
|
117
|
+
)
|
|
118
|
+
if (isScaled) continue
|
|
119
|
+
|
|
120
|
+
const recommendation = this.#getApplicationScaleRecommendation(applicationId)
|
|
121
|
+
if (recommendation.recommendation !== 'scaleUp') continue
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
!scaleUpCandidate ||
|
|
125
|
+
(recommendation.scaleUpELU > scaleUpCandidate.scaleUpELU) ||
|
|
126
|
+
(recommendation.scaleUpELU === scaleUpCandidate.scaleUpELU &&
|
|
127
|
+
workersCount < scaleUpCandidate.workersCount
|
|
128
|
+
)
|
|
129
|
+
) {
|
|
130
|
+
scaleUpCandidate = {
|
|
131
|
+
applicationId,
|
|
132
|
+
workersCount,
|
|
133
|
+
heapUsed: recommendation.avgHeapUsage,
|
|
134
|
+
elu: recommendation.scaleUpELU
|
|
106
135
|
}
|
|
107
136
|
}
|
|
108
|
-
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (scaleUpCandidate) {
|
|
109
140
|
recommendations.push({
|
|
110
|
-
applicationId,
|
|
111
|
-
workersCount: workersCount + 1,
|
|
141
|
+
applicationId: scaleUpCandidate.applicationId,
|
|
142
|
+
workersCount: scaleUpCandidate.workersCount + 1,
|
|
112
143
|
direction: 'up'
|
|
113
144
|
})
|
|
114
145
|
totalWorkersCount++
|
|
146
|
+
totalAvailableMemory -= scaleUpCandidate.heapUsed
|
|
115
147
|
}
|
|
116
|
-
break
|
|
117
148
|
}
|
|
118
149
|
|
|
119
150
|
return recommendations
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
#
|
|
153
|
+
#calculateAppAvgMetrics (applicationId, options = {}) {
|
|
123
154
|
this.#removeOutdatedAppELUs(applicationId)
|
|
124
155
|
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
156
|
+
const appMetrics = this.#appsMetrics[applicationId]
|
|
157
|
+
if (!appMetrics) return { elu: 0, heapUsed: 0 }
|
|
158
|
+
|
|
159
|
+
const defaultTimeWindow = this.#getMetricsTimeWindow()
|
|
160
|
+
const timeWindow = options.timeWindow ?? defaultTimeWindow
|
|
127
161
|
|
|
128
162
|
let eluSum = 0
|
|
129
|
-
let
|
|
163
|
+
let heapUsedSum = 0
|
|
164
|
+
let count = 0
|
|
130
165
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
const now = Date.now()
|
|
167
|
+
|
|
168
|
+
for (const workerId in appMetrics) {
|
|
169
|
+
const workerMetrics = appMetrics[workerId]
|
|
170
|
+
|
|
171
|
+
let workerELUSum = 0
|
|
172
|
+
let workerHeapUsedSum = 0
|
|
173
|
+
let metricCount = 0
|
|
174
|
+
|
|
175
|
+
for (const metric of workerMetrics) {
|
|
176
|
+
if (metric.timestamp < now - timeWindow) continue
|
|
177
|
+
workerELUSum += metric.elu
|
|
178
|
+
workerHeapUsedSum += metric.heapUsed
|
|
179
|
+
metricCount++
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (metricCount === 0) continue
|
|
139
183
|
|
|
140
|
-
|
|
184
|
+
eluSum += workerELUSum / metricCount
|
|
185
|
+
heapUsedSum += workerHeapUsedSum / metricCount
|
|
186
|
+
count++
|
|
187
|
+
}
|
|
141
188
|
|
|
142
|
-
|
|
189
|
+
const elu = Math.round(eluSum / count * 100) / 100
|
|
190
|
+
const heapUsed = Math.round(heapUsedSum / count * 100) / 100
|
|
191
|
+
return { elu, heapUsed }
|
|
143
192
|
}
|
|
144
193
|
|
|
145
194
|
#removeOutdatedAppELUs (applicationId) {
|
|
146
|
-
const appELUs = this.#
|
|
195
|
+
const appELUs = this.#appsMetrics[applicationId]
|
|
147
196
|
if (!appELUs) return
|
|
148
197
|
|
|
149
198
|
const now = Date.now()
|
|
199
|
+
const timeWindow = this.#getMetricsTimeWindow()
|
|
150
200
|
|
|
151
201
|
for (const workerId in appELUs) {
|
|
152
202
|
const workerELUs = appELUs[workerId]
|
|
@@ -154,7 +204,7 @@ class ScalingAlgorithm {
|
|
|
154
204
|
let firstValidIndex = -1
|
|
155
205
|
for (let i = 0; i < workerELUs.length; i++) {
|
|
156
206
|
const timestamp = workerELUs[i].timestamp
|
|
157
|
-
if (timestamp >= now -
|
|
207
|
+
if (timestamp >= now - timeWindow) {
|
|
158
208
|
firstValidIndex = i
|
|
159
209
|
break
|
|
160
210
|
}
|
|
@@ -174,6 +224,30 @@ class ScalingAlgorithm {
|
|
|
174
224
|
}
|
|
175
225
|
}
|
|
176
226
|
}
|
|
227
|
+
|
|
228
|
+
#getMetricsTimeWindow () {
|
|
229
|
+
return Math.max(this.#scaleUpTimeWindowSec, this.#scaleDownTimeWindowSec) * 1000
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#getApplicationScaleRecommendation (applicationId) {
|
|
233
|
+
const { elu: scaleUpELU } = this.#calculateAppAvgMetrics(applicationId, {
|
|
234
|
+
timeWindow: this.#scaleUpTimeWindowSec * 1000
|
|
235
|
+
})
|
|
236
|
+
const { elu: scaleDownELU } = this.#calculateAppAvgMetrics(applicationId, {
|
|
237
|
+
timeWindow: this.#scaleDownTimeWindowSec * 1000
|
|
238
|
+
})
|
|
239
|
+
const { heapUsed: avgHeapUsage } = this.#calculateAppAvgMetrics(applicationId)
|
|
240
|
+
|
|
241
|
+
let recommendation = null
|
|
242
|
+
if (scaleUpELU > this.#scaleUpELU) {
|
|
243
|
+
recommendation = 'scaleUp'
|
|
244
|
+
}
|
|
245
|
+
if (scaleDownELU < this.#scaleDownELU) {
|
|
246
|
+
recommendation = 'scaleDown'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { recommendation, scaleUpELU, scaleDownELU, avgHeapUsage }
|
|
250
|
+
}
|
|
177
251
|
}
|
|
178
252
|
|
|
179
253
|
export default ScalingAlgorithm
|
package/lib/worker/symbols.js
CHANGED
|
@@ -6,6 +6,7 @@ export const kWorkerId = Symbol.for('plt.runtime.worker.id')
|
|
|
6
6
|
export const kITC = Symbol.for('plt.runtime.itc')
|
|
7
7
|
export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer')
|
|
8
8
|
export const kWorkerStatus = Symbol('plt.runtime.worker.status')
|
|
9
|
+
export const kWorkerStartTime = Symbol.for('plt.runtime.worker.startTime')
|
|
9
10
|
export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
|
|
10
11
|
export const kLastELU = Symbol.for('plt.runtime.worker.lastELU')
|
|
11
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"@fastify/compress": "^8.0.0",
|
|
19
19
|
"@fastify/express": "^4.0.0",
|
|
20
20
|
"@fastify/formbody": "^8.0.0",
|
|
21
|
-
"autocannon": "^8.0.0",
|
|
22
21
|
"atomic-sleep": "^1.0.0",
|
|
22
|
+
"autocannon": "^8.0.0",
|
|
23
23
|
"c8": "^10.0.0",
|
|
24
24
|
"cleaner-spec-reporter": "^0.5.0",
|
|
25
25
|
"eslint": "9",
|
|
@@ -35,14 +35,14 @@
|
|
|
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": "3.
|
|
39
|
-
"@platformatic/db": "3.
|
|
40
|
-
"@platformatic/gateway": "3.
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/sql-mapper": "3.
|
|
44
|
-
"@platformatic/
|
|
45
|
-
"@platformatic/
|
|
38
|
+
"@platformatic/composer": "3.11.0",
|
|
39
|
+
"@platformatic/db": "3.11.0",
|
|
40
|
+
"@platformatic/gateway": "3.11.0",
|
|
41
|
+
"@platformatic/node": "3.11.0",
|
|
42
|
+
"@platformatic/service": "3.11.0",
|
|
43
|
+
"@platformatic/sql-mapper": "3.11.0",
|
|
44
|
+
"@platformatic/wattpm-pprof-capture": "3.11.0",
|
|
45
|
+
"@platformatic/sql-graphql": "3.11.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -69,15 +69,16 @@
|
|
|
69
69
|
"prom-client": "^15.1.2",
|
|
70
70
|
"semgrator": "^0.3.0",
|
|
71
71
|
"sonic-boom": "^4.2.0",
|
|
72
|
+
"systeminformation": "^5.27.11",
|
|
72
73
|
"undici": "^7.0.0",
|
|
73
74
|
"undici-thread-interceptor": "^0.14.0",
|
|
74
75
|
"ws": "^8.16.0",
|
|
75
|
-
"@platformatic/
|
|
76
|
-
"@platformatic/
|
|
77
|
-
"@platformatic/itc": "3.
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/metrics": "3.
|
|
80
|
-
"@platformatic/telemetry": "3.
|
|
76
|
+
"@platformatic/basic": "3.11.0",
|
|
77
|
+
"@platformatic/foundation": "3.11.0",
|
|
78
|
+
"@platformatic/itc": "3.11.0",
|
|
79
|
+
"@platformatic/generators": "3.11.0",
|
|
80
|
+
"@platformatic/metrics": "3.11.0",
|
|
81
|
+
"@platformatic/telemetry": "3.11.0"
|
|
81
82
|
},
|
|
82
83
|
"engines": {
|
|
83
84
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.11.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -171,6 +171,40 @@
|
|
|
171
171
|
},
|
|
172
172
|
"additionalProperties": false
|
|
173
173
|
},
|
|
174
|
+
"dependencies": {
|
|
175
|
+
"type": "array",
|
|
176
|
+
"items": {
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
"default": []
|
|
180
|
+
},
|
|
181
|
+
"arguments": {
|
|
182
|
+
"type": "array",
|
|
183
|
+
"items": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"env": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"additionalProperties": {
|
|
190
|
+
"type": "string"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"envfile": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
},
|
|
196
|
+
"sourceMaps": {
|
|
197
|
+
"type": "boolean",
|
|
198
|
+
"default": false
|
|
199
|
+
},
|
|
200
|
+
"packageManager": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"enum": [
|
|
203
|
+
"npm",
|
|
204
|
+
"pnpm",
|
|
205
|
+
"yarn"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
174
208
|
"preload": {
|
|
175
209
|
"anyOf": [
|
|
176
210
|
{
|
|
@@ -186,20 +220,66 @@
|
|
|
186
220
|
}
|
|
187
221
|
]
|
|
188
222
|
},
|
|
189
|
-
"dependencies": {
|
|
190
|
-
"type": "array",
|
|
191
|
-
"items": {
|
|
192
|
-
"type": "string"
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
"arguments": {
|
|
196
|
-
"type": "array",
|
|
197
|
-
"items": {
|
|
198
|
-
"type": "string"
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
223
|
"nodeOptions": {
|
|
202
224
|
"type": "string"
|
|
225
|
+
},
|
|
226
|
+
"permissions": {
|
|
227
|
+
"type": "object",
|
|
228
|
+
"properties": {
|
|
229
|
+
"fs": {
|
|
230
|
+
"type": "object",
|
|
231
|
+
"properties": {
|
|
232
|
+
"read": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"items": {
|
|
235
|
+
"type": "string"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"write": {
|
|
239
|
+
"type": "array",
|
|
240
|
+
"items": {
|
|
241
|
+
"type": "string"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"additionalProperties": false
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"additionalProperties": false
|
|
249
|
+
},
|
|
250
|
+
"telemetry": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"properties": {
|
|
253
|
+
"instrumentations": {
|
|
254
|
+
"type": "array",
|
|
255
|
+
"description": "An array of instrumentations loaded if telemetry is enabled",
|
|
256
|
+
"items": {
|
|
257
|
+
"oneOf": [
|
|
258
|
+
{
|
|
259
|
+
"type": "string"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "object",
|
|
263
|
+
"properties": {
|
|
264
|
+
"package": {
|
|
265
|
+
"type": "string"
|
|
266
|
+
},
|
|
267
|
+
"exportName": {
|
|
268
|
+
"type": "string"
|
|
269
|
+
},
|
|
270
|
+
"options": {
|
|
271
|
+
"type": "object",
|
|
272
|
+
"additionalProperties": true
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"required": [
|
|
276
|
+
"package"
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
203
283
|
}
|
|
204
284
|
}
|
|
205
285
|
}
|
|
@@ -405,6 +485,30 @@
|
|
|
405
485
|
"nodeOptions": {
|
|
406
486
|
"type": "string"
|
|
407
487
|
},
|
|
488
|
+
"permissions": {
|
|
489
|
+
"type": "object",
|
|
490
|
+
"properties": {
|
|
491
|
+
"fs": {
|
|
492
|
+
"type": "object",
|
|
493
|
+
"properties": {
|
|
494
|
+
"read": {
|
|
495
|
+
"type": "array",
|
|
496
|
+
"items": {
|
|
497
|
+
"type": "string"
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
"write": {
|
|
501
|
+
"type": "array",
|
|
502
|
+
"items": {
|
|
503
|
+
"type": "string"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
"additionalProperties": false
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"additionalProperties": false
|
|
511
|
+
},
|
|
408
512
|
"telemetry": {
|
|
409
513
|
"type": "object",
|
|
410
514
|
"properties": {
|
|
@@ -641,6 +745,30 @@
|
|
|
641
745
|
"nodeOptions": {
|
|
642
746
|
"type": "string"
|
|
643
747
|
},
|
|
748
|
+
"permissions": {
|
|
749
|
+
"type": "object",
|
|
750
|
+
"properties": {
|
|
751
|
+
"fs": {
|
|
752
|
+
"type": "object",
|
|
753
|
+
"properties": {
|
|
754
|
+
"read": {
|
|
755
|
+
"type": "array",
|
|
756
|
+
"items": {
|
|
757
|
+
"type": "string"
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
"write": {
|
|
761
|
+
"type": "array",
|
|
762
|
+
"items": {
|
|
763
|
+
"type": "string"
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
"additionalProperties": false
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
"additionalProperties": false
|
|
771
|
+
},
|
|
644
772
|
"telemetry": {
|
|
645
773
|
"type": "object",
|
|
646
774
|
"properties": {
|
|
@@ -877,6 +1005,30 @@
|
|
|
877
1005
|
"nodeOptions": {
|
|
878
1006
|
"type": "string"
|
|
879
1007
|
},
|
|
1008
|
+
"permissions": {
|
|
1009
|
+
"type": "object",
|
|
1010
|
+
"properties": {
|
|
1011
|
+
"fs": {
|
|
1012
|
+
"type": "object",
|
|
1013
|
+
"properties": {
|
|
1014
|
+
"read": {
|
|
1015
|
+
"type": "array",
|
|
1016
|
+
"items": {
|
|
1017
|
+
"type": "string"
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
"write": {
|
|
1021
|
+
"type": "array",
|
|
1022
|
+
"items": {
|
|
1023
|
+
"type": "string"
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
"additionalProperties": false
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
"additionalProperties": false
|
|
1031
|
+
},
|
|
880
1032
|
"telemetry": {
|
|
881
1033
|
"type": "object",
|
|
882
1034
|
"properties": {
|
|
@@ -1691,6 +1843,17 @@
|
|
|
1691
1843
|
}
|
|
1692
1844
|
]
|
|
1693
1845
|
}
|
|
1846
|
+
},
|
|
1847
|
+
"timeout": {
|
|
1848
|
+
"anyOf": [
|
|
1849
|
+
{
|
|
1850
|
+
"type": "integer"
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
"type": "string"
|
|
1854
|
+
}
|
|
1855
|
+
],
|
|
1856
|
+
"default": 10000
|
|
1694
1857
|
}
|
|
1695
1858
|
},
|
|
1696
1859
|
"additionalProperties": false
|
|
@@ -1838,6 +2001,10 @@
|
|
|
1838
2001
|
"type": "number",
|
|
1839
2002
|
"minimum": 1
|
|
1840
2003
|
},
|
|
2004
|
+
"maxTotalMemory": {
|
|
2005
|
+
"type": "number",
|
|
2006
|
+
"minimum": 0
|
|
2007
|
+
},
|
|
1841
2008
|
"minWorkers": {
|
|
1842
2009
|
"type": "number",
|
|
1843
2010
|
"minimum": 1
|
|
@@ -1856,12 +2023,11 @@
|
|
|
1856
2023
|
"minimum": 0,
|
|
1857
2024
|
"maximum": 1
|
|
1858
2025
|
},
|
|
1859
|
-
"
|
|
2026
|
+
"timeWindowSec": {
|
|
1860
2027
|
"type": "number",
|
|
1861
|
-
"minimum": 0
|
|
1862
|
-
"maximum": 1
|
|
2028
|
+
"minimum": 0
|
|
1863
2029
|
},
|
|
1864
|
-
"
|
|
2030
|
+
"scaleDownTimeWindowSec": {
|
|
1865
2031
|
"type": "number",
|
|
1866
2032
|
"minimum": 0
|
|
1867
2033
|
},
|
|
@@ -1873,6 +2039,10 @@
|
|
|
1873
2039
|
"type": "number",
|
|
1874
2040
|
"minimum": 0
|
|
1875
2041
|
},
|
|
2042
|
+
"gracePeriod": {
|
|
2043
|
+
"type": "number",
|
|
2044
|
+
"minimum": 0
|
|
2045
|
+
},
|
|
1876
2046
|
"applications": {
|
|
1877
2047
|
"type": "object",
|
|
1878
2048
|
"additionalProperties": {
|