Skip to content

Commit

Permalink
fix: report metrics for stopped machines (podman-desktop#4787)
Browse files Browse the repository at this point in the history
* fix: report metrics for stopped machines

Fixes podman-desktop#4224

Signed-off-by: Jeff MAURY <[email protected]>
  • Loading branch information
jeffmaury authored Nov 16, 2023
1 parent ab9bbea commit 1d4e9bd
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 13 deletions.
94 changes: 92 additions & 2 deletions extensions/podman/src/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type { Mock } from 'vitest';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import * as extension from './extension';
import { getPodmanCli } from './podman-cli';
import type { Configuration } from '@podman-desktop/api';
import type { Configuration, ContainerEngineInfo } from '@podman-desktop/api';
import * as extensionApi from '@podman-desktop/api';
import * as fs from 'node:fs';
import { LoggerDelegator } from './util';
Expand All @@ -32,7 +32,7 @@ const config: Configuration = {
// not implemented
},
has: () => true,
update: () => Promise.resolve(),
update: vi.fn(),
};

const provider: extensionApi.Provider = {
Expand Down Expand Up @@ -159,6 +159,9 @@ vi.mock('@podman-desktop/api', async () => {
isMac: () => vi.fn(),
isLinux: () => vi.fn(),
},
containerEngine: {
info: vi.fn(),
},
};
});

Expand Down Expand Up @@ -844,3 +847,90 @@ test('handlecompatibilitymodesetting: disable compatibility called when configur
// Make sure that it returns that the compatibility mode has been disabled
expect(disableMessage).toHaveBeenCalledWith('Docker socket compatibility mode for Podman has been disabled.');
});

test('ensure started machine reports default configuration', async () => {
extension.initExtensionContext({ subscriptions: [] } as extensionApi.ExtensionContext);
vi.spyOn(extensionApi.process, 'exec').mockImplementation(
(_command, args) =>
new Promise<extensionApi.RunResult>(resolve => {
if (args[0] === 'machine' && args[1] === 'list') {
resolve({ stdout: JSON.stringify([fakeMachineJSON[0]]) } as extensionApi.RunResult);
} else if (args[0] === 'machine' && args[1] === 'inspect') {
resolve({} as extensionApi.RunResult);
} else if (args[0] === 'system' && args[1] === 'connection' && args[2] === 'list') {
resolve({
stdout: JSON.stringify([{ Name: fakeMachineJSON[0].Name, Default: true }]),
} as extensionApi.RunResult);
}
}),
);

await extension.updateMachines(provider);
expect(config.update).toBeCalledWith('machine.cpus', fakeMachineJSON[0].CPUs);
expect(config.update).toBeCalledWith('machine.memory', Number(fakeMachineJSON[0].Memory));
expect(config.update).toBeCalledWith('machine.diskSize', Number(fakeMachineJSON[0].DiskSize));
expect(config.update).toBeCalledWith('machine.cpusUsage', 0);
expect(config.update).toBeCalledWith('machine.memoryUsage', 0);
expect(config.update).toBeCalledWith('machine.diskSizeUsage', 0);
});

test('ensure started machine reports configuration', async () => {
extension.initExtensionContext({ subscriptions: [] } as extensionApi.ExtensionContext);
vi.spyOn(extensionApi.process, 'exec').mockImplementation(
(_command, args) =>
new Promise<extensionApi.RunResult>(resolve => {
if (args[0] === 'machine' && args[1] === 'list') {
resolve({ stdout: JSON.stringify([fakeMachineJSON[0]]) } as extensionApi.RunResult);
} else if (args[0] === 'machine' && args[1] === 'inspect') {
resolve({} as extensionApi.RunResult);
} else if (args[0] === 'system' && args[1] === 'connection' && args[2] === 'list') {
resolve({
stdout: JSON.stringify([{ Name: fakeMachineJSON[0].Name, Default: true }]),
} as extensionApi.RunResult);
}
}),
);

await extension.updateMachines(provider);
(extensionApi.containerEngine.info as Mock).mockResolvedValue({
cpus: 2,
cpuIdle: 99,
memory: 1048000000,
memoryUsed: 524000000,
diskSize: 250000000000,
diskUsed: 50000000000,
} as ContainerEngineInfo);
await extension.updateMachines(provider);
expect(config.update).toBeCalledWith('machine.cpus', fakeMachineJSON[0].CPUs);
expect(config.update).toBeCalledWith('machine.memory', Number(fakeMachineJSON[0].Memory));
expect(config.update).toBeCalledWith('machine.diskSize', Number(fakeMachineJSON[0].DiskSize));
expect(config.update).toBeCalledWith('machine.cpusUsage', 1);
expect(config.update).toBeCalledWith('machine.memoryUsage', 50);
expect(config.update).toBeCalledWith('machine.diskSizeUsage', 20);
});

