KPI Autro Scale
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
|||||||
renderFleetCards,
|
renderFleetCards,
|
||||||
} from './ui/fleet.js'
|
} from './ui/fleet.js'
|
||||||
import { createLogsView } from './ui/logs.js'
|
import { createLogsView } from './ui/logs.js'
|
||||||
|
import { formatMib, formatKilobitsPerSec } from './shared/format.js'
|
||||||
|
|
||||||
const $ = (id) => document.getElementById(id)
|
const $ = (id) => document.getElementById(id)
|
||||||
|
|
||||||
@@ -711,11 +712,11 @@ function syncOverviewKpis() {
|
|||||||
const cpu = lastSeriesValue('cpu')
|
const cpu = lastSeriesValue('cpu')
|
||||||
if (els.statCpu) els.statCpu.textContent = cpu == null ? '—' : `${cpu.toFixed(1)}%`
|
if (els.statCpu) els.statCpu.textContent = cpu == null ? '—' : `${cpu.toFixed(1)}%`
|
||||||
const ram = lastSeriesValue('ram')
|
const ram = lastSeriesValue('ram')
|
||||||
if (els.statRam) els.statRam.textContent = ram == null ? '—' : `${ram.toFixed(0)} MiB`
|
if (els.statRam) els.statRam.textContent = formatMib(ram)
|
||||||
const load = lastSeriesValue('load')
|
const load = lastSeriesValue('load')
|
||||||
if (els.statLoad) els.statLoad.textContent = load == null ? '—' : load.toFixed(2)
|
if (els.statLoad) els.statLoad.textContent = load == null ? '—' : load.toFixed(2)
|
||||||
const net = lastSeriesValue('net')
|
const net = lastSeriesValue('net')
|
||||||
if (els.statNet) els.statNet.textContent = net == null ? '—' : `${net.toFixed(1)} kb/s`
|
if (els.statNet) els.statNet.textContent = formatKilobitsPerSec(net)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Map a queryData row to a values object keyed by dimension id. */
|
/** Map a queryData row to a values object keyed by dimension id. */
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Human-readable metric formatting with automatic unit scaling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
* @param {number} digits
|
||||||
|
*/
|
||||||
|
function trimFixed(n, digits) {
|
||||||
|
const s = n.toFixed(digits)
|
||||||
|
if (!s.includes('.')) return s
|
||||||
|
return s.replace(/0+$/, '').replace(/\.$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick decimal places from magnitude after scaling.
|
||||||
|
* @param {number} n
|
||||||
|
*/
|
||||||
|
function autoDigits(n) {
|
||||||
|
const a = Math.abs(n)
|
||||||
|
if (a >= 100) return 0
|
||||||
|
if (a >= 10) return 1
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale a magnitude through a unit ladder.
|
||||||
|
* @param {number} value absolute magnitude in base units
|
||||||
|
* @param {string[]} units
|
||||||
|
* @param {number} base
|
||||||
|
* @returns {{ n: number, unit: string }}
|
||||||
|
*/
|
||||||
|
function scaleUnit(value, units, base) {
|
||||||
|
let n = Math.abs(value)
|
||||||
|
let i = 0
|
||||||
|
while (n >= base && i < units.length - 1) {
|
||||||
|
n /= base
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return { n: value < 0 ? -n : n, unit: units[i] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format memory stored as MiB → B / KiB / MiB / GiB / TiB / PiB.
|
||||||
|
* @param {number|null|undefined} mib
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatMib(mib) {
|
||||||
|
if (mib == null || !Number.isFinite(Number(mib))) return '—'
|
||||||
|
const bytes = Number(mib) * 1024 * 1024
|
||||||
|
if (bytes === 0) return '0 B'
|
||||||
|
const { n, unit } = scaleUnit(bytes, ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'], 1024)
|
||||||
|
return `${trimFixed(n, autoDigits(n))} ${unit}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format network rate stored as kilobits/s → b/s / kb/s / Mb/s / Gb/s / Tb/s.
|
||||||
|
* @param {number|null|undefined} kbps
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function formatKilobitsPerSec(kbps) {
|
||||||
|
if (kbps == null || !Number.isFinite(Number(kbps))) return '—'
|
||||||
|
const bps = Number(kbps) * 1000
|
||||||
|
if (bps === 0) return '0 b/s'
|
||||||
|
const { n, unit } = scaleUnit(bps, ['b/s', 'kb/s', 'Mb/s', 'Gb/s', 'Tb/s'], 1000)
|
||||||
|
return `${trimFixed(n, autoDigits(n))} ${unit}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import test from 'brittle'
|
||||||
|
import { formatMib, formatKilobitsPerSec } from '../shared/format.js'
|
||||||
|
|
||||||
|
test('formatMib scales through binary units', async (t) => {
|
||||||
|
t.is(formatMib(null), '—')
|
||||||
|
t.is(formatMib(NaN), '—')
|
||||||
|
t.is(formatMib(0), '0 B')
|
||||||
|
t.is(formatMib(0.5), '512 KiB')
|
||||||
|
t.is(formatMib(256), '256 MiB')
|
||||||
|
t.is(formatMib(1024), '1 GiB')
|
||||||
|
t.is(formatMib(1536), '1.5 GiB')
|
||||||
|
t.is(formatMib(1024 * 1024), '1 TiB')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('formatKilobitsPerSec scales through SI bit rates', async (t) => {
|
||||||
|
t.is(formatKilobitsPerSec(null), '—')
|
||||||
|
t.is(formatKilobitsPerSec(0), '0 b/s')
|
||||||
|
t.is(formatKilobitsPerSec(0.5), '500 b/s')
|
||||||
|
t.is(formatKilobitsPerSec(12.5), '12.5 kb/s')
|
||||||
|
t.is(formatKilobitsPerSec(1500), '1.5 Mb/s')
|
||||||
|
t.is(formatKilobitsPerSec(2_500_000), '2.5 Gb/s')
|
||||||
|
t.is(formatKilobitsPerSec(3_000_000_000), '3 Tb/s')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user