@kwirthmagnify/kwirth-plugin-metrics 0.0.0-stage → 0.1.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.
Files changed (4) hide show
  1. package/README.md +51 -2
  2. package/back.js +756 -0
  3. package/front.js +838 -0
  4. package/package.json +9 -3
package/back.js ADDED
@@ -0,0 +1,756 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // kwirth-back-globals:@kwirthmagnify/kwirth-common
34
+ var require_kwirth_common = __commonJS({
35
+ "kwirth-back-globals:@kwirthmagnify/kwirth-common"(exports2, module2) {
36
+ module2.exports = global.__kwirth_back__.kwirthCommon;
37
+ }
38
+ });
39
+
40
+ // src/back/index.ts
41
+ var index_exports = {};
42
+ __export(index_exports, {
43
+ MetricsChannel: () => MetricsChannel
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+ var import_kwirth_common = __toESM(require_kwirth_common(), 1);
47
+ var CLUSTER_ASSET = "Cluster";
48
+ var isClusterMetric = (metricName) => metricName.startsWith("kwirth_cluster_");
49
+ var MetricsChannel = class {
50
+ constructor(clusterInfo, backChannelObject) {
51
+ this.channelId = "metrics";
52
+ this.requirements = {
53
+ storage: false,
54
+ providers: ["metrics"]
55
+ };
56
+ this.metricsCluster = [];
57
+ // list of intervals (and its associated metrics) that produce metrics streams
58
+ this.webSockets = [];
59
+ this.startChannel = async () => {
60
+ this.clusterInfo.addSubscriber("metrics", this, {});
61
+ };
62
+ // Called by the core before replacing this instance (hot reload) or uninstalling the plugin: drop the
63
+ // provider subscription and every running interval, so the old instance does not keep working.
64
+ this.cleanup = () => {
65
+ try {
66
+ this.clusterInfo.removeSubscriber("metrics", this);
67
+ } catch {
68
+ }
69
+ for (const socket of this.webSockets) {
70
+ for (const instance of socket.instances) {
71
+ if (instance.timeout) clearInterval(instance.timeout);
72
+ }
73
+ }
74
+ this.webSockets = [];
75
+ this.metricsCluster = [];
76
+ };
77
+ this.containsAsset = (webSocket, podNamespace, podName, containerName) => {
78
+ let socket = this.webSockets.find((s) => s.ws === webSocket);
79
+ if (socket) {
80
+ let instances = socket.instances;
81
+ if (instances) return instances.some((i) => i.assets.some((a) => a.podNamespace === podNamespace && a.podName === podName && a.containerName === containerName));
82
+ }
83
+ return false;
84
+ };
85
+ this.checkScopes = (instanceConfig, scope) => {
86
+ let resources = (0, import_kwirth_common.parseResources)((0, import_kwirth_common.accessKeyDeserialize)(instanceConfig.accessKey).resources);
87
+ let requiredLevel = this.getChannelScopeLevel(scope);
88
+ let canPerform = resources.some((r) => r.scopes.split(",").some((sc) => this.getChannelScopeLevel(sc) >= requiredLevel));
89
+ return canPerform;
90
+ };
91
+ this.addObject = async (webSocket, instanceConfig, podNamespace, podName, containerName) => {
92
+ try {
93
+ const podResponse = await this.clusterInfo.coreApi.readNamespacedPod({ name: podName, namespace: podNamespace });
94
+ const owner = podResponse.metadata?.ownerReferences?.find((owner2) => owner2.controller);
95
+ const podNode = podResponse.spec?.nodeName;
96
+ let podGroup = void 0;
97
+ if (owner) {
98
+ podGroup = owner.kind.toLocaleLowerCase() + "+" + owner.name;
99
+ } else {
100
+ this.backChannelObject.logInfo?.(`No owner found for ${podName}, assume pod/container without controller`);
101
+ }
102
+ switch (instanceConfig.data.mode) {
103
+ case "snapshot" /* SNAPSHOT */: {
104
+ if (!this.checkScopes(instanceConfig, import_kwirth_common.InstanceConfigScopeEnum.SNAPSHOT)) {
105
+ this.backChannelObject.logInfo?.("Insufficient scope for SNAPSHOT");
106
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, "Insufficient scope for SNAPSHOT", instanceConfig);
107
+ return false;
108
+ }
109
+ if (podNode) {
110
+ this.backChannelObject.logInfo?.(`Send snapshot metrics for ${podNode}/${podNamespace}/${podGroup}/${podName}/${containerName}`);
111
+ let socket = this.webSockets.find((entry) => entry.ws === webSocket);
112
+ if (!socket) {
113
+ this.backChannelObject.logInfo?.("No socket found for startInstance snapshot");
114
+ return false;
115
+ }
116
+ let instances = socket.instances;
117
+ let instance = instances?.find((instance2) => instance2.instanceId === instanceConfig.instance);
118
+ if (instance) {
119
+ this.sendMetricsDataInstance(webSocket, instanceConfig.instance, false);
120
+ return true;
121
+ } else {
122
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, `Instance ${instanceConfig.instance} not found`, instanceConfig);
123
+ return false;
124
+ }
125
+ }
126
+ }
127
+ case "stream" /* STREAM */: {
128
+ if (!this.checkScopes(instanceConfig, import_kwirth_common.InstanceConfigScopeEnum.STREAM)) {
129
+ this.backChannelObject.logInfo?.("Insufficient scope for STREAM");
130
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, "Insufficient scope for STREAM", instanceConfig);
131
+ return false;
132
+ }
133
+ if (podNode) {
134
+ this.backChannelObject.logInfo?.(`Start pod metrics for ${podNode}/${podNamespace}/${podGroup}/${podName}/${containerName}`);
135
+ let socket = this.webSockets.find((entry) => entry.ws === webSocket);
136
+ let metricsConfig = instanceConfig.data;
137
+ let interval = (metricsConfig.interval || 15) * 1e3;
138
+ if (socket) {
139
+ let instances = socket.instances;
140
+ let instance = instances?.find((instance2) => instance2.instanceId === instanceConfig.instance);
141
+ if (!instance) {
142
+ let timeout = setInterval((w, i, s) => this.sendMetricsDataInstance(w, i, s), interval, webSocket, instanceConfig.instance, false);
143
+ instances?.push({
144
+ instanceId: instanceConfig.instance,
145
+ working: false,
146
+ paused: false,
147
+ timeout,
148
+ interval,
149
+ assets: [{ podNode, podNamespace, podGroup, podName, containerName }],
150
+ instanceConfig
151
+ });
152
+ this.sendMetricsDataInstance(webSocket, instanceConfig.instance, true);
153
+ return true;
154
+ }
155
+ if (instanceConfig.view === import_kwirth_common.EInstanceConfigView.CONTAINER) {
156
+ instance.assets.push({ podNode, podNamespace, podGroup, podName, containerName });
157
+ this.sendMetricsDataInstance(webSocket, instanceConfig.instance, true);
158
+ return true;
159
+ } else {
160
+ if (!instance.assets.find((asset) => asset.podName === podName && asset.containerName === containerName)) {
161
+ instance.assets.push({ podNode, podNamespace, podGroup, podName, containerName });
162
+ this.sendMetricsDataInstance(webSocket, instanceConfig.instance, true);
163
+ return true;
164
+ }
165
+ }
166
+ return false;
167
+ } else {
168
+ this.webSockets.push({ ws: webSocket, lastRefresh: Date.now(), instances: [] });
169
+ let timeout = setInterval((w, i, s) => this.sendMetricsDataInstance(w, i, s), interval, webSocket, instanceConfig.instance, false);
170
+ let instances = this.webSockets.find((entry) => entry.ws === webSocket)?.instances;
171
+ instances?.push({
172
+ instanceId: instanceConfig.instance,
173
+ working: false,
174
+ paused: false,
175
+ timeout,
176
+ assets: [{ podNode, podNamespace, podGroup, podName, containerName }],
177
+ instanceConfig,
178
+ interval
179
+ });
180
+ this.sendMetricsDataInstance(webSocket, instanceConfig.instance, true);
181
+ return true;
182
+ }
183
+ } else {
184
+ this.backChannelObject.logInfo?.(`Cannot determine node for ${podNamespace}/${podName}, will not be added`);
185
+ return false;
186
+ }
187
+ }
188
+ default:
189
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, `Invalid mode: ${instanceConfig.data.mode}`, instanceConfig);
190
+ return false;
191
+ }
192
+ } catch (err) {
193
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, err?.stack ?? String(err), instanceConfig);
194
+ this.backChannelObject.logError?.("Generic error starting metrics instance");
195
+ this.backChannelObject.logError?.(err);
196
+ return false;
197
+ }
198
+ };
199
+ this.deleteObject = async (webSocket, instanceConfig, podNamespace, podName, containerName) => {
200
+ const socket = this.webSockets.find((s) => s.ws === webSocket);
201
+ const instance = socket?.instances.find((i) => i.instanceId === instanceConfig.instance);
202
+ if (instance) {
203
+ instance.assets = instance.assets.filter((a) => !(a.podNamespace === podNamespace && a.podName === podName && (containerName === "" || a.containerName === containerName)));
204
+ }
205
+ return true;
206
+ };
207
+ this.extractContainerMetrics = (podMetricsSet, containerMetricsSet, requestedMetricName, view, node, asset) => {
208
+ if (view === import_kwirth_common.EInstanceConfigView.CONTAINER) {
209
+ let metricName = asset.podNamespace + "/" + asset.podName + "/" + asset.containerName + "/" + requestedMetricName;
210
+ let value = containerMetricsSet.get(metricName)?.value;
211
+ if (value !== void 0) {
212
+ return { value, timestamp: node.timestamp };
213
+ } else {
214
+ return { value: 0, timestamp: node.timestamp };
215
+ }
216
+ } else {
217
+ const prefix = asset.podNamespace + "/" + asset.podName + "/";
218
+ const suffix = "/" + requestedMetricName;
219
+ let accum = 0;
220
+ let found = false;
221
+ for (const [k, v] of containerMetricsSet) {
222
+ if (k.startsWith(prefix) && k.endsWith(suffix)) {
223
+ accum += v.value;
224
+ found = true;
225
+ }
226
+ }
227
+ if (!found) {
228
+ const podValue = podMetricsSet.get(asset.podNamespace + "/" + asset.podName + "/" + requestedMetricName)?.value;
229
+ return { value: podValue ?? 0, timestamp: node.timestamp };
230
+ }
231
+ return { value: accum, timestamp: node.timestamp };
232
+ }
233
+ };
234
+ this.getAssetMetrics = (instanceConfig, assets, usePrevMetricSet) => {
235
+ if (this.metricsCluster.length < 2) {
236
+ return void 0;
237
+ }
238
+ let assetMetrics = { assetName: this.getAssetMetricName(instanceConfig, assets), values: [] };
239
+ let newAssets = [];
240
+ if (instanceConfig.view === import_kwirth_common.EInstanceConfigView.CONTAINER) {
241
+ newAssets = assets;
242
+ } else {
243
+ for (let a of assets) {
244
+ if (!newAssets.find((newAsset) => newAsset.podName === a.podName)) {
245
+ newAssets.push({
246
+ podNode: a.podNode,
247
+ podNamespace: a.podNamespace,
248
+ podGroup: a.podGroup,
249
+ podName: a.podName,
250
+ containerName: ""
251
+ });
252
+ }
253
+ }
254
+ }
255
+ for (let metricName of instanceConfig.data.metrics) {
256
+ if (isClusterMetric(metricName)) continue;
257
+ let sourceMetricName = metricName;
258
+ if (metricName === "kwirth_container_memory_percentage") sourceMetricName = "container_memory_working_set_bytes";
259
+ if (metricName === "kwirth_container_cpu_percentage") sourceMetricName = "container_cpu_usage_seconds_total";
260
+ if (metricName === "kwirth_container_transmit_percentage") sourceMetricName = "container_network_transmit_bytes_total";
261
+ if (metricName === "kwirth_container_receive_percentage") sourceMetricName = "container_network_receive_bytes_total";
262
+ if (metricName === "kwirth_container_transmit_mbps") sourceMetricName = "container_network_transmit_bytes_total";
263
+ if (metricName === "kwirth_container_receive_mbps") sourceMetricName = "container_network_receive_bytes_total";
264
+ if (metricName === "kwirth_container_write_mbps") sourceMetricName = "container_fs_writes_bytes_total";
265
+ if (metricName === "kwirth_container_read_mbps") sourceMetricName = "container_fs_reads_bytes_total";
266
+ if (metricName === "kwirth_container_random_counter" || metricName === "kwirth_container_random_gauge") sourceMetricName = "container_cpu_system_seconds_total";
267
+ let uniqueValues = [];
268
+ for (let asset of newAssets) {
269
+ let lastRead = this.metricsCluster[this.metricsCluster.length - 1];
270
+ let prevRead = this.metricsCluster[this.metricsCluster.length - 2];
271
+ let lastNode = lastRead.nodes.find((n) => n.name === asset.podNode);
272
+ let prevNode = prevRead.nodes.find((n) => n.name === asset.podNode);
273
+ if (!lastNode || !prevNode) {
274
+ this.backChannelObject.logError?.(`No node '${asset.podNode}' found for calculating metric value of ${asset.podNamespace}/${asset.podName}, asset skipped`);
275
+ continue;
276
+ }
277
+ let metric;
278
+ if (usePrevMetricSet)
279
+ metric = this.extractContainerMetrics(prevNode.podMetricValues, prevNode.containerMetricValues, sourceMetricName, instanceConfig.view, prevNode, asset);
280
+ else
281
+ metric = this.extractContainerMetrics(lastNode.podMetricValues, lastNode.containerMetricValues, sourceMetricName, instanceConfig.view, lastNode, asset);
282
+ uniqueValues.push(metric.value);
283
+ }
284
+ let metricValue = uniqueValues.reduce((acc, value) => acc + value, 0);
285
+ assetMetrics.values.push({ metricName, metricValue });
286
+ }
287
+ for (var i = 0; i < assetMetrics.values.length; i++) {
288
+ let m = assetMetrics.values[i];
289
+ switch (m.metricName) {
290
+ case "kwirth_container_memory_percentage":
291
+ let clusterMemory = Number.MAX_VALUE;
292
+ if (this.metricsCluster.length > 0) {
293
+ clusterMemory = this.metricsCluster[this.metricsCluster.length - 1].cluster.memory;
294
+ }
295
+ m.metricValue = Math.round(m.metricValue / clusterMemory * 100 * 100) / 100;
296
+ break;
297
+ case "kwirth_container_cpu_percentage":
298
+ if (!usePrevMetricSet) {
299
+ let prevValues2 = this.getAssetMetrics(instanceConfig, newAssets, true);
300
+ let prev = prevValues2?.values.find((prevMetric) => prevMetric.metricName === m.metricName);
301
+ if (prev) {
302
+ m.metricValue -= prev.metricValue;
303
+ if (m.metricValue < 0) {
304
+ m.metricValue = prev.metricValue;
305
+ } else {
306
+ let totalSecs = 1;
307
+ if (this.metricsCluster.length > 0) {
308
+ totalSecs = this.metricsCluster[this.metricsCluster.length - 1].metricsInterval * this.metricsCluster[this.metricsCluster.length - 1].cluster.vcpus;
309
+ m.metricValue = Math.round(m.metricValue / totalSecs * 100 * 100) / 100;
310
+ } else {
311
+ m.metricValue = prev.metricValue;
312
+ }
313
+ }
314
+ } else {
315
+ this.backChannelObject.logWarning?.(`No previous value [CPU] found for ${m.metricName}`);
316
+ }
317
+ }
318
+ break;
319
+ case "kwirth_container_random_counter":
320
+ m.metricValue = Date.now() % (Math.random() * 32 * 1e6);
321
+ break;
322
+ case "kwirth_container_random_gauge":
323
+ m.metricValue = Math.round(Math.random() * 100) / 100;
324
+ break;
325
+ case "kwirth_container_transmit_percentage":
326
+ case "kwirth_container_receive_percentage":
327
+ let totalBytes = 0;
328
+ for (let node of this.metricsCluster[this.metricsCluster.length - 1].nodes) {
329
+ let sourceMetricName = m.metricName === "kwirth_container_transmit_percentage" ? "container_network_transmit_bytes_total" : "container_network_receive_bytes_total";
330
+ let nodeMetrics = Array.from(node.podMetricValues.keys()).filter((k) => k.endsWith("/" + sourceMetricName));
331
+ nodeMetrics.map((m2) => {
332
+ totalBytes += node.podMetricValues.get(m2)?.value ?? 0;
333
+ });
334
+ }
335
+ m.metricValue = Math.round(m.metricValue / totalBytes * 100 * 100) / 100;
336
+ break;
337
+ case "kwirth_container_write_mbps":
338
+ case "kwirth_container_read_mbps":
339
+ case "kwirth_container_transmit_mbps":
340
+ case "kwirth_container_receive_mbps":
341
+ if (!usePrevMetricSet) {
342
+ var prevValues = this.getAssetMetrics(instanceConfig, newAssets, true);
343
+ let prev = prevValues?.values.find((prevMetric) => prevMetric.metricName === m.metricName);
344
+ if (prev) {
345
+ m.metricValue -= prev.metricValue;
346
+ m.metricValue *= 8;
347
+ m.metricValue /= 1024 * 1024;
348
+ let totalSecs = this.metricsCluster[0].metricsInterval;
349
+ m.metricValue = Math.round(m.metricValue / totalSecs * 100) / 100;
350
+ } else {
351
+ this.backChannelObject.logInfo?.(`No previous value found for ${m.metricName}`);
352
+ }
353
+ }
354
+ break;
355
+ }
356
+ }
357
+ return assetMetrics;
358
+ };
359
+ /*
360
+ The provider's cluster metrics (kwirth_cluster_*) are one value for the whole cluster. Repeating
361
+ them in every asset drew N identical series named after namespaces or pods they have nothing to do
362
+ with, so they go out ONCE, as a series of their own named CLUSTER_ASSET, whatever the view,
363
+ aggregation or merge. They need a single reading: they are not rates.
364
+ */
365
+ this.getClusterMetrics = (instanceConfig) => {
366
+ const clusterMetrics = instanceConfig.data.metrics.filter(isClusterMetric);
367
+ if (clusterMetrics.length === 0 || this.metricsCluster.length === 0) return void 0;
368
+ const lastRead = this.metricsCluster[this.metricsCluster.length - 1];
369
+ return {
370
+ assetName: CLUSTER_ASSET,
371
+ values: clusterMetrics.map((metricName) => ({ metricName, metricValue: lastRead.clusterMetricValues?.get(metricName)?.value ?? 0 }))
372
+ };
373
+ };
374
+ this.getAssetMetricName = (instanceConfig, assets) => {
375
+ switch (instanceConfig.view) {
376
+ case import_kwirth_common.EInstanceConfigView.NAMESPACE:
377
+ return [...new Set(assets.map((a) => a.podNamespace))].join(",");
378
+ case import_kwirth_common.EInstanceConfigView.GROUP:
379
+ let requestedGroups = instanceConfig.group.split(",");
380
+ return [...new Set(assets.map((a) => {
381
+ if (!a.podGroup) return void 0;
382
+ if (requestedGroups.includes(a.podGroup)) {
383
+ return a.podGroup;
384
+ } else {
385
+ let deploymentName = a.podGroup.substring(0, a.podGroup.lastIndexOf("-"));
386
+ let x = deploymentName.replace(/replicaset\+/g, "deployment+");
387
+ if (requestedGroups.includes(x)) {
388
+ return x;
389
+ } else {
390
+ return void 0;
391
+ }
392
+ }
393
+ }))].join(",");
394
+ case import_kwirth_common.EInstanceConfigView.POD:
395
+ return [...new Set(assets.map((a) => a.podName))].join(",");
396
+ case import_kwirth_common.EInstanceConfigView.CONTAINER:
397
+ return [...new Set(assets.map((a) => a.podName + "[" + a.containerName + "]"))].join(",");
398
+ default:
399
+ return "unnamedView";
400
+ }
401
+ };
402
+ this.fillData = (instanceConfig, instance, responseMessage) => {
403
+ switch (instanceConfig.view) {
404
+ case import_kwirth_common.EInstanceConfigView.NAMESPACE:
405
+ if (instanceConfig.data.aggregate) {
406
+ let assetMetrics2 = this.getAssetMetrics(instanceConfig, instance.assets, false);
407
+ if (assetMetrics2) responseMessage.assets.push(assetMetrics2);
408
+ } else {
409
+ const namespaces = [...new Set(instance.assets.map((item) => item.podNamespace))];
410
+ for (let namespace of namespaces) {
411
+ let assets2 = instance.assets.filter((a) => a.podNamespace === namespace);
412
+ let assetMetrics2 = this.getAssetMetrics(instanceConfig, assets2, false);
413
+ if (assetMetrics2) responseMessage.assets.push(assetMetrics2);
414
+ }
415
+ }
416
+ break;
417
+ case import_kwirth_common.EInstanceConfigView.GROUP:
418
+ if (instanceConfig.data.aggregate) {
419
+ var assetMetrics = this.getAssetMetrics(instanceConfig, instance.assets, false);
420
+ if (assetMetrics) responseMessage.assets.push(assetMetrics);
421
+ } else {
422
+ const groupNames = [...new Set(instance.assets.map((item) => item.podGroup))];
423
+ for (let groupName of groupNames) {
424
+ let assets2 = instance.assets.filter((a) => a.podGroup === groupName);
425
+ let assetMetrics2 = this.getAssetMetrics(instanceConfig, assets2, false);
426
+ if (assetMetrics2) responseMessage.assets.push(assetMetrics2);
427
+ }
428
+ }
429
+ break;
430
+ case import_kwirth_common.EInstanceConfigView.POD:
431
+ if (instanceConfig.data.aggregate) {
432
+ var assetMetrics = this.getAssetMetrics(instanceConfig, instance.assets, false);
433
+ if (assetMetrics) responseMessage.assets.push(assetMetrics);
434
+ } else {
435
+ const uniquePodNames = [...new Set(instance.assets.map((asset2) => asset2.podName))];
436
+ for (var podName of uniquePodNames) {
437
+ var assets = instance.assets.filter((a) => a.podName === podName);
438
+ var assetMetrics = this.getAssetMetrics(instanceConfig, assets, false);
439
+ if (assetMetrics) responseMessage.assets.push(assetMetrics);
440
+ }
441
+ }
442
+ break;
443
+ case import_kwirth_common.EInstanceConfigView.CONTAINER:
444
+ if (instanceConfig.data.aggregate) {
445
+ var assetMetrics = this.getAssetMetrics(instanceConfig, instance.assets, false);
446
+ if (assetMetrics) responseMessage.assets.push(assetMetrics);
447
+ } else {
448
+ for (var asset of instance.assets) {
449
+ var assetMetrics = this.getAssetMetrics(instanceConfig, [asset], false);
450
+ if (assetMetrics) responseMessage.assets.push(assetMetrics);
451
+ }
452
+ }
453
+ break;
454
+ default:
455
+ this.backChannelObject.logWarning?.(`Invalid view:` + instanceConfig.view);
456
+ }
457
+ responseMessage.assets = responseMessage.assets.filter((asset2) => asset2.values.length > 0);
458
+ const clusterMetrics = this.getClusterMetrics(instanceConfig);
459
+ if (clusterMetrics) responseMessage.assets.push(clusterMetrics);
460
+ };
461
+ this.sendMetricsDataInstance = (webSocket, instanceId, initial) => {
462
+ let socket = this.webSockets.find((entry) => entry.ws === webSocket);
463
+ if (!socket) {
464
+ this.backChannelObject.logInfo?.("No socket found for sendMetricsData");
465
+ return;
466
+ }
467
+ let instances = socket.instances;
468
+ if (!instances) {
469
+ this.backChannelObject.logInfo?.("No instances found for sendMetricsData");
470
+ return;
471
+ }
472
+ var instance = instances.find((i) => i.instanceId === instanceId);
473
+ if (!instance) {
474
+ this.backChannelObject.logInfo?.(`No instance found for sendMetricsData instance ${instanceId}`);
475
+ return;
476
+ }
477
+ if (instance.working) {
478
+ this.backChannelObject.logInfo?.(`Previous instance of ${instanceId} is still running`);
479
+ return;
480
+ }
481
+ if (instance.paused) {
482
+ this.backChannelObject.logInfo?.(`Instance ${instanceId} is paused, no SMD performed`);
483
+ return;
484
+ }
485
+ instance.working = true;
486
+ let instanceConfig = instance.instanceConfig;
487
+ try {
488
+ let metricsMessageResponse = {
489
+ msgtype: "metricsmessageresponse",
490
+ action: import_kwirth_common.EInstanceMessageAction.NONE,
491
+ flow: import_kwirth_common.EInstanceMessageFlow.UNSOLICITED,
492
+ channel: import_kwirth_common.EInstanceMessageChannel.METRICS,
493
+ type: import_kwirth_common.EInstanceMessageType.DATA,
494
+ instance: instanceConfig.instance,
495
+ assets: [],
496
+ namespace: instanceConfig.namespace,
497
+ group: "",
498
+ pod: instanceConfig.pod,
499
+ timestamp: initial ? 0 : Date.now(),
500
+ container: ""
501
+ };
502
+ this.fillData(instanceConfig, instance, metricsMessageResponse);
503
+ if (metricsMessageResponse.assets.length > 0) {
504
+ try {
505
+ webSocket.send(JSON.stringify(metricsMessageResponse));
506
+ } catch (err) {
507
+ this.backChannelObject.logInfo?.("Socket error, we should forget interval");
508
+ }
509
+ }
510
+ instance.working = false;
511
+ } catch (err) {
512
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.WARNING, `Cannot read metrics for instance ${instanceId}`, instanceConfig);
513
+ this.backChannelObject.logError?.("Error reading metrics");
514
+ this.backChannelObject.logError?.(err);
515
+ }
516
+ };
517
+ this.sendInstanceConfigMessage = (ws, action, flow, channel, instanceConfig, text) => {
518
+ var resp = {
519
+ action,
520
+ flow,
521
+ channel,
522
+ instance: instanceConfig.instance,
523
+ type: import_kwirth_common.EInstanceMessageType.SIGNAL,
524
+ text
525
+ };
526
+ ws.send(JSON.stringify(resp));
527
+ };
528
+ this.clusterInfo = clusterInfo;
529
+ this.backChannelObject = backChannelObject;
530
+ }
531
+ getChannelData() {
532
+ return {
533
+ id: "metrics",
534
+ routable: false,
535
+ pauseable: true,
536
+ modifiable: true,
537
+ reconnectable: true,
538
+ metrics: true,
539
+ sources: [import_kwirth_common.EClusterType.KUBERNETES],
540
+ endpoints: [],
541
+ websocket: false,
542
+ cluster: false,
543
+ resourced: true
544
+ };
545
+ }
546
+ getChannelScopeLevel(scope) {
547
+ return ["", "snapshot", "stream", "cluster", "admin"].indexOf(scope);
548
+ }
549
+ async processProviderEvent(providerId, event) {
550
+ switch (providerId) {
551
+ case "metrics":
552
+ this.metricsCluster.push(event);
553
+ if (this.metricsCluster.length > 100) this.metricsCluster.shift();
554
+ break;
555
+ default:
556
+ this.backChannelObject.logError?.(`Ignored provider event from ${providerId} to channel ${this.getChannelData().id}`);
557
+ }
558
+ }
559
+ async endpointRequest(_endpoint, _req, _res) {
560
+ }
561
+ async websocketRequest(_newWebSocket) {
562
+ }
563
+ async processCommand(webSocket, instanceMessage) {
564
+ if (instanceMessage.flow === import_kwirth_common.EInstanceMessageFlow.IMMEDIATE) {
565
+ let resp = await this.executeImmediateCommand(instanceMessage);
566
+ if (resp) webSocket.send(JSON.stringify(resp));
567
+ return Boolean(resp);
568
+ } else {
569
+ return false;
570
+ }
571
+ }
572
+ async executeImmediateCommand(instanceMessage) {
573
+ this.backChannelObject.logInfo?.("Immediate request received");
574
+ let iconfig = {
575
+ objects: import_kwirth_common.EInstanceConfigObject.PODS,
576
+ accessKey: "",
577
+ scope: "",
578
+ view: import_kwirth_common.EInstanceConfigView.NONE,
579
+ namespace: "",
580
+ group: "",
581
+ pod: "",
582
+ container: "",
583
+ action: import_kwirth_common.EInstanceMessageAction.NONE,
584
+ flow: import_kwirth_common.EInstanceMessageFlow.IMMEDIATE,
585
+ type: import_kwirth_common.EInstanceMessageType.DATA,
586
+ channel: "",
587
+ instance: ""
588
+ };
589
+ let instance = {
590
+ instanceId: instanceMessage.instance,
591
+ timeout: void 0,
592
+ working: false,
593
+ paused: false,
594
+ assets: [],
595
+ instanceConfig: iconfig,
596
+ interval: 0
597
+ };
598
+ let metricsMessageResponse = {
599
+ msgtype: "metricsmessageresponse",
600
+ action: import_kwirth_common.EInstanceMessageAction.NONE,
601
+ flow: import_kwirth_common.EInstanceMessageFlow.UNSOLICITED,
602
+ channel: import_kwirth_common.EInstanceMessageChannel.METRICS,
603
+ type: import_kwirth_common.EInstanceMessageType.DATA,
604
+ instance: iconfig.instance,
605
+ assets: [],
606
+ namespace: iconfig.namespace,
607
+ group: "",
608
+ pod: iconfig.pod,
609
+ timestamp: Date.now(),
610
+ container: ""
611
+ };
612
+ this.fillData(iconfig, instance, metricsMessageResponse);
613
+ return metricsMessageResponse;
614
+ }
615
+ // A connection counts while it carries an instance: one left with none is on its way out.
616
+ getInstances() {
617
+ const carrying = this.webSockets.filter((s) => s.instances.length > 0);
618
+ return { instances: carrying.reduce((n, s) => n + s.instances.length, 0), connections: carrying.length };
619
+ }
620
+ containsInstance(instanceId) {
621
+ for (var socket of this.webSockets) {
622
+ var exists = socket.instances.find((i) => i.instanceId === instanceId);
623
+ if (exists) return true;
624
+ }
625
+ return false;
626
+ }
627
+ stopInstance(webSocket, instanceConfig) {
628
+ let instance = this.getInstance(webSocket, instanceConfig.instance);
629
+ if (instance) {
630
+ this.removeInstance(webSocket, instanceConfig.instance);
631
+ this.sendInstanceConfigMessage(webSocket, import_kwirth_common.EInstanceMessageAction.STOP, import_kwirth_common.EInstanceMessageFlow.RESPONSE, import_kwirth_common.EInstanceMessageChannel.METRICS, instanceConfig, "Metrics instance stopped");
632
+ } else {
633
+ this.sendInstanceConfigMessage(webSocket, import_kwirth_common.EInstanceMessageAction.STOP, import_kwirth_common.EInstanceMessageFlow.RESPONSE, import_kwirth_common.EInstanceMessageChannel.METRICS, instanceConfig, "Instance not found");
634
+ }
635
+ }
636
+ modifyInstance(webSocket, instanceConfig) {
637
+ let instance = this.getInstance(webSocket, instanceConfig.instance);
638
+ if (instance) {
639
+ let destConfig = instance.instanceConfig.data;
640
+ destConfig.metrics = instanceConfig.data.metrics;
641
+ destConfig.interval = instanceConfig.data.interval;
642
+ destConfig.aggregate = instanceConfig.data.aggregate;
643
+ this.sendInstanceConfigMessage(webSocket, import_kwirth_common.EInstanceMessageAction.MODIFY, import_kwirth_common.EInstanceMessageFlow.RESPONSE, import_kwirth_common.EInstanceMessageChannel.METRICS, instanceConfig, "Metrics modified");
644
+ } else {
645
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, `Instance ${instanceConfig.instance} not found`, instanceConfig);
646
+ }
647
+ }
648
+ pauseContinueInstance(webSocket, instanceConfig, action) {
649
+ let instance = this.getInstance(webSocket, instanceConfig.instance);
650
+ if (instance) {
651
+ if (action === import_kwirth_common.EInstanceMessageAction.PAUSE) {
652
+ instance.paused = true;
653
+ this.sendInstanceConfigMessage(webSocket, import_kwirth_common.EInstanceMessageAction.PAUSE, import_kwirth_common.EInstanceMessageFlow.RESPONSE, import_kwirth_common.EInstanceMessageChannel.METRICS, instanceConfig, "Metrics paused");
654
+ }
655
+ if (action === import_kwirth_common.EInstanceMessageAction.CONTINUE) {
656
+ instance.paused = false;
657
+ this.sendInstanceConfigMessage(webSocket, import_kwirth_common.EInstanceMessageAction.CONTINUE, import_kwirth_common.EInstanceMessageFlow.RESPONSE, import_kwirth_common.EInstanceMessageChannel.METRICS, instanceConfig, "Metrics continued");
658
+ }
659
+ } else {
660
+ this.sendChannelSignal(webSocket, import_kwirth_common.ESignalMessageLevel.ERROR, `Instance ${instanceConfig.instance} not found`, instanceConfig);
661
+ }
662
+ }
663
+ removeInstance(webSocket, instanceId) {
664
+ let socket = this.webSockets.find((entry) => entry.ws === webSocket);
665
+ if (socket) {
666
+ let instances = socket.instances;
667
+ if (instances) {
668
+ let instanceIndex = instances.findIndex((t) => t.instanceId === instanceId);
669
+ if (instanceIndex >= 0) {
670
+ if (instances[instanceIndex].timeout) clearInterval(instances[instanceIndex].timeout);
671
+ instances.splice(instanceIndex, 1);
672
+ } else {
673
+ this.backChannelObject.logInfo?.("Instance not found, cannot delete");
674
+ }
675
+ } else {
676
+ this.backChannelObject.logInfo?.("There are no Instances on websocket");
677
+ }
678
+ } else {
679
+ this.backChannelObject.logInfo?.("WebSocket not found on intervals");
680
+ }
681
+ }
682
+ containsConnection(webSocket) {
683
+ return Boolean(this.webSockets.find((s) => s.ws === webSocket));
684
+ }
685
+ refreshConnection(webSocket) {
686
+ let socket = this.webSockets.find((s) => s.ws === webSocket);
687
+ if (socket) {
688
+ socket.lastRefresh = Date.now();
689
+ return true;
690
+ } else {
691
+ this.backChannelObject.logInfo?.("WebSocket not found");
692
+ return false;
693
+ }
694
+ }
695
+ updateConnection(newWebSocket, instanceId) {
696
+ for (let entry of this.webSockets) {
697
+ var exists = entry.instances.find((i) => i.instanceId === instanceId);
698
+ if (exists) {
699
+ entry.ws = newWebSocket;
700
+ for (var instance of entry.instances) {
701
+ if (instance.timeout) clearInterval(instance.timeout);
702
+ instance.timeout = setInterval((w, i, s) => this.sendMetricsDataInstance(w, i, s), instance.interval, newWebSocket, instanceId, false);
703
+ }
704
+ return true;
705
+ }
706
+ }
707
+ return false;
708
+ }
709
+ removeConnection(webSocket) {
710
+ let socket = this.webSockets.find((entry) => entry.ws === webSocket);
711
+ if (socket) {
712
+ const ids = socket.instances.map((i) => i.instanceId);
713
+ for (const id of ids) {
714
+ this.backChannelObject.logInfo?.(`Interval for instance ${id} has been removed`);
715
+ this.removeInstance(webSocket, id);
716
+ }
717
+ var pos = this.webSockets.findIndex((s) => s.ws === webSocket);
718
+ this.webSockets.splice(pos, 1);
719
+ }
720
+ }
721
+ // *************************************************************************************
722
+ // PRIVATE
723
+ // *************************************************************************************
724
+ getInstance(webSocket, instanceId) {
725
+ let socket = this.webSockets.find((entry) => entry.ws === webSocket);
726
+ if (socket) {
727
+ let instances = socket.instances;
728
+ if (instances) {
729
+ let instanceIndex = instances.findIndex((t) => t.instanceId === instanceId);
730
+ if (instanceIndex >= 0) return instances[instanceIndex];
731
+ this.backChannelObject.logInfo?.("Instance not found");
732
+ } else {
733
+ this.backChannelObject.logInfo?.("There are no Instances on websocket");
734
+ }
735
+ } else {
736
+ this.backChannelObject.logInfo?.("WebSocket not found");
737
+ }
738
+ return void 0;
739
+ }
740
+ sendChannelSignal(webSocket, level, text, instanceConfig) {
741
+ var signalMessage = {
742
+ action: import_kwirth_common.EInstanceMessageAction.NONE,
743
+ flow: import_kwirth_common.EInstanceMessageFlow.RESPONSE,
744
+ type: import_kwirth_common.EInstanceMessageType.SIGNAL,
745
+ level,
746
+ channel: instanceConfig.channel,
747
+ instance: instanceConfig.instance,
748
+ text
749
+ };
750
+ webSocket.send(JSON.stringify(signalMessage));
751
+ }
752
+ };
753
+ // Annotate the CommonJS export names for ESM import in node:
754
+ 0 && (module.exports = {
755
+ MetricsChannel
756
+ });