github.com/elastic/gosigar@v0.14.3/cgroup/blkio.go (about) 1 package cgroup 2 3 import ( 4 "bufio" 5 "os" 6 "path/filepath" 7 "strconv" 8 "strings" 9 "unicode" 10 ) 11 12 // BlockIOSubsystem contains limits and metrics from the "blkio" subsystem. The 13 // blkio subsystem controls and monitors access to I/O on block devices by tasks 14 // in a cgroup. 15 // 16 // https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt 17 type BlockIOSubsystem struct { 18 Metadata 19 Throttle ThrottlePolicy `json:"throttle,omitempty"` // Throttle limits for upper IO rates and metrics. 20 //CFQ CFQScheduler `json:"cfq,omitempty"` // Completely fair queue scheduler limits and metrics. 21 } 22 23 // CFQScheduler contains limits and metrics for the proportional weight time 24 // based division of disk policy. It is implemented in CFQ. Hence this policy 25 // takes effect only on leaf nodes when CFQ is being used. 26 // 27 // https://www.kernel.org/doc/Documentation/block/cfq-iosched.txt 28 type CFQScheduler struct { 29 Weight uint64 `json:"weight"` // Default weight for all devices unless overridden. Allowed range of weights is from 10 to 1000. 30 Devices []CFQDevice `json:"devices,omitempty"` 31 } 32 33 // CFQDevice contains CFQ limits and metrics associated with a single device. 34 type CFQDevice struct { 35 DeviceID DeviceID `json:"device_id"` // ID of the device. 36 37 // Proportional weight for the device. 0 means a per device weight is not set and 38 // that the blkio.weight value is used. 39 Weight uint64 `json:"weight"` 40 41 TimeMs uint64 `json:"time_ms"` // Disk time allocated to cgroup per device in milliseconds. 42 Sectors uint64 `json:"sectors"` // Number of sectors transferred to/from disk by the cgroup. 43 Bytes OperationValues `json:"io_service_bytes"` // Number of bytes transferred to/from the disk by the cgroup. 44 IOs OperationValues `json:"io_serviced"` // Number of IO operations issued to the disk by the cgroup. 45 ServiceTimeNanos OperationValues `json:"io_service_time"` // Amount of time between request dispatch and request completion for the IOs done by this cgroup. 46 WaitTimeNanos OperationValues `json:"io_wait_time"` // Amount of time the IOs for this cgroup spent waiting in the scheduler queues for service. 47 Merges OperationValues `json:"io_merged"` // Total number of bios/requests merged into requests belonging to this cgroup. 48 } 49 50 // ThrottlePolicy contains the upper IO limits and metrics for devices used 51 // by the cgroup. 52 type ThrottlePolicy struct { 53 Devices []ThrottleDevice `json:"devices,omitempty"` // Device centric view of limits and metrics. 54 TotalBytes uint64 `json:"total_io_service_bytes"` // Total number of bytes serviced by all devices. 55 TotalIOs uint64 `json:"total_io_serviced"` // Total number of IO operations serviced by all devices. 56 } 57 58 // ThrottleDevice contains throttle limits and metrics associated with a single device. 59 type ThrottleDevice struct { 60 DeviceID DeviceID `json:"device_id"` // ID of the device. 61 62 ReadLimitBPS uint64 `json:"read_bps_device"` // Read limit in bytes per second (BPS). Zero means no limit. 63 WriteLimitBPS uint64 `json:"write_bps_device"` // Write limit in bytes per second (BPS). Zero mean no limit. 64 ReadLimitIOPS uint64 `json:"read_iops_device"` // Read limit in IOPS. Zero means no limit. 65 WriteLimitIOPS uint64 `json:"write_iops_device"` // Write limit in IOPS. Zero means no limit. 66 67 Bytes OperationValues `json:"io_service_bytes"` // Number of bytes transferred to/from the disk by the cgroup. 68 IOs OperationValues `json:"io_serviced"` // Number of IO operations issued to the disk by the cgroup. 69 } 70 71 // OperationValues contains the I/O limits or metrics associated with read, 72 // write, sync, and async operations. 73 type OperationValues struct { 74 Read uint64 `json:"read"` 75 Write uint64 `json:"write"` 76 Async uint64 `json:"async"` 77 Sync uint64 `json:"sync"` 78 } 79 80 // DeviceID identifies a Linux block device. 81 type DeviceID struct { 82 Major uint64 83 Minor uint64 84 } 85 86 // blkioValue holds a single blkio value associated with a device. 87 type blkioValue struct { 88 DeviceID 89 Operation string 90 Value uint64 91 } 92 93 // get reads metrics from the "blkio" subsystem. path is the filepath to the 94 // cgroup hierarchy to read. 95 func (blkio *BlockIOSubsystem) get(path string) error { 96 if err := blkioThrottle(path, blkio); err != nil { 97 return err 98 } 99 100 // TODO(akroh): Implement reading for the CFQ values. 101 102 return nil 103 } 104 105 // blkioThrottle reads all of the limits and metrics associated with blkio 106 // throttling policy. 107 func blkioThrottle(path string, blkio *BlockIOSubsystem) error { 108 devices := map[DeviceID]*ThrottleDevice{} 109 110 getDevice := func(id DeviceID) *ThrottleDevice { 111 td := devices[id] 112 if td == nil { 113 td = &ThrottleDevice{DeviceID: id} 114 devices[id] = td 115 } 116 return td 117 } 118 119 values, err := readBlkioValues(path, "blkio.throttle.io_service_bytes") 120 if err != nil { 121 return err 122 } 123 if values != nil { 124 for id, opValues := range collectOpValues(values) { 125 getDevice(id).Bytes = *opValues 126 } 127 } 128 129 values, err = readBlkioValues(path, "blkio.throttle.io_serviced") 130 if err != nil { 131 return err 132 } 133 if values != nil { 134 for id, opValues := range collectOpValues(values) { 135 getDevice(id).IOs = *opValues 136 } 137 } 138 139 values, err = readBlkioValues(path, "blkio.throttle.read_bps_device") 140 if err != nil { 141 return err 142 } 143 if values != nil { 144 for _, bv := range values { 145 getDevice(bv.DeviceID).ReadLimitBPS = bv.Value 146 } 147 } 148 149 values, err = readBlkioValues(path, "blkio.throttle.write_bps_device") 150 if err != nil { 151 return err 152 } 153 if values != nil { 154 for _, bv := range values { 155 getDevice(bv.DeviceID).WriteLimitBPS = bv.Value 156 } 157 } 158 159 values, err = readBlkioValues(path, "blkio.throttle.read_iops_device") 160 if err != nil { 161 return err 162 } 163 if values != nil { 164 for _, bv := range values { 165 getDevice(bv.DeviceID).ReadLimitIOPS = bv.Value 166 } 167 } 168 169 values, err = readBlkioValues(path, "blkio.throttle.write_iops_device") 170 if err != nil { 171 return err 172 } 173 if values != nil { 174 for _, bv := range values { 175 getDevice(bv.DeviceID).WriteLimitIOPS = bv.Value 176 } 177 } 178 179 blkio.Throttle.Devices = make([]ThrottleDevice, 0, len(devices)) 180 for _, dev := range devices { 181 blkio.Throttle.Devices = append(blkio.Throttle.Devices, *dev) 182 blkio.Throttle.TotalBytes += dev.Bytes.Read + dev.Bytes.Write 183 blkio.Throttle.TotalIOs += dev.IOs.Read + dev.IOs.Write 184 } 185 186 return nil 187 } 188 189 // collectOpValues collects the discreet I/O values (e.g. read, write, sync, 190 // async) for a given device into a single OperationValues object. It returns a 191 // mapping of device ID to OperationValues. 192 func collectOpValues(values []blkioValue) map[DeviceID]*OperationValues { 193 opValues := map[DeviceID]*OperationValues{} 194 for _, bv := range values { 195 opValue := opValues[bv.DeviceID] 196 if opValue == nil { 197 opValue = &OperationValues{} 198 opValues[bv.DeviceID] = opValue 199 } 200 201 switch bv.Operation { 202 case "read": 203 opValue.Read = bv.Value 204 case "write": 205 opValue.Write = bv.Value 206 case "async": 207 opValue.Async = bv.Value 208 case "sync": 209 opValue.Sync = bv.Value 210 } 211 } 212 213 return opValues 214 } 215 216 // readDeviceValues reads values from a single blkio file. 217 // It expects to read values like "245:1 read 18880" or "254:1 1909". It returns 218 // an array containing an entry for each valid line read. 219 func readBlkioValues(path ...string) ([]blkioValue, error) { 220 f, err := os.Open(filepath.Join(path...)) 221 if err != nil { 222 if os.IsNotExist(err) { 223 return nil, nil 224 } 225 return nil, err 226 } 227 defer f.Close() 228 229 var values []blkioValue 230 sc := bufio.NewScanner(f) 231 for sc.Scan() { 232 line := strings.TrimSpace(sc.Text()) 233 if len(line) == 0 { 234 continue 235 } 236 // Valid lines start with a device ID. 237 if !unicode.IsNumber(rune(line[0])) { 238 continue 239 } 240 241 v, err := parseBlkioValue(sc.Text()) 242 if err != nil { 243 return nil, err 244 } 245 246 values = append(values, v) 247 } 248 249 return values, sc.Err() 250 } 251 252 func isColonOrSpace(r rune) bool { 253 return unicode.IsSpace(r) || r == ':' 254 } 255 256 func parseBlkioValue(line string) (blkioValue, error) { 257 fields := strings.FieldsFunc(line, isColonOrSpace) 258 if len(fields) != 3 && len(fields) != 4 { 259 return blkioValue{}, ErrInvalidFormat 260 } 261 262 major, err := strconv.ParseUint(fields[0], 10, 64) 263 if err != nil { 264 return blkioValue{}, err 265 } 266 267 minor, err := strconv.ParseUint(fields[1], 10, 64) 268 if err != nil { 269 return blkioValue{}, err 270 } 271 272 var value uint64 273 var operation string 274 if len(fields) == 3 { 275 value, err = parseUint([]byte(fields[2])) 276 if err != nil { 277 return blkioValue{}, err 278 } 279 } else { 280 operation = strings.ToLower(fields[2]) 281 282 value, err = parseUint([]byte(fields[3])) 283 if err != nil { 284 return blkioValue{}, err 285 } 286 } 287 288 return blkioValue{ 289 DeviceID: DeviceID{major, minor}, 290 Operation: operation, 291 Value: value, 292 }, nil 293 }