fix(@xen-orchestra/web&lite): handle graphs with null value (#8722)

This commit is contained in:
Mathieu
2025-06-27 16:54:36 +02:00
committed by GitHub
parent 541411586b
commit 0778c6c53c
36 changed files with 194 additions and 266 deletions

View File

@@ -660,67 +660,71 @@ type XapiStatsResponse<T> = {
interval: number
stats: T
}
type StatValues = (number | null)[]
type RecordStatValues = Record<string, StatValues>
export type XapiStatsGranularity = 'seconds' | 'minutes' | 'hours' | 'days'
export type XapiHostStats = XapiStatsResponse<{
cpus?: Record<string, number[]>
export type XapiHostStatsRaw = {
cpus?: RecordStatValues
ioThroughput?: {
r: Record<string, number[]>
w: Record<string, number[]>
r: RecordStatValues
w: RecordStatValues
}
iops?: {
r: Record<string, number[]>
w: Record<string, number[]>
r: RecordStatValues
w: RecordStatValues
}
iowait?: Record<string, number[]>
iowait?: RecordStatValues
latency?: {
r: Record<string, number[]>
w: Record<string, number[]>
r: RecordStatValues
w: RecordStatValues
}
load?: number[]
memory?: number[]
memoryFree?: number[]
load?: StatValues
memory?: StatValues
memoryFree?: StatValues
pifs?: {
rx: Record<string, number[]>
tx: Record<string, number[]>
rx: RecordStatValues
tx: RecordStatValues
}
}>
}
export type XapiHostStats = XapiStatsResponse<XapiHostStatsRaw>
export type XapiVmStats = XapiStatsResponse<{
cpus?: Record<string, number[]>
cpuUsage?: number[]
runstateFullrun?: number[]
runstateFullContention?: number[]
runstatePartialRun?: number[]
runstatePartialContention?: number[]
runstateConcurrencyHazard?: number[]
runstateBlocked?: number[]
export type XapiVmStatsRaw = {
cpus?: RecordStatValues
cpuUsage?: StatValues
runstateFullrun?: StatValues
runstateFullContention?: StatValues
runstatePartialRun?: StatValues
runstatePartialContention?: StatValues
runstateConcurrencyHazard?: StatValues
runstateBlocked?: StatValues
iops?: {
r: Record<string, number[]>
w: Record<string, number[]>
r: RecordStatValues
w: RecordStatValues
}
memory?: number[]
memoryFree?: number[]
memoryTarget?: number[]
memory?: StatValues
memoryFree?: StatValues
memoryTarget?: StatValues
vifs?: {
rx: Record<string, number[]>
tx: Record<string, number[]>
rx: RecordStatValues
tx: RecordStatValues
}
vifErrors?: {
rx: Record<string, number[]>
tx: Record<string, number[]>
rx: RecordStatValues
tx: RecordStatValues
}
xvds?: {
w?: Record<string, number[]>
r?: Record<string, number[]>
total?: Record<string, number[]>
w?: RecordStatValues
r?: RecordStatValues
total?: RecordStatValues
}
vbdLatency?: {
w: Record<string, number[]>
r: Record<string, number[]>
w: RecordStatValues
r: RecordStatValues
}
vbdIowait?: Record<string, number[]>
vbdInflight?: Record<string, number[]>
vbdAvgquSz?: Record<string, number[]>
}>
vbdIowait?: RecordStatValues
vbdInflight?: RecordStatValues
vbdAvgquSz?: RecordStatValues
}
export type XapiVmStats = XapiStatsResponse<XapiVmStatsRaw>

View File

@@ -4,7 +4,8 @@
- [Pool/system] Display pool information in pool/system tab (PR [#8660](https://github.com/vatesfr/xen-orchestra/pull/8660))
- [VM/Dashboard] Display VM information in dashboard tab (PR [#8529](https://github.com/vatesfr/xen-orchestra/pull/8529))
- [Tab/Network] Updated side panel in tab network behavior for mobile view (PR [#8688](https://github.com/vatesfr/xen-orchestra/pull/8688)
- [Tab/Network] Updated side panel in tab network behavior for mobile view (PR [#8688](https://github.com/vatesfr/xen-orchestra/pull/8688))
- [Stats] Fix graphs that were sometimes not displayed or displayed incorrectly (PR [#8722](https://github.com/vatesfr/xen-orchestra/pull/8722))
## **0.11.0** (2025-05-27)

View File

@@ -11,18 +11,19 @@
</template>
<script lang="ts" setup>
import { type HostStats, RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData, ValueFormatter } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import type { XapiHostStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: HostStats | undefined
stats: XapiHostStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -41,11 +42,11 @@ const cpuUsage = computed<LinearChartData>(() => {
return []
}
const cpus = Object.values(stats.cpus)
const cpus = Object.values(stats.cpus ?? {})
const result = cpus[0].map((_, hourIndex) => {
const timestamp = (timestampStart + hourIndex * RRD_STEP_FROM_STRING.hours) * 1000
const value = Math.round(cpus.reduce((total, cpu) => total + cpu[hourIndex], 0) / cpus.length)
const value = Math.round(cpus.reduce((total, cpu) => total + (cpu[hourIndex] ?? NaN), 0) / cpus.length)
return { timestamp, value }
})
@@ -59,7 +60,7 @@ const cpuUsage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = cpuUsage.value[0]?.data.map(item => item.value ?? 0) ?? []
const values = cpuUsage.value[0]?.data.map(item => item.value || 0) ?? []
if (values.length === 0) {
return 100
}

View File

@@ -11,18 +11,19 @@
</template>
<script lang="ts" setup>
import { type HostStats, RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import type { XapiHostStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: HostStats | undefined
stats: XapiHostStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -43,7 +44,7 @@ const loadAverage = computed<LinearChartData>(() => {
const result = load.map((value, index) => ({
timestamp: (timestampStart + index * RRD_STEP_FROM_STRING.hours) * 1000,
value: Number(value.toFixed(2)),
value: Number(value?.toFixed(2)),
}))
return [
@@ -55,7 +56,7 @@ const loadAverage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = loadAverage.value[0]?.data.map(item => item.value ?? 0) ?? []
const values = loadAverage.value[0]?.data.map(item => item.value || 0) ?? []
if (values.length === 0) {
return 10

View File

@@ -11,19 +11,20 @@
</template>
<script lang="ts" setup>
import { type HostStats, RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiHostStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: HostStats | undefined
stats: XapiHostStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -50,7 +51,7 @@ const networkUsage = computed<LinearChartData>(() => {
RRD_STEP_FROM_STRING.hours * (Object.values(pifs[type])[0].length - 1) +
index * RRD_STEP_FROM_STRING.hours) *
1000,
value: Object.values(pifs[type]).reduce((sum, values) => sum + values[index], 0),
value: Object.values(pifs[type]).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
})
@@ -59,7 +60,7 @@ const networkUsage = computed<LinearChartData>(() => {
const maxValue = computed(() => {
const values = networkUsage.value.reduce(
(acc, series) => [...acc, ...series.data.map(item => item.value ?? 0)],
(acc, series) => [...acc, ...series.data.map(item => item.value || 0)],
[] as number[]
)

View File

@@ -11,19 +11,20 @@
</template>
<script lang="ts" setup>
import { type HostStats, RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiHostStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: HostStats | undefined
stats: XapiHostStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -46,7 +47,7 @@ const memoryUsage = computed<LinearChartData>(() => {
timestamp:
(timestampStart - RRD_STEP_FROM_STRING.hours * (memoryTotal.length - 1) + index * RRD_STEP_FROM_STRING.hours) *
1000,
value: total - memoryFree[index],
value: (total ?? NaN) - (memoryFree[index] ?? NaN),
}))
return [
@@ -62,7 +63,7 @@ const maxValue = computed(() => {
return 1024 * 1024 * 1024 // 1 GB as fallback
}
return Math.max(...data.stats.memory, 0)
return Math.max(...data.stats.memory.map(value => value || 0), 0)
})
const byteFormatter = (value: number | null) => {

View File

@@ -18,10 +18,10 @@ import UiCard from '@/components/ui/UiCard.vue'
import UiCardTitle from '@/components/ui/UiCardTitle.vue'
import UiSpinner from '@/components/ui/UiSpinner.vue'
import type { Stat } from '@/composables/fetch-stats.composable'
import type { HostStats, VmStats } from '@/libs/xapi-stats'
import { useHostStore } from '@/stores/xen-api/host.store'
import { useVmStore } from '@/stores/xen-api/vm.store'
import { vTooltip } from '@core/directives/tooltip.directive'
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types/common'
import { computed, type ComputedRef, inject } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -30,12 +30,12 @@ const { t } = useI18n()
const { hasError: hasVmError } = useVmStore().subscribe()
const { hasError: hasHostError } = useHostStore().subscribe()
const vmStats = inject<ComputedRef<Stat<VmStats>[]>>(
const vmStats = inject<ComputedRef<Stat<XapiVmStatsRaw>[]>>(
'vmStats',
computed(() => [])
)
const hostStats = inject<ComputedRef<Stat<HostStats>[]>>(
const hostStats = inject<ComputedRef<Stat<XapiHostStatsRaw>[]>>(
'hostStats',
computed(() => [])
)

View File

@@ -16,12 +16,12 @@ import UiCard from '@/components/ui/UiCard.vue'
import UiCardSpinner from '@/components/ui/UiCardSpinner.vue'
import UiCardTitle from '@/components/ui/UiCardTitle.vue'
import { formatSize } from '@/libs/utils'
import type { HostStats } from '@/libs/xapi-stats'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats'
import { useHostStore } from '@/stores/xen-api/host.store'
import { UiCardTitleLevel } from '@/types/enums'
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys'
import type { LinearChartData } from '@core/types/chart'
import type { XapiHostStatsRaw } from '@vates/types'
import { computed, defineAsyncComponent, inject } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -45,13 +45,13 @@ const data = computed<LinearChartData>(() => {
rx: new Map<number, { timestamp: number; value: number }>(),
}
const addResult = (stats: HostStats, type: 'tx' | 'rx') => {
const networkStats = Object.values(stats.pifs[type])
const addResult = (stats: XapiHostStatsRaw, type: 'tx' | 'rx') => {
const networkStats = Object.values(stats.pifs?.[type] ?? {})
for (let hourIndex = 0; hourIndex < networkStats[0].length; hourIndex++) {
const timestamp = (timestampStart + hourIndex * RRD_STEP_FROM_STRING.hours) * 1000
const networkThroughput = networkStats.reduce((total, throughput) => total + throughput[hourIndex], 0)
const networkThroughput = networkStats.reduce((total, throughput) => total + (throughput[hourIndex] ?? NaN), 0)
results[type].set(timestamp, {
timestamp,
@@ -91,7 +91,7 @@ const isStatFetched = computed(() => {
const hostStats = host.stats
return (
hostStats != null &&
Object.values(hostStats.pifs.rx)[0].length + Object.values(hostStats.pifs.tx)[0].length ===
Object.values(hostStats.pifs?.rx ?? {})[0].length + Object.values(hostStats.pifs?.tx ?? {})[0].length ===
data.value[0].data.length + data.value[1].data.length
)
})
@@ -101,7 +101,7 @@ const isLoading = computed(() => isFetching.value || !isStatFetched.value)
const customMaxValue = computed(() => {
const values = data.value.reduce(
(acc, series) => [...acc, ...series.data.map(item => item.value ?? 0)],
(acc, series) => [...acc, ...series.data.map(item => item.value || 0)],
[] as number[]
)

View File

@@ -18,10 +18,10 @@ import UiCard from '@/components/ui/UiCard.vue'
import UiCardTitle from '@/components/ui/UiCardTitle.vue'
import UiSpinner from '@/components/ui/UiSpinner.vue'
import type { Stat } from '@/composables/fetch-stats.composable'
import type { HostStats, VmStats } from '@/libs/xapi-stats'
import { useHostStore } from '@/stores/xen-api/host.store'
import { useVmStore } from '@/stores/xen-api/vm.store'
import { vTooltip } from '@core/directives/tooltip.directive'
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types/common'
import type { ComputedRef } from 'vue'
import { computed, inject } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -31,12 +31,12 @@ const { t } = useI18n()
const { hasError: hasVmError } = useVmStore().subscribe()
const { hasError: hasHostError } = useHostStore().subscribe()
const vmStats = inject<ComputedRef<Stat<VmStats>[]>>(
const vmStats = inject<ComputedRef<Stat<XapiVmStatsRaw>[]>>(
'vmStats',
computed(() => [])
)
const hostStats = inject<ComputedRef<Stat<HostStats>[]>>(
const hostStats = inject<ComputedRef<Stat<XapiHostStatsRaw>[]>>(
'hostStats',
computed(() => [])
)

View File

@@ -15,12 +15,12 @@ import NoDataError from '@/components/NoDataError.vue'
import UiCard from '@/components/ui/UiCard.vue'
import UiCardSpinner from '@/components/ui/UiCardSpinner.vue'
import UiCardTitle from '@/components/ui/UiCardTitle.vue'
import type { HostStats } from '@/libs/xapi-stats'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats'
import { useHostStore } from '@/stores/xen-api/host.store'
import { UiCardTitleLevel } from '@/types/enums'
import { IK_HOST_LAST_WEEK_STATS } from '@/types/injection-keys'
import type { LinearChartData, ValueFormatter } from '@core/types/chart'
import type { XapiHostStatsRaw } from '@vates/types/common'
import { sumBy } from 'lodash-es'
import { computed, defineAsyncComponent, inject } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -44,13 +44,13 @@ const data = computed<LinearChartData>(() => {
const result = new Map<number, { timestamp: number; value: number }>()
const addResult = (stats: HostStats) => {
const cpus = Object.values(stats.cpus)
const addResult = (stats: XapiHostStatsRaw) => {
const cpus = Object.values(stats.cpus ?? {})
for (let hourIndex = 0; hourIndex < cpus[0].length; hourIndex++) {
const timestamp = (timestampStart + hourIndex * RRD_STEP_FROM_STRING.hours) * 1000
const cpuUsageSum = cpus.reduce((total, cpu) => total + cpu[hourIndex], 0)
const cpuUsageSum = cpus.reduce((total, cpu) => total + (cpu[hourIndex] ?? NaN), 0)
result.set(timestamp, {
timestamp,
@@ -82,7 +82,7 @@ const isStatFetched = computed(() => {
return stats.every(host => {
const hostStats = host.stats
return hostStats != null && Object.values(hostStats.cpus)[0].length === data.value[0].data.length
return hostStats != null && Object.values(hostStats.cpus ?? {})[0].length === data.value[0].data.length
})
})

View File

@@ -66,7 +66,7 @@ const data = computed<LinearChartData>(() => {
}
const memoryFree = stats.memoryFree
const memoryUsage = stats.memory.map((memory, index) => memory - memoryFree[index])
const memoryUsage = stats.memory.map((memory, index) => (memory ?? NaN) - (memoryFree?.[index] ?? NaN))
memoryUsage.forEach((value, hourIndex) => {
const timestamp = (timestampStart + hourIndex * RRD_STEP_FROM_STRING.hours) * 1000
@@ -94,7 +94,7 @@ const isStatFetched = computed(() => {
return stats.every(host => {
const hostStats = host.stats
return hostStats != null && hostStats.memory.length === data.value[0].data.length
return hostStats != null && hostStats.memory?.length === data.value[0].data.length
})
})

View File

@@ -12,19 +12,20 @@
</template>
<script lang="ts" setup>
import { RRD_STEP_FROM_STRING, type VmStats } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData, ValueFormatter } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import type { XapiVmStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: VmStats | undefined
stats: XapiVmStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -42,12 +43,12 @@ const cpuUsage = computed<LinearChartData>(() => {
return []
}
const cpus = Object.values(stats.cpus)
const cpus = Object.values(stats.cpus ?? {})
const result = cpus[0].map((_, hourIndex) => {
const timestamp = (timestampStart + hourIndex * RRD_STEP_FROM_STRING.hours) * 1000
const value = Math.round(cpus.reduce((total, cpu) => total + cpu[hourIndex], 0) / cpus.length)
const value = Math.round(cpus.reduce((total, cpu) => total + (cpu[hourIndex] ?? NaN), 0) / cpus.length)
return { timestamp, value }
})
@@ -61,7 +62,7 @@ const cpuUsage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = cpuUsage.value[0]?.data.map(item => item.value ?? 0) ?? []
const values = cpuUsage.value[0]?.data.map(item => item.value || 0) ?? []
if (values.length === 0) {
return 100

View File

@@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { RRD_STEP_FROM_STRING, type VmStats } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
@@ -20,12 +20,13 @@ import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiVmStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: VmStats | undefined
stats: XapiVmStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -57,7 +58,7 @@ const networkUsage = computed<LinearChartData>(() => {
(timestampStart - RRD_STEP_FROM_STRING.hours * (vifArrays[0].length - 1) + idx * RRD_STEP_FROM_STRING.hours) *
1000
const value = vifArrays.reduce((sum, arr) => sum + (arr[idx] ?? 0), 0)
const value = vifArrays.reduce((sum, arr) => sum + (arr[idx] ?? NaN), 0)
return {
timestamp,
@@ -77,7 +78,7 @@ const networkUsage = computed<LinearChartData>(() => {
const maxValue = computed(() => {
const values = networkUsage.value.reduce(
(acc, series) => [...acc, ...series.data.map(item => item.value ?? 0)],
(acc, series) => [...acc, ...series.data.map(item => item.value || 0)],
[] as number[]
)

View File

@@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { RRD_STEP_FROM_STRING, type VmStats } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
@@ -20,12 +20,13 @@ import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiVmStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: VmStats | undefined
stats: XapiVmStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -48,7 +49,7 @@ const ramUsage = computed<LinearChartData>(() => {
const result = ramTotal.map((total, index) => ({
timestamp:
(timestampStart - RRD_STEP_FROM_STRING.hours * (ramTotal.length - 1) + index * RRD_STEP_FROM_STRING.hours) * 1000,
value: total - memoryFree[index],
value: (total ?? NaN) - (memoryFree[index] ?? NaN),
}))
return [
@@ -64,7 +65,7 @@ const maxValue = computed(() => {
return 1024 * 1024 * 1024 // 1 GB as fallback
}
return Math.max(...data.stats.memory, 0)
return Math.max(...data.stats.memory.map(memoryValue => memoryValue || 0), 0)
})
const byteFormatter = (value: number | null) => {

View File

@@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { RRD_STEP_FROM_STRING, type VmStats } from '@/libs/xapi-stats.ts'
import { RRD_STEP_FROM_STRING } from '@/libs/xapi-stats.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
@@ -20,12 +20,13 @@ import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiVmStatsRaw } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: {
stats: VmStats | undefined
stats: XapiVmStatsRaw | undefined
timestampStart: number
}
loading: boolean
@@ -46,7 +47,7 @@ const vdiUsage = computed((): LinearChartData => {
}
const addVdiData = (type: 'r' | 'w') => {
const xvdsArrays = Object.values(xvds[type])
const xvdsArrays = Object.values(xvds[type] ?? {})
if (xvdsArrays.length === 0) {
return {
@@ -60,7 +61,7 @@ const vdiUsage = computed((): LinearChartData => {
(timestampStart - RRD_STEP_FROM_STRING.hours * (xvdsArrays[0].length - 1) + idx * RRD_STEP_FROM_STRING.hours) *
1000
const value = xvdsArrays.reduce((sum, arr) => sum + (arr[idx] ?? 0), 0)
const value = xvdsArrays.reduce((sum, arr) => sum + (arr[idx] ?? NaN), 0)
return {
timestamp,
@@ -80,7 +81,7 @@ const vdiUsage = computed((): LinearChartData => {
const maxValue = computed(() => {
const values = vdiUsage.value.reduce(
(acc, series) => [...acc, ...series.data.map(item => item.value ?? 0)],
(acc, series) => [...acc, ...series.data.map(item => item.value || 0)],
[] as number[]
)

View File

@@ -1,11 +1,6 @@
import {
type GRANULARITY,
type HostStats,
RRD_STEP_FROM_STRING,
type VmStats,
type XapiStatsResponse,
} from '@/libs/xapi-stats'
import { type GRANULARITY, RRD_STEP_FROM_STRING, type XapiStatsResponse } from '@/libs/xapi-stats'
import type { XenApiHost, XenApiVm } from '@/libs/xen-api/xen-api.types'
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types/common'
import { type Pausable, promiseTimeout, useTimeoutPoll } from '@vueuse/core'
import { computed, type ComputedRef, onUnmounted, ref, type Ref } from 'vue'
@@ -19,7 +14,7 @@ export type Stat<T> = {
export type GetStats<
T extends XenApiHost | XenApiVm,
S extends HostStats | VmStats = T extends XenApiHost ? HostStats : VmStats,
S extends XapiHostStatsRaw | XapiVmStatsRaw = T extends XenApiHost ? XapiHostStatsRaw : XapiVmStatsRaw,
> = (
uuid: T['uuid'],
granularity: GRANULARITY,
@@ -29,7 +24,7 @@ export type GetStats<
export type FetchedStats<
T extends XenApiHost | XenApiVm,
S extends HostStats | VmStats = T extends XenApiHost ? HostStats : VmStats,
S extends XapiHostStatsRaw | XapiVmStatsRaw = T extends XenApiHost ? XapiHostStatsRaw : XapiVmStatsRaw,
> = {
register: (object: T) => void
unregister: (object: T) => void
@@ -40,7 +35,7 @@ export type FetchedStats<
export default function useFetchStats<
T extends XenApiHost | XenApiVm,
S extends HostStats | VmStats = T extends XenApiHost ? HostStats : VmStats,
S extends XapiHostStatsRaw | XapiVmStatsRaw = T extends XenApiHost ? XapiHostStatsRaw : XapiVmStatsRaw,
>(getStats: GetStats<T, S>, granularity: GRANULARITY): FetchedStats<T, S> {
const stats = ref(new Map()) as Ref<Map<string, Stat<S>>>
const timestamp = ref<number[]>([0, 0])
@@ -66,7 +61,8 @@ export default function useFetchStats<
}
timestamp.value = [
newStats.endTimestamp - RRD_STEP_FROM_STRING[granularity] * (newStats.stats.memory.length - 1),
newStats.endTimestamp -
RRD_STEP_FROM_STRING[granularity] * ((Object.values(newStats.stats).find(Array.isArray)?.length ?? 0) - 1),
newStats.endTimestamp,
]

View File

@@ -82,22 +82,22 @@ export function parseRamUsage(
memory,
memoryFree,
}: {
memory: number[]
memoryFree?: number[]
},
memory?: (number | null)[]
memoryFree?: (number | null)[]
} = {},
{ nSequence = 4 } = {}
) {
const _nSequence = Math.min(memory.length, nSequence)
const _nSequence = Math.min(memory?.length ?? 0, nSequence)
let total = 0
let used = 0
memory = memory.slice(memory.length - _nSequence)
memory = memory?.slice(memory.length - _nSequence)
memoryFree = memoryFree?.slice(memoryFree.length - _nSequence)
memory.forEach((ram, key) => {
total += ram
used += ram - (memoryFree?.[key] ?? 0)
memory?.forEach((ram, key) => {
total += ram ?? NaN
used += (ram ?? NaN) - (memoryFree?.[key] ?? NaN)
})
const percentUsed = percent(used, total)

View File

@@ -1,5 +1,6 @@
import type XenApi from '@/libs/xen-api/xen-api'
import type { XenApiHost, XenApiStats } from '@/libs/xen-api/xen-api.types'
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types'
import { synchronized } from 'decorator-synchronized'
// eslint-disable-next-line import/default -- https://github.com/json5/json5/issues/287
import JSON5 from 'json5'
@@ -317,48 +318,6 @@ const STATS: { [key: string]: object } = {
// }
// }
export type VmStats = {
cpus: XenApiStats
iops: {
r: XenApiStats
w: XenApiStats
}
memory: number[]
memoryFree?: number[]
vifs: {
rx: XenApiStats
tx: XenApiStats
}
xvds: {
w: XenApiStats
r: XenApiStats
}
}
export type HostStats = {
cpus: XenApiStats
ioThroughput: {
r: XenApiStats
w: XenApiStats
}
iops: {
r: XenApiStats
w: XenApiStats
}
iowait: XenApiStats
latency: {
r: XenApiStats
w: XenApiStats
}
load: number[]
memory: number[]
memoryFree: number[]
pifs: {
rx: XenApiStats
tx: XenApiStats
}
}
export type XapiStatsResponse<T> = {
canBeExpired: boolean
endTimestamp: number
@@ -368,7 +327,7 @@ export type XapiStatsResponse<T> = {
type StatsByObject = {
[uuid: string]: {
[step: string]: XapiStatsResponse<HostStats | VmStats>
[step: string]: XapiStatsResponse<XapiHostStatsRaw | XapiVmStatsRaw>
}
}
@@ -434,7 +393,7 @@ export default class XapiStats {
}
@synchronized.withKey(({ host }: { host: XenApiHost }) => host.uuid)
async _getAndUpdateStats<T extends VmStats | HostStats>({
async _getAndUpdateStats<T extends XapiVmStatsRaw | XapiHostStatsRaw>({
abortSignal,
host,
ignoreExpired = false,

View File

@@ -726,6 +726,4 @@ export interface XenApiError extends Error {
data?: any
}
export type XenApiStats = Record<string, number[]>
/* eslint-enable no-use-before-define */

View File

@@ -1,5 +1,4 @@
import type { GetStats } from '@/composables/fetch-stats.composable'
import type { VmStats } from '@/libs/xapi-stats'
import { VM_POWER_STATE } from '@/libs/xen-api/xen-api.enums'
import type { XenApiHost, XenApiVm } from '@/libs/xen-api/xen-api.types'
import { useHostStore } from '@/stores/xen-api/host.store'
@@ -10,6 +9,7 @@ import { useVdiStore } from '@/stores/xen-api/vdi.store.ts'
import { useVmRawStore } from '@/stores/xen-api/vm-raw.store'
import { useXenApiStore } from '@/stores/xen-api.store'
import { createSubscribableStoreContext } from '@core/utils/create-subscribable-store-context.util'
import type { XapiVmStatsRaw } from '@vates/types/common'
import { defineStore } from 'pinia'
import { computed } from 'vue'
@@ -115,7 +115,7 @@ export const useVmStore = defineStore('xen-api-vm', () => {
throw new Error(`VM ${id} is halted or host could not be found.`)
}
return xenApiStore.getXapiStats()._getAndUpdateStats<VmStats>({
return xenApiStore.getXapiStats()._getAndUpdateStats<XapiVmStatsRaw>({
abortSignal,
host,
ignoreExpired,

View File

@@ -1,7 +1,7 @@
import type { FetchedStats, Stat } from '@/composables/fetch-stats.composable'
import type { HostStats, VmStats } from '@/libs/xapi-stats'
import type { XenApiHost } from '@/libs/xen-api/xen-api.types'
import type { ModalController } from '@/types/index'
import type { XapiHostStatsRaw, XapiVmStatsRaw } from '@vates/types/common'
import type { ComputedRef, InjectionKey } from 'vue'
export const IK_INPUT_TYPE = Symbol('IK_INPUT_TYPE') as InjectionKey<'select' | 'textarea'>
@@ -10,12 +10,12 @@ export const IK_CHECKBOX_TYPE = Symbol('IK_CHECKBOX_TYPE') as InjectionKey<'chec
export const IK_FORM_HAS_LABEL = Symbol('IK_FORM_HAS_LABEL') as InjectionKey<ComputedRef<boolean>>
export const IK_HOST_STATS = Symbol('IK_HOST_STATS') as InjectionKey<ComputedRef<Stat<HostStats>[]>>
export const IK_HOST_STATS = Symbol('IK_HOST_STATS') as InjectionKey<ComputedRef<Stat<XapiHostStatsRaw>[]>>
export const IK_VM_STATS = Symbol('IK_VM_STATS') as InjectionKey<ComputedRef<Stat<VmStats>[]>>
export const IK_VM_STATS = Symbol('IK_VM_STATS') as InjectionKey<ComputedRef<Stat<XapiVmStatsRaw>[]>>
export const IK_HOST_LAST_WEEK_STATS = Symbol('IK_HOST_LAST_WEEK_STATS') as InjectionKey<
FetchedStats<XenApiHost, HostStats>
FetchedStats<XenApiHost, XapiHostStatsRaw>
>
export const IK_BUTTON_GROUP_OUTLINED = Symbol('IK_BUTTON_GROUP_OUTLINED') as InjectionKey<ComputedRef<boolean>>

View File

@@ -69,7 +69,7 @@ const option = computed<EChartsOption>(() => ({
type: 'line',
name: series.label,
zlevel: index + 1,
data: series.data.map(item => [item.timestamp, item.value]),
data: series.data.map(item => [item.timestamp, isNaN(item.value ?? NaN) ? null : item.value]),
})),
}))
</script>

View File

@@ -25,6 +25,7 @@
"@types/d3-time-format": "^4.0.3",
"@types/lodash-es": "^4.17.12",
"@types/node": "^18.19.80",
"@vates/types": "^1.4.1",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"@vueuse/core": "^13.0.0",

View File

@@ -11,17 +11,17 @@
</template>
<script lang="ts" setup>
import type { XoHostStats } from '@/types/xo/host-stats.type.ts'
import type { LinearChartData, ValueFormatter } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import type { XapiHostStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoHostStats | null
data: XapiHostStats | null
loading: boolean
error?: string
}>()
@@ -41,7 +41,7 @@ const cpuUsage = computed<LinearChartData>(() => {
for (let hourIndex = 0; hourIndex < cpus[0].length; hourIndex++) {
const timestamp = (timestampStart + hourIndex * data.interval) * 1000
const cpuUsageSum = cpus.reduce((total, cpu) => total + cpu[hourIndex], 0)
const cpuUsageSum = cpus.reduce((total, cpu) => total + (cpu[hourIndex] ?? NaN), 0)
result.set(timestamp, {
timestamp,
@@ -58,7 +58,7 @@ const cpuUsage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = cpuUsage.value[0]?.data.map(item => item.value ?? 0) ?? []
const values = cpuUsage.value[0]?.data.map(item => item.value || 0) ?? []
if (values.length === 0) {
return 100

View File

@@ -11,17 +11,17 @@
</template>
<script lang="ts" setup>
import type { XoHostStats } from '@/types/xo/host-stats.type.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import type { XapiHostStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoHostStats | null
data: XapiHostStats | null
loading: boolean
error?: string
}>()
@@ -45,7 +45,7 @@ const loadAverage = computed<LinearChartData>(() => {
result.set(timestamp, {
timestamp,
value: Number(load.toFixed(2)),
value: Number(load?.toFixed(2)),
})
}
@@ -58,7 +58,7 @@ const loadAverage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = loadAverage.value[0]?.data.map(item => item.value ?? 0) ?? []
const values = loadAverage.value[0]?.data.map(item => item.value || 0) ?? []
if (values.length === 0) {
return 10
}

View File

@@ -11,18 +11,18 @@
</template>
<script lang="ts" setup>
import type { XoHostStats } from '@/types/xo/host-stats.type.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiHostStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoHostStats | null
data: XapiHostStats | null
loading: boolean
error?: string
}>()
@@ -38,7 +38,7 @@ const networkUsage = computed<LinearChartData>(() => {
const timestamps = Array.from(
{ length: data.stats.pifs.rx['0'].length },
(_, i) => data.endTimestamp * 1000 - (data.stats.pifs.rx['0'].length - 1 - i) * data.interval * 1000
(_, i) => data.endTimestamp * 1000 - ((data.stats.pifs?.rx?.['0'].length ?? 0) - 1 - i) * data.interval * 1000
)
const rxSeries = [
@@ -46,7 +46,7 @@ const networkUsage = computed<LinearChartData>(() => {
label: t('network-upload'),
data: timestamps.map((timestamp, index) => ({
timestamp,
value: Object.values(data.stats.pifs.rx).reduce((sum, values) => sum + values[index], 0),
value: Object.values(data.stats.pifs?.rx ?? {}).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
},
]
@@ -56,7 +56,7 @@ const networkUsage = computed<LinearChartData>(() => {
label: t('network-download'),
data: timestamps.map((timestamp, index) => ({
timestamp,
value: Object.values(data.stats.pifs.tx).reduce((sum, values) => sum + values[index], 0),
value: Object.values(data.stats.pifs?.tx ?? {}).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
},
]
@@ -65,7 +65,7 @@ const networkUsage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = networkUsage.value.flatMap(series => series.data.map(item => item.value ?? 0))
const values = networkUsage.value.flatMap(series => series.data.map(item => item.value || 0))
if (values.length === 0) {
return 100

View File

@@ -11,18 +11,18 @@
</template>
<script lang="ts" setup>
import type { XoHostStats } from '@/types/xo/host-stats.type.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiHostStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoHostStats | null
data: XapiHostStats | null
loading: boolean
error?: string
}>()
@@ -45,7 +45,7 @@ const ramUsage = computed<LinearChartData>(() => {
const timestamp = (timestampStart + hourIndex * data.interval) * 1000
const memoryTotal = memoryTotalValues[hourIndex]
const memoryFree = memoryFreeValues[hourIndex]
const memoryUsed = memoryTotal - memoryFree
const memoryUsed = (memoryTotal ?? NaN) - (memoryFree ?? NaN)
result.set(timestamp, {
timestamp,
@@ -66,7 +66,7 @@ const maxValue = computed(() => {
return 1024 * 1024 * 1024 // 1 GB as fallback
}
return Math.max(...data.stats.memory, 0)
return Math.max(...data.stats.memory.map(memoryValue => memoryValue || 0), 0)
})
const byteFormatter = (value: number | null) => {

View File

@@ -12,18 +12,18 @@
</template>
<script lang="ts" setup>
import type { XoVmStats } from '@/types/xo/vm-stats.type.ts'
import type { LinearChartData, ValueFormatter } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import type { XapiVmStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoVmStats | null
data: XapiVmStats | null
loading: boolean
error?: string
}>()
@@ -46,7 +46,7 @@ const cpuUsage = computed<LinearChartData>(() => {
for (let hourIndex = 0; hourIndex < cpus[0].length; hourIndex++) {
const timestamp = (timestampStart + hourIndex * data.interval) * 1000
const cpuUsageSum = cpus.reduce((total, cpu) => total + cpu[hourIndex], 0)
const cpuUsageSum = cpus.reduce((total, cpu) => total + (cpu[hourIndex] ?? NaN), 0)
result.set(timestamp, {
timestamp,
@@ -62,7 +62,7 @@ const cpuUsage = computed<LinearChartData>(() => {
})
const maxValue = computed(() => {
const values = cpuUsage.value[0]?.data.map(item => item.value ?? 0) ?? []
const values = cpuUsage.value[0]?.data.map(item => item.value || 0) ?? []
if (values.length === 0) {
return 100

View File

@@ -12,7 +12,6 @@
</template>
<script lang="ts" setup>
import type { XoVmStats } from '@/types/xo/vm-stats.type.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
@@ -20,11 +19,12 @@ import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiVmStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoVmStats | null
data: XapiVmStats | null
loading: boolean
error?: string
}>()
@@ -50,7 +50,7 @@ const networkUsage = computed<LinearChartData>(() => {
label: t('network-upload'),
data: timestamps.map((timestamp, index) => ({
timestamp,
value: Object.values(data.stats.vifs.rx).reduce((sum, values) => sum + values[index], 0),
value: Object.values(data.stats.vifs?.rx ?? {}).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
},
]
@@ -60,7 +60,7 @@ const networkUsage = computed<LinearChartData>(() => {
label: t('network-download'),
data: timestamps.map((timestamp, index) => ({
timestamp,
value: Object.values(data.stats.vifs.tx).reduce((sum, values) => sum + values[index], 0),
value: Object.values(data.stats.vifs?.tx ?? {}).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
},
]
@@ -70,7 +70,7 @@ const networkUsage = computed<LinearChartData>(() => {
const maxValue = computed(() => {
const values = networkUsage.value.reduce(
(acc, series) => [...acc, ...series.data.map(item => item.value ?? 0)],
(acc, series) => [...acc, ...series.data.map(item => item.value || 0)],
[] as number[]
)

View File

@@ -12,7 +12,6 @@
</template>
<script lang="ts" setup>
import type { XoVmStats } from '@/types/xo/vm-stats.type.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
@@ -20,11 +19,12 @@ import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiVmStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoVmStats | null
data: XapiVmStats | null
loading: boolean
error?: string
}>()
@@ -53,7 +53,7 @@ const ramUsage = computed<LinearChartData>(() => {
const ramFree = ramFreeValues[hourIndex]
const ramUsed = ramTotal - ramFree
const ramUsed = (ramTotal ?? NaN) - (ramFree ?? NaN)
result.set(timestamp, {
timestamp,
@@ -73,7 +73,7 @@ const maxValue = computed(() => {
return 1024 * 1024 * 1024 // 1 GB as fallback
}
return Math.max(...data.stats.memory, 0)
return Math.max(...data.stats.memory.map(memoryValue => memoryValue || 0), 0)
})
const byteFormatter = (value: number | null) => {

View File

@@ -12,7 +12,6 @@
</template>
<script lang="ts" setup>
import type { XoVmStats } from '@/types/xo/vm-stats.type.ts'
import type { LinearChartData } from '@core/types/chart.ts'
import VtsErrorNoDataHero from '@core/components/state-hero/VtsErrorNoDataHero.vue'
import VtsLoadingHero from '@core/components/state-hero/VtsLoadingHero.vue'
@@ -20,11 +19,12 @@ import VtsNoDataHero from '@core/components/state-hero/VtsNoDataHero.vue'
import UiCard from '@core/components/ui/card/UiCard.vue'
import UiCardTitle from '@core/components/ui/card-title/UiCardTitle.vue'
import { formatSizeRaw } from '@core/utils/size.util.ts'
import type { XapiVmStats } from '@vates/types/common'
import { computed, defineAsyncComponent } from 'vue'
import { useI18n } from 'vue-i18n'
const { data } = defineProps<{
data: XoVmStats | null
data: XapiVmStats | null
loading: boolean
error?: string
}>()
@@ -38,7 +38,7 @@ const vdiUsage = computed<LinearChartData>(() => {
return []
}
const readArrays = Object.values(data.stats.xvds.r)
const readArrays = Object.values(data.stats.xvds.r ?? {})
const timestamps = Array.from(
{ length: readArrays[0].length },
@@ -50,7 +50,7 @@ const vdiUsage = computed<LinearChartData>(() => {
label: t('read'),
data: timestamps.map((timestamp, index) => ({
timestamp,
value: Object.values(data.stats.xvds.r).reduce((sum, values) => sum + values[index], 0),
value: Object.values(data.stats.xvds?.r ?? {}).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
},
]
@@ -60,7 +60,7 @@ const vdiUsage = computed<LinearChartData>(() => {
label: t('write'),
data: timestamps.map((timestamp, index) => ({
timestamp,
value: Object.values(data.stats.xvds.w).reduce((sum, values) => sum + values[index], 0),
value: Object.values(data.stats.xvds?.w ?? {}).reduce((sum, values) => sum + (values[index] ?? NaN), 0),
})),
},
]
@@ -70,7 +70,7 @@ const vdiUsage = computed<LinearChartData>(() => {
const maxValue = computed(() => {
const values = vdiUsage.value.reduce(
(acc, series) => [...acc, ...series.data.map(item => item.value ?? 0)],
(acc, series) => [...acc, ...series.data.map(item => item.value || 0)],
[] as number[]
)

View File

@@ -1,16 +1,27 @@
import type { XoHost } from '@/types/xo/host.type.ts'
import type { XoPool } from '@/types/xo/pool.type.ts'
import type { XoVm } from '@/types/xo/vm.type.ts'
import { type GRANULARITY, RRD_STEP_FROM_STRING } from '@/utils/rest-api-stats.ts'
import type { XapiHostStats, XapiVmStats } from '@vates/types/common'
import { useFetch, useIntervalFn } from '@vueuse/core'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import { toValue } from 'vue'
import { toValue, type ShallowRef } from 'vue'
export function useFetchStats(
objectType: 'host' | 'vm' | 'pool',
id: MaybeRefOrGetter<XoVm['id'] | XoHost['id'] | XoPool['id']>,
type StatsByObjectType = {
host: { stats: XapiHostStats; id: XoHost['id'] }
vm: { stats: XapiVmStats; id: XoVm['id'] }
// @TODO: Add pool when implemented in the API
// pool: { stats: XapiPoolStats; id: XoPool['id'] }
}
export function useFetchStats<T extends keyof StatsByObjectType>(
objectType: T,
id: MaybeRefOrGetter<StatsByObjectType[T]['id']>,
granularity: MaybeRefOrGetter<GRANULARITY>
) {
): {
data: ShallowRef<StatsByObjectType[T]['stats']>
error: ShallowRef<any>
isFetching: Readonly<ShallowRef<boolean>>
} {
const result = useFetch(() => `/rest/v0/${objectType}s/${toValue(id)}/stats?granularity=${toValue(granularity)}`, {
immediate: false,
refetch: true,

View File

@@ -1,29 +0,0 @@
import type { XoStats } from '@/types/xo/xo-stats.type.ts'
export type XoHostStats = {
endTimestamp: number
interval: number
stats: {
cpus: XoStats
ioThroughput: {
r: XoStats
w: XoStats
}
iops: {
r: XoStats
w: XoStats
}
iowait: XoStats
latency: {
r: XoStats
w: XoStats
}
load: number[]
memory: number[]
memoryFree: number[]
pifs: {
rx: XoStats
tx: XoStats
}
}
}

View File

@@ -1,23 +0,0 @@
import type { XoStats } from '@/types/xo/xo-stats.type.ts'
export type XoVmStats = {
endTimestamp: number
interval: number
stats: {
cpus: XoStats
memory: number[]
iops: {
r: XoStats
w: XoStats
}
xvds: {
r: XoStats
w: XoStats
}
vifs: {
rx: XoStats
tx: XoStats
}
memoryFree: number[]
}
}

View File

@@ -1 +0,0 @@
export type XoStats = Record<string, number[]>

View File

@@ -13,6 +13,9 @@
### Bug fixes
- **XO 6:**
- [XO6/stats] Fix graphs that were sometimes not displayed or displayed incorrectly (PR [#8722](https://github.com/vatesfr/xen-orchestra/pull/8722))
> Users must be able to say: “I had this issue, happy to know it's fixed”
### Packages to release