pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/system/info_fs_linux.go (about) 1 package system 2 3 // ////////////////////////////////////////////////////////////////////////////////// // 4 // // 5 // Copyright (c) 2022 ESSENTIAL KAOS // 6 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 7 // // 8 // ////////////////////////////////////////////////////////////////////////////////// // 9 10 import ( 11 "bufio" 12 "errors" 13 "path/filepath" 14 "strconv" 15 "syscall" 16 "time" 17 18 "pkg.re/essentialkaos/ek.v12/strutil" 19 ) 20 21 // ////////////////////////////////////////////////////////////////////////////////// // 22 23 // Path to file with disk info in procfs 24 var procDiskStatsFile = "/proc/diskstats" 25 26 // Path to mtab file 27 var mtabFile = "/etc/mtab" 28 29 // ////////////////////////////////////////////////////////////////////////////////// // 30 31 // GetFSUsage returns info about mounted filesystems 32 func GetFSUsage() (map[string]*FSUsage, error) { 33 s, closer, err := getFileScanner(mtabFile) 34 35 if err != nil { 36 return nil, err 37 } 38 39 defer closer() 40 41 return parseFSInfo(s, true) 42 } 43 44 // GetIOStats returns IO statistics as map device -> statistics 45 func GetIOStats() (map[string]*IOStats, error) { 46 s, closer, err := getFileScanner(procDiskStatsFile) 47 48 if err != nil { 49 return nil, err 50 } 51 52 defer closer() 53 54 return parseIOStats(s) 55 } 56 57 // GetIOUtil returns slice (device -> utilization) with IO utilization 58 func GetIOUtil(duration time.Duration) (map[string]float64, error) { 59 io1, err := GetIOStats() 60 61 if err != nil { 62 return nil, err 63 } 64 65 time.Sleep(duration) 66 67 io2, err := GetIOStats() 68 69 if err != nil { 70 return nil, err 71 } 72 73 return CalculateIOUtil(io1, io2, duration), nil 74 } 75 76 // CalculateIOUtil calculates IO utilization for all devices 77 func CalculateIOUtil(io1, io2 map[string]*IOStats, duration time.Duration) map[string]float64 { 78 if io1 == nil || io2 == nil { 79 return nil 80 } 81 82 result := make(map[string]float64) 83 84 // convert duration to jiffies 85 itv := uint64(duration / (time.Millisecond * 10)) 86 87 for device := range io1 { 88 if io1[device] == nil || io2[device] == nil { 89 continue 90 } 91 92 util := float64(io2[device].IOMs-io1[device].IOMs) / float64(itv) * getHZ() 93 94 util /= 10.0 95 96 if util > 100.0 { 97 util = 100.0 98 } 99 100 result[device] = util 101 } 102 103 return result 104 } 105 106 // ////////////////////////////////////////////////////////////////////////////////// // 107 108 // codebeat:disable[LOC,ABC,CYCLO] 109 110 // parseIOStats parses IO stats data 111 func parseIOStats(s *bufio.Scanner) (map[string]*IOStats, error) { 112 var err error 113 114 iostats := make(map[string]*IOStats) 115 116 for s.Scan() { 117 text := s.Text() 118 device := strutil.ReadField(text, 2, true) 119 120 if len(device) > 3 { 121 if device[:3] == "ram" || device[:3] == "loo" { 122 continue 123 } 124 } 125 126 ios := &IOStats{} 127 128 ios.ReadComplete, err = strconv.ParseUint(strutil.ReadField(text, 3, true), 10, 64) 129 130 if err != nil { 131 return nil, errors.New("Can't parse field 3 as unsigned integer in " + procDiskStatsFile) 132 } 133 134 ios.ReadMerged, err = strconv.ParseUint(strutil.ReadField(text, 4, true), 10, 64) 135 136 if err != nil { 137 return nil, errors.New("Can't parse field 4 as unsigned integer in " + procDiskStatsFile) 138 } 139 140 ios.ReadSectors, err = strconv.ParseUint(strutil.ReadField(text, 5, true), 10, 64) 141 142 if err != nil { 143 return nil, errors.New("Can't parse field 5 as unsigned integer in " + procDiskStatsFile) 144 } 145 146 ios.ReadMs, err = strconv.ParseUint(strutil.ReadField(text, 6, true), 10, 64) 147 148 if err != nil { 149 return nil, errors.New("Can't parse field 6 as unsigned integer in " + procDiskStatsFile) 150 } 151 152 ios.WriteComplete, err = strconv.ParseUint(strutil.ReadField(text, 7, true), 10, 64) 153 154 if err != nil { 155 return nil, errors.New("Can't parse field 7 as unsigned integer in " + procDiskStatsFile) 156 } 157 158 ios.WriteMerged, err = strconv.ParseUint(strutil.ReadField(text, 8, true), 10, 64) 159 160 if err != nil { 161 return nil, errors.New("Can't parse field 8 as unsigned integer in " + procDiskStatsFile) 162 } 163 164 ios.WriteSectors, err = strconv.ParseUint(strutil.ReadField(text, 9, true), 10, 64) 165 166 if err != nil { 167 return nil, errors.New("Can't parse field 9 as unsigned integer in " + procDiskStatsFile) 168 } 169 170 ios.WriteMs, err = strconv.ParseUint(strutil.ReadField(text, 10, true), 10, 64) 171 172 if err != nil { 173 return nil, errors.New("Can't parse field 10 as unsigned integer in " + procDiskStatsFile) 174 } 175 176 ios.IOPending, err = strconv.ParseUint(strutil.ReadField(text, 11, true), 10, 64) 177 178 if err != nil { 179 return nil, errors.New("Can't parse field 11 as unsigned integer in " + procDiskStatsFile) 180 } 181 182 ios.IOMs, err = strconv.ParseUint(strutil.ReadField(text, 12, true), 10, 64) 183 184 if err != nil { 185 return nil, errors.New("Can't parse field 12 as unsigned integer in " + procDiskStatsFile) 186 } 187 188 ios.IOQueueMs, err = strconv.ParseUint(strutil.ReadField(text, 13, true), 10, 64) 189 190 if err != nil { 191 return nil, errors.New("Can't parse field 13 as unsigned integer in " + procDiskStatsFile) 192 } 193 194 iostats["/dev/"+device] = ios 195 } 196 197 return iostats, nil 198 } 199 200 // parseFSInfo parses fs info data 201 func parseFSInfo(s *bufio.Scanner, calculateStats bool) (map[string]*FSUsage, error) { 202 var err error 203 204 info := make(map[string]*FSUsage) 205 206 for s.Scan() { 207 text := s.Text() 208 209 if text == "" || text[:1] == "#" || text[:1] != "/" { 210 continue 211 } 212 213 device := strutil.ReadField(text, 0, true) 214 path := strutil.ReadField(text, 1, true) 215 fsInfo := &FSUsage{Type: strutil.ReadField(text, 2, true)} 216 217 if !calculateStats { 218 continue 219 } 220 221 stats := &syscall.Statfs_t{} 222 223 err = syscall.Statfs(path, stats) 224 225 if err != nil { 226 return nil, err 227 } 228 229 fsDevice, err := filepath.EvalSymlinks(device) 230 231 if err == nil { 232 fsInfo.Device = fsDevice 233 } else { 234 fsInfo.Device = device 235 } 236 237 fsInfo.Used = (stats.Blocks * uint64(stats.Bsize)) - (stats.Bfree * uint64(stats.Bsize)) 238 fsInfo.Total = fsInfo.Used + (stats.Bavail * uint64(stats.Bsize)) 239 fsInfo.Free = fsInfo.Total - fsInfo.Used 240 241 info[path] = fsInfo 242 } 243 244 if len(info) == 0 { 245 return nil, errors.New("Can't parse file " + mtabFile) 246 } 247 248 return info, nil 249 } 250 251 // enable:disable[LOC,ABC,CYCLO] 252 253 // getHZ returns number of processor clock ticks per second 254 func getHZ() float64 { 255 // CLK_TCK is a constant on Linux 256 // https://git.musl-libc.org/cgit/musl/tree/src/conf/sysconf.c#n30 257 return 100 258 }