feat(extension-api): adding stats container api (#6212)

* feat: adding stats container api

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* test(extension-loader): ensuring containerEngine statsContainer is working as expected

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* refactor(extension-api): resolving comments

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* Revert "refactor(extension-api): resolving comments"

This reverts commit b8b8b63098686bd4871eb1befa09dbee6e225c2d.

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* Apply suggestions from code review

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

---------

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
This commit is contained in:
axel7083 2024-03-07 11:01:35 +01:00 committed by GitHub
parent 9aa4169b50
commit fefd722f7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 187 additions and 0 deletions

View file

@ -2655,6 +2655,130 @@ declare module '@podman-desktop/api' {
title: string;
}
export interface PidsStats {
current?: number;
limit?: number;
}
export interface BlkioStatEntry {
major: number;
minor: number;
op: string;
value: number;
}
export interface BlkioStats {
io_service_bytes_recursive: BlkioStatEntry[];
io_serviced_recursive: BlkioStatEntry[];
io_queue_recursive: BlkioStatEntry[];
io_service_time_recursive: BlkioStatEntry[];
io_wait_time_recursive: BlkioStatEntry[];
io_merged_recursive: BlkioStatEntry[];
io_time_recursive: BlkioStatEntry[];
sectors_recursive: BlkioStatEntry[];
}
export interface StorageStats {
read_count_normalized?: number;
read_size_bytes?: number;
write_count_normalized?: number;
write_size_bytes?: number;
}
export interface NetworkStats {
[name: string]: {
rx_bytes: number;
rx_dropped: number;
rx_errors: number;
rx_packets: number;
tx_bytes: number;
tx_dropped: number;
tx_errors: number;
tx_packets: number;
endpoint_id?: string; // not used on linux
instance_id?: string; // not used on linux
};
}
export interface MemoryStats {
// Linux Memory Stats
stats: {
total_pgmajfault: number;
cache: number;
mapped_file: number;
total_inactive_file: number;
pgpgout: number;
rss: number;
total_mapped_file: number;
writeback: number;
unevictable: number;
pgpgin: number;
total_unevictable: number;
pgmajfault: number;
total_rss: number;
total_rss_huge: number;
total_writeback: number;
total_inactive_anon: number;
rss_huge: number;
hierarchical_memory_limit: number;
total_pgfault: number;
total_active_file: number;
active_anon: number;
total_active_anon: number;
total_pgpgout: number;
total_cache: number;
inactive_anon: number;
active_file: number;
pgfault: number;
inactive_file: number;
total_pgpgin: number;
};
max_usage: number;
usage: number;
failcnt: number;
limit: number;
// Windows Memory Stats
commitbytes?: number;
commitpeakbytes?: number;
privateworkingset?: number;
}
export interface CPUUsage {
percpu_usage: number[];
usage_in_usermode: number;
total_usage: number;
usage_in_kernelmode: number;
}
export interface ThrottlingData {
periods: number;
throttled_periods: number;
throttled_time: number;
}
export interface CPUStats {
cpu_usage: CPUUsage;
system_cpu_usage: number;
online_cpus: number;
throttling_data: ThrottlingData;
}
export interface ContainerStatsInfo {
engineId: string;
engineName: string;
read: string;
preread: string;
pids_stats?: PidsStats;
blkio_stats?: BlkioStats;
num_procs: number;
storage_stats?: StorageStats;
networks: NetworkStats;
memory_stats: MemoryStats;
cpu_stats: CPUStats;
precpu_stats: CPUStats;
}
export interface BuildImageOptions {
/**
* Specifies a Containerfile which contains instructions for building the image
@ -2975,6 +3099,32 @@ declare module '@podman-desktop/api' {
callback: (name: string, data: string) => void,
): Promise<void>;
/**
* Get the streamed stats of a running container.
*
* @param engineId the id of the engine managing the container, obtained from the result of {@link containerEngine.listContainers}
* @param id the id or name of the container on this engine, obtained from the result of {@link containerEngine.listContainers} or as the result of {@link containerEngine.createContainer}
* @param callback the function called when container stats info are emitted.
*
* @return A Promise resolving a {@link Disposable} that unregister the callback when called.
*
* @example
* Here is a usage example
* ```ts
* const disposable = await statsContainer('engineId', 'containerId', (stats: ContainerStatsInfo): void => {
* console.log('CPU Usage', stats.cpu_stats.cpu_usage.total_usage);
* });
*
* // When no longer needed
* disposable.dispose();
* ```
*/
export function statsContainer(
engineId: string,
id: string,
callback: (stats: ContainerStatsInfo) => void,
): Promise<Disposable>;
/**
* Stop an existing container
*

View file

@ -143,6 +143,8 @@ const containerProviderRegistry: ContainerProviderRegistry = {
listPods: vi.fn(),
stopPod: vi.fn(),
removePod: vi.fn(),
getContainerStats: vi.fn(),
stopContainerStats: vi.fn(),
listImages: vi.fn(),
listInfos: vi.fn(),
} as unknown as ContainerProviderRegistry;
@ -1654,6 +1656,29 @@ describe('window', async () => {
});
describe('containerEngine', async () => {
test('statsContainer ', async () => {
vi.mocked(containerProviderRegistry.getContainerStats).mockResolvedValue(99);
vi.mocked(containerProviderRegistry.stopContainerStats).mockResolvedValue(undefined);
const api = extensionLoader.createApi('/path', {});
expect(api).toBeDefined();
const disposable = await api.containerEngine.statsContainer('dummyEngineId', 'dummyContainerId', () => {});
expect(disposable).toBeDefined();
expect(disposable instanceof Disposable).toBeTruthy();
expect(containerProviderRegistry.getContainerStats).toHaveBeenCalledWith(
'dummyEngineId',
'dummyContainerId',
expect.anything(),
);
disposable.dispose();
await vi.waitUntil(() => {
expect(containerProviderRegistry.stopContainerStats).toHaveBeenCalledWith(99);
return true;
});
});
test('listImages without option ', async () => {
vi.mocked(containerProviderRegistry.listImages).mockResolvedValue([]);

View file

@ -1043,6 +1043,18 @@ export class ExtensionLoader {
startPod(engineId: string, podId: string): Promise<void> {
return containerProviderRegistry.startPod(engineId, podId);
},
async statsContainer(
engineId: string,
containerId: string,
callback: (stats: containerDesktopAPI.ContainerStatsInfo) => void,
): Promise<Disposable> {
const identifier = await containerProviderRegistry.getContainerStats(engineId, containerId, callback);
return Disposable.create(() => {
containerProviderRegistry.stopContainerStats(identifier).catch((err: unknown): void => {
console.error('Something went wrong while trying to stop container stats', err);
});
});
},
};
const authenticationProviderRegistry = this.authenticationProviderRegistry;