@matterbridge/core 3.5.3
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/LICENSE +202 -0
- package/README.md +22 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +268 -0
- package/dist/cli.js.map +1 -0
- package/dist/cliEmitter.d.ts +50 -0
- package/dist/cliEmitter.d.ts.map +1 -0
- package/dist/cliEmitter.js +49 -0
- package/dist/cliEmitter.js.map +1 -0
- package/dist/cliHistory.d.ts +48 -0
- package/dist/cliHistory.d.ts.map +1 -0
- package/dist/cliHistory.js +826 -0
- package/dist/cliHistory.js.map +1 -0
- package/dist/clusters/export.d.ts +2 -0
- package/dist/clusters/export.d.ts.map +1 -0
- package/dist/clusters/export.js +3 -0
- package/dist/clusters/export.js.map +1 -0
- package/dist/crypto/attestationDecoder.d.ts +180 -0
- package/dist/crypto/attestationDecoder.d.ts.map +1 -0
- package/dist/crypto/attestationDecoder.js +176 -0
- package/dist/crypto/attestationDecoder.js.map +1 -0
- package/dist/crypto/declarationDecoder.d.ts +72 -0
- package/dist/crypto/declarationDecoder.d.ts.map +1 -0
- package/dist/crypto/declarationDecoder.js +241 -0
- package/dist/crypto/declarationDecoder.js.map +1 -0
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.d.ts +9 -0
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.d.ts.map +1 -0
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.js +120 -0
- package/dist/crypto/extract/342/200/220cert/342/200/220extensions.js.map +1 -0
- package/dist/crypto/read-extensions.d.ts +2 -0
- package/dist/crypto/read-extensions.d.ts.map +1 -0
- package/dist/crypto/read-extensions.js +81 -0
- package/dist/crypto/read-extensions.js.map +1 -0
- package/dist/crypto/testData.d.ts +31 -0
- package/dist/crypto/testData.d.ts.map +1 -0
- package/dist/crypto/testData.js +131 -0
- package/dist/crypto/testData.js.map +1 -0
- package/dist/crypto/walk-der.d.ts +2 -0
- package/dist/crypto/walk-der.d.ts.map +1 -0
- package/dist/crypto/walk-der.js +165 -0
- package/dist/crypto/walk-der.js.map +1 -0
- package/dist/deviceManager.d.ts +135 -0
- package/dist/deviceManager.d.ts.map +1 -0
- package/dist/deviceManager.js +270 -0
- package/dist/deviceManager.js.map +1 -0
- package/dist/devices/airConditioner.d.ts +98 -0
- package/dist/devices/airConditioner.d.ts.map +1 -0
- package/dist/devices/airConditioner.js +74 -0
- package/dist/devices/airConditioner.js.map +1 -0
- package/dist/devices/basicVideoPlayer.d.ts +88 -0
- package/dist/devices/basicVideoPlayer.d.ts.map +1 -0
- package/dist/devices/basicVideoPlayer.js +155 -0
- package/dist/devices/basicVideoPlayer.js.map +1 -0
- package/dist/devices/batteryStorage.d.ts +48 -0
- package/dist/devices/batteryStorage.d.ts.map +1 -0
- package/dist/devices/batteryStorage.js +75 -0
- package/dist/devices/batteryStorage.js.map +1 -0
- package/dist/devices/castingVideoPlayer.d.ts +79 -0
- package/dist/devices/castingVideoPlayer.d.ts.map +1 -0
- package/dist/devices/castingVideoPlayer.js +101 -0
- package/dist/devices/castingVideoPlayer.js.map +1 -0
- package/dist/devices/cooktop.d.ts +61 -0
- package/dist/devices/cooktop.d.ts.map +1 -0
- package/dist/devices/cooktop.js +77 -0
- package/dist/devices/cooktop.js.map +1 -0
- package/dist/devices/dishwasher.d.ts +71 -0
- package/dist/devices/dishwasher.d.ts.map +1 -0
- package/dist/devices/dishwasher.js +130 -0
- package/dist/devices/dishwasher.js.map +1 -0
- package/dist/devices/evse.d.ts +76 -0
- package/dist/devices/evse.d.ts.map +1 -0
- package/dist/devices/evse.js +156 -0
- package/dist/devices/evse.js.map +1 -0
- package/dist/devices/export.d.ts +19 -0
- package/dist/devices/export.d.ts.map +1 -0
- package/dist/devices/export.js +23 -0
- package/dist/devices/export.js.map +1 -0
- package/dist/devices/extractorHood.d.ts +46 -0
- package/dist/devices/extractorHood.d.ts.map +1 -0
- package/dist/devices/extractorHood.js +78 -0
- package/dist/devices/extractorHood.js.map +1 -0
- package/dist/devices/heatPump.d.ts +47 -0
- package/dist/devices/heatPump.d.ts.map +1 -0
- package/dist/devices/heatPump.js +84 -0
- package/dist/devices/heatPump.js.map +1 -0
- package/dist/devices/laundryDryer.d.ts +67 -0
- package/dist/devices/laundryDryer.d.ts.map +1 -0
- package/dist/devices/laundryDryer.js +106 -0
- package/dist/devices/laundryDryer.js.map +1 -0
- package/dist/devices/laundryWasher.d.ts +81 -0
- package/dist/devices/laundryWasher.d.ts.map +1 -0
- package/dist/devices/laundryWasher.js +147 -0
- package/dist/devices/laundryWasher.js.map +1 -0
- package/dist/devices/microwaveOven.d.ts +168 -0
- package/dist/devices/microwaveOven.d.ts.map +1 -0
- package/dist/devices/microwaveOven.js +179 -0
- package/dist/devices/microwaveOven.js.map +1 -0
- package/dist/devices/oven.d.ts +105 -0
- package/dist/devices/oven.d.ts.map +1 -0
- package/dist/devices/oven.js +190 -0
- package/dist/devices/oven.js.map +1 -0
- package/dist/devices/refrigerator.d.ts +118 -0
- package/dist/devices/refrigerator.d.ts.map +1 -0
- package/dist/devices/refrigerator.js +186 -0
- package/dist/devices/refrigerator.js.map +1 -0
- package/dist/devices/roboticVacuumCleaner.d.ts +112 -0
- package/dist/devices/roboticVacuumCleaner.d.ts.map +1 -0
- package/dist/devices/roboticVacuumCleaner.js +268 -0
- package/dist/devices/roboticVacuumCleaner.js.map +1 -0
- package/dist/devices/solarPower.d.ts +40 -0
- package/dist/devices/solarPower.d.ts.map +1 -0
- package/dist/devices/solarPower.js +59 -0
- package/dist/devices/solarPower.js.map +1 -0
- package/dist/devices/speaker.d.ts +87 -0
- package/dist/devices/speaker.d.ts.map +1 -0
- package/dist/devices/speaker.js +120 -0
- package/dist/devices/speaker.js.map +1 -0
- package/dist/devices/temperatureControl.d.ts +166 -0
- package/dist/devices/temperatureControl.d.ts.map +1 -0
- package/dist/devices/temperatureControl.js +78 -0
- package/dist/devices/temperatureControl.js.map +1 -0
- package/dist/devices/waterHeater.d.ts +111 -0
- package/dist/devices/waterHeater.d.ts.map +1 -0
- package/dist/devices/waterHeater.js +166 -0
- package/dist/devices/waterHeater.js.map +1 -0
- package/dist/dgram/export.d.ts +2 -0
- package/dist/dgram/export.d.ts.map +1 -0
- package/dist/dgram/export.js +2 -0
- package/dist/dgram/export.js.map +1 -0
- package/dist/export.d.ts +32 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +39 -0
- package/dist/export.js.map +1 -0
- package/dist/frontend.d.ts +248 -0
- package/dist/frontend.d.ts.map +1 -0
- package/dist/frontend.js +2605 -0
- package/dist/frontend.js.map +1 -0
- package/dist/helpers.d.ts +48 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +161 -0
- package/dist/helpers.js.map +1 -0
- package/dist/jestutils/export.d.ts +2 -0
- package/dist/jestutils/export.d.ts.map +1 -0
- package/dist/jestutils/export.js +2 -0
- package/dist/jestutils/export.js.map +1 -0
- package/dist/jestutils/jestHelpers.d.ts +349 -0
- package/dist/jestutils/jestHelpers.d.ts.map +1 -0
- package/dist/jestutils/jestHelpers.js +980 -0
- package/dist/jestutils/jestHelpers.js.map +1 -0
- package/dist/matter/behaviors.d.ts +2 -0
- package/dist/matter/behaviors.d.ts.map +1 -0
- package/dist/matter/behaviors.js +3 -0
- package/dist/matter/behaviors.js.map +1 -0
- package/dist/matter/clusters.d.ts +2 -0
- package/dist/matter/clusters.d.ts.map +1 -0
- package/dist/matter/clusters.js +3 -0
- package/dist/matter/clusters.js.map +1 -0
- package/dist/matter/devices.d.ts +2 -0
- package/dist/matter/devices.d.ts.map +1 -0
- package/dist/matter/devices.js +3 -0
- package/dist/matter/devices.js.map +1 -0
- package/dist/matter/endpoints.d.ts +2 -0
- package/dist/matter/endpoints.d.ts.map +1 -0
- package/dist/matter/endpoints.js +3 -0
- package/dist/matter/endpoints.js.map +1 -0
- package/dist/matter/export.d.ts +4 -0
- package/dist/matter/export.d.ts.map +1 -0
- package/dist/matter/export.js +5 -0
- package/dist/matter/export.js.map +1 -0
- package/dist/matter/types.d.ts +2 -0
- package/dist/matter/types.d.ts.map +1 -0
- package/dist/matter/types.js +3 -0
- package/dist/matter/types.js.map +1 -0
- package/dist/matterNode.d.ts +341 -0
- package/dist/matterNode.d.ts.map +1 -0
- package/dist/matterNode.js +1329 -0
- package/dist/matterNode.js.map +1 -0
- package/dist/matterbridge.d.ts +544 -0
- package/dist/matterbridge.d.ts.map +1 -0
- package/dist/matterbridge.js +2880 -0
- package/dist/matterbridge.js.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts +49 -0
- package/dist/matterbridgeAccessoryPlatform.d.ts.map +1 -0
- package/dist/matterbridgeAccessoryPlatform.js +80 -0
- package/dist/matterbridgeAccessoryPlatform.js.map +1 -0
- package/dist/matterbridgeBehaviors.d.ts +2428 -0
- package/dist/matterbridgeBehaviors.d.ts.map +1 -0
- package/dist/matterbridgeBehaviors.js +620 -0
- package/dist/matterbridgeBehaviors.js.map +1 -0
- package/dist/matterbridgeDeviceTypes.d.ts +744 -0
- package/dist/matterbridgeDeviceTypes.d.ts.map +1 -0
- package/dist/matterbridgeDeviceTypes.js +1312 -0
- package/dist/matterbridgeDeviceTypes.js.map +1 -0
- package/dist/matterbridgeDynamicPlatform.d.ts +49 -0
- package/dist/matterbridgeDynamicPlatform.d.ts.map +1 -0
- package/dist/matterbridgeDynamicPlatform.js +80 -0
- package/dist/matterbridgeDynamicPlatform.js.map +1 -0
- package/dist/matterbridgeEndpoint.d.ts +1548 -0
- package/dist/matterbridgeEndpoint.d.ts.map +1 -0
- package/dist/matterbridgeEndpoint.js +2883 -0
- package/dist/matterbridgeEndpoint.js.map +1 -0
- package/dist/matterbridgeEndpointHelpers.d.ts +1855 -0
- package/dist/matterbridgeEndpointHelpers.d.ts.map +1 -0
- package/dist/matterbridgeEndpointHelpers.js +1270 -0
- package/dist/matterbridgeEndpointHelpers.js.map +1 -0
- package/dist/matterbridgeEndpointTypes.d.ts +172 -0
- package/dist/matterbridgeEndpointTypes.d.ts.map +1 -0
- package/dist/matterbridgeEndpointTypes.js +28 -0
- package/dist/matterbridgeEndpointTypes.js.map +1 -0
- package/dist/matterbridgePlatform.d.ts +520 -0
- package/dist/matterbridgePlatform.d.ts.map +1 -0
- package/dist/matterbridgePlatform.js +921 -0
- package/dist/matterbridgePlatform.js.map +1 -0
- package/dist/mb_coap.d.ts +24 -0
- package/dist/mb_coap.d.ts.map +1 -0
- package/dist/mb_coap.js +89 -0
- package/dist/mb_coap.js.map +1 -0
- package/dist/mb_health.d.ts +77 -0
- package/dist/mb_health.d.ts.map +1 -0
- package/dist/mb_health.js +147 -0
- package/dist/mb_health.js.map +1 -0
- package/dist/mb_mdns.d.ts +24 -0
- package/dist/mb_mdns.d.ts.map +1 -0
- package/dist/mb_mdns.js +285 -0
- package/dist/mb_mdns.js.map +1 -0
- package/dist/pluginManager.d.ts +388 -0
- package/dist/pluginManager.d.ts.map +1 -0
- package/dist/pluginManager.js +1574 -0
- package/dist/pluginManager.js.map +1 -0
- package/dist/spawn.d.ts +33 -0
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +165 -0
- package/dist/spawn.js.map +1 -0
- package/dist/utils/export.d.ts +2 -0
- package/dist/utils/export.d.ts.map +1 -0
- package/dist/utils/export.js +2 -0
- package/dist/utils/export.js.map +1 -0
- package/dist/workers/brand.d.ts +25 -0
- package/dist/workers/brand.d.ts.map +1 -0
- package/dist/workers/brand.extend.d.ts +10 -0
- package/dist/workers/brand.extend.d.ts.map +1 -0
- package/dist/workers/brand.extend.js +15 -0
- package/dist/workers/brand.extend.js.map +1 -0
- package/dist/workers/brand.invalid.d.ts +9 -0
- package/dist/workers/brand.invalid.d.ts.map +1 -0
- package/dist/workers/brand.invalid.js +19 -0
- package/dist/workers/brand.invalid.js.map +1 -0
- package/dist/workers/brand.js +67 -0
- package/dist/workers/brand.js.map +1 -0
- package/dist/workers/clusterTypes.d.ts +47 -0
- package/dist/workers/clusterTypes.d.ts.map +1 -0
- package/dist/workers/clusterTypes.js +57 -0
- package/dist/workers/clusterTypes.js.map +1 -0
- package/dist/workers/frontendWorker.d.ts +2 -0
- package/dist/workers/frontendWorker.d.ts.map +1 -0
- package/dist/workers/frontendWorker.js +90 -0
- package/dist/workers/frontendWorker.js.map +1 -0
- package/dist/workers/helloWorld.d.ts +2 -0
- package/dist/workers/helloWorld.d.ts.map +1 -0
- package/dist/workers/helloWorld.js +135 -0
- package/dist/workers/helloWorld.js.map +1 -0
- package/dist/workers/matterWorker.d.ts +2 -0
- package/dist/workers/matterWorker.d.ts.map +1 -0
- package/dist/workers/matterWorker.js +104 -0
- package/dist/workers/matterWorker.js.map +1 -0
- package/dist/workers/matterbridgeWorker.d.ts +2 -0
- package/dist/workers/matterbridgeWorker.d.ts.map +1 -0
- package/dist/workers/matterbridgeWorker.js +75 -0
- package/dist/workers/matterbridgeWorker.js.map +1 -0
- package/dist/workers/messageLab.d.ts +134 -0
- package/dist/workers/messageLab.d.ts.map +1 -0
- package/dist/workers/messageLab.js +129 -0
- package/dist/workers/messageLab.js.map +1 -0
- package/dist/workers/testWorker.d.ts +2 -0
- package/dist/workers/testWorker.d.ts.map +1 -0
- package/dist/workers/testWorker.js +45 -0
- package/dist/workers/testWorker.js.map +1 -0
- package/dist/workers/usage.d.ts +19 -0
- package/dist/workers/usage.d.ts.map +1 -0
- package/dist/workers/usage.js +140 -0
- package/dist/workers/usage.js.map +1 -0
- package/dist/workers/workerManager.d.ts +115 -0
- package/dist/workers/workerManager.d.ts.map +1 -0
- package/dist/workers/workerManager.js +464 -0
- package/dist/workers/workerManager.js.map +1 -0
- package/dist/workers/workerServer.d.ts +126 -0
- package/dist/workers/workerServer.d.ts.map +1 -0
- package/dist/workers/workerServer.js +340 -0
- package/dist/workers/workerServer.js.map +1 -0
- package/dist/workers/workerTypes.d.ts +23 -0
- package/dist/workers/workerTypes.d.ts.map +1 -0
- package/dist/workers/workerTypes.js +3 -0
- package/dist/workers/workerTypes.js.map +1 -0
- package/package.json +120 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains the CLI history page generator.
|
|
3
|
+
*
|
|
4
|
+
* @file cliHistory.ts
|
|
5
|
+
* @author Luca Liguori
|
|
6
|
+
* @created 2025-10-09
|
|
7
|
+
* @version 1.0.1
|
|
8
|
+
* @license Apache-2.0
|
|
9
|
+
*
|
|
10
|
+
* Copyright 2025, 2026, 2027 Luca Liguori.
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
* you may not use this file except in compliance with the License.
|
|
14
|
+
* You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
if (process.argv.includes('--loader') || process.argv.includes('-loader'))
|
|
26
|
+
console.log('\u001B[32mCli history loaded.\u001B[40;0m');
|
|
27
|
+
import { writeFileSync } from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import os from 'node:os';
|
|
30
|
+
import { Tracker } from '@matterbridge/utils';
|
|
31
|
+
/**
|
|
32
|
+
* Generates a static HTML dashboard displaying CPU and memory history.
|
|
33
|
+
*
|
|
34
|
+
* @param {GenerateHistoryPageOptions} [options] - Optional configuration for output path, page title, and refresh interval.
|
|
35
|
+
*
|
|
36
|
+
* @returns {string | undefined} The absolute path to the generated HTML file, or undefined if no samples exist.
|
|
37
|
+
*/
|
|
38
|
+
export function generateHistoryPage(options = {}) {
|
|
39
|
+
const pageTitle = options.pageTitle ?? 'Matterbridge CPU & Memory History';
|
|
40
|
+
const hostname = options.hostname ?? os.hostname();
|
|
41
|
+
const outputPath = path.resolve(options.outputPath ?? path.join(process.cwd(), 'history.html'));
|
|
42
|
+
const normalizedHistory = [];
|
|
43
|
+
for (let offset = 0; offset < Tracker.historySize; offset += 1) {
|
|
44
|
+
const index = (Tracker.historyIndex + offset) % Tracker.historySize;
|
|
45
|
+
const entry = Tracker.history[index];
|
|
46
|
+
if (!entry || entry.timestamp === 0)
|
|
47
|
+
continue;
|
|
48
|
+
normalizedHistory.push(entry);
|
|
49
|
+
}
|
|
50
|
+
if (normalizedHistory.length === 0) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
const peakOsCpu = Math.max(...normalizedHistory.map((entry) => entry.peakOsCpu ?? entry.osCpu));
|
|
54
|
+
const peakProcessCpu = Math.max(...normalizedHistory.map((entry) => entry.peakProcessCpu ?? entry.processCpu));
|
|
55
|
+
const peakRss = Math.max(...normalizedHistory.map((entry) => entry.peakRss ?? entry.rss));
|
|
56
|
+
const peakHeapUsed = Math.max(...normalizedHistory.map((entry) => entry.peakHeapUsed ?? entry.heapUsed));
|
|
57
|
+
const peakHeapTotal = Math.max(...normalizedHistory.map((entry) => entry.peakHeapTotal ?? entry.heapTotal));
|
|
58
|
+
const peakExternal = Math.max(...normalizedHistory.map((entry) => entry.peakExternal ?? entry.external));
|
|
59
|
+
const peakArrayBuffers = Math.max(...normalizedHistory.map((entry) => entry.peakArrayBuffers ?? entry.arrayBuffers));
|
|
60
|
+
const firstTimestamp = normalizedHistory[0]?.timestamp ?? Date.now();
|
|
61
|
+
const lastTimestamp = normalizedHistory[normalizedHistory.length - 1]?.timestamp ?? Date.now();
|
|
62
|
+
const summary = {
|
|
63
|
+
entries: normalizedHistory.length,
|
|
64
|
+
timeRange: `${new Date(firstTimestamp).toLocaleString()} → ${new Date(lastTimestamp).toLocaleString()}`,
|
|
65
|
+
peakOsCpu,
|
|
66
|
+
peakProcessCpu,
|
|
67
|
+
peakRss,
|
|
68
|
+
peakHeapUsed,
|
|
69
|
+
peakHeapTotal,
|
|
70
|
+
peakExternal,
|
|
71
|
+
peakArrayBuffers,
|
|
72
|
+
};
|
|
73
|
+
const html = `<!DOCTYPE html>
|
|
74
|
+
<html lang="en" translate="no">
|
|
75
|
+
<head>
|
|
76
|
+
<meta charset="UTF-8" />
|
|
77
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
78
|
+
<meta name="google" content="notranslate" />
|
|
79
|
+
<meta http-equiv="Content-Language" content="en" />
|
|
80
|
+
<title>${escapeHtml(pageTitle)}</title>
|
|
81
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
82
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
83
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
84
|
+
<style>
|
|
85
|
+
:root {
|
|
86
|
+
color-scheme: dark light;
|
|
87
|
+
--bg: #0f172a;
|
|
88
|
+
--bg-card: rgba(15, 23, 42, 0.72);
|
|
89
|
+
--fg: #e2e8f0;
|
|
90
|
+
--accent: #38bdf8;
|
|
91
|
+
--muted: #94a3b8;
|
|
92
|
+
--border: rgba(148, 163, 184, 0.2);
|
|
93
|
+
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
94
|
+
}
|
|
95
|
+
body {
|
|
96
|
+
margin: 0;
|
|
97
|
+
padding: 20px;
|
|
98
|
+
background: linear-gradient(145deg, #020617, #0f172a);
|
|
99
|
+
color: var(--fg);
|
|
100
|
+
min-width: 320px;
|
|
101
|
+
min-height: 100vh;
|
|
102
|
+
}
|
|
103
|
+
h1 {
|
|
104
|
+
margin-top: 0;
|
|
105
|
+
font-size: clamp(1.4rem, 1.8vw, 2.0rem);
|
|
106
|
+
font-weight: 700;
|
|
107
|
+
color: var(--accent);
|
|
108
|
+
}
|
|
109
|
+
.container {
|
|
110
|
+
min-width: 320px;
|
|
111
|
+
max-width: 1240px;
|
|
112
|
+
margin: 0 auto;
|
|
113
|
+
padding: 0;
|
|
114
|
+
display: grid;
|
|
115
|
+
gap: 20px;
|
|
116
|
+
}
|
|
117
|
+
.card {
|
|
118
|
+
background: var(--bg-card);
|
|
119
|
+
border: 1px solid var(--border);
|
|
120
|
+
border-radius: 16px;
|
|
121
|
+
padding: 20px;
|
|
122
|
+
box-shadow: 0 20px 60px rgba(15, 23, 42, 0.35);
|
|
123
|
+
backdrop-filter: blur(12px);
|
|
124
|
+
margin: 0;
|
|
125
|
+
width: calc(100% - 40px);
|
|
126
|
+
max-width: 1200px;
|
|
127
|
+
position: relative;
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
}
|
|
130
|
+
.summary-grid {
|
|
131
|
+
display: grid;
|
|
132
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
133
|
+
gap: 16px;
|
|
134
|
+
}
|
|
135
|
+
.summary-item {
|
|
136
|
+
border: 1px solid var(--border);
|
|
137
|
+
border-radius: 12px;
|
|
138
|
+
padding: 16px;
|
|
139
|
+
background: rgba(30, 41, 59, 0.85);
|
|
140
|
+
}
|
|
141
|
+
.summary-item h2 {
|
|
142
|
+
margin: 0 0 8px;
|
|
143
|
+
font-size: 0.95rem;
|
|
144
|
+
text-transform: uppercase;
|
|
145
|
+
letter-spacing: 0.08em;
|
|
146
|
+
color: var(--muted);
|
|
147
|
+
}
|
|
148
|
+
.summary-item p {
|
|
149
|
+
margin: 0;
|
|
150
|
+
font-size: 1.25rem;
|
|
151
|
+
font-weight: 600;
|
|
152
|
+
}
|
|
153
|
+
canvas {
|
|
154
|
+
width: min(100%, 1200px);
|
|
155
|
+
height: 320px;
|
|
156
|
+
display: block;
|
|
157
|
+
margin: 0 auto;
|
|
158
|
+
}
|
|
159
|
+
.chart-legend {
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-wrap: wrap;
|
|
162
|
+
gap: 12px;
|
|
163
|
+
margin-top: 16px;
|
|
164
|
+
font-size: 0.85rem;
|
|
165
|
+
color: var(--muted);
|
|
166
|
+
}
|
|
167
|
+
.chart-legend span {
|
|
168
|
+
display: inline-flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 8px;
|
|
171
|
+
}
|
|
172
|
+
.chart-legend span::before {
|
|
173
|
+
content: '';
|
|
174
|
+
width: 12px;
|
|
175
|
+
height: 12px;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: currentColor;
|
|
178
|
+
opacity: 0.85;
|
|
179
|
+
border: 1px solid rgba(255, 255, 255, 0.4);
|
|
180
|
+
}
|
|
181
|
+
table {
|
|
182
|
+
width: 100%;
|
|
183
|
+
border-collapse: collapse;
|
|
184
|
+
font-size: 0.95rem;
|
|
185
|
+
}
|
|
186
|
+
th, td {
|
|
187
|
+
padding: 8px;
|
|
188
|
+
border-bottom: 1px solid var(--border);
|
|
189
|
+
text-align: left;
|
|
190
|
+
white-space: nowrap;
|
|
191
|
+
}
|
|
192
|
+
th {
|
|
193
|
+
text-transform: uppercase;
|
|
194
|
+
font-size: 0.75rem;
|
|
195
|
+
letter-spacing: 0.08em;
|
|
196
|
+
color: var(--muted);
|
|
197
|
+
}
|
|
198
|
+
td {
|
|
199
|
+
font-size: 0.75rem;
|
|
200
|
+
}
|
|
201
|
+
tr:hover {
|
|
202
|
+
background: rgba(148, 163, 184, 0.08);
|
|
203
|
+
}
|
|
204
|
+
.table-wrapper {
|
|
205
|
+
overflow: auto;
|
|
206
|
+
max-height: 400px;
|
|
207
|
+
scrollbar-color: rgba(148, 163, 184, 0.35) var(--bg-card);
|
|
208
|
+
scrollbar-width: thin;
|
|
209
|
+
}
|
|
210
|
+
.table-wrapper::-webkit-scrollbar {
|
|
211
|
+
width: 10px;
|
|
212
|
+
}
|
|
213
|
+
.table-wrapper::-webkit-scrollbar-track {
|
|
214
|
+
background: var(--bg-card);
|
|
215
|
+
}
|
|
216
|
+
.table-wrapper::-webkit-scrollbar-thumb {
|
|
217
|
+
background-color: rgba(148, 163, 184, 0.45);
|
|
218
|
+
border-radius: 999px;
|
|
219
|
+
border: 2px solid var(--bg-card);
|
|
220
|
+
}
|
|
221
|
+
@media (max-width: 720px) {
|
|
222
|
+
body {
|
|
223
|
+
padding: 16px;
|
|
224
|
+
}
|
|
225
|
+
.card {
|
|
226
|
+
padding: 18px;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
</style>
|
|
230
|
+
</head>
|
|
231
|
+
<body>
|
|
232
|
+
<div class="container">
|
|
233
|
+
<header>
|
|
234
|
+
<h1>${escapeHtml(pageTitle)}</h1>
|
|
235
|
+
<p>Hostname: ${escapeHtml(hostname)}</p>
|
|
236
|
+
<p>Generated: ${new Date().toLocaleString()}</p>
|
|
237
|
+
</header>
|
|
238
|
+
|
|
239
|
+
<section class="card">
|
|
240
|
+
<div class="summary-grid" id="summary"></div>
|
|
241
|
+
</section>
|
|
242
|
+
|
|
243
|
+
<section class="card">
|
|
244
|
+
<h2>Host CPU Usage (%)</h2>
|
|
245
|
+
<canvas id="cpuChart"></canvas>
|
|
246
|
+
</section>
|
|
247
|
+
|
|
248
|
+
<section class="card">
|
|
249
|
+
<h2>Process CPU Usage (%)</h2>
|
|
250
|
+
<canvas id="processCpuChart"></canvas>
|
|
251
|
+
</section>
|
|
252
|
+
|
|
253
|
+
<section class="card">
|
|
254
|
+
<h2>Memory Usage (MB)</h2>
|
|
255
|
+
<canvas id="memoryChart"></canvas>
|
|
256
|
+
</section>
|
|
257
|
+
|
|
258
|
+
<section class="card">
|
|
259
|
+
<h2>External and Array Buffers (MB)</h2>
|
|
260
|
+
<canvas id="extArrayChart"></canvas>
|
|
261
|
+
</section>
|
|
262
|
+
|
|
263
|
+
<section class="card">
|
|
264
|
+
<h2>Samples</h2>
|
|
265
|
+
<div class="table-wrapper">
|
|
266
|
+
<table>
|
|
267
|
+
<thead>
|
|
268
|
+
<tr>
|
|
269
|
+
<th>Timestamp</th>
|
|
270
|
+
<th>Host CPU %</th>
|
|
271
|
+
<th title="Host CPU Peak">Peak %</th>
|
|
272
|
+
<th>Process CPU %</th>
|
|
273
|
+
<th title="Process CPU Peak">Peak %</th>
|
|
274
|
+
<th>RSS (MB)</th>
|
|
275
|
+
<th title="RSS Peak">Peak MB</th>
|
|
276
|
+
<th>Heap Used (MB)</th>
|
|
277
|
+
<th title="Heap Used Peak">Peak MB</th>
|
|
278
|
+
<th>Heap Total (MB)</th>
|
|
279
|
+
<th title="Heap Total Peak">Peak MB</th>
|
|
280
|
+
</tr>
|
|
281
|
+
</thead>
|
|
282
|
+
<tbody id="historyTable"></tbody>
|
|
283
|
+
</table>
|
|
284
|
+
</div>
|
|
285
|
+
</section>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<script type="module">
|
|
289
|
+
const HISTORY_DATA = ${JSON.stringify(normalizedHistory)};
|
|
290
|
+
const SUMMARY_DATA = ${JSON.stringify(summary)};
|
|
291
|
+
let cleanup = () => {};
|
|
292
|
+
|
|
293
|
+
const summaryContainer = document.getElementById('summary');
|
|
294
|
+
const summaryEntries = [
|
|
295
|
+
{ label: 'Samples', value: SUMMARY_DATA.entries.toLocaleString() },
|
|
296
|
+
{ label: 'Time Range', value: SUMMARY_DATA.timeRange },
|
|
297
|
+
{ label: 'Host CPU Peak', value: SUMMARY_DATA.peakOsCpu.toFixed(2) + ' %' },
|
|
298
|
+
{ label: 'Process CPU Peak', value: SUMMARY_DATA.peakProcessCpu.toFixed(2) + ' %' },
|
|
299
|
+
{ label: 'RSS Peak', value: formatBytes(SUMMARY_DATA.peakRss) },
|
|
300
|
+
{ label: 'Heap Used Peak', value: formatBytes(SUMMARY_DATA.peakHeapUsed) },
|
|
301
|
+
{ label: 'Heap Total Peak', value: formatBytes(SUMMARY_DATA.peakHeapTotal) },
|
|
302
|
+
{ label: 'External Peak', value: formatBytes(SUMMARY_DATA.peakExternal) },
|
|
303
|
+
{ label: 'Array Buffers Peak', value: formatBytes(SUMMARY_DATA.peakArrayBuffers) }
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
summaryEntries.forEach(function (itemData) {
|
|
307
|
+
const item = document.createElement('div');
|
|
308
|
+
item.className = 'summary-item';
|
|
309
|
+
item.innerHTML = '<h2>' + itemData.label + '</h2><p>' + itemData.value + '</p>';
|
|
310
|
+
summaryContainer.appendChild(item);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const tableBody = document.getElementById('historyTable');
|
|
314
|
+
HISTORY_DATA.forEach(function (entry) {
|
|
315
|
+
const row = document.createElement('tr');
|
|
316
|
+
const cells = [
|
|
317
|
+
new Date(entry.timestamp).toLocaleString(),
|
|
318
|
+
entry.osCpu.toFixed(2),
|
|
319
|
+
entry.peakOsCpu.toFixed(2),
|
|
320
|
+
(Number.isFinite(entry.processCpu) ? entry.processCpu : 0).toFixed(2),
|
|
321
|
+
(
|
|
322
|
+
Number.isFinite(entry.peakProcessCpu)
|
|
323
|
+
? entry.peakProcessCpu
|
|
324
|
+
: Number.isFinite(entry.processCpu)
|
|
325
|
+
? entry.processCpu
|
|
326
|
+
: 0
|
|
327
|
+
).toFixed(2),
|
|
328
|
+
bytesToMb(entry.rss).toFixed(2),
|
|
329
|
+
bytesToMb(entry.peakRss).toFixed(2),
|
|
330
|
+
bytesToMb(entry.heapUsed).toFixed(2),
|
|
331
|
+
bytesToMb(entry.peakHeapUsed).toFixed(2),
|
|
332
|
+
bytesToMb(entry.heapTotal).toFixed(2),
|
|
333
|
+
bytesToMb(entry.peakHeapTotal).toFixed(2)
|
|
334
|
+
];
|
|
335
|
+
row.innerHTML = '<td>' + cells.join('</td><td>') + '</td>';
|
|
336
|
+
tableBody.appendChild(row);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const labels = HISTORY_DATA.map(function (entry) {
|
|
340
|
+
return formatTimestamp(entry.timestamp);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const cpuPeakValue = HISTORY_DATA.reduce(function (acc, entry) {
|
|
344
|
+
return Math.max(acc, Number.isFinite(entry.peakOsCpu) ? entry.peakOsCpu : 0, Number.isFinite(entry.osCpu) ? entry.osCpu : 0);
|
|
345
|
+
}, 0);
|
|
346
|
+
const cpuMaxYAxis = cpuPeakValue > 0 ? cpuPeakValue * 1.05 : undefined;
|
|
347
|
+
|
|
348
|
+
const processCpuPeakValue = HISTORY_DATA.reduce(function (acc, entry) {
|
|
349
|
+
return Math.max(acc, Number.isFinite(entry.peakProcessCpu) ? entry.peakProcessCpu : 0, Number.isFinite(entry.processCpu) ? entry.processCpu : 0);
|
|
350
|
+
}, 0);
|
|
351
|
+
const processCpuMaxYAxis = processCpuPeakValue > 0 ? processCpuPeakValue * 1.05 : undefined;
|
|
352
|
+
const useProcessCpuDecimals = (processCpuMaxYAxis ?? 0) <= 3;
|
|
353
|
+
|
|
354
|
+
// Compute memory chart dynamic minimum Y as (min observed MB) - 10%
|
|
355
|
+
const memoryMinMb = HISTORY_DATA.reduce(function (acc, entry) {
|
|
356
|
+
const values = [entry.rss, entry.heapTotal, entry.heapUsed].map(bytesToMb);
|
|
357
|
+
const finiteValues = values.filter(function (v) { return Number.isFinite(v); });
|
|
358
|
+
const minEntry = finiteValues.length ? Math.min.apply(Math, finiteValues) : acc;
|
|
359
|
+
return Math.min(acc, minEntry);
|
|
360
|
+
}, Number.POSITIVE_INFINITY);
|
|
361
|
+
const memoryMinYAxis = Number.isFinite(memoryMinMb) && memoryMinMb > 0 ? Math.max(0, memoryMinMb - memoryMinMb * 0.1) : 0;
|
|
362
|
+
|
|
363
|
+
// Compute memory chart dynamic maximum Y as (max observed MB including peaks) + 5%
|
|
364
|
+
const memoryMaxMb = HISTORY_DATA.reduce(function (acc, entry) {
|
|
365
|
+
const values = [
|
|
366
|
+
entry.rss,
|
|
367
|
+
entry.heapTotal,
|
|
368
|
+
entry.heapUsed,
|
|
369
|
+
entry.peakRss,
|
|
370
|
+
entry.peakHeapTotal,
|
|
371
|
+
entry.peakHeapUsed
|
|
372
|
+
].map(bytesToMb);
|
|
373
|
+
const finiteValues = values.filter(function (v) { return Number.isFinite(v); });
|
|
374
|
+
const maxEntry = finiteValues.length ? Math.max.apply(Math, finiteValues) : acc;
|
|
375
|
+
return Math.max(acc, maxEntry);
|
|
376
|
+
}, 0);
|
|
377
|
+
const memoryMaxYAxis = Number.isFinite(memoryMaxMb) && memoryMaxMb > 0 ? memoryMaxMb * 1.05 : undefined;
|
|
378
|
+
|
|
379
|
+
// Compute External/ArrayBuffers chart dynamic min/max
|
|
380
|
+
const extArrayMinMb = HISTORY_DATA.reduce(function (acc, entry) {
|
|
381
|
+
const values = [entry.external, entry.arrayBuffers].map(bytesToMb);
|
|
382
|
+
const finiteValues = values.filter(function (v) { return Number.isFinite(v); });
|
|
383
|
+
const minEntry = finiteValues.length ? Math.min.apply(Math, finiteValues) : acc;
|
|
384
|
+
return Math.min(acc, minEntry);
|
|
385
|
+
}, Number.POSITIVE_INFINITY);
|
|
386
|
+
|
|
387
|
+
const extArrayMinYAxis = Number.isFinite(extArrayMinMb) && extArrayMinMb > 0
|
|
388
|
+
? Math.max(0, extArrayMinMb - extArrayMinMb * 0.1)
|
|
389
|
+
: 0;
|
|
390
|
+
|
|
391
|
+
const extArrayMaxMb = HISTORY_DATA.reduce(function (acc, entry) {
|
|
392
|
+
const values = [
|
|
393
|
+
entry.external,
|
|
394
|
+
entry.arrayBuffers,
|
|
395
|
+
entry.peakExternal,
|
|
396
|
+
entry.peakArrayBuffers,
|
|
397
|
+
].map(bytesToMb);
|
|
398
|
+
const finiteValues = values.filter(function (v) { return Number.isFinite(v); });
|
|
399
|
+
const maxEntry = finiteValues.length ? Math.max.apply(Math, finiteValues) : acc;
|
|
400
|
+
return Math.max(acc, maxEntry);
|
|
401
|
+
}, 0);
|
|
402
|
+
const extArrayMaxYAxis = Number.isFinite(extArrayMaxMb) && extArrayMaxMb > 0 ? extArrayMaxMb * 1.05 : undefined;
|
|
403
|
+
|
|
404
|
+
renderCharts();
|
|
405
|
+
|
|
406
|
+
function renderCharts() {
|
|
407
|
+
cleanup();
|
|
408
|
+
|
|
409
|
+
function draw() {
|
|
410
|
+
renderLineChart('cpuChart', {
|
|
411
|
+
labels: labels,
|
|
412
|
+
datasets: [
|
|
413
|
+
{
|
|
414
|
+
label: 'Host CPU %',
|
|
415
|
+
values: HISTORY_DATA.map(function (entry) {
|
|
416
|
+
return Number.isFinite(entry.osCpu) ? Number(entry.osCpu.toFixed(2)) : 0;
|
|
417
|
+
}),
|
|
418
|
+
color: '#38bdf8',
|
|
419
|
+
fill: 'rgba(56, 189, 248, 0.18)',
|
|
420
|
+
markPeaks: true,
|
|
421
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
422
|
+
if (Number.isFinite(entry.peakOsCpu)) return entry.peakOsCpu;
|
|
423
|
+
if (Number.isFinite(entry.osCpu)) return entry.osCpu;
|
|
424
|
+
return Number.NEGATIVE_INFINITY;
|
|
425
|
+
}),
|
|
426
|
+
markerRadius: 2.5
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
label: 'Host Peak CPU %',
|
|
430
|
+
values: HISTORY_DATA.map(function (entry) {
|
|
431
|
+
return Number.isFinite(entry.peakOsCpu) ? Number(entry.peakOsCpu.toFixed(2)) : 0;
|
|
432
|
+
}),
|
|
433
|
+
color: '#facc15',
|
|
434
|
+
dashed: [6, 4]
|
|
435
|
+
}
|
|
436
|
+
],
|
|
437
|
+
minY: 0,
|
|
438
|
+
maxY: cpuMaxYAxis,
|
|
439
|
+
yFormatter: function (value) {
|
|
440
|
+
return value.toFixed(0) + ' %';
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
renderLineChart('processCpuChart', {
|
|
445
|
+
labels: labels,
|
|
446
|
+
datasets: [
|
|
447
|
+
{
|
|
448
|
+
label: 'Process CPU %',
|
|
449
|
+
values: HISTORY_DATA.map(function (entry) {
|
|
450
|
+
return Number.isFinite(entry.processCpu) ? Number(entry.processCpu.toFixed(2)) : 0;
|
|
451
|
+
}),
|
|
452
|
+
color: '#a855f7',
|
|
453
|
+
fill: 'rgba(168, 85, 247, 0.18)',
|
|
454
|
+
markPeaks: true,
|
|
455
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
456
|
+
if (Number.isFinite(entry.peakProcessCpu)) return entry.peakProcessCpu;
|
|
457
|
+
if (Number.isFinite(entry.processCpu)) return entry.processCpu;
|
|
458
|
+
return Number.NEGATIVE_INFINITY;
|
|
459
|
+
}),
|
|
460
|
+
markerRadius: 2.5
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
label: 'Process Peak CPU %',
|
|
464
|
+
values: HISTORY_DATA.map(function (entry) {
|
|
465
|
+
if (Number.isFinite(entry.peakProcessCpu)) {
|
|
466
|
+
return Number(entry.peakProcessCpu.toFixed(2));
|
|
467
|
+
}
|
|
468
|
+
if (Number.isFinite(entry.processCpu)) {
|
|
469
|
+
return Number(entry.processCpu.toFixed(2));
|
|
470
|
+
}
|
|
471
|
+
return 0;
|
|
472
|
+
}),
|
|
473
|
+
color: '#f97316',
|
|
474
|
+
dashed: [6, 4]
|
|
475
|
+
}
|
|
476
|
+
],
|
|
477
|
+
minY: 0,
|
|
478
|
+
maxY: processCpuMaxYAxis,
|
|
479
|
+
yFormatter: function (value) {
|
|
480
|
+
return value.toFixed(useProcessCpuDecimals ? 1 : 0) + ' %';
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
renderLineChart('memoryChart', {
|
|
485
|
+
labels: labels,
|
|
486
|
+
datasets: [
|
|
487
|
+
{
|
|
488
|
+
label: 'RSS (MB)',
|
|
489
|
+
values: HISTORY_DATA.map(function (entry) { return bytesToMb(entry.rss); }),
|
|
490
|
+
color: '#34d399',
|
|
491
|
+
fill: 'rgba(52, 211, 153, 0.18)',
|
|
492
|
+
markPeaks: true,
|
|
493
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
494
|
+
if (Number.isFinite(entry.peakRss)) return entry.peakRss;
|
|
495
|
+
if (Number.isFinite(entry.rss)) return entry.rss;
|
|
496
|
+
return Number.NEGATIVE_INFINITY;
|
|
497
|
+
}),
|
|
498
|
+
markerRadius: 2.5
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
label: 'Heap Total (MB)',
|
|
502
|
+
values: HISTORY_DATA.map(function (entry) { return bytesToMb(entry.heapTotal); }),
|
|
503
|
+
color: '#fb923c',
|
|
504
|
+
markPeaks: true,
|
|
505
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
506
|
+
if (Number.isFinite(entry.peakHeapTotal)) return entry.peakHeapTotal;
|
|
507
|
+
if (Number.isFinite(entry.heapTotal)) return entry.heapTotal;
|
|
508
|
+
return Number.NEGATIVE_INFINITY;
|
|
509
|
+
}),
|
|
510
|
+
markerRadius: 2.5
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
label: 'Heap Used (MB)',
|
|
514
|
+
values: HISTORY_DATA.map(function (entry) { return bytesToMb(entry.heapUsed); }),
|
|
515
|
+
color: '#f472b6',
|
|
516
|
+
markPeaks: true,
|
|
517
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
518
|
+
if (Number.isFinite(entry.peakHeapUsed)) return entry.peakHeapUsed;
|
|
519
|
+
if (Number.isFinite(entry.heapUsed)) return entry.heapUsed;
|
|
520
|
+
return Number.NEGATIVE_INFINITY;
|
|
521
|
+
}),
|
|
522
|
+
markerRadius: 2.5
|
|
523
|
+
}
|
|
524
|
+
],
|
|
525
|
+
minY: memoryMinYAxis,
|
|
526
|
+
maxY: memoryMaxYAxis,
|
|
527
|
+
yFormatter: function (value) {
|
|
528
|
+
return value.toFixed(0) + ' MB';
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// External and Array Buffers chart
|
|
533
|
+
renderLineChart('extArrayChart', {
|
|
534
|
+
labels: labels,
|
|
535
|
+
datasets: [
|
|
536
|
+
{
|
|
537
|
+
label: 'External (MB)',
|
|
538
|
+
values: HISTORY_DATA.map(function (entry) { return bytesToMb(entry.external); }),
|
|
539
|
+
color: '#60a5fa',
|
|
540
|
+
fill: 'rgba(96, 165, 250, 0.18)',
|
|
541
|
+
markPeaks: true,
|
|
542
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
543
|
+
if (Number.isFinite(entry.peakExternal)) return entry.peakExternal;
|
|
544
|
+
if (Number.isFinite(entry.external)) return entry.external;
|
|
545
|
+
return Number.NEGATIVE_INFINITY;
|
|
546
|
+
}),
|
|
547
|
+
markerRadius: 2.5
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
label: 'Array Buffers (MB)',
|
|
551
|
+
values: HISTORY_DATA.map(function (entry) { return bytesToMb(entry.arrayBuffers); }),
|
|
552
|
+
color: '#22d3ee',
|
|
553
|
+
markPeaks: true,
|
|
554
|
+
markerPeakValues: HISTORY_DATA.map(function (entry) {
|
|
555
|
+
if (Number.isFinite(entry.peakArrayBuffers)) return entry.peakArrayBuffers;
|
|
556
|
+
if (Number.isFinite(entry.arrayBuffers)) return entry.arrayBuffers;
|
|
557
|
+
return Number.NEGATIVE_INFINITY;
|
|
558
|
+
}),
|
|
559
|
+
markerRadius: 2.5
|
|
560
|
+
}
|
|
561
|
+
],
|
|
562
|
+
minY: extArrayMinYAxis,
|
|
563
|
+
maxY: extArrayMaxYAxis,
|
|
564
|
+
yFormatter: function (value) {
|
|
565
|
+
return value.toFixed(0) + ' MB';
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
draw();
|
|
571
|
+
|
|
572
|
+
const debouncedRender = debounce(draw, 150);
|
|
573
|
+
window.addEventListener('resize', debouncedRender);
|
|
574
|
+
|
|
575
|
+
cleanup = function () {
|
|
576
|
+
window.removeEventListener('resize', debouncedRender);
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function renderLineChart(canvasId, config) {
|
|
581
|
+
const canvas = document.getElementById(canvasId);
|
|
582
|
+
if (!canvas) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const parent = canvas.parentElement;
|
|
587
|
+
if (parent) {
|
|
588
|
+
const existingLegend = parent.querySelector('.chart-legend');
|
|
589
|
+
if (existingLegend) {
|
|
590
|
+
existingLegend.remove();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const parentWidth = parent && parent.clientWidth ? parent.clientWidth : canvas.clientWidth || 720;
|
|
595
|
+
const cssHeight = canvas.dataset.height ? Number(canvas.dataset.height) : 320;
|
|
596
|
+
const dpr = window.devicePixelRatio || 1;
|
|
597
|
+
|
|
598
|
+
canvas.width = parentWidth * dpr;
|
|
599
|
+
canvas.height = cssHeight * dpr;
|
|
600
|
+
canvas.style.width = '100%';
|
|
601
|
+
canvas.style.height = cssHeight + 'px';
|
|
602
|
+
|
|
603
|
+
const ctx = canvas.getContext('2d');
|
|
604
|
+
if (!ctx) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
609
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
610
|
+
ctx.scale(dpr, dpr);
|
|
611
|
+
|
|
612
|
+
const margin = { top: 20, right: 24, bottom: 48, left: 72 };
|
|
613
|
+
const innerWidth = Math.max(10, parentWidth - margin.left - margin.right);
|
|
614
|
+
const innerHeight = Math.max(10, cssHeight - margin.top - margin.bottom);
|
|
615
|
+
|
|
616
|
+
ctx.translate(margin.left, margin.top);
|
|
617
|
+
|
|
618
|
+
const allValues = [];
|
|
619
|
+
config.datasets.forEach(function (dataset) {
|
|
620
|
+
dataset.values.forEach(function (value) {
|
|
621
|
+
if (Number.isFinite(value)) {
|
|
622
|
+
allValues.push(value);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
let minY = typeof config.minY === 'number' ? config.minY : Math.min.apply(Math, allValues);
|
|
628
|
+
let maxY = typeof config.maxY === 'number' ? config.maxY : Math.max.apply(Math, allValues);
|
|
629
|
+
|
|
630
|
+
if (!Number.isFinite(minY) || !Number.isFinite(maxY)) {
|
|
631
|
+
minY = 0;
|
|
632
|
+
maxY = 1;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (minY === maxY) {
|
|
636
|
+
const padding = Math.abs(minY) * 0.05 || 1;
|
|
637
|
+
minY -= padding;
|
|
638
|
+
maxY += padding;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const valueRange = maxY - minY;
|
|
642
|
+
const gridLines = config.yTicks || 4;
|
|
643
|
+
|
|
644
|
+
ctx.lineWidth = 1;
|
|
645
|
+
ctx.font = '12px Inter, sans-serif';
|
|
646
|
+
ctx.textAlign = 'right';
|
|
647
|
+
ctx.textBaseline = 'middle';
|
|
648
|
+
ctx.fillStyle = '#94a3b8';
|
|
649
|
+
|
|
650
|
+
for (let i = 0; i <= gridLines; i += 1) {
|
|
651
|
+
const ratio = i / gridLines;
|
|
652
|
+
const y = innerHeight - ratio * innerHeight;
|
|
653
|
+
ctx.strokeStyle = 'rgba(148, 163, 184, 0.12)';
|
|
654
|
+
ctx.beginPath();
|
|
655
|
+
ctx.moveTo(0, y);
|
|
656
|
+
ctx.lineTo(innerWidth, y);
|
|
657
|
+
ctx.stroke();
|
|
658
|
+
|
|
659
|
+
const value = minY + ratio * valueRange;
|
|
660
|
+
const label = config.yFormatter ? config.yFormatter(value) : value.toFixed(1);
|
|
661
|
+
ctx.fillText(label, -12, y);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const xCount = config.labels.length;
|
|
665
|
+
const xTicks = config.xTickCount || Math.min(6, xCount);
|
|
666
|
+
const xStep = xCount > 1 ? Math.max(1, Math.floor(xCount / xTicks)) : 1;
|
|
667
|
+
|
|
668
|
+
ctx.textAlign = 'center';
|
|
669
|
+
ctx.textBaseline = 'top';
|
|
670
|
+
|
|
671
|
+
for (let i = 0; i < xCount; i += xStep) {
|
|
672
|
+
const x = xCount === 1 ? innerWidth / 2 : (i / (xCount - 1)) * innerWidth;
|
|
673
|
+
ctx.strokeStyle = 'rgba(148, 163, 184, 0.08)';
|
|
674
|
+
ctx.beginPath();
|
|
675
|
+
ctx.moveTo(x, 0);
|
|
676
|
+
ctx.lineTo(x, innerHeight);
|
|
677
|
+
ctx.stroke();
|
|
678
|
+
|
|
679
|
+
ctx.save();
|
|
680
|
+
ctx.translate(x, innerHeight + 14);
|
|
681
|
+
ctx.rotate(-Math.PI / 8);
|
|
682
|
+
ctx.fillStyle = '#94a3b8';
|
|
683
|
+
ctx.fillText(config.labels[i], 0, 0);
|
|
684
|
+
ctx.restore();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
ctx.strokeStyle = 'rgba(148, 163, 184, 0.35)';
|
|
688
|
+
ctx.lineWidth = 1.2;
|
|
689
|
+
ctx.beginPath();
|
|
690
|
+
ctx.moveTo(0, 0);
|
|
691
|
+
ctx.lineTo(0, innerHeight);
|
|
692
|
+
ctx.lineTo(innerWidth, innerHeight);
|
|
693
|
+
ctx.stroke();
|
|
694
|
+
|
|
695
|
+
config.datasets.forEach(function (dataset) {
|
|
696
|
+
const points = dataset.values.map(function (value, index) {
|
|
697
|
+
const safeValue = Number.isFinite(value) ? value : minY;
|
|
698
|
+
const x = xCount === 1 ? innerWidth / 2 : (index / (xCount - 1)) * innerWidth;
|
|
699
|
+
const ratio = (safeValue - minY) / valueRange;
|
|
700
|
+
const y = innerHeight - ratio * innerHeight;
|
|
701
|
+
return { x: x, y: y };
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
if (dataset.fill && points.length > 1) {
|
|
705
|
+
ctx.fillStyle = dataset.fill;
|
|
706
|
+
ctx.beginPath();
|
|
707
|
+
points.forEach(function (point, pointIndex) {
|
|
708
|
+
if (pointIndex === 0) {
|
|
709
|
+
ctx.moveTo(point.x, point.y);
|
|
710
|
+
} else {
|
|
711
|
+
ctx.lineTo(point.x, point.y);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
const last = points[points.length - 1];
|
|
715
|
+
const first = points[0];
|
|
716
|
+
ctx.lineTo(last.x, innerHeight);
|
|
717
|
+
ctx.lineTo(first.x, innerHeight);
|
|
718
|
+
ctx.closePath();
|
|
719
|
+
ctx.fill();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (dataset.dashed) {
|
|
723
|
+
ctx.setLineDash(dataset.dashed);
|
|
724
|
+
} else {
|
|
725
|
+
ctx.setLineDash([]);
|
|
726
|
+
}
|
|
727
|
+
ctx.strokeStyle = dataset.color;
|
|
728
|
+
ctx.lineWidth = dataset.width || 2;
|
|
729
|
+
ctx.beginPath();
|
|
730
|
+
points.forEach(function (point, pointIndex) {
|
|
731
|
+
if (pointIndex === 0) {
|
|
732
|
+
ctx.moveTo(point.x, point.y);
|
|
733
|
+
} else {
|
|
734
|
+
ctx.lineTo(point.x, point.y);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
ctx.stroke();
|
|
738
|
+
ctx.setLineDash([]);
|
|
739
|
+
|
|
740
|
+
// Draw new-peak markers if enabled
|
|
741
|
+
if (dataset.markPeaks) {
|
|
742
|
+
let runningMax = -Infinity;
|
|
743
|
+
const radius = Number.isFinite(dataset.markerRadius) ? dataset.markerRadius : 2.5;
|
|
744
|
+
ctx.save();
|
|
745
|
+
ctx.fillStyle = dataset.markerColor || dataset.color;
|
|
746
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.85)';
|
|
747
|
+
ctx.lineWidth = 1;
|
|
748
|
+
const peakSeries = Array.isArray(dataset.markerPeakValues) ? dataset.markerPeakValues : dataset.values;
|
|
749
|
+
peakSeries.forEach(function (value, idx) {
|
|
750
|
+
if (!Number.isFinite(value)) return;
|
|
751
|
+
if (value > runningMax) {
|
|
752
|
+
runningMax = value;
|
|
753
|
+
const p = points[idx];
|
|
754
|
+
ctx.beginPath();
|
|
755
|
+
ctx.arc(p.x, p.y, radius, 0, Math.PI * 2);
|
|
756
|
+
ctx.fill();
|
|
757
|
+
ctx.stroke();
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
ctx.restore();
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
765
|
+
|
|
766
|
+
if (parent) {
|
|
767
|
+
const legend = document.createElement('div');
|
|
768
|
+
legend.className = 'chart-legend';
|
|
769
|
+
config.datasets.forEach(function (dataset) {
|
|
770
|
+
const legendItem = document.createElement('span');
|
|
771
|
+
legendItem.style.color = dataset.color;
|
|
772
|
+
legendItem.textContent = dataset.label;
|
|
773
|
+
legend.appendChild(legendItem);
|
|
774
|
+
});
|
|
775
|
+
parent.appendChild(legend);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function formatTimestamp(timestamp) {
|
|
780
|
+
const date = new Date(timestamp);
|
|
781
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function debounce(fn, delay) {
|
|
785
|
+
let timeout;
|
|
786
|
+
return function () {
|
|
787
|
+
const context = this;
|
|
788
|
+
const args = arguments;
|
|
789
|
+
clearTimeout(timeout);
|
|
790
|
+
timeout = setTimeout(function () {
|
|
791
|
+
fn.apply(context, args);
|
|
792
|
+
}, delay);
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function bytesToMb(bytes) {
|
|
797
|
+
return bytes / (1024 * 1024);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function formatBytes(bytes) {
|
|
801
|
+
if (!Number.isFinite(bytes)) return '0 B';
|
|
802
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
803
|
+
let value = bytes;
|
|
804
|
+
let unitIndex = 0;
|
|
805
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
806
|
+
value /= 1024;
|
|
807
|
+
unitIndex += 1;
|
|
808
|
+
}
|
|
809
|
+
return value.toFixed(2) + ' ' + units[unitIndex];
|
|
810
|
+
}
|
|
811
|
+
</script>
|
|
812
|
+
</body>
|
|
813
|
+
</html>`;
|
|
814
|
+
writeFileSync(outputPath, html, { encoding: 'utf-8' });
|
|
815
|
+
return outputPath;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Escapes HTML special characters to prevent breaking embedded markup.
|
|
819
|
+
*
|
|
820
|
+
* @param {string} input - The string to escape.
|
|
821
|
+
* @returns {string} The escaped string safe for HTML contexts.
|
|
822
|
+
*/
|
|
823
|
+
function escapeHtml(input) {
|
|
824
|
+
return input.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
825
|
+
}
|
|
826
|
+
//# sourceMappingURL=cliHistory.js.map
|