1
16
17 package cgroups
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/stats/v1"
29 specs "github.com/opencontainers/runtime-spec/specs-go"
30 )
31
32
33
34 func NewBlkio(root string, options ...func(controller *blkioController)) *blkioController {
35 ctrl := &blkioController{
36 root: filepath.Join(root, string(Blkio)),
37 procRoot: "/proc",
38 }
39 for _, opt := range options {
40 opt(ctrl)
41 }
42 return ctrl
43 }
44
45
46 func ProcRoot(path string) func(controller *blkioController) {
47 return func(c *blkioController) {
48 c.procRoot = path
49 }
50 }
51
52 type blkioController struct {
53 root string
54 procRoot string
55 }
56
57 func (b *blkioController) Name() Name {
58 return Blkio
59 }
60
61 func (b *blkioController) Path(path string) string {
62 return filepath.Join(b.root, path)
63 }
64
65 func (b *blkioController) Create(path string, resources *specs.LinuxResources) error {
66 if err := os.MkdirAll(b.Path(path), defaultDirPerm); err != nil {
67 return err
68 }
69 if resources.BlockIO == nil {
70 return nil
71 }
72 for _, t := range createBlkioSettings(resources.BlockIO) {
73 if t.value != nil {
74 if err := retryingWriteFile(
75 filepath.Join(b.Path(path), "blkio."+t.name),
76 t.format(t.value),
77 defaultFilePerm,
78 ); err != nil {
79 return err
80 }
81 }
82 }
83 return nil
84 }
85
86 func (b *blkioController) Update(path string, resources *specs.LinuxResources) error {
87 return b.Create(path, resources)
88 }
89
90 func (b *blkioController) Stat(path string, stats *v1.Metrics) error {
91 stats.Blkio = &v1.BlkIOStat{}
92
93 var settings []blkioStatSettings
94
95
96 if _, err := os.Lstat(filepath.Join(b.Path(path), "blkio.io_serviced_recursive")); err == nil {
97 settings = []blkioStatSettings{
98 {
99 name: "sectors_recursive",
100 entry: &stats.Blkio.SectorsRecursive,
101 },
102 {
103 name: "io_service_bytes_recursive",
104 entry: &stats.Blkio.IoServiceBytesRecursive,
105 },
106 {
107 name: "io_serviced_recursive",
108 entry: &stats.Blkio.IoServicedRecursive,
109 },
110 {
111 name: "io_queued_recursive",
112 entry: &stats.Blkio.IoQueuedRecursive,
113 },
114 {
115 name: "io_service_time_recursive",
116 entry: &stats.Blkio.IoServiceTimeRecursive,
117 },
118 {
119 name: "io_wait_time_recursive",
120 entry: &stats.Blkio.IoWaitTimeRecursive,
121 },
122 {
123 name: "io_merged_recursive",
124 entry: &stats.Blkio.IoMergedRecursive,
125 },
126 {
127 name: "time_recursive",
128 entry: &stats.Blkio.IoTimeRecursive,
129 },
130 }
131 }
132
133 f, err := os.Open(filepath.Join(b.procRoot, "partitions"))
134 if err != nil {
135 return err
136 }
137 defer f.Close()
138
139 devices, err := getDevices(f)
140 if err != nil {
141 return err
142 }
143
144 var size int
145 for _, t := range settings {
146 if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
147 return err
148 }
149 size += len(*t.entry)
150 }
151 if size > 0 {
152 return nil
153 }
154
155
156
157 settings = []blkioStatSettings{
158 {
159 name: "throttle.io_serviced",
160 entry: &stats.Blkio.IoServicedRecursive,
161 },
162 {
163 name: "throttle.io_service_bytes",
164 entry: &stats.Blkio.IoServiceBytesRecursive,
165 },
166 }
167 for _, t := range settings {
168 if err := b.readEntry(devices, path, t.name, t.entry); err != nil {
169 return err
170 }
171 }
172 return nil
173 }
174
175 func (b *blkioController) readEntry(devices map[deviceKey]string, path, name string, entry *[]*v1.BlkIOEntry) error {
176 f, err := os.Open(filepath.Join(b.Path(path), "blkio."+name))
177 if err != nil {
178 return err
179 }
180 defer f.Close()
181 sc := bufio.NewScanner(f)
182 for sc.Scan() {
183
184 fields := strings.FieldsFunc(sc.Text(), splitBlkIOStatLine)
185 if len(fields) < 3 {
186 if len(fields) == 2 && fields[0] == "Total" {
187
188 continue
189 } else {
190 return fmt.Errorf("invalid line found while parsing %s: %s", path, sc.Text())
191 }
192 }
193 major, err := strconv.ParseUint(fields[0], 10, 64)
194 if err != nil {
195 return err
196 }
197 minor, err := strconv.ParseUint(fields[1], 10, 64)
198 if err != nil {
199 return err
200 }
201 op := ""
202 valueField := 2
203 if len(fields) == 4 {
204 op = fields[2]
205 valueField = 3
206 }
207 v, err := strconv.ParseUint(fields[valueField], 10, 64)
208 if err != nil {
209 return err
210 }
211 *entry = append(*entry, &v1.BlkIOEntry{
212 Device: devices[deviceKey{major, minor}],
213 Major: major,
214 Minor: minor,
215 Op: op,
216 Value: v,
217 })
218 }
219 return sc.Err()
220 }
221
222 func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings {
223 settings := []blkioSettings{}
224
225 if blkio.Weight != nil {
226 settings = append(settings,
227 blkioSettings{
228 name: "weight",
229 value: blkio.Weight,
230 format: uintf,
231 })
232 }
233 if blkio.LeafWeight != nil {
234 settings = append(settings,
235 blkioSettings{
236 name: "leaf_weight",
237 value: blkio.LeafWeight,
238 format: uintf,
239 })
240 }
241 for _, wd := range blkio.WeightDevice {
242 if wd.Weight != nil {
243 settings = append(settings,
244 blkioSettings{
245 name: "weight_device",
246 value: wd,
247 format: weightdev,
248 })
249 }
250 if wd.LeafWeight != nil {
251 settings = append(settings,
252 blkioSettings{
253 name: "leaf_weight_device",
254 value: wd,
255 format: weightleafdev,
256 })
257 }
258 }
259 for _, t := range []struct {
260 name string
261 list []specs.LinuxThrottleDevice
262 }{
263 {
264 name: "throttle.read_bps_device",
265 list: blkio.ThrottleReadBpsDevice,
266 },
267 {
268 name: "throttle.read_iops_device",
269 list: blkio.ThrottleReadIOPSDevice,
270 },
271 {
272 name: "throttle.write_bps_device",
273 list: blkio.ThrottleWriteBpsDevice,
274 },
275 {
276 name: "throttle.write_iops_device",
277 list: blkio.ThrottleWriteIOPSDevice,
278 },
279 } {
280 for _, td := range t.list {
281 settings = append(settings, blkioSettings{
282 name: t.name,
283 value: td,
284 format: throttleddev,
285 })
286 }
287 }
288 return settings
289 }
290
291 type blkioSettings struct {
292 name string
293 value interface{}
294 format func(v interface{}) []byte
295 }
296
297 type blkioStatSettings struct {
298 name string
299 entry *[]*v1.BlkIOEntry
300 }
301
302 func uintf(v interface{}) []byte {
303 return []byte(strconv.FormatUint(uint64(*v.(*uint16)), 10))
304 }
305
306 func weightdev(v interface{}) []byte {
307 wd := v.(specs.LinuxWeightDevice)
308 return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.Weight))
309 }
310
311 func weightleafdev(v interface{}) []byte {
312 wd := v.(specs.LinuxWeightDevice)
313 return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.LeafWeight))
314 }
315
316 func throttleddev(v interface{}) []byte {
317 td := v.(specs.LinuxThrottleDevice)
318 return []byte(fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate))
319 }
320
321 func splitBlkIOStatLine(r rune) bool {
322 return r == ' ' || r == ':'
323 }
324
325 type deviceKey struct {
326 major, minor uint64
327 }
328
329
330
331
332 func getDevices(r io.Reader) (map[deviceKey]string, error) {
333
334 var (
335 s = bufio.NewScanner(r)
336 devices = make(map[deviceKey]string)
337 )
338 for i := 0; s.Scan(); i++ {
339 if i < 2 {
340 continue
341 }
342 fields := strings.Fields(s.Text())
343 major, err := strconv.Atoi(fields[0])
344 if err != nil {
345 return nil, err
346 }
347 minor, err := strconv.Atoi(fields[1])
348 if err != nil {
349 return nil, err
350 }
351 key := deviceKey{
352 major: uint64(major),
353 minor: uint64(minor),
354 }
355 if _, ok := devices[key]; ok {
356 continue
357 }
358 devices[key] = filepath.Join("/dev", fields[3])
359 }
360 return devices, s.Err()
361 }
362
View as plain text