@platformatic/runtime 3.10.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 +2 -1
- package/lib/metrics.js +73 -0
- package/lib/runtime.js +18 -7
- package/lib/scaling-algorithm.js +144 -95
- package/package.json +17 -16
- package/schema.json +8 -5
package/config.d.ts
CHANGED
|
@@ -365,12 +365,13 @@ export type PlatformaticRuntimeConfig = {
|
|
|
365
365
|
verticalScaler?: {
|
|
366
366
|
enabled?: boolean;
|
|
367
367
|
maxTotalWorkers?: number;
|
|
368
|
+
maxTotalMemory?: number;
|
|
368
369
|
minWorkers?: number;
|
|
369
370
|
maxWorkers?: number;
|
|
370
371
|
scaleUpELU?: number;
|
|
371
372
|
scaleDownELU?: number;
|
|
372
|
-
minELUDiff?: number;
|
|
373
373
|
timeWindowSec?: number;
|
|
374
|
+
scaleDownTimeWindowSec?: number;
|
|
374
375
|
cooldownSec?: number;
|
|
375
376
|
scaleIntervalSec?: number;
|
|
376
377
|
gracePeriod?: number;
|
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
|
@@ -47,6 +47,7 @@ import { createSharedStore } from './shared-http-cache.js'
|
|
|
47
47
|
import { version } from './version.js'
|
|
48
48
|
import { sendViaITC, waitEventFromITC } from './worker/itc.js'
|
|
49
49
|
import { RoundRobinMap } from './worker/round-robin-map.js'
|
|
50
|
+
import { getMemoryInfo } from './metrics.js'
|
|
50
51
|
import {
|
|
51
52
|
kApplicationId,
|
|
52
53
|
kConfig,
|
|
@@ -2473,28 +2474,32 @@ export class Runtime extends EventEmitter {
|
|
|
2473
2474
|
}
|
|
2474
2475
|
|
|
2475
2476
|
const scalerConfig = this.#config.verticalScaler
|
|
2477
|
+
const memInfo = await getMemoryInfo()
|
|
2478
|
+
const memScope = memInfo.scope
|
|
2476
2479
|
|
|
2477
2480
|
scalerConfig.maxTotalWorkers ??= os.availableParallelism()
|
|
2481
|
+
scalerConfig.maxTotalMemory ??= memInfo.total * 0.9
|
|
2478
2482
|
scalerConfig.maxWorkers ??= scalerConfig.maxTotalWorkers
|
|
2479
2483
|
scalerConfig.minWorkers ??= 1
|
|
2480
2484
|
scalerConfig.cooldownSec ??= 60
|
|
2481
2485
|
scalerConfig.scaleUpELU ??= 0.8
|
|
2482
2486
|
scalerConfig.scaleDownELU ??= 0.2
|
|
2483
|
-
scalerConfig.minELUDiff ??= 0.2
|
|
2484
2487
|
scalerConfig.scaleIntervalSec ??= 60
|
|
2485
|
-
scalerConfig.timeWindowSec ??=
|
|
2488
|
+
scalerConfig.timeWindowSec ??= 10
|
|
2489
|
+
scalerConfig.scaleDownTimeWindowSec ??= 60
|
|
2486
2490
|
scalerConfig.gracePeriod ??= 30 * 1000
|
|
2487
2491
|
scalerConfig.applications ??= {}
|
|
2488
2492
|
|
|
2489
2493
|
const maxTotalWorkers = scalerConfig.maxTotalWorkers
|
|
2494
|
+
const maxTotalMemory = scalerConfig.maxTotalMemory
|
|
2490
2495
|
const maxWorkers = scalerConfig.maxWorkers
|
|
2491
2496
|
const minWorkers = scalerConfig.minWorkers
|
|
2492
2497
|
const cooldown = scalerConfig.cooldownSec
|
|
2493
2498
|
const scaleUpELU = scalerConfig.scaleUpELU
|
|
2494
2499
|
const scaleDownELU = scalerConfig.scaleDownELU
|
|
2495
|
-
const minELUDiff = scalerConfig.minELUDiff
|
|
2496
2500
|
const scaleIntervalSec = scalerConfig.scaleIntervalSec
|
|
2497
2501
|
const timeWindowSec = scalerConfig.timeWindowSec
|
|
2502
|
+
const scaleDownTimeWindowSec = scalerConfig.scaleDownTimeWindowSec
|
|
2498
2503
|
const applicationsConfigs = scalerConfig.applications
|
|
2499
2504
|
const gracePeriod = scalerConfig.gracePeriod
|
|
2500
2505
|
const healthCheckInterval = 1000
|
|
@@ -2558,8 +2563,8 @@ export class Runtime extends EventEmitter {
|
|
|
2558
2563
|
maxTotalWorkers,
|
|
2559
2564
|
scaleUpELU,
|
|
2560
2565
|
scaleDownELU,
|
|
2561
|
-
|
|
2562
|
-
|
|
2566
|
+
scaleUpTimeWindowSec: timeWindowSec,
|
|
2567
|
+
scaleDownTimeWindowSec,
|
|
2563
2568
|
applications: applicationsConfigs
|
|
2564
2569
|
})
|
|
2565
2570
|
|
|
@@ -2583,7 +2588,9 @@ export class Runtime extends EventEmitter {
|
|
|
2583
2588
|
scalingAlgorithm.addWorkerHealthInfo({
|
|
2584
2589
|
workerId: worker[kId],
|
|
2585
2590
|
applicationId: worker[kApplicationId],
|
|
2586
|
-
elu: health.elu
|
|
2591
|
+
elu: health.elu,
|
|
2592
|
+
heapUsed: health.heapUsed,
|
|
2593
|
+
heapTotal: health.heapTotal
|
|
2587
2594
|
})
|
|
2588
2595
|
|
|
2589
2596
|
if (health.elu > scaleUpELU) {
|
|
@@ -2611,6 +2618,7 @@ export class Runtime extends EventEmitter {
|
|
|
2611
2618
|
|
|
2612
2619
|
try {
|
|
2613
2620
|
const workersInfo = await this.getWorkers()
|
|
2621
|
+
const mem = await getMemoryInfo({ scope: memScope })
|
|
2614
2622
|
|
|
2615
2623
|
const appsWorkersInfo = {}
|
|
2616
2624
|
for (const worker of Object.values(workersInfo)) {
|
|
@@ -2621,7 +2629,10 @@ export class Runtime extends EventEmitter {
|
|
|
2621
2629
|
appsWorkersInfo[applicationId]++
|
|
2622
2630
|
}
|
|
2623
2631
|
|
|
2624
|
-
const
|
|
2632
|
+
const availableMemory = maxTotalMemory - mem.used
|
|
2633
|
+
const recommendations = scalingAlgorithm.getRecommendations(appsWorkersInfo, {
|
|
2634
|
+
availableMemory
|
|
2635
|
+
})
|
|
2625
2636
|
if (recommendations.length > 0) {
|
|
2626
2637
|
await applyRecommendations(recommendations)
|
|
2627
2638
|
lastScaling = Date.now()
|
package/lib/scaling-algorithm.js
CHANGED
|
@@ -2,60 +2,63 @@ 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, applicationId, elu } = healthInfo
|
|
22
|
+
const { workerId, applicationId, elu, heapUsed } = healthInfo
|
|
23
23
|
const timestamp = Date.now()
|
|
24
24
|
|
|
25
|
-
if (!this.#
|
|
26
|
-
this.#
|
|
25
|
+
if (!this.#appsMetrics[applicationId]) {
|
|
26
|
+
this.#appsMetrics[applicationId] = {}
|
|
27
27
|
}
|
|
28
|
-
if (!this.#
|
|
29
|
-
this.#
|
|
28
|
+
if (!this.#appsMetrics[applicationId][workerId]) {
|
|
29
|
+
this.#appsMetrics[applicationId][workerId] = []
|
|
30
30
|
}
|
|
31
|
-
this.#
|
|
31
|
+
this.#appsMetrics[applicationId][workerId].push({
|
|
32
|
+
elu,
|
|
33
|
+
timestamp,
|
|
34
|
+
heapUsed
|
|
35
|
+
})
|
|
32
36
|
this.#removeOutdatedAppELUs(applicationId)
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
getRecommendations (appsWorkersInfo) {
|
|
39
|
+
getRecommendations (appsWorkersInfo, options = {}) {
|
|
36
40
|
let totalWorkersCount = 0
|
|
37
|
-
let
|
|
41
|
+
let totalAvailableMemory = options.availableMemory ?? Infinity
|
|
42
|
+
|
|
43
|
+
const appsInfo = []
|
|
38
44
|
|
|
39
45
|
for (const applicationId in appsWorkersInfo) {
|
|
40
46
|
const workersCount = appsWorkersInfo[applicationId]
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
|
|
48
|
+
const { heapUsed } = this.#calculateAppAvgMetrics(applicationId)
|
|
49
|
+
|
|
50
|
+
appsInfo.push({
|
|
51
|
+
applicationId,
|
|
52
|
+
workersCount,
|
|
53
|
+
avgHeapUsed: heapUsed,
|
|
54
|
+
})
|
|
55
|
+
|
|
43
56
|
totalWorkersCount += workersCount
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
appsInfo = appsInfo.sort(
|
|
47
|
-
(app1, app2) => {
|
|
48
|
-
if (app1.elu > app2.elu) return 1
|
|
49
|
-
if (app1.elu < app2.elu) return -1
|
|
50
|
-
if (app1.workersCount < app2.workersCount) return 1
|
|
51
|
-
if (app1.workersCount > app2.workersCount) return -1
|
|
52
|
-
return 0
|
|
53
|
-
}
|
|
54
|
-
)
|
|
55
|
-
|
|
56
59
|
const recommendations = []
|
|
57
60
|
|
|
58
|
-
for (const { applicationId,
|
|
61
|
+
for (const { applicationId, workersCount, avgHeapUsed } of appsInfo) {
|
|
59
62
|
const appMinWorkers = this.#appsConfigs[applicationId]?.minWorkers ?? 1
|
|
60
63
|
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
61
64
|
|
|
@@ -65,7 +68,10 @@ class ScalingAlgorithm {
|
|
|
65
68
|
workersCount: appMinWorkers,
|
|
66
69
|
direction: 'up'
|
|
67
70
|
})
|
|
68
|
-
|
|
71
|
+
|
|
72
|
+
const newWorkersCount = appMinWorkers - workersCount
|
|
73
|
+
totalWorkersCount += newWorkersCount
|
|
74
|
+
totalAvailableMemory += newWorkersCount * avgHeapUsed
|
|
69
75
|
continue
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -75,103 +81,122 @@ class ScalingAlgorithm {
|
|
|
75
81
|
workersCount: appMaxWorkers,
|
|
76
82
|
direction: 'down'
|
|
77
83
|
})
|
|
78
|
-
|
|
84
|
+
|
|
85
|
+
const removedWorkersCount = workersCount - appMaxWorkers
|
|
86
|
+
totalWorkersCount -= removedWorkersCount
|
|
87
|
+
totalAvailableMemory -= removedWorkersCount * avgHeapUsed
|
|
79
88
|
continue
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
if (
|
|
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
|
|
103
|
+
}
|
|
89
104
|
}
|
|
90
105
|
}
|
|
91
106
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const { applicationId, workersCount }
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
113
135
|
}
|
|
114
136
|
}
|
|
137
|
+
}
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
const eluDiff = scaleUpCandidate.elu - scaleDownCandidate.elu
|
|
118
|
-
const workersDiff = scaleDownCandidate.workersCount - scaleUpCandidate.workersCount
|
|
119
|
-
|
|
120
|
-
if (eluDiff >= this.#minELUDiff || workersDiff >= 2) {
|
|
121
|
-
recommendations.push({
|
|
122
|
-
applicationId: scaleDownCandidate.applicationId,
|
|
123
|
-
workersCount: scaleDownCandidate.workersCount - 1,
|
|
124
|
-
direction: 'down'
|
|
125
|
-
})
|
|
126
|
-
recommendations.push({
|
|
127
|
-
applicationId,
|
|
128
|
-
workersCount: workersCount + 1,
|
|
129
|
-
direction: 'up'
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
139
|
+
if (scaleUpCandidate) {
|
|
134
140
|
recommendations.push({
|
|
135
|
-
applicationId,
|
|
136
|
-
workersCount: workersCount + 1,
|
|
141
|
+
applicationId: scaleUpCandidate.applicationId,
|
|
142
|
+
workersCount: scaleUpCandidate.workersCount + 1,
|
|
137
143
|
direction: 'up'
|
|
138
144
|
})
|
|
139
145
|
totalWorkersCount++
|
|
146
|
+
totalAvailableMemory -= scaleUpCandidate.heapUsed
|
|
140
147
|
}
|
|
141
|
-
break
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
return recommendations
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
#
|
|
153
|
+
#calculateAppAvgMetrics (applicationId, options = {}) {
|
|
148
154
|
this.#removeOutdatedAppELUs(applicationId)
|
|
149
155
|
|
|
150
|
-
const
|
|
151
|
-
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
|
|
152
161
|
|
|
153
162
|
let eluSum = 0
|
|
154
|
-
let
|
|
163
|
+
let heapUsedSum = 0
|
|
164
|
+
let count = 0
|
|
155
165
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
181
|
|
|
165
|
-
|
|
182
|
+
if (metricCount === 0) continue
|
|
166
183
|
|
|
167
|
-
|
|
184
|
+
eluSum += workerELUSum / metricCount
|
|
185
|
+
heapUsedSum += workerHeapUsedSum / metricCount
|
|
186
|
+
count++
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const elu = Math.round(eluSum / count * 100) / 100
|
|
190
|
+
const heapUsed = Math.round(heapUsedSum / count * 100) / 100
|
|
191
|
+
return { elu, heapUsed }
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
#removeOutdatedAppELUs (applicationId) {
|
|
171
|
-
const appELUs = this.#
|
|
195
|
+
const appELUs = this.#appsMetrics[applicationId]
|
|
172
196
|
if (!appELUs) return
|
|
173
197
|
|
|
174
198
|
const now = Date.now()
|
|
199
|
+
const timeWindow = this.#getMetricsTimeWindow()
|
|
175
200
|
|
|
176
201
|
for (const workerId in appELUs) {
|
|
177
202
|
const workerELUs = appELUs[workerId]
|
|
@@ -179,7 +204,7 @@ class ScalingAlgorithm {
|
|
|
179
204
|
let firstValidIndex = -1
|
|
180
205
|
for (let i = 0; i < workerELUs.length; i++) {
|
|
181
206
|
const timestamp = workerELUs[i].timestamp
|
|
182
|
-
if (timestamp >= now -
|
|
207
|
+
if (timestamp >= now - timeWindow) {
|
|
183
208
|
firstValidIndex = i
|
|
184
209
|
break
|
|
185
210
|
}
|
|
@@ -199,6 +224,30 @@ class ScalingAlgorithm {
|
|
|
199
224
|
}
|
|
200
225
|
}
|
|
201
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
|
+
}
|
|
202
251
|
}
|
|
203
252
|
|
|
204
253
|
export default ScalingAlgorithm
|
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/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/service": "3.
|
|
43
|
-
"@platformatic/sql-
|
|
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/basic": "3.
|
|
76
|
-
"@platformatic/foundation": "3.
|
|
77
|
-
"@platformatic/itc": "3.
|
|
78
|
-
"@platformatic/generators": "3.
|
|
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",
|
|
@@ -2001,6 +2001,10 @@
|
|
|
2001
2001
|
"type": "number",
|
|
2002
2002
|
"minimum": 1
|
|
2003
2003
|
},
|
|
2004
|
+
"maxTotalMemory": {
|
|
2005
|
+
"type": "number",
|
|
2006
|
+
"minimum": 0
|
|
2007
|
+
},
|
|
2004
2008
|
"minWorkers": {
|
|
2005
2009
|
"type": "number",
|
|
2006
2010
|
"minimum": 1
|
|
@@ -2019,12 +2023,11 @@
|
|
|
2019
2023
|
"minimum": 0,
|
|
2020
2024
|
"maximum": 1
|
|
2021
2025
|
},
|
|
2022
|
-
"
|
|
2026
|
+
"timeWindowSec": {
|
|
2023
2027
|
"type": "number",
|
|
2024
|
-
"minimum": 0
|
|
2025
|
-
"maximum": 1
|
|
2028
|
+
"minimum": 0
|
|
2026
2029
|
},
|
|
2027
|
-
"
|
|
2030
|
+
"scaleDownTimeWindowSec": {
|
|
2028
2031
|
"type": "number",
|
|
2029
2032
|
"minimum": 0
|
|
2030
2033
|
},
|