diff --git a/agent/disk.go b/agent/disk.go index 4a5d1cdf..e76a999e 100644 --- a/agent/disk.go +++ b/agent/disk.go @@ -699,6 +699,8 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) { systemStats.DiskWritePs = stats.DiskWritePs systemStats.DiskIO[0] = diskIORead systemStats.DiskIO[1] = diskIOWrite + systemStats.DiskIOTotal[0] = d.ReadBytes + systemStats.DiskIOTotal[1] = d.WriteBytes systemStats.DiskIoStats[0] = diskReadTime systemStats.DiskIoStats[1] = diskWriteTime systemStats.DiskIoStats[2] = diskIoUtilPct diff --git a/internal/entities/system/system.go b/internal/entities/system/system.go index 1e85b98a..a73db34e 100644 --- a/internal/entities/system/system.go +++ b/internal/entities/system/system.go @@ -52,6 +52,7 @@ type Stats struct { CpuCoresUsage Uint8Slice `json:"cpus,omitempty" cbor:"34,keyasint,omitempty"` // per-core busy usage [CPU0..] DiskIoStats [6]float64 `json:"dios,omitzero" cbor:"35,keyasint,omitzero"` // [read time %, write time %, io utilization %, r_await ms, w_await ms, weighted io %] MaxDiskIoStats [6]float64 `json:"diosm,omitzero" cbor:"-"` // max values for DiskIoStats + DiskIOTotal [2]uint64 `json:"diot,omitzero" cbor:"38,keyasint,omitzero"` // [total read bytes, total write bytes] cumulative device counters } // Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient. @@ -99,8 +100,8 @@ type FsStats struct { Name string `json:"-"` DiskTotal float64 `json:"d" cbor:"0,keyasint"` DiskUsed float64 `json:"du" cbor:"1,keyasint"` - TotalRead uint64 `json:"-"` - TotalWrite uint64 `json:"-"` + TotalRead uint64 `json:"tr,omitzero" cbor:"9,keyasint,omitzero"` // cumulative device read bytes + TotalWrite uint64 `json:"tw,omitzero" cbor:"10,keyasint,omitzero"` // cumulative device write bytes DiskReadPs float64 `json:"r" cbor:"2,keyasint"` DiskWritePs float64 `json:"w" cbor:"3,keyasint"` MaxDiskReadPS float64 `json:"rm,omitempty" cbor:"-"` diff --git a/internal/entities/system/system_test.go b/internal/entities/system/system_test.go index 95c2a904..665019cb 100644 --- a/internal/entities/system/system_test.go +++ b/internal/entities/system/system_test.go @@ -35,6 +35,20 @@ func TestStatsBatteryTransport(t *testing.T) { assert.Equal(t, stats.Batteries, decoded.Batteries) } +func TestStatsDiskIOTotalAndFansTransport(t *testing.T) { + stats := Stats{ + DiskIOTotal: [2]uint64{437348527104, 331522465792}, + Fans: map[string]uint16{"cpu": 1200}, + } + + cborData, err := cbor.Marshal(stats) + require.NoError(t, err) + var decoded Stats + require.NoError(t, cbor.Unmarshal(cborData, &decoded)) + assert.Equal(t, stats.DiskIOTotal, decoded.DiskIOTotal) + assert.Equal(t, stats.Fans, decoded.Fans) +} + func TestStatsBatteryNumericArrayUnmarshal(t *testing.T) { var stats Stats require.NoError(t, json.Unmarshal([]byte(`{"bat":[50,4]}`), &stats)) diff --git a/internal/records/records.go b/internal/records/records.go index 8e8dd2e1..465d0bf2 100644 --- a/internal/records/records.go +++ b/internal/records/records.go @@ -266,6 +266,8 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats { sum.MaxBandwidth[1] = max(sum.MaxBandwidth[1], stats.MaxBandwidth[1], stats.Bandwidth[1]) sum.MaxDiskIO[0] = max(sum.MaxDiskIO[0], stats.MaxDiskIO[0], stats.DiskIO[0]) sum.MaxDiskIO[1] = max(sum.MaxDiskIO[1], stats.MaxDiskIO[1], stats.DiskIO[1]) + sum.DiskIOTotal[0] = max(sum.DiskIOTotal[0], stats.DiskIOTotal[0]) + sum.DiskIOTotal[1] = max(sum.DiskIOTotal[1], stats.DiskIOTotal[1]) for i := range stats.DiskIoStats { sum.MaxDiskIoStats[i] = max(sum.MaxDiskIoStats[i], stats.MaxDiskIoStats[i], stats.DiskIoStats[i]) } @@ -325,6 +327,8 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats { fs.DiskWriteBytes += value.DiskWriteBytes fs.MaxDiskReadBytes = max(fs.MaxDiskReadBytes, value.MaxDiskReadBytes, value.DiskReadBytes) fs.MaxDiskWriteBytes = max(fs.MaxDiskWriteBytes, value.MaxDiskWriteBytes, value.DiskWriteBytes) + fs.TotalRead = max(fs.TotalRead, value.TotalRead) + fs.TotalWrite = max(fs.TotalWrite, value.TotalWrite) for i := range value.DiskIoStats { fs.DiskIoStats[i] += value.DiskIoStats[i] fs.MaxDiskIoStats[i] = max(fs.MaxDiskIoStats[i], value.MaxDiskIoStats[i], value.DiskIoStats[i]) diff --git a/internal/site/src/components/routes/system/charts/disk-charts.tsx b/internal/site/src/components/routes/system/charts/disk-charts.tsx index 75d5e8ed..b258704a 100644 --- a/internal/site/src/components/routes/system/charts/disk-charts.tsx +++ b/internal/site/src/components/routes/system/charts/disk-charts.tsx @@ -57,6 +57,17 @@ export const diskDataFns = { (name: string) => ({ stats }: SystemStatsRecord) => stats?.efs?.[name]?.wbm ?? (stats?.efs?.[name]?.wm ?? 0) * 1024 * 1024, + // cumulative totals + totalRead: ({ stats }: SystemStatsRecord) => stats?.diot?.[0] ?? 0, + totalWrite: ({ stats }: SystemStatsRecord) => stats?.diot?.[1] ?? 0, + extraTotalRead: + (name: string) => + ({ stats }: SystemStatsRecord) => + stats?.efs?.[name]?.tr ?? 0, + extraTotalWrite: + (name: string) => + ({ stats }: SystemStatsRecord) => + stats?.efs?.[name]?.tw ?? 0, // read/write time readTime: dios(0), readTimeMax: diosMax(0), diff --git a/internal/site/src/components/routes/system/disk-io-sheet.tsx b/internal/site/src/components/routes/system/disk-io-sheet.tsx index fcaf208c..93acfec8 100644 --- a/internal/site/src/components/routes/system/disk-io-sheet.tsx +++ b/internal/site/src/components/routes/system/disk-io-sheet.tsx @@ -52,6 +52,14 @@ export default memo(function DiskIOSheet({ writeTimeFn = showMax ? diskDataFns.extraWriteTimeMax(extraFsName) : diskDataFns.extraWriteTime(extraFsName) } + // cumulative total functions, with extra fs variants if needed + let totalReadFn = diskDataFns.totalRead + let totalWriteFn = diskDataFns.totalWrite + if (extraFsName) { + totalReadFn = diskDataFns.extraTotalRead(extraFsName) + totalWriteFn = diskDataFns.extraTotalWrite(extraFsName) + } + // I/O await functions, with extra fs variants if needed let rAwaitFn = showMax ? diskDataFns.rAwaitMax : diskDataFns.rAwait let wAwaitFn = showMax ? diskDataFns.wAwaitMax : diskDataFns.wAwait @@ -70,12 +78,16 @@ export default memo(function DiskIOSheet({ let hasUtilization = false let hasAwait = false let hasWeightedIO = false + let hasCumulative = false for (const record of chartData.systemStats ?? []) { const dios = record.stats?.dios if ((dios?.at(2) ?? 0) > 0) hasUtilization = true if ((dios?.at(3) ?? 0) > 0) hasAwait = true if ((dios?.at(5) ?? 0) > 0) hasWeightedIO = true - if (hasUtilization && hasAwait && hasWeightedIO) { + if (!hasCumulative && (totalReadFn(record) > 0 || totalWriteFn(record) > 0)) { + hasCumulative = true + } + if (hasUtilization && hasAwait && hasWeightedIO && hasCumulative) { break } } @@ -258,6 +270,69 @@ export default memo(function DiskIOSheet({ /> )} + + {hasCumulative && ( + + { + const { value, unit } = formatBytes(val, false, userSettings.unitDisk, false) + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` + }} + contentFormatter={({ value }) => { + const { value: convertedValue, unit } = formatBytes(value, false, userSettings.unitDisk, false) + return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` + }} + /> + + )} + + {hasCumulative && ( + + { + const { value, unit } = formatBytes(val, false, userSettings.unitDisk, false) + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` + }} + contentFormatter={({ value }) => { + const { value: convertedValue, unit } = formatBytes(value, false, userSettings.unitDisk, false) + return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` + }} + /> + + )} + )} diff --git a/internal/site/src/types.d.ts b/internal/site/src/types.d.ts index cbb1ce9a..a35cb5a1 100644 --- a/internal/site/src/types.d.ts +++ b/internal/site/src/types.d.ts @@ -131,6 +131,8 @@ export interface SystemStats { dios?: [number, number, number, number, number, number] /** max disk io stats */ diosm?: [number, number, number, number, number, number] + /** cumulative device I/O bytes [total read, total write] */ + diot?: [number, number] /** network sent (mb) */ ns: number /** network received (mb) */ @@ -201,6 +203,10 @@ export interface ExtraFsStats { dios?: [number, number, number, number, number, number] /** max disk io stats */ diosm?: [number, number, number, number, number, number] + /** cumulative device read bytes */ + tr?: number + /** cumulative device write bytes */ + tw?: number } export interface ContainerStatsRecord extends RecordModel {