Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@xen-orchestra/rest-api): expose VM stats #8359

Merged
merged 15 commits into from
Mar 6, 2025
28 changes: 28 additions & 0 deletions @vates/types/src/common.mts
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,31 @@ export const CERTIFICATE_TYPE = {
} as const

export type CERTIFICATE_TYPE = (typeof CERTIFICATE_TYPE)[keyof typeof CERTIFICATE_TYPE]

// ----- XAPI Stats

type XapiStatsResponse<T> = {
endTimestamp: number
interval: number
stats: T
}

export type XapiStatsGranularity = 'seconds' | 'minutes' | 'hours' | 'days'

export type XapiVmStats = XapiStatsResponse<{
cpus: Record<string, number[]>
iops: {
r: Record<string, number[]>
w: Record<string, number[]>
}
memory: number[]
memoryFree?: number[]
vifs: {
rx: Record<string, number[]>
tx: Record<string, number[]>
}
xvds: {
w: Record<string, number[]>
r: Record<string, number[]>
}
}>
1 change: 1 addition & 0 deletions @xen-orchestra/rest-api/.USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The REST API is based on the `TSOA` framework and therefore we use decorators a
@Routes('foo')
@Security('*')
@Response(401)
@Tags('vms')
@provide(Foo)
class Foo extends Controller {}
```
Expand Down
1 change: 1 addition & 0 deletions @xen-orchestra/rest-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The REST API is based on the `TSOA` framework and therefore we use decorators a
@Routes('foo')
@Security('*')
@Response(401)
@Tags('vms')
@provide(Foo)
class Foo extends Controller {}
```
Expand Down
145 changes: 145 additions & 0 deletions @xen-orchestra/rest-api/src/open-api/oa-examples/vm.oa-example.mts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,148 @@ export const vm = {
$poolId: 'b7569d99-30f8-178a-7d94-801de3e29b5b',
_xapiRef: 'OpaqueRef:ffdf8863-5331-9394-5c1b-d1db7de20a76',
}

