Skip to content

Add supports for more unit notations #668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/package_test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
24 changes: 12 additions & 12 deletions src/top.ts
Original file line number Diff line number Diff line change
@@ -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,
) {}
}

Expand All @@ -24,20 +24,20 @@ export async function topNodes(api: CoreV1Api): Promise<NodeStatus[]> {
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);
Expand Down
78 changes: 62 additions & 16 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,65 @@
import { isNumber } from 'underscore';
import { CoreV1Api, V1Container, V1Pod } from './gen/api';

export async function podsForNode(api: CoreV1Api, nodeName: string): Promise<V1Pod[]> {
const allPods = await api.listPodForAllNamespaces();
return allPods.body.items.filter((pod: V1Pod) => 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,
) {}
}
Expand All @@ -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]));
}
}
});
Expand Down
29 changes: 23 additions & 6 deletions src/util_test.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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('');
});
});
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"module": "commonjs",
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"target": "es6",
"target": "es2019",
"lib": ["es2020"],
"moduleResolution": "node",
"removeComments": false,
"sourceMap": true,
Expand Down