@@ -34,6 +34,34 @@ type diskDiscovery struct {
3434 ctx fsRegistrationContext
3535}
3636
37+ // prevDisk stores previous per-device disk counters for a given cache interval
38+ type prevDisk struct {
39+ readBytes uint64
40+ writeBytes uint64
41+ readTime uint64 // cumulative ms spent on reads (from ReadTime)
42+ writeTime uint64 // cumulative ms spent on writes (from WriteTime)
43+ ioTime uint64 // cumulative ms spent doing I/O (from IoTime)
44+ weightedIO uint64 // cumulative weighted ms (queue-depth × ms, from WeightedIO)
45+ readCount uint64 // cumulative read operation count
46+ writeCount uint64 // cumulative write operation count
47+ at time.Time
48+ }
49+
50+ // prevDiskFromCounter creates a prevDisk snapshot from a disk.IOCountersStat at time t.
51+ func prevDiskFromCounter (d disk.IOCountersStat , t time.Time ) prevDisk {
52+ return prevDisk {
53+ readBytes : d .ReadBytes ,
54+ writeBytes : d .WriteBytes ,
55+ readTime : d .ReadTime ,
56+ writeTime : d .WriteTime ,
57+ ioTime : d .IoTime ,
58+ weightedIO : d .WeightedIO ,
59+ readCount : d .ReadCount ,
60+ writeCount : d .WriteCount ,
61+ at : t ,
62+ }
63+ }
64+
3765// parseFilesystemEntry parses a filesystem entry in the format "device__customname"
3866// Returns the device/filesystem part and the custom name part
3967func parseFilesystemEntry (entry string ) (device , customName string ) {
@@ -581,16 +609,29 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
581609 prev , hasPrev := a.diskPrev [cacheTimeMs ][name ]
582610 if ! hasPrev {
583611 // Seed from agent-level fsStats if present, else seed from current
584- prev = prevDisk {readBytes : stats .TotalRead , writeBytes : stats .TotalWrite , at : stats .Time }
612+ prev = prevDisk {
613+ readBytes : stats .TotalRead ,
614+ writeBytes : stats .TotalWrite ,
615+ readTime : d .ReadTime ,
616+ writeTime : d .WriteTime ,
617+ ioTime : d .IoTime ,
618+ weightedIO : d .WeightedIO ,
619+ readCount : d .ReadCount ,
620+ writeCount : d .WriteCount ,
621+ at : stats .Time ,
622+ }
585623 if prev .at .IsZero () {
586- prev = prevDisk { readBytes : d . ReadBytes , writeBytes : d . WriteBytes , at : now }
624+ prev = prevDiskFromCounter ( d , now )
587625 }
588626 }
589627
590628 msElapsed := uint64 (now .Sub (prev .at ).Milliseconds ())
629+
630+ // Update per-interval snapshot
631+ a.diskPrev [cacheTimeMs ][name ] = prevDiskFromCounter (d , now )
632+
633+ // Avoid division by zero or clock issues
591634 if msElapsed < 100 {
592- // Avoid division by zero or clock issues; update snapshot and continue
593- a.diskPrev [cacheTimeMs ][name ] = prevDisk {readBytes : d .ReadBytes , writeBytes : d .WriteBytes , at : now }
594635 continue
595636 }
596637
@@ -602,15 +643,38 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
602643 // validate values
603644 if readMbPerSecond > 50_000 || writeMbPerSecond > 50_000 {
604645 slog .Warn ("Invalid disk I/O. Resetting." , "name" , d .Name , "read" , readMbPerSecond , "write" , writeMbPerSecond )
605- // Reset interval snapshot and seed from current
606- a.diskPrev [cacheTimeMs ][name ] = prevDisk {readBytes : d .ReadBytes , writeBytes : d .WriteBytes , at : now }
607646 // also refresh agent baseline to avoid future negatives
608647 a .initializeDiskIoStats (ioCounters )
609648 continue
610649 }
611650
612- // Update per-interval snapshot
613- a.diskPrev [cacheTimeMs ][name ] = prevDisk {readBytes : d .ReadBytes , writeBytes : d .WriteBytes , at : now }
651+ // These properties are calculated differently on different platforms,
652+ // but generally represent cumulative time spent doing reads/writes on the device.
653+ // This can surpass 100% if there are multiple concurrent I/O operations.
654+ // Linux kernel docs:
655+ // This is the total number of milliseconds spent by all reads (as
656+ // measured from __make_request() to end_that_request_last()).
657+ // https://www.kernel.org/doc/Documentation/iostats.txt (fields 4, 8)
658+ diskReadTime := utils .TwoDecimals (float64 (d .ReadTime - prev .readTime ) / float64 (msElapsed ) * 100 )
659+ diskWriteTime := utils .TwoDecimals (float64 (d .WriteTime - prev .writeTime ) / float64 (msElapsed ) * 100 )
660+
661+ // I/O utilization %: fraction of wall time the device had any I/O in progress (0-100).
662+ diskIoUtilPct := utils .TwoDecimals (float64 (d .IoTime - prev .ioTime ) / float64 (msElapsed ) * 100 )
663+
664+ // Weighted I/O: queue-depth weighted I/O time, normalized to interval (can exceed 100%).
665+ // Linux kernel field 11: incremented by iops_in_progress × ms_since_last_update.
666+ // Used to display queue depth. Multipled by 100 to increase accuracy of digit truncation (divided by 100 in UI).
667+ diskWeightedIO := utils .TwoDecimals (float64 (d .WeightedIO - prev .weightedIO ) / float64 (msElapsed ) * 100 )
668+
669+ // r_await / w_await: average time per read/write operation in milliseconds.
670+ // Equivalent to r_await and w_await in iostat.
671+ var rAwait , wAwait float64
672+ if deltaReadCount := d .ReadCount - prev .readCount ; deltaReadCount > 0 {
673+ rAwait = utils .TwoDecimals (float64 (d .ReadTime - prev .readTime ) / float64 (deltaReadCount ))
674+ }
675+ if deltaWriteCount := d .WriteCount - prev .writeCount ; deltaWriteCount > 0 {
676+ wAwait = utils .TwoDecimals (float64 (d .WriteTime - prev .writeTime ) / float64 (deltaWriteCount ))
677+ }
614678
615679 // Update global fsStats baseline for cross-interval correctness
616680 stats .Time = now
@@ -620,12 +684,24 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
620684 stats .DiskWritePs = writeMbPerSecond
621685 stats .DiskReadBytes = diskIORead
622686 stats .DiskWriteBytes = diskIOWrite
687+ stats .DiskIoStats [0 ] = diskReadTime
688+ stats .DiskIoStats [1 ] = diskWriteTime
689+ stats .DiskIoStats [2 ] = diskIoUtilPct
690+ stats .DiskIoStats [3 ] = rAwait
691+ stats .DiskIoStats [4 ] = wAwait
692+ stats .DiskIoStats [5 ] = diskWeightedIO
623693
624694 if stats .Root {
625695 systemStats .DiskReadPs = stats .DiskReadPs
626696 systemStats .DiskWritePs = stats .DiskWritePs
627697 systemStats .DiskIO [0 ] = diskIORead
628698 systemStats .DiskIO [1 ] = diskIOWrite
699+ systemStats .DiskIoStats [0 ] = diskReadTime
700+ systemStats .DiskIoStats [1 ] = diskWriteTime
701+ systemStats .DiskIoStats [2 ] = diskIoUtilPct
702+ systemStats .DiskIoStats [3 ] = rAwait
703+ systemStats .DiskIoStats [4 ] = wAwait
704+ systemStats .DiskIoStats [5 ] = diskWeightedIO
629705 }
630706 }
631707 }
0 commit comments