export const vmStatsExample = {
endTimestamp: 1739958540,
interval: 60,
stats: {
cpus: {
'0': [
0.10329, 0.11014, 0.11465, 0.11352000000000001, 0.10727, 0.10522999999999999, 0.14641, 0.10748, 0.12263,
0.12427, 0.10453000000000001, 0.08896699999999999, 0.10916000000000001, 0.088654, 0.10563, 0.11789000000000001,
0.15555, 0.11686, 0.12934, 0.11566, 0.11984999999999998, 0.11495, 0.11615, 0.11151, 0.10779, 0.1222, 0.14765,
0.10556, 0.094178, 0.10196000000000001, 0.11102, 0.10920999999999999, 0.11478000000000001, 0.11789000000000001,
0.11563000000000001, 0.11739000000000001, 0.1362, 0.11498000000000001, 0.11574000000000001, 0.11762999999999998,
0.13917000000000002, 0.11061, 0.13446, 0.14161, 0.12666000000000002, 0.11344, 0.1374, 0.10191, 0.10052,
0.10721000000000001, 0.1026, 0.088527, 0.11909, 0.09958600000000001, 0.11494000000000001, 0.11477, 0.12858,
0.12194, 0.09911099999999999, 0.10887, 0.10215999999999999, 0.10981, 0.12030999999999999, 0.10596,
0.09993999999999999, 0.093929, 0.12344999999999999, 0.11797, 0.10663999999999998, 0.10486, 0.12799, 0.12128,
0.12048, 0.12692, 0.14813, 0.12057999999999999, 0.15755999999999998, 0.1343, 0.10422000000000001, 0.11711,
0.12649, 0.12182, 0.12440000000000001, 0.12294000000000001, 0.11839999999999999, 0.10195, 0.1127,
0.09663300000000001, 0.11427, 0.08722099999999999, 0.098883, 0.10908999999999999, 0.10545000000000002, 0.11312,
0.11949, 0.13102999999999998, 0.13713, 0.13815, 0.14027, 0.11937, 0.13204, 0.11929, 0.12465,
0.11314999999999999, 0.11397, 0.11687, 0.13404, 0.11865, 0.13029000000000002, 0.11449999999999999, 0.11681,
0.17134, 0.31911, 0.18656, 0.21658, 0.23914000000000002, 0.16117, 0.13316999999999998, 0.13163,
],
'1': [
0.29756, 0.30574, 0.29464, 0.30307, 0.30457, 0.29107, 0.33127999999999996, 0.30188, 0.3016, 0.32508, 0.29493,
0.31295, 0.28245000000000003, 0.28689000000000003, 0.29069999999999996, 0.29743, 0.2773, 0.27088, 0.27829,
0.28284, 0.30478, 0.28864, 0.3065, 0.3035, 0.3188, 0.34719, 0.3156, 0.28662000000000004, 0.29383, 0.29981,
0.31473999999999996, 0.29885, 0.27696, 0.28219, 0.27029, 0.29085, 0.2726, 0.28369, 0.26852, 0.27536, 0.27914,
0.28652, 0.29397999999999996, 0.28976, 0.27662, 0.29937, 0.30713, 0.31584999999999996, 0.30047, 0.30318,
0.30081, 0.33138, 0.30329, 0.30674999999999997, 0.30323, 0.30611, 0.30973, 0.30456, 0.29344000000000003,
0.31641, 0.33455, 0.31020000000000003, 0.28782, 0.31499, 0.29937, 0.30573, 0.33871, 0.31035, 0.29512, 0.29625,
0.28441, 0.27936, 0.28203999999999996, 0.26668, 0.27676, 0.28300000000000003, 0.28148, 0.28251, 0.30144,
0.28032, 0.26444, 0.28741, 0.28525, 0.27945000000000003, 0.27417, 0.33799, 0.32702, 0.32241000000000003,
0.30667, 0.32703, 0.31335999999999997, 0.30452999999999997, 0.32867999999999997, 0.29619, 0.28617, 0.27939,
0.28831999999999997, 0.27728, 0.28192999999999996, 0.27025, 0.26878, 0.28253, 0.28008, 0.26922999999999997,
0.29331999999999997, 0.29295, 0.28464, 0.2788, 0.28493, 0.29115, 0.28254999999999997, 0.28454999999999997,
0.27237, 0.28496, 0.28065999999999997, 0.27951, 0.2867, 0.30019999999999997, 0.28556,
],
},
memory: [
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000, 2147500000,
2147500000, 2147500000,
],
iops: {
r: {
a: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
},
w: {
a: [
0.097648, 0.081883, 0.081339, 0.080419, 0.080809, 0.081243, 0.081491, 0.081343, 0.1398, 0.18972, 0.10195,
0.060924, 0.08152, 0.081328, 0.08151, 0.081099, 0.13029, 0.098147, 0.08134, 0.08152, 0.081328, 0.081515,
0.081338, 0.08153, 0.081341, 0.1315, 0.18377, 0.095813, 0.080532, 0.080775, 0.081524, 0.08133, 0.081519,
0.081842, 0.083333, 0.095709, 0.081514, 0.081345, 0.081507, 0.08132, 0.11194, 0.033333, 0.083333, 0.13333,
0.093697, 0.081357, 0.081485, 0.081353, 0.081502, 0.081338, 0.082602, 0.083333, 0.083333, 0.092391, 0.080733,
0.11021, 0.052649, 0.081509, 0.082509, 0.083333, 0.083333, 0.083333, 0.091228, 0.08151, 0.081315, 0.081363,
0.081251, 0.083333, 0.083333, 0.083333, 0.1406, 0.081517, 0.081355, 0.081508, 0.081682, 0.083333, 0.083333,
0.13333, 0.16667, 0.0887, 0.081485, 0.081352, 0.081796, 0.083333, 0.083333, 0.13786, 0.19548, 0.083333,
0.088326, 0.081065, 0.088574, 0.075369, 0.083333, 0.13333, 0.033333, 0.083333, 0.087691, 0.081366, 0.1315,
0.082773, 0.1, 0.083333, 0.13333, 0.033333, 0.083333, 0.085806, 0.081349, 0.082845, 0.083333, 0.083333,
0.083333, 0.13333, 0.033333, 0.083333, 0.085255, 0.10573, 0.075677, 0.083333, 0.083333,
],
},
},
xvds: {
r: {
a: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
],
},
w: {
a: [
468.23, 403.66, 401.43, 397.66, 399.26, 401.04, 402.05, 401.45, 1187, 981.89, 554.12, 317.81, 402.17, 401.39,
402.13, 412.93, 862.5, 470.28, 401.44, 402.17, 401.39, 402.15, 401.43, 402.21, 401.44, 675.17, 1503.7, 460.72,
398.12, 399.12, 402.19, 401.39, 402.17, 403.49, 409.6, 460.29, 402.15, 401.46, 402.12, 401.35, 595.03, 204.8,
409.6, 887.47, 452.05, 401.5, 402.03, 401.49, 402.1, 401.43, 406.6, 409.6, 409.6, 446.7, 398.95, 587.95,
283.92, 402.13, 406.22, 409.6, 409.6, 409.6, 441.94, 402.13, 401.33, 401.53, 401.07, 409.6, 409.6, 409.6,
917.25, 402.16, 401.5, 402.12, 402.83, 409.6, 409.6, 1092.3, 887.47, 431.58, 402.03, 401.49, 403.3, 409.6,
409.6, 1048.4, 1204.4, 409.6, 430.05, 400.31, 431.07, 376.98, 409.6, 614.4, 204.8, 409.6, 427.45, 411.33,
870.18, 407.31, 546.13, 409.6, 614.4, 204.8, 409.6, 419.73, 401.47, 407.6, 409.6, 409.6, 409.6, 614.4, 204.8,
409.6, 417.47, 569.62, 378.24, 409.6, 409.6,
],
},
},
vifs: {
rx: {
'0': [
26.093, 32.014, 67.697, 58.753, 33.467, 37.491, 58.597, 30.445, 35.183, 80.516, 24.93, 31.059, 30.958, 82.292,
41.095, 42.146, 72.437, 79.315, 27.17, 34.682, 29.654, 31.031, 38.662, 83.563, 32.857, 45.96, 62.557, 29.964,
30.536, 30.067, 75.823, 37.306, 32.438, 34.156, 50.525, 41.277, 84.548, 39.422, 35.256, 43.333, 37.533,
30.967, 87.266, 52.716, 34.614, 35.031, 127.45, 33.611, 32.06, 32.095, 102.09, 37.166, 36.9, 39.71, 32.681,
28.122, 49.495, 90.197, 28.328, 32.967, 35.467, 33.679, 38.205, 43.533, 33.467, 29.404, 62.77, 84.119, 30.865,
32.131, 35.253, 32.386, 29.647, 79.757, 45.637, 31.853, 63.045, 43.291, 35.722, 31.07, 36.9, 35.9, 28.501,
81.165, 34.123, 32.011, 68.544, 43.598, 42.074, 26.279, 106.37, 41.084, 26.758, 41.401, 42.847, 40.596,
56.853, 37.168, 97.783, 30.315, 33.118, 38.833, 42.22, 52.069, 36.294, 37.828, 107.76, 39.728, 46.087, 31.574,
34.889, 89.279, 624.37, 128.66, 116.32, 120.88, 76.055, 57.768, 57.251,
],
},
tx: {
'0': [
0, 0, 0, 0, 0, 1.7333, 1.7333, 0, 1.7333, 1.7333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.7333, 1.2667, 0,
5.9211, 3.0122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2.6608, 0.80588, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3, 0, 0, 1.7333, 1.7333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.9737, 0.026261, 0, 1.7333, 1.7333, 0, 0,
0, 0, 0, 0, 3.475, 2.4583, 0, 0, 0, 0, 1.7333, 1.2667, 0, 1.4958, 1.9709, 0, 0, 0, 0, 1.7333, 0, 0, 0, 0, 0,
0, 0, 1.7333, 1.7333, 0, 1.4832, 0.25009, 1.7333, 0, 0, 0, 0, 1.7333,
],
},
},
memoryFree: [
1952358400, 1952358400, 1952358400, 1952256000, 1952460800, 1952563200, 1952460800, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952051200, 1952051200, 1952051200, 1952256000,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952051200, 1952256000,
1952358400, 1952256000, 1952051200, 1952256000, 1952358400, 1952153600, 1952051200, 1952256000, 1952358400,
1952358400, 1952358400, 1952256000, 1952051200, 1952256000, 1952358400, 1952358400, 1952358400, 1952358400,
1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952256000, 1952153600,
1952256000, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400, 1952358400,
1952256000, 1952153600,
],
},
}
4 changes: 4 additions & 0 deletions @xen-orchestra/rest-api/src/rest-api/rest-api.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export class RestApi {
this.#xoApp = xoApp
}

