mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
fix: add missing CPU guest metrics to CPU responses (#1644)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - CPU load metrics now include guest runtime and hypervisor steal time percentages, exposed as additional fields in CPU load responses (per‑CPU). - Tests - Added comprehensive unit tests for CPU info and load generation, including edge cases and validation of the new guest and steal metrics. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -27,6 +27,16 @@ export class CpuLoad {
|
|||||||
description: 'The percentage of time the CPU spent servicing hardware interrupts.',
|
description: 'The percentage of time the CPU spent servicing hardware interrupts.',
|
||||||
})
|
})
|
||||||
percentIrq!: number;
|
percentIrq!: number;
|
||||||
|
|
||||||
|
@Field(() => Float, {
|
||||||
|
description: 'The percentage of time the CPU spent running virtual machines (guest).',
|
||||||
|
})
|
||||||
|
percentGuest!: number;
|
||||||
|
|
||||||
|
@Field(() => Float, {
|
||||||
|
description: 'The percentage of CPU time stolen by the hypervisor.',
|
||||||
|
})
|
||||||
|
percentSteal!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType({ implements: () => Node })
|
@ObjectType({ implements: () => Node })
|
||||||
|
|||||||
246
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
Normal file
246
api/src/unraid-api/graph/resolvers/info/cpu/cpu.service.spec.ts
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||||
|
|
||||||
|
vi.mock('systeminformation', () => ({
|
||||||
|
cpu: vi.fn().mockResolvedValue({
|
||||||
|
manufacturer: 'Intel',
|
||||||
|
brand: 'Core i7-9700K',
|
||||||
|
vendor: 'Intel',
|
||||||
|
family: '6',
|
||||||
|
model: '158',
|
||||||
|
stepping: '12',
|
||||||
|
revision: '',
|
||||||
|
voltage: '1.2V',
|
||||||
|
speed: 3.6,
|
||||||
|
speedMin: 800,
|
||||||
|
speedMax: 4900,
|
||||||
|
cores: 16,
|
||||||
|
physicalCores: 8,
|
||||||
|
processors: 1,
|
||||||
|
socket: 'LGA1151',
|
||||||
|
cache: {
|
||||||
|
l1d: 32768,
|
||||||
|
l1i: 32768,
|
||||||
|
l2: 262144,
|
||||||
|
l3: 12582912,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
cpuFlags: vi.fn().mockResolvedValue('fpu vme de pse tsc msr pae mce cx8'),
|
||||||
|
currentLoad: vi.fn().mockResolvedValue({
|
||||||
|
avgLoad: 2.5,
|
||||||
|
currentLoad: 25.5,
|
||||||
|
currentLoadUser: 15.0,
|
||||||
|
currentLoadSystem: 8.0,
|
||||||
|
currentLoadNice: 0.5,
|
||||||
|
currentLoadIdle: 74.5,
|
||||||
|
currentLoadIrq: 1.0,
|
||||||
|
currentLoadSteal: 0.2,
|
||||||
|
currentLoadGuest: 0.3,
|
||||||
|
rawCurrentLoad: 25500,
|
||||||
|
rawCurrentLoadUser: 15000,
|
||||||
|
rawCurrentLoadSystem: 8000,
|
||||||
|
rawCurrentLoadNice: 500,
|
||||||
|
rawCurrentLoadIdle: 74500,
|
||||||
|
rawCurrentLoadIrq: 1000,
|
||||||
|
rawCurrentLoadSteal: 200,
|
||||||
|
rawCurrentLoadGuest: 300,
|
||||||
|
cpus: [
|
||||||
|
{
|
||||||
|
load: 30.0,
|
||||||
|
loadUser: 20.0,
|
||||||
|
loadSystem: 10.0,
|
||||||
|
loadNice: 0,
|
||||||
|
loadIdle: 70.0,
|
||||||
|
loadIrq: 0,
|
||||||
|
loadSteal: 0,
|
||||||
|
loadGuest: 0,
|
||||||
|
rawLoad: 30000,
|
||||||
|
rawLoadUser: 20000,
|
||||||
|
rawLoadSystem: 10000,
|
||||||
|
rawLoadNice: 0,
|
||||||
|
rawLoadIdle: 70000,
|
||||||
|
rawLoadIrq: 0,
|
||||||
|
rawLoadSteal: 0,
|
||||||
|
rawLoadGuest: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
load: 21.0,
|
||||||
|
loadUser: 15.0,
|
||||||
|
loadSystem: 6.0,
|
||||||
|
loadNice: 0,
|
||||||
|
loadIdle: 79.0,
|
||||||
|
loadIrq: 0,
|
||||||
|
loadSteal: 0,
|
||||||
|
loadGuest: 0,
|
||||||
|
rawLoad: 21000,
|
||||||
|
rawLoadUser: 15000,
|
||||||
|
rawLoadSystem: 6000,
|
||||||
|
rawLoadNice: 0,
|
||||||
|
rawLoadIdle: 79000,
|
||||||
|
rawLoadIrq: 0,
|
||||||
|
rawLoadSteal: 0,
|
||||||
|
rawLoadGuest: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('CpuService', () => {
|
||||||
|
let service: CpuService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new CpuService();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateCpu', () => {
|
||||||
|
it('should return CPU information with correct structure', async () => {
|
||||||
|
const result = await service.generateCpu();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'info/cpu',
|
||||||
|
manufacturer: 'Intel',
|
||||||
|
brand: 'Core i7-9700K',
|
||||||
|
vendor: 'Intel',
|
||||||
|
family: '6',
|
||||||
|
model: '158',
|
||||||
|
stepping: 12,
|
||||||
|
revision: '',
|
||||||
|
voltage: '1.2V',
|
||||||
|
speed: 3.6,
|
||||||
|
speedmin: 800,
|
||||||
|
speedmax: 4900,
|
||||||
|
cores: 8,
|
||||||
|
threads: 16,
|
||||||
|
processors: 1,
|
||||||
|
socket: 'LGA1151',
|
||||||
|
cache: {
|
||||||
|
l1d: 32768,
|
||||||
|
l1i: 32768,
|
||||||
|
l2: 262144,
|
||||||
|
l3: 12582912,
|
||||||
|
},
|
||||||
|
flags: ['fpu', 'vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce', 'cx8'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing speed values', async () => {
|
||||||
|
const { cpu } = await import('systeminformation');
|
||||||
|
vi.mocked(cpu).mockResolvedValueOnce({
|
||||||
|
manufacturer: 'Intel',
|
||||||
|
brand: 'Core i7-9700K',
|
||||||
|
vendor: 'Intel',
|
||||||
|
family: '6',
|
||||||
|
model: '158',
|
||||||
|
stepping: '12',
|
||||||
|
revision: '',
|
||||||
|
voltage: '1.2V',
|
||||||
|
speed: 3.6,
|
||||||
|
cores: 16,
|
||||||
|
physicalCores: 8,
|
||||||
|
processors: 1,
|
||||||
|
socket: 'LGA1151',
|
||||||
|
cache: { l1d: 32768, l1i: 32768, l2: 262144, l3: 12582912 },
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
const result = await service.generateCpu();
|
||||||
|
|
||||||
|
expect(result.speedmin).toBe(-1);
|
||||||
|
expect(result.speedmax).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle cpuFlags error gracefully', async () => {
|
||||||
|
const { cpuFlags } = await import('systeminformation');
|
||||||
|
vi.mocked(cpuFlags).mockRejectedValueOnce(new Error('flags error'));
|
||||||
|
|
||||||
|
const result = await service.generateCpu();
|
||||||
|
|
||||||
|
expect(result.flags).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateCpuLoad', () => {
|
||||||
|
it('should return CPU utilization with all load metrics', async () => {
|
||||||
|
const result = await service.generateCpuLoad();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'info/cpu-load',
|
||||||
|
percentTotal: 25.5,
|
||||||
|
cpus: [
|
||||||
|
{
|
||||||
|
percentTotal: 30.0,
|
||||||
|
percentUser: 20.0,
|
||||||
|
percentSystem: 10.0,
|
||||||
|
percentNice: 0,
|
||||||
|
percentIdle: 70.0,
|
||||||
|
percentIrq: 0,
|
||||||
|
percentGuest: 0,
|
||||||
|
percentSteal: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
percentTotal: 21.0,
|
||||||
|
percentUser: 15.0,
|
||||||
|
percentSystem: 6.0,
|
||||||
|
percentNice: 0,
|
||||||
|
percentIdle: 79.0,
|
||||||
|
percentIrq: 0,
|
||||||
|
percentGuest: 0,
|
||||||
|
percentSteal: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include guest and steal metrics when present', async () => {
|
||||||
|
const { currentLoad } = await import('systeminformation');
|
||||||
|
vi.mocked(currentLoad).mockResolvedValueOnce({
|
||||||
|
avgLoad: 2.5,
|
||||||
|
currentLoad: 25.5,
|
||||||
|
currentLoadUser: 15.0,
|
||||||
|
currentLoadSystem: 8.0,
|
||||||
|
currentLoadNice: 0.5,
|
||||||
|
currentLoadIdle: 74.5,
|
||||||
|
currentLoadIrq: 1.0,
|
||||||
|
currentLoadSteal: 0.2,
|
||||||
|
currentLoadGuest: 0.3,
|
||||||
|
rawCurrentLoad: 25500,
|
||||||
|
rawCurrentLoadUser: 15000,
|
||||||
|
rawCurrentLoadSystem: 8000,
|
||||||
|
rawCurrentLoadNice: 500,
|
||||||
|
rawCurrentLoadIdle: 74500,
|
||||||
|
rawCurrentLoadIrq: 1000,
|
||||||
|
rawCurrentLoadSteal: 200,
|
||||||
|
rawCurrentLoadGuest: 300,
|
||||||
|
cpus: [
|
||||||
|
{
|
||||||
|
load: 30.0,
|
||||||
|
loadUser: 20.0,
|
||||||
|
loadSystem: 10.0,
|
||||||
|
loadNice: 0,
|
||||||
|
loadIdle: 70.0,
|
||||||
|
loadIrq: 0,
|
||||||
|
loadGuest: 2.5,
|
||||||
|
loadSteal: 1.2,
|
||||||
|
rawLoad: 30000,
|
||||||
|
rawLoadUser: 20000,
|
||||||
|
rawLoadSystem: 10000,
|
||||||
|
rawLoadNice: 0,
|
||||||
|
rawLoadIdle: 70000,
|
||||||
|
rawLoadIrq: 0,
|
||||||
|
rawLoadGuest: 2500,
|
||||||
|
rawLoadSteal: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.generateCpuLoad();
|
||||||
|
|
||||||
|
expect(result.cpus[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
percentGuest: 2.5,
|
||||||
|
percentSteal: 1.2,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -37,6 +37,8 @@ export class CpuService {
|
|||||||
percentNice: cpu.loadNice,
|
percentNice: cpu.loadNice,
|
||||||
percentIdle: cpu.loadIdle,
|
percentIdle: cpu.loadIdle,
|
||||||
percentIrq: cpu.loadIrq,
|
percentIrq: cpu.loadIrq,
|
||||||
|
percentGuest: cpu.loadGuest || 0,
|
||||||
|
percentSteal: cpu.loadSteal || 0,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ describe('MetricsResolver', () => {
|
|||||||
loadNice: 0,
|
loadNice: 0,
|
||||||
loadIdle: 70.0,
|
loadIdle: 70.0,
|
||||||
loadIrq: 0,
|
loadIrq: 0,
|
||||||
|
loadGuest: 0,
|
||||||
|
loadSteal: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
load: 21.0,
|
load: 21.0,
|
||||||
@@ -40,6 +42,8 @@ describe('MetricsResolver', () => {
|
|||||||
loadNice: 0,
|
loadNice: 0,
|
||||||
loadIdle: 79.0,
|
loadIdle: 79.0,
|
||||||
loadIrq: 0,
|
loadIrq: 0,
|
||||||
|
loadGuest: 0,
|
||||||
|
loadSteal: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user