Overview
The IOCountersStat struct contains disk I/O performance statistics for a specific device, including read/write operations, bytes transferred, timing information, and device metadata. These are cumulative counters since system boot.
Struct Definition
type IOCountersStat struct {
ReadCount uint64 `json:"readCount"`
MergedReadCount uint64 `json:"mergedReadCount"`
WriteCount uint64 `json:"writeCount"`
MergedWriteCount uint64 `json:"mergedWriteCount"`
ReadBytes uint64 `json:"readBytes"`
WriteBytes uint64 `json:"writeBytes"`
ReadTime uint64 `json:"readTime"`
WriteTime uint64 `json:"writeTime"`
IopsInProgress uint64 `json:"iopsInProgress"`
IoTime uint64 `json:"ioTime"`
WeightedIO uint64 `json:"weightedIO"`
Name string `json:"name"`
SerialNumber string `json:"serialNumber"`
Label string `json:"label"`
}
Fields
Operation Counters
These fields count the number of I/O operations since boot.
ReadCount
- Type:
uint64
- Description: Total number of read operations completed
- Unit: Operations (IOPS when calculated as delta per second)
- Note: This is a cumulative counter; calculate delta between samples for rates
WriteCount
- Type:
uint64
- Description: Total number of write operations completed
- Unit: Operations (IOPS when calculated as delta per second)
MergedReadCount
- Type:
uint64
- Description: Number of read operations that were merged (Linux)
- Platform: Linux (always 0 on other platforms)
- Explanation: Adjacent read requests merged by the I/O scheduler for efficiency
- Use case: High merge ratios indicate sequential I/O patterns
MergedWriteCount
- Type:
uint64
- Description: Number of write operations that were merged (Linux)
- Platform: Linux (always 0 on other platforms)
- Explanation: Adjacent write requests merged by the I/O scheduler
Data Transfer
These fields track the amount of data transferred.
ReadBytes
- Type:
uint64
- Description: Total bytes read from the device
- Unit: Bytes
- Use case: Calculate read throughput (MB/s) by taking delta per time interval
WriteBytes
- Type:
uint64
- Description: Total bytes written to the device
- Unit: Bytes
- Use case: Calculate write throughput (MB/s) by taking delta per time interval
These fields track time spent on I/O operations.
ReadTime
- Type:
uint64
- Description: Time spent on read operations
- Unit: Milliseconds (Linux, macOS), varies by platform
- Note: Cumulative time, not wall clock time
- Use case: Calculate average read latency:
ReadTime / ReadCount
WriteTime
- Type:
uint64
- Description: Time spent on write operations
- Unit: Milliseconds (Linux, macOS), varies by platform
- Use case: Calculate average write latency:
WriteTime / WriteCount
IoTime
- Type:
uint64
- Description: Time spent doing I/O (device utilization time)
- Unit: Milliseconds (Linux)
- Platform: Linux, some Unix systems
- Note: This is NOT cumulative; it’s the time the device had I/O in progress
- Use case: Calculate device utilization percentage
WeightedIO
- Type:
uint64
- Description: Weighted time spent doing I/O
- Unit: Milliseconds (Linux)
- Platform: Linux
- Explanation: This field is incremented at each I/O start, I/O completion, I/O merge, or read of these stats by the number of I/O operations in progress multiplied by the time spent
- Use case: Calculate average queue depth and service times
Current State
IopsInProgress
- Type:
uint64
- Description: Number of I/O operations currently in progress (snapshot)
- Platform: Linux, some Unix systems
- Note: This is an instantaneous value, not a cumulative counter
- Use case: Monitor current I/O load
Name
- Type:
string
- Description: Device name
- Examples:
- Linux:
sda, nvme0n1, md0
- Windows:
PhysicalDrive0, C:
- macOS:
disk0, disk1s1
- Note: This is typically a short name, not a full path
SerialNumber
- Type:
string
- Description: Device serial number
- Platform availability: Linux, Windows, macOS (varies by device)
- Note: May be empty if serial cannot be determined or for virtual devices
- Use case: Uniquely identify physical disks across reboots
Label
- Type:
string
- Description: Device label or volume name
- Platform: Linux (device mapper), some systems
- Note: Often empty, especially for raw block devices
- Linux: Supports device mapper (DM) labels from
/sys/block/dm-*/dm/name
Methods
String
func (d IOCountersStat) String() string
Returns a JSON string representation of the struct.
Example output:
{
"readCount": 1234567,
"mergedReadCount": 456789,
"writeCount": 987654,
"mergedWriteCount": 234567,
"readBytes": 12345678900,
"writeBytes": 9876543210,
"readTime": 567890,
"writeTime": 123456,
"iopsInProgress": 2,
"ioTime": 345678,
"weightedIO": 456789,
"name": "sda",
"serialNumber": "WDXXXXXX",
"label": ""
}
IOCounters Function
func IOCounters(names ...string) (map[string]IOCountersStat, error)
func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error)
Parameters:
names - Optional variadic list of device names to query
- If empty: Returns stats for all devices
- If specified: Returns stats only for named devices
- Examples:
"sda", "nvme0n1", "PhysicalDrive0"
Returns:
map[string]IOCountersStat - Map of device name to I/O statistics
error - Error if stats cannot be retrieved
Note: The map key is the device name (same as the Name field)
Usage Examples
Get All Device I/O Stats
package main
import (
"fmt"
"log"
"github.com/shirou/gopsutil/v4/disk"
)
func main() {
io, err := disk.IOCounters()
if err != nil {
log.Fatal(err)
}
for name, counters := range io {
fmt.Printf("Device: %s\n", name)
fmt.Printf(" Reads: %v operations, %v MB\n",
counters.ReadCount, counters.ReadBytes/1024/1024)
fmt.Printf(" Writes: %v operations, %v MB\n",
counters.WriteCount, counters.WriteBytes/1024/1024)
if counters.IopsInProgress > 0 {
fmt.Printf(" I/O in progress: %v\n", counters.IopsInProgress)
}
if counters.SerialNumber != "" {
fmt.Printf(" Serial: %s\n", counters.SerialNumber)
}
fmt.Println()
}
}
Query Specific Devices
package main
import (
"fmt"
"log"
"github.com/shirou/gopsutil/v4/disk"
)
func main() {
// Get stats for specific devices
io, err := disk.IOCounters("sda", "sdb")
if err != nil {
log.Fatal(err)
}
for name, counters := range io {
fmt.Printf("%s: R=%v W=%v\n",
name, counters.ReadCount, counters.WriteCount)
}
}
Calculate I/O Rates (Throughput and IOPS)
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v4/disk"
)
func main() {
// Get initial counters
io1, _ := disk.IOCounters()
// Wait 1 second
time.Sleep(1 * time.Second)
// Get new counters
io2, _ := disk.IOCounters()
// Calculate rates
for name, counters2 := range io2 {
counters1 := io1[name]
// Calculate deltas (change over 1 second)
readOps := counters2.ReadCount - counters1.ReadCount
writeOps := counters2.WriteCount - counters1.WriteCount
readMB := float64(counters2.ReadBytes-counters1.ReadBytes) / 1024 / 1024
writeMB := float64(counters2.WriteBytes-counters1.WriteBytes) / 1024 / 1024
if readOps > 0 || writeOps > 0 {
fmt.Printf("%s:\n", name)
fmt.Printf(" Read: %v IOPS, %.2f MB/s\n", readOps, readMB)
fmt.Printf(" Write: %v IOPS, %.2f MB/s\n", writeOps, writeMB)
fmt.Printf(" Total: %v IOPS, %.2f MB/s\n",
readOps+writeOps, readMB+writeMB)
}
}
}
Calculate Average Latency
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v4/disk"
)
func main() {
// Get initial counters
io1, _ := disk.IOCounters()
time.Sleep(5 * time.Second)
// Get new counters
io2, _ := disk.IOCounters()
// Calculate average latency
for name, counters2 := range io2 {
counters1 := io1[name]
readOps := counters2.ReadCount - counters1.ReadCount
writeOps := counters2.WriteCount - counters1.WriteCount
readTime := counters2.ReadTime - counters1.ReadTime
writeTime := counters2.WriteTime - counters1.WriteTime
if readOps > 0 || writeOps > 0 {
fmt.Printf("%s:\n", name)
if readOps > 0 {
avgReadLatency := float64(readTime) / float64(readOps)
fmt.Printf(" Avg Read Latency: %.2f ms\n", avgReadLatency)
}
if writeOps > 0 {
avgWriteLatency := float64(writeTime) / float64(writeOps)
fmt.Printf(" Avg Write Latency: %.2f ms\n", avgWriteLatency)
}
}
}
}
Monitor Device Utilization (Linux)
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v4/disk"
)
func main() {
io1, _ := disk.IOCounters()
startTime := time.Now()
time.Sleep(1 * time.Second)
io2, _ := disk.IOCounters()
elapsed := time.Since(startTime).Seconds()
for name, counters2 := range io2 {
counters1 := io1[name]
// IoTime is in milliseconds
ioTimeDelta := float64(counters2.IoTime - counters1.IoTime)
// Calculate utilization percentage
utilization := (ioTimeDelta / (elapsed * 1000)) * 100
if utilization > 0 {
fmt.Printf("%s: %.2f%% utilized\n", name, utilization)
if utilization > 80 {
fmt.Printf(" WARNING: High disk utilization!\n")
}
}
}
}
Detect Active Disks
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v4/disk"
)
func main() {
io1, _ := disk.IOCounters()
time.Sleep(2 * time.Second)
io2, _ := disk.IOCounters()
fmt.Println("Active disks:")
for name, counters2 := range io2 {
counters1 := io1[name]
// Check if there was any I/O activity
if counters2.ReadCount > counters1.ReadCount ||
counters2.WriteCount > counters1.WriteCount {
fmt.Printf(" %s - Active\n", name)
}
}
}
Comprehensive I/O Monitor
package main
import (
"fmt"
"time"
"github.com/shirou/gopsutil/v4/disk"
)
func formatBytes(bytes uint64) string {
mb := float64(bytes) / 1024 / 1024
if mb < 1024 {
return fmt.Sprintf("%.2f MB", mb)
}
return fmt.Sprintf("%.2f GB", mb/1024)
}
func main() {
fmt.Println("Starting I/O monitor (press Ctrl+C to exit)...\n")
interval := 2 * time.Second
prevCounters, _ := disk.IOCounters()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
currCounters, _ := disk.IOCounters()
seconds := interval.Seconds()
for name, curr := range currCounters {
prev := prevCounters[name]
// Calculate deltas
readOps := curr.ReadCount - prev.ReadCount
writeOps := curr.WriteCount - prev.WriteCount
// Skip inactive devices
if readOps == 0 && writeOps == 0 {
continue
}
readBytes := curr.ReadBytes - prev.ReadBytes
writeBytes := curr.WriteBytes - prev.WriteBytes
// Calculate rates
readIOPS := float64(readOps) / seconds
writeIOPS := float64(writeOps) / seconds
readMBps := float64(readBytes) / seconds / 1024 / 1024
writeMBps := float64(writeBytes) / seconds / 1024 / 1024
fmt.Printf("%s:\n", name)
fmt.Printf(" Read: %.1f IOPS, %.2f MB/s\n", readIOPS, readMBps)
fmt.Printf(" Write: %.1f IOPS, %.2f MB/s\n", writeIOPS, writeMBps)
// Show total cumulative stats
fmt.Printf(" Total since boot: %s read, %s written\n",
formatBytes(curr.ReadBytes), formatBytes(curr.WriteBytes))
if curr.IopsInProgress > 0 {
fmt.Printf(" Queue depth: %v\n", curr.IopsInProgress)
}
fmt.Println()
}
prevCounters = currCounters
}
}
Linux
- All fields populated from
/proc/diskstats or /sys/block/*/stat
- Times are in milliseconds
- Merged counters available
- Device mapper (DM) labels supported
- Virtual devices (md, dm, loop) included
Windows
- Uses WMI queries or Windows Performance Counters
- Some fields may be 0 or unavailable depending on Windows version
- Device names like
PhysicalDrive0 or drive letters C:
- Times may be in different units or unavailable
macOS
- Uses
IOKit framework
- Device names like
disk0, disk1
- Some timing fields available, others may be 0
- Merged counters typically 0
BSD
- Platform-dependent implementation
- Basic counters typically available
- Advanced metrics (merges, timings) may be limited
IOPS (I/O Operations Per Second)
iops = (counter2 - counter1) / seconds
Throughput (MB/s)
throughput = (bytes2 - bytes1) / seconds / 1024 / 1024
Average Latency (ms)
avgLatency = (time2 - time1) / (count2 - count1)
Device Utilization (%) - Linux only
utilization = ((ioTime2 - ioTime1) / (seconds * 1000)) * 100
Average Request Size (KB)
avgSize = (bytes2 - bytes1) / (count2 - count1) / 1024
Important Notes
All counters are cumulative since boot. To calculate rates (IOPS, MB/s), you must:
- Sample counters at time T1
- Wait a known interval
- Sample counters at time T2
- Calculate delta and divide by time interval
The IopsInProgress field is a snapshot value (current queue depth), not a cumulative counter. All other numeric fields are cumulative.
Field availability varies by platform. On Windows and macOS, some fields (especially timing and merge counters) may be 0 or unavailable. Always check for zero values before calculating derived metrics.
Device names are short names (e.g., sda), not full paths (e.g., /dev/sda). When correlating with partition information, you may need to construct full paths or match by name.
See Also