test('ensure stopped machine reports configuration', async () => {
extension.initExtensionContext({ subscriptions: [] } as extensionApi.ExtensionContext);
vi.spyOn(extensionApi.process, 'exec').mockImplementation(
(_command, args) =>
new Promise<extensionApi.RunResult>(resolve => {
if (args[0] === 'machine' && args[1] === 'list') {
resolve({ stdout: JSON.stringify([fakeMachineJSON[1]]) } as extensionApi.RunResult);
} else if (args[0] === 'machine' && args[1] === 'inspect') {
resolve({} as extensionApi.RunResult);
} else if (args[0] === 'system' && args[1] === 'connection' && args[2] === 'list') {
resolve({
stdout: JSON.stringify([{ Name: fakeMachineJSON[1].Name, Default: true }]),
} as extensionApi.RunResult);
}
}),
);

await extension.updateMachines(provider);
expect(config.update).toBeCalledWith('machine.cpus', fakeMachineJSON[0].CPUs);
expect(config.update).toBeCalledWith('machine.memory', Number(fakeMachineJSON[0].Memory));
expect(config.update).toBeCalledWith('machine.diskSize', Number(fakeMachineJSON[0].DiskSize));
expect(config.update).toBeCalledWith('machine.cpusUsage', 0);
expect(config.update).toBeCalledWith('machine.memoryUsage', 0);
expect(config.update).toBeCalledWith('machine.diskSizeUsage', 0);
});
28 changes: 19 additions & 9 deletions extensions/podman/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export type MachineInfo = {
memoryUsage: number;
};

async function updateMachines(provider: extensionApi.Provider): Promise<void> {
export async function updateMachines(provider: extensionApi.Provider): Promise<void> {
// init machines available
let machineListOutput: string;
try {
Expand Down Expand Up @@ -169,15 +169,19 @@ async function updateMachines(provider: extensionApi.Provider): Promise<void> {
const userModeNetworking = isWindows() ? machine.UserModeNetworking : true;
podmanMachinesInfo.set(machine.Name, {
name: machine.Name,
memory: machineInfo?.memory,
cpus: machineInfo?.cpus,
diskSize: machineInfo?.diskSize,
memory: machineInfo ? machineInfo.memory : Number(machine.Memory),
cpus: machineInfo ? machineInfo.cpus : machine.CPUs,
diskSize: machineInfo ? machineInfo.diskSize : Number(machine.DiskSize),
userModeNetworking: userModeNetworking,
cpuUsage: machineInfo?.cpuIdle ? 100 - machineInfo?.cpuIdle : 0,
cpuUsage: machineInfo?.cpuIdle !== undefined ? 100 - machineInfo?.cpuIdle : 0,
diskUsage:
machineInfo?.diskUsed && machineInfo?.diskSize ? (machineInfo?.diskUsed * 100) / machineInfo?.diskSize : 0,
machineInfo?.diskUsed !== undefined && machineInfo?.diskSize > 0
? (machineInfo?.diskUsed * 100) / machineInfo?.diskSize
: 0,
memoryUsage:
machineInfo?.memory && machineInfo?.memoryUsed ? (machineInfo?.memoryUsed * 100) / machineInfo?.memory : 0,
machineInfo?.memory !== undefined && machineInfo?.memoryUsed > 0
? (machineInfo?.memoryUsed * 100) / machineInfo?.memory
: 0,
});

if (!podmanMachinesStatuses.has(machine.Name)) {
Expand Down Expand Up @@ -626,7 +630,9 @@ async function registerProviderFor(provider: extensionApi.Provider, machineInfo:
await containerConfiguration.update('machine.cpus', machineInfo.cpus);
await containerConfiguration.update('machine.memory', machineInfo.memory);
await containerConfiguration.update('machine.diskSize', machineInfo.diskSize);
await containerConfiguration.update('machine.cpuUsage', machineInfo.cpuUsage);
await containerConfiguration.update('machine.cpusUsage', machineInfo.cpuUsage);
await containerConfiguration.update('machine.memoryUsage', machineInfo.memoryUsage);
await containerConfiguration.update('machine.diskSizeUsage', machineInfo.diskUsage);

currentConnections.set(machineInfo.name, disposable);
storedExtensionContext.subscriptions.push(disposable);
Expand Down Expand Up @@ -754,8 +760,12 @@ export function initTelemetryLogger(): void {
telemetryLogger = extensionApi.env.createTelemetryLogger();
}

export async function activate(extensionContext: extensionApi.ExtensionContext): Promise<void> {
export function initExtensionContext(extensionContext: extensionApi.ExtensionContext) {
storedExtensionContext = extensionContext;
}

export async function activate(extensionContext: extensionApi.ExtensionContext): Promise<void> {
initExtensionContext(extensionContext);

initTelemetryLogger();

Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/plugin/configuration-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ConfigurationImpl implements containerDesktopAPI.Configuration {

// now look if we have this value
const localView = this.getLocalView();
if (localView[localKey]) {
if (localView[localKey] !== undefined) {
return localView[localKey];
} else if (defaultValue) {
// not there but we have a default value, then return it
Expand Down
2 changes: 1 addition & 1 deletion packages/renderer/src/lib/donut/Donut.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ $: tooltip = percent ? percent.toFixed(0) + '% ' + title + ' usage' : '';
text-anchor="middle"
font-size="{size / 4.5}"
dominant-baseline="central"
class="fill-gray-400">{value || ''}</text>
class="fill-gray-400">{value !== undefined ? value : ''}</text>
</svg>
</Tooltip>

0 comments on commit 1d4e9bd

Please sign in to comment.