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 {