gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

blkio.go (8348B)


      1 /*
      2    Copyright The containerd Authors.
      3 
      4    Licensed under the Apache License, Version 2.0 (the "License");
      5    you may not use this file except in compliance with the License.
      6    You may obtain a copy of the License at
      7 
      8        http://www.apache.org/licenses/LICENSE-2.0
      9 
     10    Unless required by applicable law or agreed to in writing, software
     11    distributed under the License is distributed on an "AS IS" BASIS,
     12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13    See the License for the specific language governing permissions and
     14    limitations under the License.
     15 */
     16 
     17 package cgroup1
     18 
     19 import (
     20 	"bufio"
     21 	"fmt"
     22 	"io"
     23 	"os"
     24 	"path/filepath"
     25 	"strconv"
     26 	"strings"
     27 
     28 	v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
     29 
     30 	specs "github.com/opencontainers/runtime-spec/specs-go"
     31 )
     32 
     33 // NewBlkio returns a Blkio controller given the root folder of cgroups.
     34 // It may optionally accept other configuration options, such as ProcRoot(path)
     35 func NewBlkio(root string, options ...func(controller *blkioController)) *blkioController {
     36 	ctrl := &blkioController{
     37 		root:     filepath.Join(root, string(Blkio)),
     38 		procRoot: "/proc",
     39 	}
     40 	for _, opt := range options {
     41 		opt(ctrl)
     42 	}
     43 	return ctrl
     44 }
     45 
     46 // ProcRoot overrides the default location of the "/proc" filesystem
     47 func ProcRoot(path string) func(controller *blkioController) {
     48 	return func(c *blkioController) {
     49 		c.procRoot = path
     50 	}
     51 }
     52 
     53 type blkioController struct {
     54 	root     string
     55 	procRoot string
     56 }
     57 
     58 func (b *blkioController) Name() Name {
     59 	return Blkio
     60 }
     61 
     62 func (b *blkioController) Path(path string) string {
     63 	return filepath.Join(b.root, path)
     64 }
     65 
     66 func (b *blkioController) Create(path string, resources *specs.LinuxResources) error {
     67 	if err := os.MkdirAll(b.Path(path), defaultDirPerm); err != nil {
     68 		return err
     69 	}
     70 	if resources.BlockIO == nil {
     71 		return nil
     72 	}
     73 	for _, t := range createBlkioSettings(resources.BlockIO) {
     74 		if t.value != nil {
     75 			if err := os.WriteFile(
     76 				filepath.Join(b.Path(path), "blkio."+t.name),
     77 				t.format(t.value),
     78 				defaultFilePerm,
     79 			); err != nil {
     80 				return err
     81 			}
     82 		}
     83 	}
     84 	return nil
     85 }
     86 
     87 func (b *blkioController) Update(path string, resources *specs.LinuxResources) error {
     88 	return b.Create(path, resources)
     89 }
     90 
     91 func (b *blkioController) Stat(path string, stats *v1.Metrics) error {
     92 	stats.Blkio = &v1.BlkIOStat{}
     93 
     94 	var settings []blkioStatSettings
     95 
     96 	// Try to read CFQ stats available on all CFQ enabled kernels first
     97 	if _, err := os.Lstat(filepath.Join(b.Path(path), "blkio.io_serviced_recursive")); err == nil {
     98 		settings = []blkioStatSettings{
     99 			{
    100 				name:  "sectors_recursive",
    101 				entry: &stats.Blkio.SectorsRecursive,
    102 			},
    103 			{
    104 				name:  "io_service_bytes_recursive",
    105 				entry: &stats.Blkio.IoServiceBytesRecursive,
    106 			},
    107 			{
    108 				name:  "io_serviced_recursive",
    109 				entry: &stats.Blkio.IoServicedRecursive,
    110 			},
    111 			{
    112 				name:  "io_queued_recursive",
    113 				entry: &stats.Blkio.IoQueuedRecursive,
    114 			},
    115 			{
    116 				name:  "io_service_time_recursive",
    117 				entry: &stats.Blkio.IoServiceTimeRecursive,
    118 			},
    119 			{
    120 				name:  "io_wait_time_recursive",
    121 				entry: &stats.Blkio.IoWaitTimeRecursive,
    122 			},
    123 			{
    124 				name:  "io_merged_recursive",
    125 				entry: &stats.Blkio.IoMergedRecursive,
    126 			},
    127 			{
    128 				name:  "time_recursive",
    129 				entry: &stats.Blkio.IoTimeRecursive,
    130 			},
    131 		}
    132 	}
    133 
    134 	f, err := os.Open(filepath.Join(b.procRoot, "partitions"))
    135 	if err != nil {
    136 		return err
    137 	}
    138 	defer f.Close()
    139 
    140 	devices, err := getDevices(f)
    141 	if err != nil {
    142 		return err
    143 	}
    144 
    145 	var size int
    146 	for _, t := range settings {
    147 		if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
    148 			return err
    149 		}
    150 		size += len(*t.entry)
    151 	}
    152 	if size > 0 {
    153 		return nil
    154 	}
    155 
    156 	// Even the kernel is compiled with the CFQ scheduler, the cgroup may not use
    157 	// block devices with the CFQ scheduler. If so, we should fallback to throttle.* files.
    158 	settings = []blkioStatSettings{
    159 		{
    160 			name:  "throttle.io_serviced",
    161 			entry: &stats.Blkio.IoServicedRecursive,
    162 		},
    163 		{
    164 			name:  "throttle.io_service_bytes",
    165 			entry: &stats.Blkio.IoServiceBytesRecursive,
    166 		},
    167 	}
    168 	for _, t := range settings {
    169 		if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
    170 			return err
    171 		}
    172 	}
    173 	return nil
    174 }
    175 
    176 func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]*v1.BlkIOEntry) error {
    177 	f, err := os.Open(filepath.Join(b.Path(path), "blkio."+name))
    178 	if err != nil {
    179 		return err
    180 	}
    181 	defer f.Close()
    182 	sc := bufio.NewScanner(f)
    183 	for sc.Scan() {
    184 		// format: dev type amount
    185 		fields := strings.FieldsFunc(sc.Text(), splitBlkIOStatLine)
    186 		if len(fields) < 3 {
    187 			if len(fields) == 2 && fields[0] == "Total" {
    188 				// skip total line
    189 				continue
    190 			} else {
    191 				return fmt.Errorf("invalid line found while parsing %s: %s", path, sc.Text())
    192 			}
    193 		}
    194 		major, err := strconv.ParseUint(fields[0], 10, 64)
    195 		if err != nil {
    196 			return err
    197 		}
    198 		minor, err := strconv.ParseUint(fields[1], 10, 64)
    199 		if err != nil {
    200 			return err
    201 		}
    202 		op := ""
    203 		valueField := 2
    204 		if len(fields) == 4 {
    205 			op = fields[2]
    206 			valueField = 3
    207 		}
    208 		v, err := strconv.ParseUint(fields[valueField], 10, 64)
    209 		if err != nil {
    210 			return err
    211 		}
    212 		*entry = append(*entry, &v1.BlkIOEntry{
    213 			Device: devices[deviceKey{major, minor}],
    214 			Major:  major,
    215 			Minor:  minor,
    216 			Op:     op,
    217 			Value:  v,
    218 		})
    219 	}
    220 	return sc.Err()
    221 }
    222 
    223 func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings {
    224 	settings := []blkioSettings{}
    225 
    226 	if blkio.Weight != nil {
    227 		settings = append(settings,
    228 			blkioSettings{
    229 				name:   "weight",
    230 				value:  blkio.Weight,
    231 				format: uintf,
    232 			})
    233 	}
    234 	if blkio.LeafWeight != nil {
    235 		settings = append(settings,
    236 			blkioSettings{
    237 				name:   "leaf_weight",
    238 				value:  blkio.LeafWeight,
    239 				format: uintf,
    240 			})
    241 	}
    242 	for _, wd := range blkio.WeightDevice {
    243 		if wd.Weight != nil {
    244 			settings = append(settings,
    245 				blkioSettings{
    246 					name:   "weight_device",
    247 					value:  wd,
    248 					format: weightdev,
    249 				})
    250 		}
    251 		if wd.LeafWeight != nil {
    252 			settings = append(settings,
    253 				blkioSettings{
    254 					name:   "leaf_weight_device",
    255 					value:  wd,
    256 					format: weightleafdev,
    257 				})
    258 		}
    259 	}
    260 	for _, t := range []struct {
    261 		name string
    262 		list []specs.LinuxThrottleDevice
    263 	}{
    264 		{
    265 			name: "throttle.read_bps_device",
    266 			list: blkio.ThrottleReadBpsDevice,
    267 		},
    268 		{
    269 			name: "throttle.read_iops_device",
    270 			list: blkio.ThrottleReadIOPSDevice,
    271 		},
    272 		{
    273 			name: "throttle.write_bps_device",
    274 			list: blkio.ThrottleWriteBpsDevice,
    275 		},
    276 		{
    277 			name: "throttle.write_iops_device",
    278 			list: blkio.ThrottleWriteIOPSDevice,
    279 		},
    280 	} {
    281 		for _, td := range t.list {
    282 			settings = append(settings, blkioSettings{
    283 				name:   t.name,
    284 				value:  td,
    285 				format: throttleddev,
    286 			})
    287 		}
    288 	}
    289 	return settings
    290 }
    291 
    292 type blkioSettings struct {
    293 	name   string
    294 	value  interface{}
    295 	format func(v interface{}) []byte
    296 }
    297 
    298 type blkioStatSettings struct {
    299 	name  string
    300 	entry *[]*v1.BlkIOEntry
    301 }
    302 
    303 func uintf(v interface{}) []byte {
    304 	return []byte(strconv.FormatUint(uint64(*v.(*uint16)), 10))
    305 }
    306 
    307 func weightdev(v interface{}) []byte {
    308 	wd := v.(specs.LinuxWeightDevice)
    309 	return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.Weight))
    310 }
    311 
    312 func weightleafdev(v interface{}) []byte {
    313 	wd := v.(specs.LinuxWeightDevice)
    314 	return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.LeafWeight))
    315 }
    316 
    317 func throttleddev(v interface{}) []byte {
    318 	td := v.(specs.LinuxThrottleDevice)
    319 	return []byte(fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate))
    320 }
    321 
    322 func splitBlkIOStatLine(r rune) bool {
    323 	return r == ' ' || r == ':'
    324 }
    325 
    326 type deviceKey struct {
    327 	major, minor uint64
    328 }
    329 
    330 // getDevices makes a best effort attempt to read all the devices into a map
    331 // keyed by major and minor number. Since devices may be mapped multiple times,
    332 // we err on taking the first occurrence.
    333 func getDevices(r io.Reader) (map[deviceKey]string, error) {
    334 
    335 	var (
    336 		s       = bufio.NewScanner(r)
    337 		devices = make(map[deviceKey]string)
    338 	)
    339 	for i := 0; s.Scan(); i++ {
    340 		if i < 2 {
    341 			continue
    342 		}
    343 		fields := strings.Fields(s.Text())
    344 		major, err := strconv.Atoi(fields[0])
    345 		if err != nil {
    346 			return nil, err
    347 		}
    348 		minor, err := strconv.Atoi(fields[1])
    349 		if err != nil {
    350 			return nil, err
    351 		}
    352 		key := deviceKey{
    353 			major: uint64(major),
    354 			minor: uint64(minor),
    355 		}
    356 		if _, ok := devices[key]; ok {
    357 			continue
    358 		}
    359 		devices[key] = filepath.Join("/dev", fields[3])
    360 	}
    361 	return devices, s.Err()
    362 }