1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add B to bytesToUnits to make clear that they are bytes (#5170)

This commit is contained in:
Sebastian Malton 2022-04-06 08:48:42 -07:00 committed by GitHub
parent 1a29759bff
commit 7c34ba36a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 24 deletions

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { bytesToUnits, unitsToBytes } from "../convertMemory";
describe("unitsToBytes", () => {
it("without any units, just parse as float", () => {
expect(unitsToBytes("1234")).toBe(1234);
});
it("given unrelated data, return NaN", () => {
expect(unitsToBytes("I am not a number")).toBeNaN();
});
it("given unrelated data, but has number, return that", () => {
expect(unitsToBytes("I am not a number, but this is 0.1")).toBe(0.1);
});
});
describe("bytesToUnits", () => {
it("should return N/A for invalid bytes", () => {
expect(bytesToUnits(-1)).toBe("N/A");
expect(bytesToUnits(Infinity)).toBe("N/A");
expect(bytesToUnits(NaN)).toBe("N/A");
});
it("given a number within the magnitude of 0..124, format with B", () => {
expect(bytesToUnits(100)).toBe("100.0B");
});
it("given a number within the magnitude of 1024..1024^2, format with KiB", () => {
expect(bytesToUnits(1024)).toBe("1.0KiB");
expect(bytesToUnits(2048)).toBe("2.0KiB");
expect(bytesToUnits(1900)).toBe("1.9KiB");
expect(bytesToUnits(50*1024 + 1)).toBe("50.0KiB");
});
it("given a number within the magnitude of 1024^2..1024^3, format with MiB", () => {
expect(bytesToUnits(1024**2)).toBe("1.0MiB");
expect(bytesToUnits(2048**2)).toBe("4.0MiB");
expect(bytesToUnits(1900 * 1024)).toBe("1.9MiB");
expect(bytesToUnits(50*(1024 ** 2) + 1)).toBe("50.0MiB");
});
it("given a number within the magnitude of 1024^3..1024^4, format with GiB", () => {
expect(bytesToUnits(1024**3)).toBe("1.0GiB");
expect(bytesToUnits(2048**3)).toBe("8.0GiB");
expect(bytesToUnits(1900 * 1024 ** 2)).toBe("1.9GiB");
expect(bytesToUnits(50*(1024 ** 3) + 1)).toBe("50.0GiB");
});
it("given a number within the magnitude of 1024^4..1024^5, format with TiB", () => {
expect(bytesToUnits(1024**4)).toBe("1.0TiB");
expect(bytesToUnits(2048**4)).toBe("16.0TiB");
expect(bytesToUnits(1900 * 1024 ** 3)).toBe("1.9TiB");
expect(bytesToUnits(50*(1024 ** 4) + 1)).toBe("50.0TiB");
});
it("given a number within the magnitude of 1024^5..1024^6, format with PiB", () => {
expect(bytesToUnits(1024**5)).toBe("1.0PiB");
expect(bytesToUnits(2048**5)).toBe("32.0PiB");
expect(bytesToUnits(1900 * 1024 ** 4)).toBe("1.9PiB");
expect(bytesToUnits(50*(1024 ** 5) + 1)).toBe("50.0PiB");
});
it("given a number within the magnitude of 1024^6.., format with EiB", () => {
expect(bytesToUnits(1024**6)).toBe("1.0EiB");
expect(bytesToUnits(2048**6)).toBe("64.0EiB");
expect(bytesToUnits(1900 * 1024 ** 5)).toBe("1.9EiB");
expect(bytesToUnits(50*(1024 ** 6) + 1)).toBe("50.0EiB");
expect(bytesToUnits(1024**8)).toBe("1048576.0EiB");
});
});
describe("bytesToUnits -> unitsToBytes", () => {
it("given an input, round trip to the same value, given enough precision", () => {
expect(unitsToBytes(bytesToUnits(123))).toBe(123);
expect(unitsToBytes(bytesToUnits(1024**0 + 1, { precision: 2 }))).toBe(1024**0 + 1);
expect(unitsToBytes(bytesToUnits(1024**1 + 2, { precision: 3 }))).toBe(1024**1 + 2);
expect(unitsToBytes(bytesToUnits(1024**2 + 3, { precision: 6 }))).toBe(1024**2 + 3);
expect(unitsToBytes(bytesToUnits(1024**3 + 4, { precision: 9 }))).toBe(1024**3 + 4);
expect(unitsToBytes(bytesToUnits(1024**4 + 5, { precision: 12 }))).toBe(1024**4 + 5);
expect(unitsToBytes(bytesToUnits(1024**5 + 6, { precision: 16 }))).toBe(1024**5 + 6);
expect(unitsToBytes(bytesToUnits(1024**6 + 7, { precision: 20 }))).toBe(1024**6 + 7);
});
it("given an invalid input, round trips to NaN", () => {
expect(unitsToBytes(bytesToUnits(-1))).toBeNaN();
});
});

View File

@ -3,35 +3,59 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import assert from "assert";
import * as iter from "./iter";
// Helper to convert memory from units Ki, Mi, Gi, Ti, Pi to bytes and vise versa
const base = 1024;
const suffixes = ["K", "M", "G", "T", "P", "E"]; // Equivalents: Ki, Mi, Gi, Ti, Pi, Ei
const baseMagnitude = 1024;
const maxMagnitude = ["EiB", baseMagnitude ** 6] as const;
const magnitudes = new Map([
["B", 1] as const,
["KiB", baseMagnitude ** 1] as const,
["MiB", baseMagnitude ** 2] as const,
["GiB", baseMagnitude ** 3] as const,
["TiB", baseMagnitude ** 4] as const,
["PiB", baseMagnitude ** 5] as const,
maxMagnitude,
]);
const unitRegex = /(?<value>[0-9]+(\.[0-9]*)?)(?<suffix>(B|[KMGTPE]iB))?/;
export function unitsToBytes(value: string) {
if (!suffixes.some(suffix => value.includes(suffix))) {
return parseFloat(value);
export function unitsToBytes(value: string): number {
const unitsMatch = value.match(unitRegex);
if (!unitsMatch?.groups) {
return NaN;
}
const suffix = value.replace(/[0-9]|i|\./g, "");
const index = suffixes.indexOf(suffix);
return parseInt(
(parseFloat(value) * Math.pow(base, index + 1)).toFixed(1),
);
const parsedValue = parseFloat(unitsMatch.groups.value);
if (!unitsMatch.groups?.suffix) {
return parsedValue;
}
const magnitude = magnitudes.get(unitsMatch.groups.suffix as never);
assert(magnitude, "UnitRegex is wrong some how");
return parseInt((parsedValue * magnitude).toFixed(1));
}
export function bytesToUnits(bytes: number, precision = 1) {
const sizes = ["B", ...suffixes];
const index = Math.floor(Math.log(bytes) / Math.log(base));
export interface BytesToUnitesOptions {
/**
* The number of decimal places. MUST be an integer. MUST be in the range [0, 20].
* @default 1
*/
precision?: number;
}
if (!bytes) {
export function bytesToUnits(bytes: number, { precision = 1 }: BytesToUnitesOptions = {}): string {
if (bytes <= 0 || isNaN(bytes) || !isFinite(bytes)) {
return "N/A";
}
if (index === 0) {
return `${bytes}${sizes[index]}`;
}
const index = Math.floor(Math.log(bytes) / Math.log(baseMagnitude));
const [suffix, magnitude] = iter.nth(magnitudes.entries(), index) ?? maxMagnitude;
return `${(bytes / (1024 ** index)).toFixed(precision)}${sizes[index]}i`;
return `${(bytes / magnitude).toFixed(precision)}${suffix}`;
}

View File

@ -171,6 +171,23 @@ export function join(src: Iterable<string>, connector = ","): string {
return reduce(src, (acc, cur) => `${acc}${connector}${cur}`, "");
}
/**
* Returns the next value after iterating over the iterable `index` times.
*
* For example: `nth(["a", "b"], 0)` will return `"a"`
* For example: `nth(["a", "b"], 1)` will return `"b"`
* For example: `nth(["a", "b"], 2)` will return `undefined`
*/
export function nth<T>(src: Iterable<T>, index: number): T | undefined {
const iteree = src[Symbol.iterator]();
while (index-- > 0) {
iteree.next();
}
return iteree.next().value;
}
/**
* Iterate through `src` and return `true` if `fn` returns a thruthy value for every yielded value.
* Otherwise, return `false`. This function short circuits.

View File

@ -73,7 +73,7 @@ const NonInjectedClusterMetrics = observer(({ clusterOverviewStore: { metricType
label: ({ index }, data) => {
const value = data.datasets[0].data[index] as ChartPoint;
return bytesToUnits(parseInt(value.y as string), 3);
return bytesToUnits(parseInt(value.y as string), { precision: 3 });
},
},
},

View File

@ -127,7 +127,7 @@ export class NodesRoute extends React.Component {
metricNames: ["workloadMemoryUsage", "memoryAllocatableCapacity"],
formatters: [
([usage, capacity]) => `${(usage * 100 / capacity).toFixed(2)}%`,
([usage]) => bytesToUnits(usage, 3),
([usage]) => bytesToUnits(usage, { precision: 3 }),
],
});
}
@ -139,7 +139,7 @@ export class NodesRoute extends React.Component {
metricNames: ["fsUsage", "fsSize"],
formatters: [
([usage, capacity]) => `${(usage * 100 / capacity).toFixed(2)}%`,
([usage]) => bytesToUnits(usage, 3),
([usage]) => bytesToUnits(usage, { precision: 3 }),
],
});
}

View File

@ -75,7 +75,7 @@ export class PodDetailsList extends React.Component<PodDetailsListProps> {
renderMemoryUsage(id: string, usage: number) {
const { maxMemory } = this.props;
const tooltip = (
<p>Memory: {Math.ceil(usage * 100 / maxMemory)}%<br/>{bytesToUnits(usage, 3)}</p>
<p>Memory: {Math.ceil(usage * 100 / maxMemory)}%<br/>{bytesToUnits(usage, { precision: 3 })}</p>
);
if (!maxMemory) return usage ? bytesToUnits(usage) : 0;

View File

@ -195,7 +195,7 @@ export const memoryOptions: ChartOptions = {
const { label, data } = datasets[datasetIndex];
const value = data[index] as ChartPoint;
return `${label}: ${bytesToUnits(parseInt(value.y.toString()), 3)}`;
return `${label}: ${bytesToUnits(parseInt(value.y.toString()), { precision: 3 })}`;
},
},
},