From fefd722f7e8b5beead71773f751bafaf64d174fe Mon Sep 17 00:00:00 2001 From: axel7083 <42176370+axel7083@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:01:35 +0100 Subject: [PATCH] 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> --- packages/extension-api/src/extension-api.d.ts | 150 ++++++++++++++++++ .../main/src/plugin/extension-loader.spec.ts | 25 +++ packages/main/src/plugin/extension-loader.ts | 12 ++ 3 files changed, 187 insertions(+) diff --git a/packages/extension-api/src/extension-api.d.ts b/packages/extension-api/src/extension-api.d.ts index 75459c7bdfe..f90545f857b 100644 --- a/packages/extension-api/src/extension-api.d.ts +++ b/packages/extension-api/src/extension-api.d.ts @@ -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; + /** + * 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; + /** * Stop an existing container * diff --git a/packages/main/src/plugin/extension-loader.spec.ts b/packages/main/src/plugin/extension-loader.spec.ts index 27dad004b6e..5fd436e7e48 100644 --- a/packages/main/src/plugin/extension-loader.spec.ts +++ b/packages/main/src/plugin/extension-loader.spec.ts @@ -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([]); diff --git a/packages/main/src/plugin/extension-loader.ts b/packages/main/src/plugin/extension-loader.ts index c1203cee983..88a1a9ffb3d 100644 --- a/packages/main/src/plugin/extension-loader.ts +++ b/packages/main/src/plugin/extension-loader.ts @@ -1043,6 +1043,18 @@ export class ExtensionLoader { startPod(engineId: string, podId: string): Promise { return containerProviderRegistry.startPod(engineId, podId); }, + async statsContainer( + engineId: string, + containerId: string, + callback: (stats: containerDesktopAPI.ContainerStatsInfo) => void, + ): Promise { + 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;