disk.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package analytic
  2. import (
  3. "fmt"
  4. "github.com/dustin/go-humanize"
  5. "github.com/pkg/errors"
  6. "github.com/shirou/gopsutil/v4/disk"
  7. "github.com/spf13/cast"
  8. )
  9. func GetDiskStat() (DiskStat, error) {
  10. // Get all partitions
  11. partitions, err := disk.Partitions(false)
  12. if err != nil {
  13. return DiskStat{}, errors.Wrap(err, "error analytic getDiskStat - getting partitions")
  14. }
  15. var totalSize uint64
  16. var totalUsed uint64
  17. var partitionStats []PartitionStat
  18. // Track partitions to avoid double counting same partition with multiple mount points
  19. partitionUsage := make(map[string]*disk.UsageStat)
  20. // Get usage for each partition
  21. for _, partition := range partitions {
  22. usage, err := disk.Usage(partition.Mountpoint)
  23. if err != nil {
  24. // Skip partitions that can't be accessed
  25. continue
  26. }
  27. // Skip virtual filesystems and special filesystems
  28. if isVirtualFilesystem(partition.Fstype) {
  29. continue
  30. }
  31. // Skip OS-specific paths that shouldn't be counted
  32. if shouldSkipPath(partition.Mountpoint, partition.Device) {
  33. continue
  34. }
  35. // Create partition stat for display purposes
  36. partitionStat := PartitionStat{
  37. Mountpoint: partition.Mountpoint,
  38. Device: partition.Device,
  39. Fstype: partition.Fstype,
  40. Total: humanize.Bytes(usage.Total),
  41. Used: humanize.Bytes(usage.Used),
  42. Free: humanize.Bytes(usage.Free),
  43. Percentage: cast.ToFloat64(fmt.Sprintf("%.2f", usage.UsedPercent)),
  44. }
  45. partitionStats = append(partitionStats, partitionStat)
  46. // Only count each partition device once for total calculation
  47. // This handles cases where same partition is mounted multiple times (e.g., bind mounts, overlayfs)
  48. if _, exists := partitionUsage[partition.Device]; !exists {
  49. partitionUsage[partition.Device] = usage
  50. totalSize += usage.Total
  51. totalUsed += usage.Used
  52. }
  53. }
  54. // Calculate overall percentage
  55. var overallPercentage float64
  56. if totalSize > 0 {
  57. overallPercentage = cast.ToFloat64(fmt.Sprintf("%.2f", float64(totalUsed)/float64(totalSize)*100))
  58. }
  59. return DiskStat{
  60. Used: humanize.Bytes(totalUsed),
  61. Total: humanize.Bytes(totalSize),
  62. Percentage: overallPercentage,
  63. Writes: DiskWriteRecord[len(DiskWriteRecord)-1],
  64. Reads: DiskReadRecord[len(DiskReadRecord)-1],
  65. Partitions: partitionStats,
  66. }, nil
  67. }
  68. // isVirtualFilesystem checks if the filesystem type is virtual
  69. func isVirtualFilesystem(fstype string) bool {
  70. virtualFSTypes := map[string]bool{
  71. // Common virtual filesystems
  72. "proc": true,
  73. "sysfs": true,
  74. "devfs": true,
  75. "devpts": true,
  76. "tmpfs": true,
  77. "debugfs": true,
  78. "securityfs": true,
  79. "cgroup": true,
  80. "cgroup2": true,
  81. "pstore": true,
  82. "bpf": true,
  83. "tracefs": true,
  84. "hugetlbfs": true,
  85. "mqueue": true,
  86. "overlay": true,
  87. "autofs": true,
  88. "binfmt_misc": true,
  89. "configfs": true,
  90. "fusectl": true,
  91. "rpc_pipefs": true,
  92. "selinuxfs": true,
  93. "systemd-1": true,
  94. "none": true,
  95. // Network filesystems (should be excluded from total disk calculation)
  96. "nfs": true,
  97. "nfs4": true,
  98. "cifs": true,
  99. "smb": true,
  100. "smbfs": true,
  101. "afpfs": true,
  102. "webdav": true,
  103. "ftpfs": true,
  104. }
  105. // Check common virtual filesystems first
  106. if virtualFSTypes[fstype] {
  107. return true
  108. }
  109. // Check OS-specific additional virtual filesystems
  110. additionalFS := getAdditionalVirtualFilesystems()
  111. return additionalFS[fstype]
  112. }