get xoApp() {
return this.#xoApp
}

authenticateUser(...args: Parameters<XoApp['authenticateUser']>) {
return this.#xoApp.authenticateUser(...args)
}
Expand Down
4 changes: 3 additions & 1 deletion @xen-orchestra/rest-api/src/rest-api/rest-api.type.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { XoUser, XapiXoRecord } from '@vates/types/xo'
import type { XapiVmStats, XapiStatsGranularity } from '@vates/types/common'
import type { XoUser, XapiXoRecord, XoVm } from '@vates/types/xo'

export type XoApp = {
authenticateUser: (
Expand All @@ -11,5 +12,6 @@ export type XoApp = {
type: T['type'],
opts?: { filter?: string; limit?: number }
) => Record<T['id'], T>
getXapiVmStats: (vmId: XoVm['id'], granularity?: XapiStatsGranularity) => Promise<XapiVmStats>
runWithApiContext: (user: XoUser, fn: () => void) => Promise<unknown>
}
37 changes: 32 additions & 5 deletions @xen-orchestra/rest-api/src/vms/vm.controller.mts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Example, Get, Path, Query, Request, Response, Route, Security } from 'tsoa'
import { Example, Get, Path, Query, Request, Response, Route, Security, Tags } from 'tsoa'
import { Request as ExRequest } from 'express'
import { inject } from 'inversify'
import { incorrectState, invalidParameters } from 'xo-common/api-errors.js'
import { provide } from 'inversify-binding-decorators'
import type { XoVm } from '@vates/types'
import type { XapiStatsGranularity, XapiVmStats, XoVm } from '@vates/types'

