Skip to main content

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

Timing Information

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

Device Metadata

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
    }
}

Platform-Specific Behavior

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

Performance Metrics Calculations

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:
  1. Sample counters at time T1
  2. Wait a known interval
  3. Sample counters at time T2
  4. 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

Build docs developers (and LLMs) love