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  }