mirror of
https://github.com/unraid/api.git
synced 2025-12-30 21:19:49 -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.',
|
||||
})
|
||||
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 })
|
||||
|
||||
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,
|
||||
percentIdle: cpu.loadIdle,
|
||||
percentIrq: cpu.loadIrq,
|
||||
percentGuest: cpu.loadGuest || 0,
|
||||
percentSteal: cpu.loadSteal || 0,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ describe('MetricsResolver', () => {
|
||||
loadNice: 0,
|
||||
loadIdle: 70.0,
|
||||
loadIrq: 0,
|
||||
loadGuest: 0,
|
||||
loadSteal: 0,
|
||||
},
|
||||
{
|
||||
load: 21.0,
|
||||
@@ -40,6 +42,8 @@ describe('MetricsResolver', () => {
|
||||
loadNice: 0,
|
||||
loadIdle: 79.0,
|
||||
loadIrq: 0,
|
||||
loadGuest: 0,
|
||||
loadSteal: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user