mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-21 08:57:48 +02:00
feat: Add cumulative disk read/write totals to Disk I/O sheet (#2179)
This commit is contained in:
@@ -699,6 +699,8 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
|
|||||||
systemStats.DiskWritePs = stats.DiskWritePs
|
systemStats.DiskWritePs = stats.DiskWritePs
|
||||||
systemStats.DiskIO[0] = diskIORead
|
systemStats.DiskIO[0] = diskIORead
|
||||||
systemStats.DiskIO[1] = diskIOWrite
|
systemStats.DiskIO[1] = diskIOWrite
|
||||||
|
systemStats.DiskIOTotal[0] = d.ReadBytes
|
||||||
|
systemStats.DiskIOTotal[1] = d.WriteBytes
|
||||||
systemStats.DiskIoStats[0] = diskReadTime
|
systemStats.DiskIoStats[0] = diskReadTime
|
||||||
systemStats.DiskIoStats[1] = diskWriteTime
|
systemStats.DiskIoStats[1] = diskWriteTime
|
||||||
systemStats.DiskIoStats[2] = diskIoUtilPct
|
systemStats.DiskIoStats[2] = diskIoUtilPct
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type Stats struct {
|
|||||||
CpuCoresUsage Uint8Slice `json:"cpus,omitempty" cbor:"34,keyasint,omitempty"` // per-core busy usage [CPU0..]
|
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 %]
|
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
|
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.
|
// Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient.
|
||||||
@@ -99,8 +100,8 @@ type FsStats struct {
|
|||||||
Name string `json:"-"`
|
Name string `json:"-"`
|
||||||
DiskTotal float64 `json:"d" cbor:"0,keyasint"`
|
DiskTotal float64 `json:"d" cbor:"0,keyasint"`
|
||||||
DiskUsed float64 `json:"du" cbor:"1,keyasint"`
|
DiskUsed float64 `json:"du" cbor:"1,keyasint"`
|
||||||
TotalRead uint64 `json:"-"`
|
TotalRead uint64 `json:"tr,omitzero" cbor:"9,keyasint,omitzero"` // cumulative device read bytes
|
||||||
TotalWrite uint64 `json:"-"`
|
TotalWrite uint64 `json:"tw,omitzero" cbor:"10,keyasint,omitzero"` // cumulative device write bytes
|
||||||
DiskReadPs float64 `json:"r" cbor:"2,keyasint"`
|
DiskReadPs float64 `json:"r" cbor:"2,keyasint"`
|
||||||
DiskWritePs float64 `json:"w" cbor:"3,keyasint"`
|
DiskWritePs float64 `json:"w" cbor:"3,keyasint"`
|
||||||
MaxDiskReadPS float64 `json:"rm,omitempty" cbor:"-"`
|
MaxDiskReadPS float64 `json:"rm,omitempty" cbor:"-"`
|
||||||
|
|||||||
@@ -35,6 +35,20 @@ func TestStatsBatteryTransport(t *testing.T) {
|
|||||||
assert.Equal(t, stats.Batteries, decoded.Batteries)
|
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) {
|
func TestStatsBatteryNumericArrayUnmarshal(t *testing.T) {
|
||||||
var stats Stats
|
var stats Stats
|
||||||
require.NoError(t, json.Unmarshal([]byte(`{"bat":[50,4]}`), &stats))
|
require.NoError(t, json.Unmarshal([]byte(`{"bat":[50,4]}`), &stats))
|
||||||
|
|||||||
@@ -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.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[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.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 {
|
for i := range stats.DiskIoStats {
|
||||||
sum.MaxDiskIoStats[i] = max(sum.MaxDiskIoStats[i], stats.MaxDiskIoStats[i], stats.DiskIoStats[i])
|
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.DiskWriteBytes += value.DiskWriteBytes
|
||||||
fs.MaxDiskReadBytes = max(fs.MaxDiskReadBytes, value.MaxDiskReadBytes, value.DiskReadBytes)
|
fs.MaxDiskReadBytes = max(fs.MaxDiskReadBytes, value.MaxDiskReadBytes, value.DiskReadBytes)
|
||||||
fs.MaxDiskWriteBytes = max(fs.MaxDiskWriteBytes, value.MaxDiskWriteBytes, value.DiskWriteBytes)
|
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 {
|
for i := range value.DiskIoStats {
|
||||||
fs.DiskIoStats[i] += value.DiskIoStats[i]
|
fs.DiskIoStats[i] += value.DiskIoStats[i]
|
||||||
fs.MaxDiskIoStats[i] = max(fs.MaxDiskIoStats[i], value.MaxDiskIoStats[i], value.DiskIoStats[i])
|
fs.MaxDiskIoStats[i] = max(fs.MaxDiskIoStats[i], value.MaxDiskIoStats[i], value.DiskIoStats[i])
|
||||||
|
|||||||
@@ -57,6 +57,17 @@ export const diskDataFns = {
|
|||||||
(name: string) =>
|
(name: string) =>
|
||||||
({ stats }: SystemStatsRecord) =>
|
({ stats }: SystemStatsRecord) =>
|
||||||
stats?.efs?.[name]?.wbm ?? (stats?.efs?.[name]?.wm ?? 0) * 1024 * 1024,
|
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
|
// read/write time
|
||||||
readTime: dios(0),
|
readTime: dios(0),
|
||||||
readTimeMax: diosMax(0),
|
readTimeMax: diosMax(0),
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ export default memo(function DiskIOSheet({
|
|||||||
writeTimeFn = showMax ? diskDataFns.extraWriteTimeMax(extraFsName) : diskDataFns.extraWriteTime(extraFsName)
|
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
|
// I/O await functions, with extra fs variants if needed
|
||||||
let rAwaitFn = showMax ? diskDataFns.rAwaitMax : diskDataFns.rAwait
|
let rAwaitFn = showMax ? diskDataFns.rAwaitMax : diskDataFns.rAwait
|
||||||
let wAwaitFn = showMax ? diskDataFns.wAwaitMax : diskDataFns.wAwait
|
let wAwaitFn = showMax ? diskDataFns.wAwaitMax : diskDataFns.wAwait
|
||||||
@@ -70,12 +78,16 @@ export default memo(function DiskIOSheet({
|
|||||||
let hasUtilization = false
|
let hasUtilization = false
|
||||||
let hasAwait = false
|
let hasAwait = false
|
||||||
let hasWeightedIO = false
|
let hasWeightedIO = false
|
||||||
|
let hasCumulative = false
|
||||||
for (const record of chartData.systemStats ?? []) {
|
for (const record of chartData.systemStats ?? []) {
|
||||||
const dios = record.stats?.dios
|
const dios = record.stats?.dios
|
||||||
if ((dios?.at(2) ?? 0) > 0) hasUtilization = true
|
if ((dios?.at(2) ?? 0) > 0) hasUtilization = true
|
||||||
if ((dios?.at(3) ?? 0) > 0) hasAwait = true
|
if ((dios?.at(3) ?? 0) > 0) hasAwait = true
|
||||||
if ((dios?.at(5) ?? 0) > 0) hasWeightedIO = 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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,6 +270,69 @@ export default memo(function DiskIOSheet({
|
|||||||
/>
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{hasCumulative && (
|
||||||
|
<ChartCard
|
||||||
|
empty={dataEmpty}
|
||||||
|
grid={grid}
|
||||||
|
title={t`Cumulative Read`}
|
||||||
|
description={t`Cumulative data read since boot`}
|
||||||
|
className="min-h-auto"
|
||||||
|
>
|
||||||
|
<AreaChartDefault
|
||||||
|
chartData={chartData}
|
||||||
|
chartProps={{syncId: "c"}}
|
||||||
|
dataPoints={[
|
||||||
|
{
|
||||||
|
label: t`Read`,
|
||||||
|
dataKey: totalReadFn,
|
||||||
|
color: 1,
|
||||||
|
opacity: 0.4,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
tickFormatter={(val) => {
|
||||||
|
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}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ChartCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasCumulative && (
|
||||||
|
<ChartCard
|
||||||
|
empty={dataEmpty}
|
||||||
|
grid={grid}
|
||||||
|
title={t`Cumulative Write`}
|
||||||
|
description={t`Cumulative data written since boot`}
|
||||||
|
className="min-h-auto"
|
||||||
|
>
|
||||||
|
<AreaChartDefault
|
||||||
|
chartData={chartData}
|
||||||
|
chartProps={{syncId: "c"}}
|
||||||
|
dataPoints={[
|
||||||
|
{
|
||||||
|
label: t`Write`,
|
||||||
|
dataKey: totalWriteFn,
|
||||||
|
color: 3,
|
||||||
|
opacity: 0.4,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
tickFormatter={(val) => {
|
||||||
|
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}`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ChartCard>
|
||||||
|
)}
|
||||||
|
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
)}
|
)}
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|||||||
6
internal/site/src/types.d.ts
vendored
6
internal/site/src/types.d.ts
vendored
@@ -131,6 +131,8 @@ export interface SystemStats {
|
|||||||
dios?: [number, number, number, number, number, number]
|
dios?: [number, number, number, number, number, number]
|
||||||
/** max disk io stats */
|
/** max disk io stats */
|
||||||
diosm?: [number, number, number, number, number, number]
|
diosm?: [number, number, number, number, number, number]
|
||||||
|
/** cumulative device I/O bytes [total read, total write] */
|
||||||
|
diot?: [number, number]
|
||||||
/** network sent (mb) */
|
/** network sent (mb) */
|
||||||
ns: number
|
ns: number
|
||||||
/** network received (mb) */
|
/** network received (mb) */
|
||||||
@@ -201,6 +203,10 @@ export interface ExtraFsStats {
|
|||||||
dios?: [number, number, number, number, number, number]
|
dios?: [number, number, number, number, number, number]
|
||||||
/** max disk io stats */
|
/** max disk io stats */
|
||||||
diosm?: [number, number, number, number, number, number]
|
diosm?: [number, number, number, number, number, number]
|
||||||
|
/** cumulative device read bytes */
|
||||||
|
tr?: number
|
||||||
|
/** cumulative device write bytes */
|
||||||
|
tw?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContainerStatsRecord extends RecordModel {
|
export interface ContainerStatsRecord extends RecordModel {
|
||||||
|
|||||||
Reference in New Issue
Block a user