Skip to content

Commit 40f5d2e

Browse files
committed
Split up cpuUtilization and memoryUtilization into host and limit relative parts
1 parent a37dac9 commit 40f5d2e

File tree

5 files changed

+80
-20
lines changed

5 files changed

+80
-20
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lup-docker",
3-
"version": "1.1.4",
3+
"version": "1.2.0",
44
"description": "NodeJS library to interact with the Docker engine.",
55
"main": "./lib/index",
66
"types": "./lib/index.d.ts",

src/__tests__/Docker.test.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,17 @@ test('createContainer', async () => {
2020
*/
2121

2222
/*
23-
test('getContainerStats', async () => {
23+
test('getContainerStatsReader', async () => {
2424
const client = new DockerClient();
25-
const response = await client.getContainerStats('postgresql', { stream: true });
26-
if(response.success?.stream){
27-
const stream = response.success.stream;
28-
const reader = stream.getReader();
29-
for(let i=0; i < 5; i++){
30-
const { done, value: stats } = await reader.read();
31-
if(done) break;
25+
const response = await client.getContainerStatsReader('postgresql');
26+
if (response.success) {
27+
const reader = response.success;
28+
for (let i = 0; i < 5; i++) {
29+
const stats = await reader.next();
30+
if (!stats) break;
3231
console.log(JSON.stringify(stats, null, 2));
3332
}
34-
await reader.cancel();
33+
await reader.close();
3534
}
3635
});
3736
*/

src/client.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
DockerExportImageOptions,
3333
DockerExportImagesOptions,
3434
DockerContainerStats,
35+
DockerContainerDetails,
3536
} from './types';
3637
import { DockerStatsStream, DockerLogStream, DockerStatsStreamReader } from './stream';
3738
import {
@@ -635,8 +636,10 @@ class DockerClient {
635636
}
636637

637638
// stats object
639+
const hostConfig =
640+
(options?.hostConfig as DockerContainerDetails)?.hostConfig || options?.hostConfig || undefined;
638641
return {
639-
success: decodeDockerContainerStats(JSON.parse((response as DockerRequestResultBody).body!))!,
642+
success: decodeDockerContainerStats(JSON.parse((response as DockerRequestResultBody).body!), hostConfig)!,
640643
};
641644
},
642645
);

src/convert.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import {
1212
DockerContainerStorageStats,
1313
DockerCreateContainerOptions,
1414
DockerCredentials,
15+
DockerHostConfig,
1516
DockerIdentityToken,
1617
DockerImage,
1718
DockerImageManifestSummary,
1819
DockerOCIDescriptor,
1920
DockerOCIPlatform,
2021
} from './types';
22+
import os from 'os';
2123

2224
export function encodeCreateContainerOptions(image: string, options?: DockerCreateContainerOptions): string {
2325
const body: any = {};
@@ -553,21 +555,40 @@ export function decodeDockerContainerNetworkStats(json: any | undefined): Docker
553555
};
554556
}
555557

