github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/cgroups/cgroups.go (about) 1 package cgroups 2 3 import ( 4 "bufio" 5 "fmt" 6 "io/ioutil" 7 "math" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 13 "github.com/containers/libpod/pkg/rootless" 14 systemdDbus "github.com/coreos/go-systemd/v22/dbus" 15 "github.com/godbus/dbus/v5" 16 spec "github.com/opencontainers/runtime-spec/specs-go" 17 "github.com/pkg/errors" 18 "github.com/sirupsen/logrus" 19 ) 20 21 var ( 22 // ErrCgroupDeleted means the cgroup was deleted 23 ErrCgroupDeleted = errors.New("cgroup deleted") 24 // ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environmen 25 ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments") 26 ) 27 28 // CgroupControl controls a cgroup hierarchy 29 type CgroupControl struct { 30 cgroup2 bool 31 path string 32 systemd bool 33 // List of additional cgroup subsystems joined that 34 // do not have a custom handler. 35 additionalControllers []controller 36 } 37 38 // CPUUsage keeps stats for the CPU usage (unit: nanoseconds) 39 type CPUUsage struct { 40 Kernel uint64 41 Total uint64 42 PerCPU []uint64 43 } 44 45 // MemoryUsage keeps stats for the memory usage 46 type MemoryUsage struct { 47 Usage uint64 48 Limit uint64 49 } 50 51 // CPUMetrics keeps stats for the CPU usage 52 type CPUMetrics struct { 53 Usage CPUUsage 54 } 55 56 // BlkIOEntry describes an entry in the blkio stats 57 type BlkIOEntry struct { 58 Op string 59 Major uint64 60 Minor uint64 61 Value uint64 62 } 63 64 // BlkioMetrics keeps usage stats for the blkio cgroup controller 65 type BlkioMetrics struct { 66 IoServiceBytesRecursive []BlkIOEntry 67 } 68 69 // MemoryMetrics keeps usage stats for the memory cgroup controller 70 type MemoryMetrics struct { 71 Usage MemoryUsage 72 } 73 74 // PidsMetrics keeps usage stats for the pids cgroup controller 75 type PidsMetrics struct { 76 Current uint64 77 } 78 79 // Metrics keeps usage stats for the cgroup controllers 80 type Metrics struct { 81 CPU CPUMetrics 82 Blkio BlkioMetrics 83 Memory MemoryMetrics 84 Pids PidsMetrics 85 } 86 87 type controller struct { 88 name string 89 symlink bool 90 } 91 92 type controllerHandler interface { 93 Create(*CgroupControl) (bool, error) 94 Apply(*CgroupControl, *spec.LinuxResources) error 95 Destroy(*CgroupControl) error 96 Stat(*CgroupControl, *Metrics) error 97 } 98 99 const ( 100 cgroupRoot = "/sys/fs/cgroup" 101 // CPU is the cpu controller 102 CPU = "cpu" 103 // CPUAcct is the cpuacct controller 104 CPUAcct = "cpuacct" 105 // CPUset is the cpuset controller 106 CPUset = "cpuset" 107 // Memory is the memory controller 108 Memory = "memory" 109 // Pids is the pids controller 110 Pids = "pids" 111 // Blkio is the blkio controller 112 Blkio = "blkio" 113 ) 114 115 var handlers map[string]controllerHandler 116 117 func init() { 118 handlers = make(map[string]controllerHandler) 119 handlers[CPU] = getCPUHandler() 120 handlers[CPUset] = getCpusetHandler() 121 handlers[Memory] = getMemoryHandler() 122 handlers[Pids] = getPidsHandler() 123 handlers[Blkio] = getBlkioHandler() 124 } 125 126 // getAvailableControllers get the available controllers 127 func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) { 128 if cgroup2 { 129 return nil, fmt.Errorf("getAvailableControllers not implemented yet for cgroup v2") 130 } 131 132 infos, err := ioutil.ReadDir(cgroupRoot) 133 if err != nil { 134 return nil, errors.Wrapf(err, "read directory %s", cgroupRoot) 135 } 136 var controllers []controller 137 for _, i := range infos { 138 name := i.Name() 139 if _, found := exclude[name]; found { 140 continue 141 } 142 c := controller{ 143 name: name, 144 symlink: !i.IsDir(), 145 } 146 controllers = append(controllers, c) 147 } 148 return controllers, nil 149 } 150 151 // getCgroupv1Path is a helper function to get the cgroup v1 path 152 func (c *CgroupControl) getCgroupv1Path(name string) string { 153 return filepath.Join(cgroupRoot, name, c.path) 154 } 155 156 // createCgroupv2Path creates the cgroupv2 path and enables all the available controllers 157 func createCgroupv2Path(path string) (deferredError error) { 158 content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers") 159 if err != nil { 160 return errors.Wrapf(err, "read /sys/fs/cgroup/cgroup.controllers") 161 } 162 if !strings.HasPrefix(path, "/sys/fs/cgroup/") { 163 return fmt.Errorf("invalid cgroup path %s", path) 164 } 165 166 res := "" 167 for i, c := range strings.Split(strings.TrimSpace(string(content)), " ") { 168 if i == 0 { 169 res = fmt.Sprintf("+%s", c) 170 } else { 171 res += fmt.Sprintf(" +%s", c) 172 } 173 } 174 resByte := []byte(res) 175 176 current := "/sys/fs" 177 elements := strings.Split(path, "/") 178 for i, e := range elements[3:] { 179 current = filepath.Join(current, e) 180 if i > 0 { 181 if err := os.Mkdir(current, 0755); err != nil { 182 if !os.IsExist(err) { 183 return errors.Wrapf(err, "mkdir %s", path) 184 } 185 } else { 186 // If the directory was created, be sure it is not left around on errors. 187 defer func() { 188 if deferredError != nil { 189 os.Remove(current) 190 } 191 }() 192 } 193 } 194 // We enable the controllers for all the path components except the last one. It is not allowed to add 195 // PIDs if there are already enabled controllers. 196 if i < len(elements[3:])-1 { 197 if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), resByte, 0755); err != nil { 198 return errors.Wrapf(err, "write %s", filepath.Join(current, "cgroup.subtree_control")) 199 } 200 } 201 } 202 return nil 203 } 204 205 // initialize initializes the specified hierarchy 206 func (c *CgroupControl) initialize() (err error) { 207 createdSoFar := map[string]controllerHandler{} 208 defer func() { 209 if err != nil { 210 for name, ctr := range createdSoFar { 211 if err := ctr.Destroy(c); err != nil { 212 logrus.Warningf("error cleaning up controller %s for %s", name, c.path) 213 } 214 } 215 } 216 }() 217 if c.cgroup2 { 218 if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.path)); err != nil { 219 return errors.Wrapf(err, "error creating cgroup path %s", c.path) 220 } 221 } 222 for name, handler := range handlers { 223 created, err := handler.Create(c) 224 if err != nil { 225 return err 226 } 227 if created { 228 createdSoFar[name] = handler 229 } 230 } 231 232 if !c.cgroup2 { 233 // We won't need to do this for cgroup v2 234 for _, ctr := range c.additionalControllers { 235 if ctr.symlink { 236 continue 237 } 238 path := c.getCgroupv1Path(ctr.name) 239 if err := os.MkdirAll(path, 0755); err != nil { 240 return errors.Wrapf(err, "error creating cgroup path %s for %s", path, ctr.name) 241 } 242 } 243 } 244 245 return nil 246 } 247 248 func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) { 249 cPath := c.getCgroupv1Path(controller) 250 _, err := os.Stat(cPath) 251 if err == nil { 252 return false, nil 253 } 254 255 if !os.IsNotExist(err) { 256 return false, err 257 } 258 259 if err := os.MkdirAll(cPath, 0755); err != nil { 260 return false, errors.Wrapf(err, "error creating cgroup for %s", controller) 261 } 262 return true, nil 263 } 264 265 func readFileAsUint64(path string) (uint64, error) { 266 data, err := ioutil.ReadFile(path) 267 if err != nil { 268 return 0, errors.Wrapf(err, "open %s", path) 269 } 270 v := cleanString(string(data)) 271 if v == "max" { 272 return math.MaxUint64, nil 273 } 274 ret, err := strconv.ParseUint(v, 10, 0) 275 if err != nil { 276 return ret, errors.Wrapf(err, "parse %s from %s", v, path) 277 } 278 return ret, nil 279 } 280 281 // New creates a new cgroup control 282 func New(path string, resources *spec.LinuxResources) (*CgroupControl, error) { 283 cgroup2, err := IsCgroup2UnifiedMode() 284 if err != nil { 285 return nil, err 286 } 287 control := &CgroupControl{ 288 cgroup2: cgroup2, 289 path: path, 290 } 291 292 if !cgroup2 { 293 controllers, err := getAvailableControllers(handlers, false) 294 if err != nil { 295 return nil, err 296 } 297 control.additionalControllers = controllers 298 } 299 300 if err := control.initialize(); err != nil { 301 return nil, err 302 } 303 304 return control, nil 305 } 306 307 // NewSystemd creates a new cgroup control 308 func NewSystemd(path string) (*CgroupControl, error) { 309 cgroup2, err := IsCgroup2UnifiedMode() 310 if err != nil { 311 return nil, err 312 } 313 control := &CgroupControl{ 314 cgroup2: cgroup2, 315 path: path, 316 systemd: true, 317 } 318 return control, nil 319 } 320 321 // Load loads an existing cgroup control 322 func Load(path string) (*CgroupControl, error) { 323 cgroup2, err := IsCgroup2UnifiedMode() 324 if err != nil { 325 return nil, err 326 } 327 control := &CgroupControl{ 328 cgroup2: cgroup2, 329 path: path, 330 systemd: false, 331 } 332 if !cgroup2 { 333 controllers, err := getAvailableControllers(handlers, false) 334 if err != nil { 335 return nil, err 336 } 337 control.additionalControllers = controllers 338 } 339 if !cgroup2 { 340 for name := range handlers { 341 p := control.getCgroupv1Path(name) 342 if _, err := os.Stat(p); err != nil { 343 if os.IsNotExist(err) { 344 if rootless.IsRootless() { 345 return nil, ErrCgroupV1Rootless 346 } 347 // compatible with the error code 348 // used by containerd/cgroups 349 return nil, ErrCgroupDeleted 350 } 351 } 352 } 353 } 354 return control, nil 355 } 356 357 // CreateSystemdUnit creates the systemd cgroup 358 func (c *CgroupControl) CreateSystemdUnit(path string) error { 359 if !c.systemd { 360 return fmt.Errorf("the cgroup controller is not using systemd") 361 } 362 363 conn, err := systemdDbus.New() 364 if err != nil { 365 return err 366 } 367 defer conn.Close() 368 369 return systemdCreate(path, conn) 370 } 371 372 // GetUserConnection returns an user connection to D-BUS 373 func GetUserConnection(uid int) (*systemdDbus.Conn, error) { 374 return systemdDbus.NewConnection(func() (*dbus.Conn, error) { 375 return dbusAuthConnection(uid, dbus.SessionBusPrivate) 376 }) 377 } 378 379 // CreateSystemdUserUnit creates the systemd cgroup for the specified user 380 func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error { 381 if !c.systemd { 382 return fmt.Errorf("the cgroup controller is not using systemd") 383 } 384 385 conn, err := GetUserConnection(uid) 386 if err != nil { 387 return err 388 } 389 defer conn.Close() 390 391 return systemdCreate(path, conn) 392 } 393 394 func dbusAuthConnection(uid int, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { 395 conn, err := createBus() 396 if err != nil { 397 return nil, err 398 } 399 400 methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(uid))} 401 402 err = conn.Auth(methods) 403 if err != nil { 404 conn.Close() 405 return nil, err 406 } 407 if err := conn.Hello(); err != nil { 408 return nil, err 409 } 410 411 return conn, nil 412 } 413 414 // Delete cleans a cgroup 415 func (c *CgroupControl) Delete() error { 416 return c.DeleteByPath(c.path) 417 } 418 419 // rmDirRecursively delete recursively a cgroup directory. 420 // It differs from os.RemoveAll as it doesn't attempt to unlink files. 421 // On cgroupfs we are allowed only to rmdir empty directories. 422 func rmDirRecursively(path string) error { 423 if err := os.Remove(path); err == nil || os.IsNotExist(err) { 424 return nil 425 } 426 entries, err := ioutil.ReadDir(path) 427 if err != nil { 428 return errors.Wrapf(err, "read %s", path) 429 } 430 for _, i := range entries { 431 if i.IsDir() { 432 if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil { 433 return err 434 } 435 } 436 } 437 if err := os.Remove(path); err != nil { 438 if !os.IsNotExist(err) { 439 return errors.Wrapf(err, "remove %s", path) 440 } 441 } 442 return nil 443 } 444 445 // DeleteByPathConn deletes the specified cgroup path using the specified 446 // dbus connection if needed. 447 func (c *CgroupControl) DeleteByPathConn(path string, conn *systemdDbus.Conn) error { 448 if c.systemd { 449 return systemdDestroyConn(path, conn) 450 } 451 if c.cgroup2 { 452 return rmDirRecursively(filepath.Join(cgroupRoot, c.path)) 453 } 454 var lastError error 455 for _, h := range handlers { 456 if err := h.Destroy(c); err != nil { 457 lastError = err 458 } 459 } 460 461 for _, ctr := range c.additionalControllers { 462 if ctr.symlink { 463 continue 464 } 465 p := c.getCgroupv1Path(ctr.name) 466 if err := rmDirRecursively(p); err != nil { 467 lastError = errors.Wrapf(err, "remove %s", p) 468 } 469 } 470 return lastError 471 } 472 473 // DeleteByPath deletes the specified cgroup path 474 func (c *CgroupControl) DeleteByPath(path string) error { 475 if c.systemd { 476 conn, err := systemdDbus.New() 477 if err != nil { 478 return err 479 } 480 defer conn.Close() 481 return c.DeleteByPathConn(path, conn) 482 } 483 return c.DeleteByPathConn(path, nil) 484 } 485 486 // Update updates the cgroups 487 func (c *CgroupControl) Update(resources *spec.LinuxResources) error { 488 for _, h := range handlers { 489 if err := h.Apply(c, resources); err != nil { 490 return err 491 } 492 } 493 return nil 494 } 495 496 // AddPid moves the specified pid to the cgroup 497 func (c *CgroupControl) AddPid(pid int) error { 498 pidString := []byte(fmt.Sprintf("%d\n", pid)) 499 500 if c.cgroup2 { 501 p := filepath.Join(cgroupRoot, c.path, "cgroup.procs") 502 if err := ioutil.WriteFile(p, pidString, 0644); err != nil { 503 return errors.Wrapf(err, "write %s", p) 504 } 505 return nil 506 } 507 508 var names []string 509 for n := range handlers { 510 names = append(names, n) 511 } 512 513 for _, c := range c.additionalControllers { 514 if !c.symlink { 515 names = append(names, c.name) 516 } 517 } 518 519 for _, n := range names { 520 // If we aren't using cgroup2, we won't write correctly to unified hierarchy 521 if !c.cgroup2 && n == "unified" { 522 continue 523 } 524 p := filepath.Join(c.getCgroupv1Path(n), "tasks") 525 if err := ioutil.WriteFile(p, pidString, 0644); err != nil { 526 return errors.Wrapf(err, "write %s", p) 527 } 528 } 529 return nil 530 } 531 532 // Stat returns usage statistics for the cgroup 533 func (c *CgroupControl) Stat() (*Metrics, error) { 534 m := Metrics{} 535 for _, h := range handlers { 536 if err := h.Stat(c, &m); err != nil { 537 return nil, err 538 } 539 } 540 return &m, nil 541 } 542 543 func readCgroup2MapPath(path string) (map[string][]string, error) { 544 ret := map[string][]string{} 545 f, err := os.Open(path) 546 if err != nil { 547 if os.IsNotExist(err) { 548 return ret, nil 549 } 550 return nil, errors.Wrapf(err, "open file %s", path) 551 } 552 defer f.Close() 553 scanner := bufio.NewScanner(f) 554 for scanner.Scan() { 555 line := scanner.Text() 556 parts := strings.Fields(line) 557 if len(parts) < 2 { 558 continue 559 } 560 ret[parts[0]] = parts[1:] 561 } 562 if err := scanner.Err(); err != nil { 563 return nil, errors.Wrapf(err, "parsing file %s", path) 564 } 565 return ret, nil 566 } 567 568 func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) { 569 p := filepath.Join(cgroupRoot, ctr.path, name) 570 571 return readCgroup2MapPath(p) 572 }