From 33d810f2d7287a6a00f40654966078690bcffd8f Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 4 Jun 2021 08:56:17 -0700 Subject: [PATCH 1/2] Update our typescript settings. --- src/package_test.ts | 1 + tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/package_test.ts b/src/package_test.ts index 0d4d17920c9..68a80e9ae33 100644 --- a/src/package_test.ts +++ b/src/package_test.ts @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { URL } from 'url'; // Generic set of tests to verify the package is built and configured correctly describe('package', () => { diff --git a/tsconfig.json b/tsconfig.json index 68cd4bb16d5..bf18bad069c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "module": "commonjs", "noImplicitAny": false, "suppressImplicitAnyIndexErrors": true, - "target": "es6", + "target": "es2019", + "lib": ["es2020"], "moduleResolution": "node", "removeComments": false, "sourceMap": true, From bfe3abe8f228ac91ca3229785f08a1f471869a5a Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Mon, 7 Jun 2021 07:41:14 -0700 Subject: [PATCH 2/2] Add initial support for bigint quantities. --- src/top.ts | 24 +++++++-------- src/util.ts | 78 ++++++++++++++++++++++++++++++++++++++---------- src/util_test.ts | 29 ++++++++++++++---- 3 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/top.ts b/src/top.ts index 1fadb678f48..463b1d661c3 100644 --- a/src/top.ts +++ b/src/top.ts @@ -1,11 +1,11 @@ import { CoreV1Api, V1Node, V1Pod } from './gen/api'; -import { podsForNode, quantityToScalar, totalCPU, totalMemory } from './util'; +import { add, podsForNode, quantityToScalar, totalCPU, totalMemory } from './util'; export class ResourceUsage { constructor( - public readonly Capacity: number, - public readonly RequestTotal: number, - public readonly LimitTotal: number, + public readonly Capacity: number | BigInt, + public readonly RequestTotal: number | BigInt, + public readonly LimitTotal: number | BigInt, ) {} } @@ -24,20 +24,20 @@ export async function topNodes(api: CoreV1Api): Promise { for (const node of nodes.body!.items) { const availableCPU = quantityToScalar(node.status!.allocatable!.cpu); const availableMem = quantityToScalar(node.status!.allocatable!.memory); - let totalPodCPU = 0; - let totalPodCPULimit = 0; - let totalPodMem = 0; - let totalPodMemLimit = 0; + let totalPodCPU: number | bigint = 0; + let totalPodCPULimit: number | bigint = 0; + let totalPodMem: number | bigint = 0; + let totalPodMemLimit: number | bigint = 0; let pods = await podsForNode(api, node.metadata!.name!); pods = pods.filter((pod: V1Pod) => pod.status!.phase === 'Running'); pods.forEach((pod: V1Pod) => { const cpuTotal = totalCPU(pod); - totalPodCPU += cpuTotal.request; - totalPodCPULimit += cpuTotal.limit; + totalPodCPU = add(totalPodCPU, cpuTotal.request); + totalPodCPULimit = add(totalPodCPULimit, cpuTotal.limit); const memTotal = totalMemory(pod); - totalPodMem += memTotal.request; - totalPodMemLimit += memTotal.limit; + totalPodMem = add(totalPodMem, memTotal.request); + totalPodMemLimit = add(totalPodMemLimit, memTotal.limit); }); const cpuUsage = new ResourceUsage(availableCPU, totalPodCPU, totalPodCPULimit); diff --git a/src/util.ts b/src/util.ts index bfb0dd74cf7..37efc3ef2b3 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,4 @@ +import { isNumber } from 'underscore'; import { CoreV1Api, V1Container, V1Pod } from './gen/api'; export async function podsForNode(api: CoreV1Api, nodeName: string): Promise { @@ -5,27 +6,60 @@ export async function podsForNode(api: CoreV1Api, nodeName: string): Promise pod.spec!.nodeName === nodeName); } -export function quantityToScalar(quantity: string): number { +export function findSuffix(quantity: string): string { + let ix = quantity.length - 1; + while (ix >= 0 && !/[\.0-9]/.test(quantity.charAt(ix))) { + ix--; + } + return ix === -1 ? '' : quantity.substring(ix + 1); +} + +export function quantityToScalar(quantity: string): number | bigint { if (!quantity) { return 0; } - if (quantity.endsWith('m')) { - return parseInt(quantity.substr(0, quantity.length - 1), 10) / 1000.0; - } - if (quantity.endsWith('Ki')) { - return parseInt(quantity.substr(0, quantity.length - 2), 10) * 1024; + const suffix = findSuffix(quantity); + if (suffix === '') { + const num = Number(quantity).valueOf(); + if (isNaN(num)) { + throw new Error('Unknown quantity ' + quantity); + } + return num; } - const num = parseInt(quantity, 10); - if (isNaN(num)) { - throw new Error('Unknown quantity ' + quantity); + switch (suffix) { + case 'm': + return Number(quantity.substr(0, quantity.length - 1)).valueOf() / 1000.0; + case 'Ki': + return BigInt(quantity.substr(0, quantity.length - 2)) * BigInt(1024); + case 'Mi': + return BigInt(quantity.substr(0, quantity.length - 2)) * BigInt(1024 * 1024); + case 'Gi': + return BigInt(quantity.substr(0, quantity.length - 2)) * BigInt(1024 * 1024 * 1024); + case 'Ti': + return ( + BigInt(quantity.substr(0, quantity.length - 2)) * BigInt(1024 * 1024 * 1024) * BigInt(1024) + ); + case 'Pi': + return ( + BigInt(quantity.substr(0, quantity.length - 2)) * + BigInt(1024 * 1024 * 1024) * + BigInt(1024 * 1024) + ); + case 'Ei': + return ( + BigInt(quantity.substr(0, quantity.length - 2)) * + BigInt(1024 * 1024 * 1024) * + BigInt(1024 * 1024 * 1024) + ); + default: + throw new Error(`Unknown suffix: ${suffix}`); } - return num; } export class ResourceStatus { constructor( - public readonly request: number, - public readonly limit: number, + public readonly request: bigint | number, + public readonly limit: bigint | number, public readonly resourceType: string, ) {} } @@ -38,16 +72,28 @@ export function totalMemory(pod: V1Pod): ResourceStatus { return totalForResource(pod, 'memory'); } +export function add(n1: number | bigint, n2: number | bigint): number | bigint { + if (isNumber(n1) && isNumber(n2)) { + return n1 + n2; + } + if (isNumber(n1)) { + return BigInt(Math.round(n1)) + (n2 as bigint); + } else if (isNumber(n2)) { + return (n1 as bigint) + BigInt(Math.round(n2)); + } + return ((n1 as bigint) + n2) as bigint; +} + export function totalForResource(pod: V1Pod, resource: string): ResourceStatus { - let reqTotal = 0; - let limitTotal = 0; + let reqTotal: number | bigint = 0; + let limitTotal: number | bigint = 0; pod.spec!.containers.forEach((container: V1Container) => { if (container.resources) { if (container.resources.requests) { - reqTotal += quantityToScalar(container.resources.requests[resource]); + reqTotal = add(reqTotal, quantityToScalar(container.resources.requests[resource])); } if (container.resources.limits) { - limitTotal += quantityToScalar(container.resources.limits[resource]); + limitTotal = add(limitTotal, quantityToScalar(container.resources.limits[resource])); } } }); diff --git a/src/util_test.ts b/src/util_test.ts index 8365bbca897..322827aafdf 100644 --- a/src/util_test.ts +++ b/src/util_test.ts @@ -1,6 +1,6 @@ -import { expect } from 'chai'; +import { expect, should } from 'chai'; import { CoreV1Api, V1Container, V1Pod } from './api'; -import { podsForNode, quantityToScalar, totalCPU, totalMemory } from './util'; +import { findSuffix, podsForNode, quantityToScalar, totalCPU, totalMemory } from './util'; describe('Utils', () => { it('should get zero pods for a node', async () => { @@ -55,13 +55,24 @@ describe('Utils', () => { expect(quantityToScalar('')).to.equal(0); expect(quantityToScalar('100m')).to.equal(0.1); + expect(quantityToScalar('0.2')).to.equal(0.2); expect(quantityToScalar('1976m')).to.equal(1.976); - expect(quantityToScalar('10Ki')).to.equal(10240); - expect(quantityToScalar('1024')).to.equal(1024); + expect(quantityToScalar('1024')).to.equal(1024); + expect(quantityToScalar('10e3')).to.equal(10000); + + expect(quantityToScalar('10Ki')).to.equal(BigInt(10240)); + expect(quantityToScalar('20Mi')).to.equal(BigInt(1024 * 1024 * 20)); + expect(quantityToScalar('30Gi')).to.equal(BigInt(1024 * 1024 * 1024 * 30)); + expect(quantityToScalar('40Ti')).to.equal(BigInt(1024 * 1024 * 1024 * 1024 * 40)); + expect(quantityToScalar('50Pi')).to.equal(BigInt(1024 * 1024 * 1024 * 1024 * 1024 * 50)); + expect(quantityToScalar('60Ei')).to.equal( + BigInt(1024 * 1024 * 1024) * BigInt(1024 * 1024 * 1024 * 60), + ); expect(() => quantityToScalar('foobar')).to.throw('Unknown quantity foobar'); + expect(() => quantityToScalar('100foobar')).to.throw('Unknown suffix: foobar'); }); it('should get resources for pod', () => { @@ -100,7 +111,13 @@ describe('Utils', () => { expect(cpuResult.limit).to.equal(0.2); const memResult = totalMemory(pod); - expect(memResult.request).to.equal(10240); - expect(memResult.limit).to.equal(20480); + expect(memResult.request).to.equal(BigInt(10240)); + expect(memResult.limit).to.equal(BigInt(20480)); + }); + + it('should find the suffix correctly', () => { + expect(findSuffix('1234567')).to.equal(''); + expect(findSuffix('1234asdf')).to.equal('asdf'); + expect(findSuffix('1.0')).to.equal(''); }); });