556-
export function decodeDockerContainerStats(json: any | undefined): DockerContainerStats | undefined {
558+
export function decodeDockerContainerStats(
559+
json: any | undefined,
560+
hostConfig?: DockerHostConfig,
561+
): DockerContainerStats | undefined {
557562
if (!json) return undefined;
558563

559564
const cpuStats = decodeDockerContainerCpuStats(json.cpu_stats);
560565
const preStats = decodeDockerContainerCpuStats(json.precpu_stats);
561566
const memoryStats = decodeDockerContainerMemoryStats(json.memory_stats);
562567

568+
const hostCpuCoreCount = os.cpus().length;
569+
const hostMemoryTotal = os.totalmem();
570+
563571
const cpuCoreCount = Math.max(cpuStats?.cpuUsage?.perCpuUsage?.length || 0, cpuStats?.onlineCpus || 0);
564572
const cpuDelta = Math.max(0, (cpuStats?.cpuUsage?.totalUsage || 0) - (preStats?.cpuUsage?.totalUsage || 0));
565573
const memoryTotal = memoryStats?.total;
566574
const memoryUsed = memoryStats?.usage ? Math.max(0, memoryStats?.usage - (memoryStats?.stats.cache || 0)) : undefined;
567575
const systemCpuDelta = Math.max(0, (cpuStats?.systemCpuUsage || 0) - (preStats?.systemCpuUsage || 0));
568576

569-
const cpuUtilization = systemCpuDelta > 0 ? (cpuDelta / systemCpuDelta) * Math.max(1, cpuCoreCount) : 0;
570-
const memoryUtilization = memoryTotal && memoryUsed ? memoryUsed / memoryTotal : undefined;
577+
let containerCpuLimit = hostCpuCoreCount; // default no limit
578+
if (hostConfig?.nanoCpus && hostConfig?.cpuPeriod) {
579+
containerCpuLimit = hostConfig.nanoCpus / 1e9;
580+
} else if (hostConfig?.cpuQuota && hostConfig?.cpuPeriod) {
581+
containerCpuLimit = hostConfig.cpuQuota / hostConfig.cpuPeriod;
582+
} else if (hostConfig?.cpuCount) {
583+
containerCpuLimit = hostConfig.cpuCount;
584+
} else if (hostConfig?.cpuPercent) {
585+
containerCpuLimit = (hostConfig.cpuPercent / 100.0) * hostCpuCoreCount;
586+
}
587+
588+
const cpuUtilizationHost = systemCpuDelta > 0 ? cpuDelta / systemCpuDelta : 0;
589+
const cpuUtilizationLimits = hostConfig ? (cpuUtilizationHost * containerCpuLimit) / hostCpuCoreCount : undefined;
590+
const memoryUtilizationHost = memoryUsed !== undefined && hostMemoryTotal ? memoryUsed / hostMemoryTotal : undefined;
591+
const memoryUtilizationLimits = memoryUsed !== undefined && memoryTotal ? memoryUsed / memoryTotal : undefined;
571592
const networkStats: { [networkInterface: string]: DockerContainerNetworkStats } = {};
572593

573594
for (const [networkInterface, stats] of Object.entries<any>(json.networks || {})) {
@@ -606,11 +627,17 @@ export function decodeDockerContainerStats(json: any | undefined): DockerContain
606627
containerId: json.id || '',
607628
cpuCoreCount,
608629
cpuStats,
609-
cpuUtilization,
630+
cpuUtilization: {
631+
host: cpuUtilizationHost,
632+
limits: cpuUtilizationLimits,
633+
},
610634
memoryStats: memoryStats!,
611635
memoryTotal,
612636
memoryUsed,
613-
memoryUtilization,
637+
memoryUtilization: {
638+
host: memoryUtilizationHost,
639+
limits: memoryUtilizationLimits,
640+
},
614641
name: json.name,
615642
networkStats: json.networks ? networkStats : undefined,
616643
pidCount: json.pids_stats

src/types.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,13 @@ export type DockerContainerCpuStats = {
231231
};
232232
};
233233

234+
export type DockerContainerDetails = {
235+
/** Host configuration of the container. */
236+
hostConfig: DockerHostConfig;
237+
238+
// TODO add more fields returned by the /containers/{id}/json endpoint ('Inspect container').
239+
};
240+
234241
export type DockerContainerHealthState = 'starting' | 'healthy' | 'paused' | 'unhealthy';
235242

236243
export type DockerContainerInheritedVolume = {
@@ -540,8 +547,23 @@ export type DockerContainerStats = {
540547
/** CPU related info of the container. */
541548
cpuStats?: DockerContainerCpuStats;
542549

543-
/** CPU utilization of the container in percentage 0.0-1.0. */
544-
cpuUtilization?: number;
550+
/** CPU utilization of the container in percentage (0.0-1.0). */
551+
cpuUtilization: {
552+
/**
553+
* CPU utilization of the container in percentage 0.0-1.0 relative to the entire host CPU resources.
554+
* If the container is limited in CPU resources this value will still be relative to the entire host CPU resources.
555+
* e.g. if container is running on a 4-core host and is limited to 1-core (25%) the maximum value of this field will be 0.25 (25% of total host CPU).
556+
*/
557+
host?: number;
558+
559+
/**
560+
* CPU utilization of the container in percentage 0.0-1.0 relative to its assigned limits.
561+
* If no limits are assigned this field will be identical to 'cpuUtilization'.
562+
*
563+
* @warning This field requires the 'hostConfig' option to be set when calling 'getContainerStats' to compute the correct value.
564+
*/
565+
limits?: number;
566+
};
545567

546568
/**
547569
* Aggregates all memory stats since container inceptions on Linux.
@@ -555,8 +577,14 @@ export type DockerContainerStats = {
555577
/** Used memory in bytes. */
556578
memoryUsed?: number;
557579

558-
/** Memory utilization of the container in percentage 0.0-1.0. */
559-
memoryUtilization?: number;
580+
/** Memory utilization of the container in percentage (0.0-1.0). */
581+
memoryUtilization: {
582+
/** Memory utilization of the container in percentage 0.0-1.0 relative to the entire host memory. */
583+
host?: number;
584+
585+
/** Memory utilization of the container in percentage 0.0-1.0 relative to its assigned limits. */
586+
limits?: number;
587+
};
560588

561589
/** Name of the container. */
562590
name?: string;
@@ -903,6 +931,9 @@ export type DockerGetContainersOptions = {
903931
};
904932

905933
export type DockerGetContainerStatsOptions = {
934+
/** Needed if CPU and memory utilization should be computed relative to the limits applied to the container. */
935+
hostConfig?: DockerContainerDetails | DockerHostConfig;
936+
906937
/** Only get a single stat instead of waiting for 2 cycles. */
907938
oneShot?: boolean;
908939
};

0 commit comments

Comments
 (0)