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:
parent
1a29759bff
commit
7c34ba36a6
92
src/common/utils/__tests__/convert-memory.test.ts
Normal file
92
src/common/utils/__tests__/convert-memory.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
@ -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}`;
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 });
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -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 }),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 })}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user