import { partialVms, vm, vmIds } from '../open-api/oa-examples/vm.oa-example.mjs'
import { partialVms, vm, vmIds, vmStatsExample } from '../open-api/oa-examples/vm.oa-example.mjs'
import { RestApi } from '../rest-api/rest-api.mjs'
import type { Unbrand, WithHref } from '../helpers/helper.type.mjs'
import { XapiXoController } from '../abstract-classes/xapi-xo-controller.mjs'

@Route('vms')
@Security('*')
@Response(401)
@Response(401, 'Authentication required')
@Tags('vms')
// the `provide` decorator is mandatory on class that injects/receives dependencies.
// It automatically bind the class to the IOC container that handles dependency injection
@provide(VmController)
Expand Down Expand Up @@ -44,8 +46,33 @@ export class VmController extends XapiXoController<XoVm> {
*/
@Example(vm)
@Get('{id}')
@Response(404)
@Response(404, 'VM not found')
getVm(@Path() id: string): Unbrand<XoVm> {
return this.getObject(id as XoVm['id'])
}

/**
*
* VM must be running
*
* @example id "f07ab729-c0e8-721c-45ec-f11276377030"
*/
@Example(vmStatsExample)
@Get('{id}/stats')
@Response(404, 'VM not found')
@Response(422, 'Invalid granularity, VM is halted or host could not be found')
async getVmStats(@Path() id: string, @Query() granularity?: XapiStatsGranularity): Promise<XapiVmStats> {
try {
return await this.restApi.xoApp.getXapiVmStats(id as XoVm['id'], granularity)
} catch (error) {
if (
incorrectState.is(error, {
property: 'resident_on',
})
) {
/* throw */ invalidParameters(`VM ${id} is halted or host could not be found.`, error)
}
throw error
}
}
}
7 changes: 6 additions & 1 deletion CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

> Users must be able to say: “Nice enhancement, I'm eager to test it”

- **Migrated REST API endpoints**:
- `/rest/v0/vms/<vm-id>/stats` (PR [#8359](https://github.com/vatesfr/xen-orchestra/pull/8359))

### Bug fixes

> Users must be able to say: “I had this issue, happy to know it's fixed”
Expand All @@ -31,6 +34,8 @@

<!--packages-start-->

- @xen-orchestra/rest-api patch
- @vates/types minor
- @xen-orchestra/rest-api minor
- xo-server patch

<!--packages-end-->
8 changes: 7 additions & 1 deletion packages/xo-server/src/xapi-stats.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import sum from 'lodash/sum.js'
import uniq from 'lodash/uniq.js'
import zipWith from 'lodash/zipWith.js'
import { BaseError } from 'make-error'
import { incorrectState } from 'xo-common/api-errors.js'
import { parseDateTime } from '@xen-orchestra/xapi'

export class FaultyGranularity extends BaseError {}
Expand Down Expand Up @@ -388,7 +389,12 @@ export default class XapiStats {
const vm = xapi.getObject(vmId)
const host = vm.$resident_on
if (!host) {
throw new Error(`VM ${vmId} is halted or host could not be found.`)
/* throw */ incorrectState({
actual: host,
expected: '<host-uuid>',
object: vm,
property: 'resident_on',
})
}

return this._getAndUpdateStats(xapi, {
Expand Down
5 changes: 2 additions & 3 deletions packages/xo-server/src/xo-mixins/rest-api.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1357,13 +1357,12 @@ export default class RestApi {
)

api.get(
'/:collection(vms|hosts)/:object/stats',
'/:collection(hosts)/:object/stats',
wrap(async (req, res) => {
const object = req.object
const method = object.type === 'VM' ? 'getXapiVmStats' : 'getXapiHostStats'
const granularity = req.query.granularity

const result = await app[method](object.id, granularity)
const result = await app.getXapiHostStats(object.id, granularity)
return res.json(result)
})
)
Expand Down
Loading