diff --git a/packages/lib/src/helpers.ts b/packages/lib/src/helpers.ts index 34d5ba28..24cb3477 100644 --- a/packages/lib/src/helpers.ts +++ b/packages/lib/src/helpers.ts @@ -217,17 +217,26 @@ export function escapeRegExp(text: string) { * * Shortens a large number of bytes using the kB/MB/GB/TB prefixes. * @param bytes - Count of bytes to format. + * @param prefixes - Whethere to use SI powers (1000) or the binary powers (1024). * @returns formatted text. */ -export function formatBytes(bytes: number) { +export function formatBytes(bytes: number, prefixes: "si" | "binary" = "si") { if (bytes === 0) { return "0\u{A0}Bytes"; // No-break space } - let units = ["\u{A0}Bytes", "\u{A0}kB", "\u{A0}MB", "\u{A0}GB", "\u{A0}TB"]; - let factor = 1000; - let power = Math.min(Math.floor(Math.log(bytes) / Math.log(factor)), units.length); - return (power > 0 ? (bytes / factor ** power).toFixed(2) : bytes) + units[power]; + let base, units; + if (prefixes === "si") { + base = 1000; + units = ["\u{A0}Bytes", "\u{A0}kB", "\u{A0}MB", "\u{A0}GB", "\u{A0}TB"]; + } else { + base = 1024; + units = ["\u{A0}Bytes", "\u{A0}kiB", "\u{A0}MiB", "\u{A0}GiB", "\u{A0}TiB"]; + } + const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(base)), units.length); + const significant = bytes / base ** exponent; + const fractionDigits = Number(significant < 99.95) + Number(significant < 9.995); + return significant.toFixed(fractionDigits) + units[exponent]; } function skipWhitespace(pos: number, input: string) { diff --git a/packages/web_ui/src/components/system_metrics.tsx b/packages/web_ui/src/components/system_metrics.tsx index aed2dd83..15ca5e5b 100644 --- a/packages/web_ui/src/components/system_metrics.tsx +++ b/packages/web_ui/src/components/system_metrics.tsx @@ -32,8 +32,8 @@ export function MetricMemoryUsed(props: { metrics?: lib.SystemMetrics }) { if (!props.metrics) { return "N/A"; } - const used = lib.formatBytes(props.metrics.memoryUsed); - const capacity = lib.formatBytes(props.metrics.memoryCapacity); + const used = lib.formatBytes(props.metrics.memoryUsed, "binary"); + const capacity = lib.formatBytes(props.metrics.memoryCapacity, "binary"); return `${used} / ${capacity}`; } diff --git a/test/lib/helpers.js b/test/lib/helpers.js index 7b898215..b9398dcb 100644 --- a/test/lib/helpers.js +++ b/test/lib/helpers.js @@ -192,6 +192,50 @@ describe("lib/helpers", function() { }); }); + describe("formatBytes()", function() { + it("should format whole number of base unit bytes", function() { + assert.equal(lib.formatBytes(100), "100\u{A0}Bytes"); + assert.equal(lib.formatBytes(100e3), "100\u{A0}kB"); + assert.equal(lib.formatBytes(100e6), "100\u{A0}MB"); + assert.equal(lib.formatBytes(100e9), "100\u{A0}GB"); + assert.equal(lib.formatBytes(100e12), "100\u{A0}TB"); + assert.equal(lib.formatBytes(100, false), "100\u{A0}Bytes"); + assert.equal(lib.formatBytes(100 * 2**10, false), "100\u{A0}kiB"); + assert.equal(lib.formatBytes(100 * 2**20, false), "100\u{A0}MiB"); + assert.equal(lib.formatBytes(100 * 2**30, false), "100\u{A0}GiB"); + assert.equal(lib.formatBytes(100 * 2**40, false), "100\u{A0}TiB"); + }); + it("should format with 3 significant digits fractional number of base unit bytes", function() { + // 1 digits base and 2 digit fraction + assert.equal(lib.formatBytes(1.234e3), "1.23\u{A0}kB"); + assert.equal(lib.formatBytes(1.234e6), "1.23\u{A0}MB"); + assert.equal(lib.formatBytes(1.234e9), "1.23\u{A0}GB"); + assert.equal(lib.formatBytes(1.234e12), "1.23\u{A0}TB"); + assert.equal(lib.formatBytes(1.234 * 2**10, "binary"), "1.23\u{A0}kiB"); + assert.equal(lib.formatBytes(1.234 * 2**20, "binary"), "1.23\u{A0}MiB"); + assert.equal(lib.formatBytes(1.234 * 2**30, "binary"), "1.23\u{A0}GiB"); + assert.equal(lib.formatBytes(1.234 * 2**40, "binary"), "1.23\u{A0}TiB"); + // 2 digits base and 1 digit fraction + assert.equal(lib.formatBytes(12.34e3), "12.3\u{A0}kB"); + assert.equal(lib.formatBytes(12.34e6), "12.3\u{A0}MB"); + assert.equal(lib.formatBytes(12.34e9), "12.3\u{A0}GB"); + assert.equal(lib.formatBytes(12.34e12), "12.3\u{A0}TB"); + assert.equal(lib.formatBytes(12.34 * 2**10, "binary"), "12.3\u{A0}kiB"); + assert.equal(lib.formatBytes(12.34 * 2**20, "binary"), "12.3\u{A0}MiB"); + assert.equal(lib.formatBytes(12.34 * 2**30, "binary"), "12.3\u{A0}GiB"); + assert.equal(lib.formatBytes(12.34 * 2**40, "binary"), "12.3\u{A0}TiB"); + // 3 digits base and no fraction + assert.equal(lib.formatBytes(123.4e3), "123\u{A0}kB"); + assert.equal(lib.formatBytes(123.4e6), "123\u{A0}MB"); + assert.equal(lib.formatBytes(123.4e9), "123\u{A0}GB"); + assert.equal(lib.formatBytes(123.4e12), "123\u{A0}TB"); + assert.equal(lib.formatBytes(123.4 * 2**10, "binary"), "123\u{A0}kiB"); + assert.equal(lib.formatBytes(123.4 * 2**20, "binary"), "123\u{A0}MiB"); + assert.equal(lib.formatBytes(123.4 * 2**30, "binary"), "123\u{A0}GiB"); + assert.equal(lib.formatBytes(123.4 * 2**40, "binary"), "123\u{A0}TiB"); + }); + }); + function parse(input, attributes) { const result = lib.parseSearchString(input, attributes); if (!result.issues.length) {