@cocreate/server-autoscaler 1.0.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/src/index.js ADDED
@@ -0,0 +1,503 @@
1
+ /********************************************************************************
2
+ * Copyright (C) 2023 CoCreate and Contributors.
3
+ *
4
+ * This program is free software: you can redistribute it and/or modify
5
+ * it under the terms of the GNU Affero General Public License as published
6
+ * by the Free Software Foundation, either version 3 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ ********************************************************************************/
18
+
19
+ import cluster from 'node:cluster';
20
+ import https from 'node:https';
21
+ import os from 'node:os';
22
+ import Config from '@cocreate/config';
23
+ import { getValueFromObject } from '@cocreate/utils';
24
+
25
+ // Guard clause verification to ensure cluster API abstraction integrity
26
+ const isPrimary = cluster.isPrimary !== undefined ? cluster.isPrimary : cluster.isMaster;
27
+
28
+ // ==============================================================================
29
+ // Module-Level Sandbox State (ESM Singleton Container)
30
+ // ==============================================================================
31
+ let serverCtx = null; // Central reference to CoCreateServer Primary instance
32
+ let autoscaleConfig = null; // Configured thresholds and duration constants
33
+ let isRunning = false; // Execution loop flag
34
+
35
+ // Consecutive tracking counters to filter transient spikes
36
+ let consecutiveCpuSpikes = 0;
37
+ let consecutiveLowLoadTicks = 0;
38
+
39
+ // Timestamp tracking to prevent cascading scaling operations (Cooldown Management)
40
+ let lastScaleOperationTimestamp = 0;
41
+
42
+ // Default operational values for metrics-driven autoscale thresholds
43
+ const defaults = {
44
+ scaleUpCpuThreshold: 80, // CPU usage percentage trigger for Scaling Up (Sustained)
45
+ scaleUpCpuExtremeThreshold: 90, // Extreme CPU usage trigger for INSTANT Scaling Up (Single Flush)
46
+ scaleDownCpuThreshold: 25, // CPU usage percentage trigger for Scaling Down
47
+ scaleUpMemoryThreshold: 85, // Heap memory utilization trigger for Scaling Up
48
+ consecutiveCpuSpikesRequired: 3, // Require 3 consecutive flushes of high CPU (e.g. 3 minutes)
49
+ consecutiveLowTicksRequired: 10, // Require 10 consecutive flushes of low load (e.g. 10 minutes)
50
+ minClusterSize: 2, // Never scale down below this active node count
51
+ cooldownDurationMs: 300000, // Cooldown lockout period to stabilize nodes (default: 5 minutes)
52
+ provisioningApiEndpoint: 'https://api.cocreate.app/v1/infrastructure/scale'
53
+ };
54
+
55
+ /**
56
+ * Normalizes dataset arrays returned from standard CRUD database operations.
57
+ */
58
+ function normalizePeersList(result) {
59
+ if (!result) return [];
60
+ if (Array.isArray(result)) return result;
61
+ if (Array.isArray(result.object)) return result.object;
62
+ if (Array.isArray(result.data)) return result.data;
63
+ return [];
64
+ }
65
+
66
+ /**
67
+ * Recursively parses and evaluates an AST rule against a provided context.
68
+ *
69
+ * @param {Object} node - AST node definition
70
+ * @param {Object} context - Combined telemetry metrics and state counters
71
+ * @returns {boolean} Outcome of the evaluation statement
72
+ */
73
+ function evaluateAST(node, context) {
74
+ if (!node) return false;
75
+
76
+ switch (node.type) {
77
+ case 'LogicalExpression': {
78
+ const leftVal = evaluateAST(node.left, context);
79
+
80
+ // Short-circuit evaluations to optimize V8 execution loops
81
+ if (node.operator === 'OR' && leftVal) return true;
82
+ if (node.operator === 'AND' && !leftVal) return false;
83
+
84
+ const rightVal = evaluateAST(node.right, context);
85
+ if (node.operator === 'OR') return leftVal || rightVal;
86
+ if (node.operator === 'AND') return leftVal && rightVal;
87
+ break;
88
+ }
89
+
90
+ case 'Condition': {
91
+ const metricValue = getValueFromObject(context, node.metric);
92
+ const targetValue = node.value;
93
+
94
+ if (metricValue === undefined) {
95
+ console.warn(`[@cocreate/server-autoscaler] AST metric resolution failed for token: "${node.metric}"`);
96
+ return false;
97
+ }
98
+
99
+ switch (node.operator) {
100
+ case 'GT': return metricValue > targetValue;
101
+ case 'GTE': return metricValue >= targetValue;
102
+ case 'LT': return metricValue < targetValue;
103
+ case 'LTE': return metricValue <= targetValue;
104
+ case 'EQ': return metricValue === targetValue;
105
+ case 'NEQ': return metricValue !== targetValue;
106
+ default: return false;
107
+ }
108
+ }
109
+
110
+ default:
111
+ console.warn(`[@cocreate/server-autoscaler] Unsupported AST node type detected: "${node.type}"`);
112
+ return false;
113
+ }
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * Invokes an external HTTPS API call modeled around standard CoCreate AST payload shapes.
119
+ * If the Cloud Endpoint is offline or unreachable, falls back to a highly descriptive simulated log.
120
+ *
121
+ * @param {string} direction - Target scaling direction ('up' | 'down')
122
+ * @param {Object} metrics - Performance statistics triggering the scale event
123
+ * @param {Object} actionConfig - Specific configuration parameters extracted from AST action node
124
+ */
125
+ async function executeScaleAction(direction, metrics, actionConfig = {}) {
126
+ const targetUrl = getValueFromObject(autoscaleConfig, 'autoscale.apiEndpoint') || defaults.provisioningApiEndpoint;
127
+ const clusterSecret = process.env.CLUSTER_SECRET || 'LOCAL_SIMULATED_SECRET';
128
+
129
+ // Build standard AST-modeled API Payload Object
130
+ const payload = {
131
+ method: actionConfig.method || 'infrastructure.scale',
132
+ timestamp: new Date().toISOString(),
133
+ serverId: serverCtx?.id || 'simulated-server-id',
134
+ direction: direction,
135
+ provider: actionConfig.provider || 'round-robin',
136
+ spec: actionConfig.spec || 'standard',
137
+ region: actionConfig.region || 'local',
138
+ reason: {
139
+ avgCpu: metrics.cpu?.avg || metrics.cpu?.totalUsage || 0,
140
+ avgMemory: metrics.memory?.avg_used_pct || metrics.memory?.percentage || 0,
141
+ message: `Autoscale execution triggered dynamically by AST rule evaluation.`
142
+ },
143
+ token: clusterSecret
144
+ };
145
+
146
+ // 1. Broadcast scaling details locally and over the P2P Server Mesh
147
+ if (serverCtx && typeof serverCtx.send === 'function') {
148
+ serverCtx.send({
149
+ method: 'infrastructure.scale',
150
+ broadcast: true,
151
+ payload: payload
152
+ });
153
+
154
+ serverCtx.send({
155
+ method: 'metric.scaleAlert',
156
+ broadcast: true,
157
+ direction,
158
+ metrics: payload.reason
159
+ });
160
+ }
161
+
162
+ // 2. Render highly visible console warning banners for local simulations
163
+ console.log(`\n=============================================================================`);
164
+ console.log(`[AUTOSCALER ALERT] DYNAMIC RESOURCE SCALE TRIGGERED`);
165
+ console.log(`=============================================================================`);
166
+ console.log(`* ACTION DIRECTION : ${direction.toUpperCase()}`);
167
+ console.log(`* TARGET REGION : ${payload.region.toUpperCase()}`);
168
+ console.log(`* SYSTEM PROFILE : ${payload.spec.toUpperCase()}`);
169
+ console.log(`* INSTANCE METRIC : CPU [${payload.reason.avgCpu}%], RAM [${payload.reason.avgMemory}%]`);
170
+ console.log(`* TRIGGER MESSAGE : ${payload.reason.message}`);
171
+ console.log(`=============================================================================`);
172
+
173
+ console.log(`[@cocreate/server-autoscaler] [ACTION] Dispatching AST scaling payload to endpoint: ${targetUrl}`);
174
+
175
+ try {
176
+ const urlObj = new URL(targetUrl);
177
+ const options = {
178
+ hostname: urlObj.hostname,
179
+ port: urlObj.port || 443,
180
+ path: urlObj.pathname + urlObj.search,
181
+ method: 'POST',
182
+ headers: {
183
+ 'Content-Type': 'application/json',
184
+ 'Content-Length': Buffer.byteLength(JSON.stringify(payload))
185
+ },
186
+ timeout: 5000 // Limit network stall times
187
+ };
188
+
189
+ return await new Promise((resolve, reject) => {
190
+ const req = https.request(options, (res) => {
191
+ let body = '';
192
+ res.on('data', (chunk) => body += chunk);
193
+ res.on('end', () => {
194
+ if (res.statusCode >= 200 && res.statusCode < 300) {
195
+ console.log(`[@cocreate/server-autoscaler] Scaling action response accepted: HTTP ${res.statusCode}`);
196
+ resolve(JSON.parse(body));
197
+ } else {
198
+ reject(new Error(`Scaling HTTP request failed: Status ${res.statusCode}`));
199
+ }
200
+ });
201
+ });
202
+
203
+ req.on('error', reject);
204
+ req.write(JSON.stringify(payload));
205
+ req.end();
206
+ });
207
+ } catch (err) {
208
+ console.warn(`\n[@cocreate/server-autoscaler] [OFFLINE SIMULATOR] Cloud API Endpoint is unconfigured or unreachable (${err.message}).`);
209
+ console.warn(`[@cocreate/server-autoscaler] [OFFLINE SIMULATOR] Gracefully running in simulation mode. scaling action succeeded locally.`);
210
+ console.log(`=============================================================================\n`);
211
+ return { simulated: true, status: 'acknowledged', direction };
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Calculates a dynamic safety memory threshold percentage based on the physical core-to-memory ratio.
217
+ * Ensures the system has enough physical MB of headroom to survive the boot provisioning delay.
218
+ *
219
+ * @returns {number} Calculated maximum safe memory percentage threshold (capped between 50% and 90%)
220
+ */
221
+ export function calculateDynamicSafetyThreshold() {
222
+ const coresCount = os.cpus().length;
223
+ const totalMemoryMb = os.totalmem() / 1024 / 1024;
224
+
225
+ const allocationRatePerCoreMs = 15; // Estimated peak allocation speed in MB/sec under sustained load
226
+ const bootWindowSeconds = 60; // Boot delay window for new instances
227
+
228
+ const requiredHeadroomMb = coresCount * allocationRatePerCoreMs * bootWindowSeconds;
229
+ const headroomPercentage = (requiredHeadroomMb / totalMemoryMb) * 100;
230
+
231
+ const maxSafePercentage = Math.round(100 - headroomPercentage);
232
+
233
+ return Math.max(50, Math.min(90, maxSafePercentage));
234
+ }
235
+
236
+ /**
237
+ * Queries the shared servers directory database to ensure we can safely remove this node
238
+ * without bringing the remaining active nodes below our configured minimum bounds.
239
+ *
240
+ * @returns {boolean} Verified consensus state allowing scale-down
241
+ */
242
+ async function isClusterSafeToScaleDown() {
243
+ if (!serverCtx?.crud) {
244
+ console.warn("[@cocreate/server-autoscaler] Database access layer is unavailable. Scaling down rejected.");
245
+ return false;
246
+ }
247
+
248
+ const minSize = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.minClusterSize'), 10) || defaults.minClusterSize;
249
+
250
+ try {
251
+ const readResponse = await serverCtx.crud.send({
252
+ method: 'object.read',
253
+ array: 'servers',
254
+ $filter: {
255
+ query: { status: 'active' },
256
+ limit: 0
257
+ },
258
+ organization_id: serverCtx.organization_id,
259
+ host: serverCtx.host
260
+ });
261
+
262
+ const activeServers = normalizePeersList(readResponse);
263
+
264
+ if (activeServers.length <= minSize) {
265
+ console.log(`[@cocreate/server-autoscaler] [CONSENSUS] Scale-down rejected. Active nodes (${activeServers.length}) is already at or below the minimum cluster limit (${minSize}).`);
266
+ return false;
267
+ }
268
+
269
+ console.log(`[@cocreate/server-autoscaler] [CONSENSUS] Scale-down authorized. Cluster contains ${activeServers.length} active nodes (Safe margin: > ${minSize}).`);
270
+ return true;
271
+ } catch (err) {
272
+ console.error("[@cocreate/server-autoscaler] Failed to verify consensus safety from DB, canceling scale down:", err.message);
273
+ return false;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Intercepts high-priority 1-second instantaneous memory alerts and executes fast-track scaling.
279
+ *
280
+ * @param {Object} metricPayload - Real-time metric snapshot collected on the 1-second tick
281
+ */
282
+ async function processEmergencyAlert(metricPayload) {
283
+ if (!isRunning) return;
284
+
285
+ const cooldownDuration = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.cooldownDurationMs'), 10) || defaults.cooldownDurationMs;
286
+
287
+ if (Date.now() - lastScaleOperationTimestamp < cooldownDuration) {
288
+ return;
289
+ }
290
+
291
+ const memoryPct = metricPayload.memory?.percentage || 0;
292
+
293
+ lastScaleOperationTimestamp = Date.now();
294
+ consecutiveCpuSpikes = 0;
295
+ consecutiveLowLoadTicks = 0;
296
+
297
+ console.warn(`[@cocreate/server-autoscaler] [EMERGENCY ALERT] Instantaneous OOM threat detected! Memory: ${memoryPct}%. Initiating immediate fast-track scale-up...`);
298
+
299
+ // Default action configuration for high-priority fast-track memory scale requests
300
+ const emergencyActionConfig = {
301
+ method: 'infrastructure.scale',
302
+ provider: 'round-robin',
303
+ spec: 'memory-optimized',
304
+ region: 'local'
305
+ };
306
+ await executeScaleAction('up', metricPayload, emergencyActionConfig);
307
+ }
308
+
309
+ /**
310
+ * Evaluates the pre-averaged flush packet directly against the threshold limits.
311
+ * Handles both classic properties and advanced declarative AST-configured orchestration rules.
312
+ *
313
+ * @param {Object} metricPayload - Normalized metrics data frame received on flush
314
+ */
315
+ async function processMetricFlush(metricPayload) {
316
+ if (!isRunning) return;
317
+
318
+ const scaleUpCpu = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.scaleUpCpuThreshold'), 10) || defaults.scaleUpCpuThreshold;
319
+ const scaleUpCpuExtreme = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.scaleUpCpuExtremeThreshold'), 10) || defaults.scaleUpCpuExtremeThreshold;
320
+ const scaleDownCpu = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.scaleDownCpuThreshold'), 10) || defaults.scaleDownCpuThreshold;
321
+ const cooldownDuration = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.cooldownDurationMs'), 10) || defaults.cooldownDurationMs;
322
+ const spikesRequired = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.consecutiveCpuSpikesRequired'), 10) || defaults.consecutiveCpuSpikesRequired;
323
+ const lowTicksRequired = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.consecutiveLowTicksRequired'), 10) || defaults.consecutiveLowTicksRequired;
324
+
325
+ let targetMemoryThreshold = parseInt(getValueFromObject(autoscaleConfig, 'autoscale.scaleUpMemoryThreshold'), 10) || defaults.scaleUpMemoryThreshold;
326
+
327
+ const isSafetyLimitsEnabled = getValueFromObject(autoscaleConfig, 'autoscale.safetyLimits') !== false && getValueFromObject(autoscaleConfig, 'autoscale.safetyLimits') !== 'false';
328
+
329
+ if (isSafetyLimitsEnabled) {
330
+ const dynamicSafetyThreshold = calculateDynamicSafetyThreshold();
331
+ if (targetMemoryThreshold > dynamicSafetyThreshold) {
332
+ console.warn(`[@cocreate/server-autoscaler] [SAFETY] Configured memory threshold (${targetMemoryThreshold}%) exceeds calculated safety limit for this system context (${dynamicSafetyThreshold}%). Clamping to ${dynamicSafetyThreshold}% to guarantee headroom during provisioning.`);
333
+ targetMemoryThreshold = dynamicSafetyThreshold;
334
+ }
335
+ }
336
+
337
+ if (Date.now() - lastScaleOperationTimestamp < cooldownDuration) {
338
+ return;
339
+ }
340
+
341
+ const cpuAvg = metricPayload.cpu?.avg || 0;
342
+ const memoryAvg = metricPayload.memory?.avg_used_pct || 0;
343
+
344
+ // Maintain and increment state accumulators
345
+ if (cpuAvg >= scaleUpCpu) {
346
+ consecutiveCpuSpikes++;
347
+ consecutiveLowLoadTicks = 0;
348
+ } else {
349
+ consecutiveCpuSpikes = 0;
350
+ }
351
+
352
+ const isUnderloaded = cpuAvg <= scaleDownCpu && memoryAvg < (targetMemoryThreshold * 0.6);
353
+
354
+ if (isUnderloaded) {
355
+ consecutiveLowLoadTicks++;
356
+ } else {
357
+ consecutiveLowLoadTicks = 0;
358
+ }
359
+
360
+ // Build the consolidated evaluation context for the AST interpreter
361
+ const evaluationContext = {
362
+ ...metricPayload,
363
+ consecutiveCpuSpikes,
364
+ consecutiveLowLoadTicks
365
+ };
366
+
367
+ // Retrieve potential AST orchestration rules from configuration
368
+ const customOrchestrationRule = getValueFromObject(autoscaleConfig, 'autoscale.orchestrationRule');
369
+
370
+ if (customOrchestrationRule) {
371
+ // --- DECLARATIVE AST EXECUTION TRACK ---
372
+ console.log(`[@cocreate/server-autoscaler] Evaluating custom declarative AST scaling rules...`);
373
+
374
+ const isTriggered = evaluateAST(customOrchestrationRule, evaluationContext);
375
+
376
+ if (isTriggered) {
377
+ const actionNode = customOrchestrationRule.action || {};
378
+ lastScaleOperationTimestamp = Date.now();
379
+ consecutiveCpuSpikes = 0;
380
+ consecutiveLowLoadTicks = 0;
381
+
382
+ console.warn(`[@cocreate/server-autoscaler] Custom AST evaluation resolved to TRUE. Triggering custom scaled action...`);
383
+ await executeScaleAction('up', metricPayload, actionNode);
384
+ return;
385
+ }
386
+ } else {
387
+ // --- TRADITIONAL DUAL-TRACK HARDCODED FALLBACK ---
388
+ const isMemoryPeaked = memoryAvg >= targetMemoryThreshold;
389
+ const isCpuExtremePeak = cpuAvg >= scaleUpCpuExtreme;
390
+ const isCpuSustainedPeak = consecutiveCpuSpikes >= spikesRequired;
391
+
392
+ if (isCpuExtremePeak || isCpuSustainedPeak || isMemoryPeaked) {
393
+ let triggerReason = '';
394
+ let targetSpec = 'standard';
395
+
396
+ if (isMemoryPeaked) {
397
+ triggerReason = `Incompressible memory threshold exceeded (${memoryAvg}% >= limit: ${targetMemoryThreshold}%)`;
398
+ targetSpec = 'memory-optimized';
399
+ } else if (isCpuExtremePeak) {
400
+ triggerReason = `Extreme CPU ceiling exceeded on a single flush (${cpuAvg}% >= limit: ${scaleUpCpuExtreme}%)`;
401
+ targetSpec = 'compute-optimized';
402
+ } else {
403
+ triggerReason = `Sustained CPU threshold exceeded for ${consecutiveCpuSpikes} consecutive cycles (${cpuAvg}% >= limit: ${scaleUpCpu}%)`;
404
+ targetSpec = 'compute-optimized';
405
+ }
406
+
407
+ lastScaleOperationTimestamp = Date.now();
408
+ consecutiveCpuSpikes = 0;
409
+
410
+ console.warn(`[@cocreate/server-autoscaler] Peak limit hit! ${triggerReason}. Scaling up...`);
411
+ await executeScaleAction('up', metricPayload, { spec: targetSpec });
412
+ return;
413
+ }
414
+ }
415
+
416
+ // Scale Down evaluation track: Consolidated across both AST and traditional modes
417
+ if (consecutiveLowLoadTicks >= lowTicksRequired) {
418
+ console.log(`[@cocreate/server-autoscaler] Low load sustained for ${consecutiveLowLoadTicks} consecutive cycles. Checking cluster state to verify consensus...`);
419
+
420
+ const safeToScaleDown = await isClusterSafeToScaleDown();
421
+ if (safeToScaleDown) {
422
+ lastScaleOperationTimestamp = Date.now();
423
+ consecutiveLowLoadTicks = 0;
424
+
425
+ console.log(`[@cocreate/server-autoscaler] Safe-down consensus verified. Scaling down...`);
426
+ await executeScaleAction('down', metricPayload);
427
+ } else {
428
+ consecutiveLowLoadTicks = Math.round(lowTicksRequired / 2);
429
+ }
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Boots the autoscaler system exclusively on the Master/Primary process context.
435
+ *
436
+ * @param {Object} server - Central CoCreateServer instance references
437
+ * @returns {Object} Active autoscale control API
438
+ */
439
+ export async function init(server) {
440
+ if (!isPrimary) {
441
+ throw new Error("[@cocreate/server-autoscaler] [FATAL] Violation of scope: ServerAutoscaler can only run on the Primary (Master) process.");
442
+ }
443
+
444
+ serverCtx = server;
445
+
446
+ autoscaleConfig = await Config({
447
+ 'autoscale': ''
448
+ });
449
+
450
+ const isEnabled = getValueFromObject(autoscaleConfig, 'autoscale.enabled') !== false && getValueFromObject(autoscaleConfig, 'autoscale.enabled') !== 'false';
451
+
452
+ if (!isEnabled) {
453
+ console.log('[@cocreate/server-autoscaler] ServerAutoscaler is disabled by configuration.');
454
+ return { init, stop };
455
+ }
456
+
457
+ isRunning = true;
458
+
459
+ consecutiveCpuSpikes = 0;
460
+ consecutiveLowLoadTicks = 0;
461
+
462
+ serverCtx.on('metric', (payload) => {
463
+ if (payload && payload.data) {
464
+ processMetricFlush(payload.data).catch(err => {
465
+ console.error("[@cocreate/server-autoscaler] Error processing autoscale execution tick:", err);
466
+ });
467
+ }
468
+ });
469
+
470
+ serverCtx.on('metric.emergency', (payload) => {
471
+ if (payload && payload.data) {
472
+ processEmergencyAlert(payload.data).catch(err => {
473
+ console.error("[@cocreate/server-autoscaler] Error processing emergency autoscale trigger:", err);
474
+ });
475
+ }
476
+ });
477
+
478
+ console.log('[@cocreate/server-autoscaler] Autoscaler initialized and subscribed to telemetry feed.');
479
+
480
+ return {
481
+ init,
482
+ stop,
483
+ processMetricFlush,
484
+ processEmergencyAlert,
485
+ executeScaleAction,
486
+ calculateDynamicSafetyThreshold
487
+ };
488
+ }
489
+
490
+ /**
491
+ * Terminates monitoring sweeps and unsubscribes from the process emitter.
492
+ */
493
+ export function stop() {
494
+ isRunning = false;
495
+ consecutiveCpuSpikes = 0;
496
+ consecutiveLowLoadTicks = 0;
497
+ console.log('[@cocreate/server-autoscaler] ServerAutoscaler monitoring stopped.');
498
+ }
499
+
500
+ export default {
501
+ init,
502
+ stop
503
+ };