blkstat.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. //go:build linux
  2. // +build linux
  3. package blkstat
  4. /*
  5. blkstat.go
  6. This file extract the realtime disk I/O statistics from the Linux kernel
  7. by reading the /sys/block/<block_name>/stat file.
  8. Mostly you will only need to use ReadIOs, ReadSectors, WriteIOs and WriteSectors
  9. to get the I/O statistics. Note that the values are accumulated since the
  10. system booted, so you will need to calculate the difference between two
  11. consecutive calls to get the I/O rate.
  12. */
  13. import (
  14. "fmt"
  15. "os"
  16. "strconv"
  17. "strings"
  18. )
  19. type BlockStat struct {
  20. ReadIOs uint64
  21. ReadMerges uint64
  22. ReadSectors uint64
  23. ReadTicks uint64
  24. WriteIOs uint64
  25. WriteMerges uint64
  26. WriteSectors uint64
  27. WriteTicks uint64
  28. InFlight uint64
  29. IoTicks uint64
  30. TimeInQueue uint64
  31. }
  32. type InstallPosition struct {
  33. PCIEBusAddress string // PCIe bus address of the device
  34. SATAPort string // SATA port location of the device
  35. USBPort string // USB port in {hub_id}-{port_num} format
  36. NVMESlot string // NVMe slot information
  37. }
  38. // GetInstalledBus retrieves the PCIe bus address, SATA port location, USB port, and NVMe slot for a given block device.
  39. func GetInstalledBus(blockName string) (*InstallPosition, error) {
  40. linkPath := fmt.Sprintf("/sys/block/%s", blockName)
  41. realPath, err := os.Readlink(linkPath)
  42. if err != nil {
  43. return nil, fmt.Errorf("failed to read symlink for block device: %w", err)
  44. }
  45. // Extract PCIe bus address, SATA port location, USB port, and NVMe slot from the resolved path
  46. parts := strings.Split(realPath, "/")
  47. var pcieBusAddress, sataPort, usbPort, nvmeSlot string
  48. for i, part := range parts {
  49. if strings.HasPrefix(part, "pci") {
  50. pcieBusAddress = part
  51. } else if strings.HasPrefix(part, "ata") {
  52. sataPort = part
  53. } else if strings.HasPrefix(part, "usb") {
  54. if i+1 < len(parts) && strings.Contains(parts[i+1], ":") {
  55. usbPort = parts[i] // USB port in {hub_id}-{port_num} format
  56. }
  57. } else if strings.HasPrefix(part, "nvme") {
  58. if i+1 < len(parts) && strings.HasPrefix(parts[i+1], "nvme") {
  59. nvmeSlot = parts[i+1] // NVMe slot information
  60. }
  61. }
  62. }
  63. if pcieBusAddress == "" && sataPort == "" && usbPort == "" && nvmeSlot == "" {
  64. return nil, fmt.Errorf("failed to extract PCIe bus address, SATA port, USB port, or NVMe slot")
  65. }
  66. return &InstallPosition{
  67. PCIEBusAddress: pcieBusAddress,
  68. SATAPort: sataPort,
  69. USBPort: usbPort,
  70. NVMESlot: nvmeSlot,
  71. }, nil
  72. }
  73. // GetBlockStat retrieves the block statistics for a given block device.
  74. func GetBlockStat(blockName string) (*BlockStat, error) {
  75. statPath := fmt.Sprintf("/sys/block/%s/stat", blockName)
  76. data, err := os.ReadFile(statPath)
  77. if err != nil {
  78. return nil, fmt.Errorf("failed to read stat file: %w", err)
  79. }
  80. fields := strings.Fields(string(data))
  81. if len(fields) < 11 {
  82. return nil, fmt.Errorf("unexpected stat file format")
  83. }
  84. values := make([]uint64, 11)
  85. for i := 0; i < 11; i++ {
  86. values[i], err = strconv.ParseUint(fields[i], 10, 64)
  87. if err != nil {
  88. return nil, fmt.Errorf("failed to parse stat value: %w", err)
  89. }
  90. }
  91. return &BlockStat{
  92. ReadIOs: values[0],
  93. ReadMerges: values[1],
  94. ReadSectors: values[2],
  95. ReadTicks: values[3],
  96. WriteIOs: values[4],
  97. WriteMerges: values[5],
  98. WriteSectors: values[6],
  99. WriteTicks: values[7],
  100. InFlight: values[8],
  101. IoTicks: values[9],
  102. TimeInQueue: values[10],
  103. }, nil
  104. }