chore: update gcr and docker cred helpers (#2910)

This commit is contained in:
Aaron Prindle
2023-12-19 12:36:13 -05:00
committed by GitHub
parent 6489eefaf0
commit dd752d9bbc
1231 changed files with 133904 additions and 18208 deletions
-46
View File
@@ -1,46 +0,0 @@
version = "unstable"
generator = "gogoctrd"
plugins = ["grpc"]
# Control protoc include paths. Below are usually some good defaults, but feel
# free to try it without them if it works for your project.
[includes]
# Include paths that will be added before all others. Typically, you want to
# treat the root of the project as an include, but this may not be necessary.
# before = ["."]
# Paths that should be treated as include roots in relation to the vendor
# directory. These will be calculated with the vendor directory nearest the
# target package.
# vendored = ["github.com/gogo/protobuf"]
packages = ["github.com/gogo/protobuf"]
# Paths that will be added untouched to the end of the includes. We use
# `/usr/local/include` to pickup the common install location of protobuf.
# This is the default.
after = ["/usr/local/include", "/usr/include"]
# This section maps protobuf imports to Go packages. These will become
# `-M` directives in the call to the go protobuf generator.
[packages]
"gogoproto/gogo.proto" = "github.com/gogo/protobuf/gogoproto"
"google/protobuf/any.proto" = "github.com/gogo/protobuf/types"
"google/protobuf/descriptor.proto" = "github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
"google/protobuf/field_mask.proto" = "github.com/gogo/protobuf/types"
"google/protobuf/timestamp.proto" = "github.com/gogo/protobuf/types"
# Aggregrate the API descriptors to lock down API changes.
[[descriptors]]
prefix = "github.com/containerd/cgroups/stats/v1"
target = "stats/v1/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
[[descriptors]]
prefix = "github.com/containerd/cgroups/v2/stats"
target = "v2/stats/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
"gogoproto/gogo.proto"
]
File diff suppressed because it is too large Load Diff
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -22,3 +22,5 @@ cgutil:
proto:
protobuild --quiet ${PACKAGES}
# Keep them Go-idiomatic and backward-compatible with the gogo/protobuf era.
go-fix-acronym -w -a '(Cpu|Tcp|Rss)' $(shell find cgroup1/stats/ cgroup2/stats/ -name '*.pb.go')
+31
View File
@@ -0,0 +1,31 @@
version = "2"
generators = ["go"]
# Control protoc include paths. Below are usually some good defaults, but feel
# free to try it without them if it works for your project.
[includes]
# Include paths that will be added before all others. Typically, you want to
# treat the root of the project as an include, but this may not be necessary.
# before = ["."]
# Paths that will be added untouched to the end of the includes. We use
# `/usr/local/include` to pickup the common install location of protobuf.
# This is the default.
after = ["/usr/local/include", "/usr/include"]
# Aggregrate the API descriptors to lock down API changes.
[[descriptors]]
prefix = "github.com/containerd/cgroups/cgroup1/stats"
target = "cgroup1/stats/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
]
[[descriptors]]
prefix = "github.com/containerd/cgroups/cgroup2/stats"
target = "cgroup2/stats/metrics.pb.txt"
ignore_files = [
"google/protobuf/descriptor.proto",
]
[parameters.go]
paths = "source_relative"
@@ -25,7 +25,7 @@ uses the v1 implementation of cgroups.
```go
shares := uint64(100)
control, err := cgroups.New(cgroups.V1, cgroups.StaticPath("/test"), &specs.LinuxResources{
control, err := cgroup1.New(cgroup1.StaticPath("/test"), &specs.LinuxResources{
CPU: &specs.LinuxCPU{
Shares: &shares,
},
@@ -37,7 +37,7 @@ defer control.Delete()
```go
control, err := cgroups.New(cgroups.Systemd, cgroups.Slice("system.slice", "runc-test"), &specs.LinuxResources{
control, err := cgroup1.New(cgroup1.Systemd, cgroup1.Slice("system.slice", "runc-test"), &specs.LinuxResources{
CPU: &specs.CPU{
Shares: &shares,
},
@@ -48,13 +48,13 @@ control, err := cgroups.New(cgroups.Systemd, cgroups.Slice("system.slice", "runc
### Load an existing cgroup
```go
control, err = cgroups.Load(cgroups.V1, cgroups.StaticPath("/test"))
control, err = cgroup1.Load(cgroup1.Default, cgroups.StaticPath("/test"))
```
### Add a process to the cgroup
```go
if err := control.Add(cgroups.Process{Pid:1234}); err != nil {
if err := control.Add(cgroup1.Process{Pid:1234}); err != nil {
}
```
@@ -84,7 +84,7 @@ if err := control.Thaw(); err != nil {
### List all processes in the cgroup or recursively
```go
processes, err := control.Processes(cgroups.Devices, recursive)
processes, err := control.Processes(cgroup1.Devices, recursive)
```
### Get Stats on the cgroup
@@ -95,7 +95,7 @@ stats, err := control.Stat()
By adding `cgroups.IgnoreNotExist` all non-existent files will be ignored, e.g. swap memory stats without swap enabled
```go
stats, err := control.Stat(cgroups.IgnoreNotExist)
stats, err := control.Stat(cgroup1.IgnoreNotExist)
```
### Move process across cgroups
@@ -117,19 +117,19 @@ subCgroup, err := control.New("child", resources)
This allows you to get notified by an eventfd for v1 memory cgroups events.
```go
event := cgroups.MemoryThresholdEvent(50 * 1024 * 1024, false)
event := cgroup1.MemoryThresholdEvent(50 * 1024 * 1024, false)
efd, err := control.RegisterMemoryEvent(event)
```
```go
event := cgroups.MemoryPressureEvent(cgroups.MediumPressure, cgroups.DefaultMode)
event := cgroup1.MemoryPressureEvent(cgroup1.MediumPressure, cgroup1.DefaultMode)
efd, err := control.RegisterMemoryEvent(event)
```
```go
efd, err := control.OOMEventFD()
// or by using RegisterMemoryEvent
event := cgroups.OOMEvent()
event := cgroup1.OOMEvent()
efd, err := control.RegisterMemoryEvent(event)
```
@@ -153,14 +153,14 @@ so the resulting slice would be located here on disk:
```go
import (
cgroupsv2 "github.com/containerd/cgroups/v2"
"github.com/containerd/cgroups/v3/cgroup2"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
res := cgroupsv2.Resources{}
res := cgroup2.Resources{}
// dummy PID of -1 is used for creating a "general slice" to be used as a parent cgroup.
// see https://github.com/containerd/cgroups/blob/1df78138f1e1e6ee593db155c6b369466f577651/v2/manager.go#L732-L735
m, err := cgroupsv2.NewSystemd("/", "my-cgroup-abc.slice", -1, &res)
m, err := cgroup2.NewSystemd("/", "my-cgroup-abc.slice", -1, &res)
if err != nil {
return err
}
@@ -169,7 +169,7 @@ if err != nil {
### Load an existing cgroup
```go
m, err := cgroupsv2.LoadSystemd("/", "my-cgroup-abc.slice")
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
@@ -178,7 +178,7 @@ if err != nil {
### Delete a cgroup
```go
m, err := cgroupsv2.LoadSystemd("/", "my-cgroup-abc.slice")
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
@@ -188,6 +188,40 @@ if err != nil {
}
```
### Kill all processes in a cgroup
```go
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
err = m.Kill()
if err != nil {
return err
}
```
### Get and set cgroup type
```go
m, err := cgroup2.LoadSystemd("/", "my-cgroup-abc.slice")
if err != nil {
return err
}
// https://www.kernel.org/doc/html/v5.0/admin-guide/cgroup-v2.html#threads
cgType, err := m.GetType()
if err != nil {
return err
}
fmt.Println(cgType)
err = m.SetType(cgroup2.Threaded)
if err != nil {
return err
}
```
### Attention
All static path should not include `/sys/fs/cgroup/` prefix, it should start with your own cgroups name
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@@ -25,7 +25,8 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -71,7 +72,7 @@ func (b *blkioController) Create(path string, resources *specs.LinuxResources) e
}
for _, t := range createBlkioSettings(resources.BlockIO) {
if t.value != nil {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(b.Path(path), "blkio."+t.name),
t.format(t.value),
defaultFilePerm,
@@ -330,7 +331,6 @@ type deviceKey struct {
// keyed by major and minor number. Since devices may be mapped multiple times,
// we err on taking the first occurrence.
func getDevices(r io.Reader) (map[deviceKey]string, error) {
var (
s = bufio.NewScanner(r)
devices = make(map[deviceKey]string)
@@ -14,31 +14,34 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
"github.com/opencontainers/runtime-spec/specs-go"
)
// New returns a new control via the cgroup cgroups interface
func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
func New(path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
config := newInitConfig()
for _, o := range opts {
if err := o(config); err != nil {
return nil, err
}
}
subsystems, err := hierarchy()
subsystems, err := config.hierarchy()
if err != nil {
return nil, err
}
@@ -68,7 +71,7 @@ func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources, opts .
// Load will load an existing cgroup and allow it to be controlled
// All static path should not include `/sys/fs/cgroup/` prefix, it should start with your own cgroups name
func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
func Load(path Path, opts ...InitOpts) (Cgroup, error) {
config := newInitConfig()
for _, o := range opts {
if err := o(config); err != nil {
@@ -76,7 +79,7 @@ func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
}
}
var activeSubsystems []Subsystem
subsystems, err := hierarchy()
subsystems, err := config.hierarchy()
if err != nil {
return nil, err
}
@@ -155,7 +158,7 @@ func (c *cgroup) subsystemsFilter(subsystems ...Name) []Subsystem {
return c.subsystems
}
var filteredSubsystems = []Subsystem{}
filteredSubsystems := []Subsystem{}
for _, s := range c.subsystems {
for _, f := range subsystems {
if s.Name() == f {
@@ -193,6 +196,31 @@ func (c *cgroup) AddTask(process Process, subsystems ...Name) error {
return c.add(process, cgroupTasks, subsystems...)
}
// writeCgroupsProcs writes to the file, but retries on EINVAL.
func writeCgroupProcs(path string, content []byte, perm fs.FileMode) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, perm)
if err != nil {
return err
}
defer f.Close()
for i := 0; i < 5; i++ {
_, err = f.Write(content)
if err == nil {
return nil
}
// If the process's associated task's state is TASK_NEW, the kernel
// returns EINVAL. The function will retry on the error like runc.
// https://github.com/torvalds/linux/blob/v6.0/kernel/sched/core.c#L10308-L10337
// https://github.com/opencontainers/runc/pull/1950
if !errors.Is(err, syscall.EINVAL) {
return err
}
time.Sleep(30 * time.Millisecond)
}
return err
}
func (c *cgroup) add(process Process, pType procType, subsystems ...Name) error {
if process.Pid <= 0 {
return ErrInvalidPid
@@ -207,7 +235,7 @@ func (c *cgroup) add(process Process, pType procType, subsystems ...Name) error
if err != nil {
return err
}
err = retryingWriteFile(
err = writeCgroupProcs(
filepath.Join(s.Path(p), pType),
[]byte(strconv.Itoa(process.Pid)),
defaultFilePerm,
@@ -231,6 +259,10 @@ func (c *cgroup) Delete() error {
// kernel prevents cgroups with running process from being removed, check the tree is empty
procs, err := c.processes(s.Name(), true, cgroupProcs)
if err != nil {
// if the control group does not exist within a subsystem, then proceed to the next subsystem
if errors.Is(err, os.ErrNotExist) {
continue
}
return err
}
if len(procs) > 0 {
@@ -14,12 +14,12 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -28,7 +28,7 @@ type procType = string
const (
cgroupProcs procType = "cgroup.procs"
cgroupTasks procType = "tasks"
defaultDirPerm = 0755
defaultDirPerm = 0o755
)
// defaultFilePerm is a var so that the test framework can change the filemode
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@@ -22,7 +22,7 @@ import (
"path/filepath"
"strconv"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -82,7 +82,7 @@ func (c *cpuController) Create(path string, resources *specs.LinuxResources) err
value = []byte(strconv.FormatInt(*t.ivalue, 10))
}
if value != nil {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(c.Path(path), "cpu."+t.name),
value,
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@@ -24,7 +24,7 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
)
const nanosecondsInSecond = 1000000000
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bytes"
@@ -68,7 +68,7 @@ func (c *cpusetController) Create(path string, resources *specs.LinuxResources)
},
} {
if t.value != "" {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(c.Path(path), "cpuset."+t.name),
[]byte(t.value),
defaultFilePerm,
@@ -133,7 +133,7 @@ func (c *cpusetController) copyIfNeeded(current, parent string) error {
return err
}
if isEmpty(currentCpus) {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(current, "cpuset.cpus"),
parentCpus,
defaultFilePerm,
@@ -142,7 +142,7 @@ func (c *cpusetController) copyIfNeeded(current, parent string) error {
}
}
if isEmpty(currentMems) {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(current, "cpuset.mems"),
parentMems,
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"fmt"
@@ -60,7 +60,7 @@ func (d *devicesController) Create(path string, resources *specs.LinuxResources)
if device.Type == "" {
device.Type = "a"
}
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(d.Path(path), file),
[]byte(deviceString(device)),
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
@@ -50,7 +50,7 @@ func (f *freezerController) Thaw(path string) error {
}
func (f *freezerController) changeState(path string, state State) error {
return retryingWriteFile(
return os.WriteFile(
filepath.Join(f.root, path, "freezer.state"),
[]byte(strings.ToUpper(string(state))),
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
// Hierarchy enables both unified and split hierarchy for cgroups
type Hierarchy func() ([]Subsystem, error)
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
@@ -22,7 +22,7 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -56,7 +56,7 @@ func (h *hugetlbController) Create(path string, resources *specs.LinuxResources)
return err
}
for _, limit := range resources.HugepageLimits {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(h.Path(path), strings.Join([]string{"hugetlb", limit.Pagesize, "limit_in_bytes"}, ".")),
[]byte(strconv.FormatUint(limit.Limit, 10)),
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@@ -25,7 +25,7 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
@@ -43,7 +43,7 @@ type memoryThresholdEvent struct {
swap bool
}
// MemoryThresholdEvent returns a new memory threshold event to be used with RegisterMemoryEvent.
// MemoryThresholdEvent returns a new [MemoryEvent] representing the memory threshold set.
// If swap is true, the event will be registered using memory.memsw.usage_in_bytes
func MemoryThresholdEvent(threshold uint64, swap bool) MemoryEvent {
return &memoryThresholdEvent{
@@ -83,7 +83,7 @@ type memoryPressureEvent struct {
hierarchy EventNotificationMode
}
// MemoryPressureEvent returns a new memory pressure event to be used with RegisterMemoryEvent.
// MemoryPressureEvent returns a new [MemoryEvent] representing the memory pressure set.
func MemoryPressureEvent(pressureLevel MemoryPressureLevel, hierarchy EventNotificationMode) MemoryEvent {
return &memoryPressureEvent{
pressureLevel,
@@ -104,22 +104,22 @@ func (m *memoryPressureEvent) EventFile() string {
type MemoryPressureLevel string
// The three memory pressure levels are as follows.
// - The "low" level means that the system is reclaiming memory for new
// allocations. Monitoring this reclaiming activity might be useful for
// maintaining cache level. Upon notification, the program (typically
// "Activity Manager") might analyze vmstat and act in advance (i.e.
// prematurely shutdown unimportant services).
// - The "medium" level means that the system is experiencing medium memory
// pressure, the system might be making swap, paging out active file caches,
// etc. Upon this event applications may decide to further analyze
// vmstat/zoneinfo/memcg or internal memory usage statistics and free any
// resources that can be easily reconstructed or re-read from a disk.
// - The "critical" level means that the system is actively thrashing, it is
// about to out of memory (OOM) or even the in-kernel OOM killer is on its
// way to trigger. Applications should do whatever they can to help the
// system. It might be too late to consult with vmstat or any other
// statistics, so it is advisable to take an immediate action.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
// - The "low" level means that the system is reclaiming memory for new
// allocations. Monitoring this reclaiming activity might be useful for
// maintaining cache level. Upon notification, the program (typically
// "Activity Manager") might analyze vmstat and act in advance (i.e.
// prematurely shutdown unimportant services).
// - The "medium" level means that the system is experiencing medium memory
// pressure, the system might be making swap, paging out active file caches,
// etc. Upon this event applications may decide to further analyze
// vmstat/zoneinfo/memcg or internal memory usage statistics and free any
// resources that can be easily reconstructed or re-read from a disk.
// - The "critical" level means that the system is actively thrashing, it is
// about to out of memory (OOM) or even the in-kernel OOM killer is on its
// way to trigger. Applications should do whatever they can to help the
// system. It might be too late to consult with vmstat or any other
// statistics, so it is advisable to take an immediate action.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
const (
LowPressure MemoryPressureLevel = "low"
MediumPressure MemoryPressureLevel = "medium"
@@ -131,21 +131,21 @@ const (
type EventNotificationMode string
// There are three optional modes that specify different propagation behavior:
// - "default": this is the default behavior specified above. This mode is the
// same as omitting the optional mode parameter, preserved by backwards
// compatibility.
// - "hierarchy": events always propagate up to the root, similar to the default
// behavior, except that propagation continues regardless of whether there are
// event listeners at each level, with the "hierarchy" mode. In the above
// example, groups A, B, and C will receive notification of memory pressure.
// - "local": events are pass-through, i.e. they only receive notifications when
// memory pressure is experienced in the memcg for which the notification is
// registered. In the above example, group C will receive notification if
// registered for "local" notification and the group experiences memory
// pressure. However, group B will never receive notification, regardless if
// there is an event listener for group C or not, if group B is registered for
// local notification.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
// - "default": this is the default behavior specified above. This mode is the
// same as omitting the optional mode parameter, preserved by backwards
// compatibility.
// - "hierarchy": events always propagate up to the root, similar to the default
// behavior, except that propagation continues regardless of whether there are
// event listeners at each level, with the "hierarchy" mode. In the above
// example, groups A, B, and C will receive notification of memory pressure.
// - "local": events are pass-through, i.e. they only receive notifications when
// memory pressure is experienced in the memcg for which the notification is
// registered. In the above example, group C will receive notification if
// registered for "local" notification and the group experiences memory
// pressure. However, group B will never receive notification, regardless if
// there is an event listener for group C or not, if group B is registered for
// local notification.
// "https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt" Section 11
const (
DefaultMode EventNotificationMode = "default"
LocalMode EventNotificationMode = "local"
@@ -394,7 +394,7 @@ func (m *memoryController) parseOomControlStats(r io.Reader, stat *v1.MemoryOomC
func (m *memoryController) set(path string, settings []memorySettings) error {
for _, t := range settings {
if t.value != nil {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(m.Path(path), "memory."+t.name),
[]byte(strconv.FormatInt(*t.value, 10)),
defaultFilePerm,
@@ -472,7 +472,7 @@ func (m *memoryController) memoryEvent(path string, event MemoryEvent) (uintptr,
defer evtFile.Close()
data := fmt.Sprintf("%d %d %s", efd, evtFile.Fd(), event.Arg())
evctlPath := filepath.Join(root, "cgroup.event_control")
if err := retryingWriteFile(evctlPath, []byte(data), 0700); err != nil {
if err := os.WriteFile(evctlPath, []byte(data), 0o700); err != nil {
unix.Close(efd)
return 0, err
}
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import "path/filepath"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
@@ -47,7 +47,7 @@ func (n *netclsController) Create(path string, resources *specs.LinuxResources)
return err
}
if resources.Network != nil && resources.Network.ClassID != nil && *resources.Network.ClassID > 0 {
return retryingWriteFile(
return os.WriteFile(
filepath.Join(n.Path(path), "net_cls.classid"),
[]byte(strconv.FormatUint(uint64(*resources.Network.ClassID), 10)),
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"fmt"
@@ -48,7 +48,7 @@ func (n *netprioController) Create(path string, resources *specs.LinuxResources)
}
if resources.Network != nil {
for _, prio := range resources.Network.Priorities {
if err := retryingWriteFile(
if err := os.WriteFile(
filepath.Join(n.Path(path), "net_prio.ifpriomap"),
formatPrio(prio.Name, prio.Priority),
defaultFilePerm,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"
@@ -36,11 +36,13 @@ type InitOpts func(*InitConfig) error
type InitConfig struct {
// InitCheck can be used to check initialization errors from the subsystem
InitCheck InitCheck
hierarchy Hierarchy
}
func newInitConfig() *InitConfig {
return &InitConfig{
InitCheck: RequireDevices,
hierarchy: Default,
}
}
@@ -59,3 +61,12 @@ func RequireDevices(s Subsystem, _ Path, _ error) error {
}
return ErrIgnoreSubsystem
}
// WithHiearchy sets a list of cgroup subsystems.
// The default list is coming from /proc/self/mountinfo.
func WithHiearchy(h Hierarchy) InitOpts {
return func(c *InitConfig) error {
c.hierarchy = h
return nil
}
}
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"errors"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import "path/filepath"
@@ -14,15 +14,14 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"os"
"path/filepath"
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -49,7 +48,7 @@ func (p *pidsController) Create(path string, resources *specs.LinuxResources) er
return err
}
if resources.Pids != nil && resources.Pids.Limit > 0 {
return retryingWriteFile(
return os.WriteFile(
filepath.Join(p.Path(path), "pids.max"),
[]byte(strconv.FormatInt(resources.Pids.Limit, 10)),
defaultFilePerm,
@@ -67,16 +66,10 @@ func (p *pidsController) Stat(path string, stats *v1.Metrics) error {
if err != nil {
return err
}
var max uint64
maxData, err := os.ReadFile(filepath.Join(p.Path(path), "pids.max"))
max, err := readUint(filepath.Join(p.Path(path), "pids.max"))
if err != nil {
return err
}
if maxS := strings.TrimSpace(string(maxData)); maxS != "max" {
if max, err = parseUint(maxS, 10, 64); err != nil {
return err
}
}
stats.Pids = &v1.PidsStat{
Current: current,
Limit: max,
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"math"
@@ -23,7 +23,7 @@ import (
"strconv"
"strings"
v1 "github.com/containerd/cgroups/stats/v1"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -67,7 +67,7 @@ func (p *rdmaController) Create(path string, resources *specs.LinuxResources) er
for device, limit := range resources.Rdma {
if device != "" && (limit.HcaHandles != nil || limit.HcaObjects != nil) {
limit := limit
return retryingWriteFile(
return os.WriteFile(
filepath.Join(p.Path(path), "rdma.max"),
[]byte(createCmdString(device, &limit)),
defaultFilePerm,
@@ -124,7 +124,6 @@ func toRdmaEntry(strEntries []string) []*v1.RdmaEntry {
}
func (p *rdmaController) Stat(path string, stats *v1.Metrics) error {
currentData, err := os.ReadFile(filepath.Join(p.Path(path), "rdma.current"))
if err != nil {
return err
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
// State is a type that represents the state of the current cgroup
type State string
File diff suppressed because it is too large Load Diff
+771
View File
@@ -0,0 +1,771 @@
file {
name: "github.com/containerd/cgroups/cgroup1/stats/metrics.proto"
package: "io.containerd.cgroups.v1"
message_type {
name: "Metrics"
field {
name: "hugetlb"
number: 1
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.HugetlbStat"
json_name: "hugetlb"
}
field {
name: "pids"
number: 2
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.PidsStat"
json_name: "pids"
}
field {
name: "cpu"
number: 3
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.CPUStat"
json_name: "cpu"
}
field {
name: "memory"
number: 4
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryStat"
json_name: "memory"
}
field {
name: "blkio"
number: 5
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOStat"
json_name: "blkio"
}
field {
name: "rdma"
number: 6
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.RdmaStat"
json_name: "rdma"
}
field {
name: "network"
number: 7
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.NetworkStat"
json_name: "network"
}
field {
name: "cgroup_stats"
number: 8
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.CgroupStats"
json_name: "cgroupStats"
}
field {
name: "memory_oom_control"
number: 9
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryOomControl"
json_name: "memoryOomControl"
}
}
message_type {
name: "HugetlbStat"
field {
name: "usage"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "usage"
}
field {
name: "max"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "max"
}
field {
name: "failcnt"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "failcnt"
}
field {
name: "pagesize"
number: 4
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "pagesize"
}
}
message_type {
name: "PidsStat"
field {
name: "current"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "current"
}
field {
name: "limit"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "limit"
}
}
message_type {
name: "CPUStat"
field {
name: "usage"
number: 1
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.CPUUsage"
json_name: "usage"
}
field {
name: "throttling"
number: 2
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.Throttle"
json_name: "throttling"
}
}
message_type {
name: "CPUUsage"
field {
name: "total"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "total"
}
field {
name: "kernel"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "kernel"
}
field {
name: "user"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "user"
}
field {
name: "per_cpu"
number: 4
label: LABEL_REPEATED
type: TYPE_UINT64
json_name: "perCpu"
}
}
message_type {
name: "Throttle"
field {
name: "periods"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "periods"
}
field {
name: "throttled_periods"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "throttledPeriods"
}
field {
name: "throttled_time"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "throttledTime"
}
}
message_type {
name: "MemoryStat"
field {
name: "cache"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "cache"
}
field {
name: "rss"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "rss"
}
field {
name: "rss_huge"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "rssHuge"
}
field {
name: "mapped_file"
number: 4
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "mappedFile"
}
field {
name: "dirty"
number: 5
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "dirty"
}
field {
name: "writeback"
number: 6
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "writeback"
}
field {
name: "pg_pg_in"
number: 7
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "pgPgIn"
}
field {
name: "pg_pg_out"
number: 8
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "pgPgOut"
}
field {
name: "pg_fault"
number: 9
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "pgFault"
}
field {
name: "pg_maj_fault"
number: 10
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "pgMajFault"
}
field {
name: "inactive_anon"
number: 11
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "inactiveAnon"
}
field {
name: "active_anon"
number: 12
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "activeAnon"
}
field {
name: "inactive_file"
number: 13
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "inactiveFile"
}
field {
name: "active_file"
number: 14
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "activeFile"
}
field {
name: "unevictable"
number: 15
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "unevictable"
}
field {
name: "hierarchical_memory_limit"
number: 16
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "hierarchicalMemoryLimit"
}
field {
name: "hierarchical_swap_limit"
number: 17
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "hierarchicalSwapLimit"
}
field {
name: "total_cache"
number: 18
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalCache"
}
field {
name: "total_rss"
number: 19
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalRss"
}
field {
name: "total_rss_huge"
number: 20
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalRssHuge"
}
field {
name: "total_mapped_file"
number: 21
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalMappedFile"
}
field {
name: "total_dirty"
number: 22
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalDirty"
}
field {
name: "total_writeback"
number: 23
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalWriteback"
}
field {
name: "total_pg_pg_in"
number: 24
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalPgPgIn"
}
field {
name: "total_pg_pg_out"
number: 25
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalPgPgOut"
}
field {
name: "total_pg_fault"
number: 26
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalPgFault"
}
field {
name: "total_pg_maj_fault"
number: 27
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalPgMajFault"
}
field {
name: "total_inactive_anon"
number: 28
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalInactiveAnon"
}
field {
name: "total_active_anon"
number: 29
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalActiveAnon"
}
field {
name: "total_inactive_file"
number: 30
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalInactiveFile"
}
field {
name: "total_active_file"
number: 31
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalActiveFile"
}
field {
name: "total_unevictable"
number: 32
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "totalUnevictable"
}
field {
name: "usage"
number: 33
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryEntry"
json_name: "usage"
}
field {
name: "swap"
number: 34
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryEntry"
json_name: "swap"
}
field {
name: "kernel"
number: 35
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryEntry"
json_name: "kernel"
}
field {
name: "kernel_tcp"
number: 36
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.MemoryEntry"
json_name: "kernelTcp"
}
}
message_type {
name: "MemoryEntry"
field {
name: "limit"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "limit"
}
field {
name: "usage"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "usage"
}
field {
name: "max"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "max"
}
field {
name: "failcnt"
number: 4
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "failcnt"
}
}
message_type {
name: "MemoryOomControl"
field {
name: "oom_kill_disable"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "oomKillDisable"
}
field {
name: "under_oom"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "underOom"
}
field {
name: "oom_kill"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "oomKill"
}
}
message_type {
name: "BlkIOStat"
field {
name: "io_service_bytes_recursive"
number: 1
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioServiceBytesRecursive"
}
field {
name: "io_serviced_recursive"
number: 2
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioServicedRecursive"
}
field {
name: "io_queued_recursive"
number: 3
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioQueuedRecursive"
}
field {
name: "io_service_time_recursive"
number: 4
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioServiceTimeRecursive"
}
field {
name: "io_wait_time_recursive"
number: 5
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioWaitTimeRecursive"
}
field {
name: "io_merged_recursive"
number: 6
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioMergedRecursive"
}
field {
name: "io_time_recursive"
number: 7
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "ioTimeRecursive"
}
field {
name: "sectors_recursive"
number: 8
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.BlkIOEntry"
json_name: "sectorsRecursive"
}
}
message_type {
name: "BlkIOEntry"
field {
name: "op"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "op"
}
field {
name: "device"
number: 2
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "device"
}
field {
name: "major"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "major"
}
field {
name: "minor"
number: 4
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "minor"
}
field {
name: "value"
number: 5
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "value"
}
}
message_type {
name: "RdmaStat"
field {
name: "current"
number: 1
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.RdmaEntry"
json_name: "current"
}
field {
name: "limit"
number: 2
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v1.RdmaEntry"
json_name: "limit"
}
}
message_type {
name: "RdmaEntry"
field {
name: "device"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "device"
}
field {
name: "hca_handles"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "hcaHandles"
}
field {
name: "hca_objects"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "hcaObjects"
}
}
message_type {
name: "NetworkStat"
field {
name: "name"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "name"
}
field {
name: "rx_bytes"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "rxBytes"
}
field {
name: "rx_packets"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "rxPackets"
}
field {
name: "rx_errors"
number: 4
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "rxErrors"
}
field {
name: "rx_dropped"
number: 5
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "rxDropped"
}
field {
name: "tx_bytes"
number: 6
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "txBytes"
}
field {
name: "tx_packets"
number: 7
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "txPackets"
}
field {
name: "tx_errors"
number: 8
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "txErrors"
}
field {
name: "tx_dropped"
number: 9
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "txDropped"
}
}
message_type {
name: "CgroupStats"
field {
name: "nr_sleeping"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "nrSleeping"
}
field {
name: "nr_running"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "nrRunning"
}
field {
name: "nr_stopped"
number: 3
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "nrStopped"
}
field {
name: "nr_uninterruptible"
number: 4
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "nrUninterruptible"
}
field {
name: "nr_io_wait"
number: 5
label: LABEL_OPTIONAL
type: TYPE_UINT64
json_name: "nrIoWait"
}
}
options {
go_package: "github.com/containerd/cgroups/cgroup1/stats"
}
syntax: "proto3"
}
+158
View File
@@ -0,0 +1,158 @@
syntax = "proto3";
package io.containerd.cgroups.v1;
option go_package = "github.com/containerd/cgroups/cgroup1/stats";
message Metrics {
repeated HugetlbStat hugetlb = 1;
PidsStat pids = 2;
CPUStat cpu = 3;
MemoryStat memory = 4;
BlkIOStat blkio = 5;
RdmaStat rdma = 6;
repeated NetworkStat network = 7;
CgroupStats cgroup_stats = 8;
MemoryOomControl memory_oom_control = 9;
}
message HugetlbStat {
uint64 usage = 1;
uint64 max = 2;
uint64 failcnt = 3;
string pagesize = 4;
}
message PidsStat {
uint64 current = 1;
uint64 limit = 2;
}
message CPUStat {
CPUUsage usage = 1;
Throttle throttling = 2;
}
message CPUUsage {
// values in nanoseconds
uint64 total = 1;
uint64 kernel = 2;
uint64 user = 3;
repeated uint64 per_cpu = 4;
}
message Throttle {
uint64 periods = 1;
uint64 throttled_periods = 2;
uint64 throttled_time = 3;
}
message MemoryStat {
uint64 cache = 1;
uint64 rss = 2;
uint64 rss_huge = 3;
uint64 mapped_file = 4;
uint64 dirty = 5;
uint64 writeback = 6;
uint64 pg_pg_in = 7;
uint64 pg_pg_out = 8;
uint64 pg_fault = 9;
uint64 pg_maj_fault = 10;
uint64 inactive_anon = 11;
uint64 active_anon = 12;
uint64 inactive_file = 13;
uint64 active_file = 14;
uint64 unevictable = 15;
uint64 hierarchical_memory_limit = 16;
uint64 hierarchical_swap_limit = 17;
uint64 total_cache = 18;
uint64 total_rss = 19;
uint64 total_rss_huge = 20;
uint64 total_mapped_file = 21;
uint64 total_dirty = 22;
uint64 total_writeback = 23;
uint64 total_pg_pg_in = 24;
uint64 total_pg_pg_out = 25;
uint64 total_pg_fault = 26;
uint64 total_pg_maj_fault = 27;
uint64 total_inactive_anon = 28;
uint64 total_active_anon = 29;
uint64 total_inactive_file = 30;
uint64 total_active_file = 31;
uint64 total_unevictable = 32;
MemoryEntry usage = 33;
MemoryEntry swap = 34;
MemoryEntry kernel = 35;
MemoryEntry kernel_tcp = 36;
}
message MemoryEntry {
uint64 limit = 1;
uint64 usage = 2;
uint64 max = 3;
uint64 failcnt = 4;
}
message MemoryOomControl {
uint64 oom_kill_disable = 1;
uint64 under_oom = 2;
uint64 oom_kill = 3;
}
message BlkIOStat {
repeated BlkIOEntry io_service_bytes_recursive = 1;
repeated BlkIOEntry io_serviced_recursive = 2;
repeated BlkIOEntry io_queued_recursive = 3;
repeated BlkIOEntry io_service_time_recursive = 4;
repeated BlkIOEntry io_wait_time_recursive = 5;
repeated BlkIOEntry io_merged_recursive = 6;
repeated BlkIOEntry io_time_recursive = 7;
repeated BlkIOEntry sectors_recursive = 8;
}
message BlkIOEntry {
string op = 1;
string device = 2;
uint64 major = 3;
uint64 minor = 4;
uint64 value = 5;
}
message RdmaStat {
repeated RdmaEntry current = 1;
repeated RdmaEntry limit = 2;
}
message RdmaEntry {
string device = 1;
uint32 hca_handles = 2;
uint32 hca_objects = 3;
}
message NetworkStat {
string name = 1;
uint64 rx_bytes = 2;
uint64 rx_packets = 3;
uint64 rx_errors = 4;
uint64 rx_dropped = 5;
uint64 tx_bytes = 6;
uint64 tx_packets = 7;
uint64 tx_errors = 8;
uint64 tx_dropped = 9;
}
// CgroupStats exports per-cgroup statistics.
message CgroupStats {
// number of tasks sleeping
uint64 nr_sleeping = 1;
// number of tasks running
uint64 nr_running = 2;
// number of tasks in stopped state
uint64 nr_stopped = 3;
// number of tasks in uninterruptible state
uint64 nr_uninterruptible = 4;
// number of tasks waiting on IO
uint64 nr_io_wait = 5;
}
@@ -14,13 +14,14 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"fmt"
"os"
v1 "github.com/containerd/cgroups/stats/v1"
"github.com/containerd/cgroups/v3"
v1 "github.com/containerd/cgroups/v3/cgroup1/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -59,7 +60,7 @@ func Subsystems() []Name {
Blkio,
Rdma,
}
if !RunningInUserNS() {
if !cgroups.RunningInUserNS() {
n = append(n, Devices)
}
if _, err := os.Stat("/sys/kernel/mm/hugepages"); err == nil {
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"context"
@@ -29,7 +29,7 @@ import (
const (
SystemdDbus Name = "systemd"
defaultSlice = "system.slice"
defaultSlice Name = "system.slice"
)
var (
@@ -56,7 +56,7 @@ func Systemd() ([]Subsystem, error) {
func Slice(slice, name string) Path {
if slice == "" {
slice = defaultSlice
slice = string(defaultSlice)
}
return func(subsystem Name) (string, error) {
return filepath.Join(slice, name), nil
@@ -70,7 +70,6 @@ func NewSystemd(root string) (*SystemdController, error) {
}
type SystemdController struct {
mu sync.Mutex
root string
}
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
func getClockTicks() uint64 {
// The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
@@ -14,106 +14,23 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
"errors"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/containerd/cgroups/v3"
units "github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
var (
nsOnce sync.Once
inUserNS bool
checkMode sync.Once
cgMode CGMode
)
const unifiedMountpoint = "/sys/fs/cgroup"
// CGMode is the cgroups mode of the host system
type CGMode int
const (
// Unavailable cgroup mountpoint
Unavailable CGMode = iota
// Legacy cgroups v1
Legacy
// Hybrid with cgroups v1 and v2 controllers mounted
Hybrid
// Unified with only cgroups v2 mounted
Unified
)
// Mode returns the cgroups mode running on the host
func Mode() CGMode {
checkMode.Do(func() {
var st unix.Statfs_t
if err := unix.Statfs(unifiedMountpoint, &st); err != nil {
cgMode = Unavailable
return
}
switch st.Type {
case unix.CGROUP2_SUPER_MAGIC:
cgMode = Unified
default:
cgMode = Legacy
if err := unix.Statfs(filepath.Join(unifiedMountpoint, "unified"), &st); err != nil {
return
}
if st.Type == unix.CGROUP2_SUPER_MAGIC {
cgMode = Hybrid
}
}
})
return cgMode
}
// RunningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}
// defaults returns all known groups
func defaults(root string) ([]Subsystem, error) {
h, err := NewHugetlb(root)
@@ -136,7 +53,7 @@ func defaults(root string) ([]Subsystem, error) {
}
// only add the devices cgroup if we are not in a user namespace
// because modifications are not allowed
if !RunningInUserNS() {
if !cgroups.RunningInUserNS() {
s = append(s, NewDevices(root))
}
// add the hugetlb cgroup if error wasn't due to missing hugetlb
@@ -215,11 +132,25 @@ func hugePageSizes() ([]string, error) {
}
func readUint(path string) (uint64, error) {
v, err := os.ReadFile(path)
f, err := os.Open(path)
if err != nil {
return 0, err
}
return parseUint(strings.TrimSpace(string(v)), 10, 64)
defer f.Close()
// We should only need 20 bytes for the max uint64, but for a nice power of 2
// lets use 32.
b := make([]byte, 32)
n, err := f.Read(b)
if err != nil {
return 0, err
}
s := string(bytes.TrimSpace(b[:n]))
if s == "max" {
// Return 0 for the max value to maintain backward compatibility.
return 0, nil
}
return parseUint(s, 10, 64)
}
func parseUint(s string, base, bitSize int) (uint64, error) {
@@ -256,12 +187,14 @@ func parseKV(raw string) (string, uint64, error) {
// ParseCgroupFile parses the given cgroup file, typically /proc/self/cgroup
// or /proc/<pid>/cgroup, into a map of subsystems to cgroup paths, e.g.
// "cpu": "/user.slice/user-1000.slice"
// "pids": "/user.slice/user-1000.slice"
//
// "cpu": "/user.slice/user-1000.slice"
// "pids": "/user.slice/user-1000.slice"
//
// etc.
//
// The resulting map does not have an element for cgroup v2 unified hierarchy.
// Use ParseCgroupFileUnified to get the unified path.
// Use [cgroups.ParseCgroupFileUnified] to get the unified path.
func ParseCgroupFile(path string) (map[string]string, error) {
x, _, err := ParseCgroupFileUnified(path)
return x, err
@@ -269,41 +202,10 @@ func ParseCgroupFile(path string) (map[string]string, error) {
// ParseCgroupFileUnified returns legacy subsystem paths as the first value,
// and returns the unified path as the second value.
//
// Deprecated: use [cgroups.ParseCgroupFileUnified] instead .
func ParseCgroupFileUnified(path string) (map[string]string, string, error) {
f, err := os.Open(path)
if err != nil {
return nil, "", err
}
defer f.Close()
return parseCgroupFromReaderUnified(f)
}
func parseCgroupFromReaderUnified(r io.Reader) (map[string]string, string, error) {
var (
cgroups = make(map[string]string)
unified = ""
s = bufio.NewScanner(r)
)
for s.Scan() {
var (
text = s.Text()
parts = strings.SplitN(text, ":", 3)
)
if len(parts) < 3 {
return nil, unified, fmt.Errorf("invalid cgroup entry: %q", text)
}
for _, subs := range strings.Split(parts[1], ",") {
if subs == "" {
unified = parts[2]
} else {
cgroups[subs] = parts[2]
}
}
}
if err := s.Err(); err != nil {
return nil, unified, err
}
return cgroups, unified, nil
return cgroups.ParseCgroupFileUnified(path)
}
func getCgroupDestination(subsystem string) (string, error) {
@@ -376,16 +278,3 @@ func cleanPath(path string) string {
}
return path
}
func retryingWriteFile(path string, data []byte, mode os.FileMode) error {
// Retry writes on EINTR; see:
// https://github.com/golang/go/issues/38033
for {
err := os.WriteFile(path, data, mode)
if err == nil {
return nil
} else if !errors.Is(err, syscall.EINTR) {
return err
}
}
}
@@ -14,7 +14,7 @@
limitations under the License.
*/
package cgroups
package cgroup1
import (
"bufio"
@@ -24,8 +24,8 @@ import (
"strings"
)
// V1 returns all the groups in the default cgroups mountpoint in a single hierarchy
func V1() ([]Subsystem, error) {
// Default returns all the groups in the default cgroups mountpoint in a single hierarchy
func Default() ([]Subsystem, error) {
root, err := v1MountPoint()
if err != nil {
return nil, err
@@ -45,7 +45,7 @@ func V1() ([]Subsystem, error) {
}
// v1MountPoint returns the mount point where the cgroup
// mountpoints are mounted in a single hiearchy
// mountpoints are mounted in a single hierarchy
func v1MountPoint() (string, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"math"
@@ -24,7 +24,7 @@
// This particular Go implementation based on runc version
// https://github.com/opencontainers/runc/blob/master/libcontainer/cgroups/ebpf/devicefilter/devicefilter.go
package v2
package cgroup2
import (
"errors"
@@ -167,7 +167,7 @@ func (p *program) appendDevice(dev specs.LinuxDeviceCgroup) error {
}
p.insts = append(p.insts, acceptBlock(dev.Allow)...)
// set blockSym to the first instruction we added in this iteration
p.insts[prevBlockLastIdx+1] = p.insts[prevBlockLastIdx+1].Sym(blockSym)
p.insts[prevBlockLastIdx+1] = p.insts[prevBlockLastIdx+1].WithSymbol(blockSym)
p.blockID++
return nil
}
@@ -180,7 +180,7 @@ func (p *program) finalize() (asm.Instructions, error) {
blockSym := fmt.Sprintf("block-%d", p.blockID)
p.insts = append(p.insts,
// R0 <- 0
asm.Mov.Imm32(asm.R0, 0).Sym(blockSym),
asm.Mov.Imm32(asm.R0, 0).WithSymbol(blockSym),
asm.Return(),
)
p.blockID = -1
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"fmt"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"errors"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import "strings"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import "fmt"
@@ -14,23 +14,21 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/containerd/cgroups/v2/stats"
"github.com/containerd/cgroups/v3/cgroup2/stats"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
@@ -42,13 +40,13 @@ import (
const (
subtreeControl = "cgroup.subtree_control"
controllersFile = "cgroup.controllers"
killFile = "cgroup.kill"
typeFile = "cgroup.type"
defaultCgroup2Path = "/sys/fs/cgroup"
defaultSlice = "system.slice"
)
var (
canDelegate bool
)
var canDelegate bool
type Event struct {
Low uint64
@@ -98,7 +96,9 @@ func (r *Resources) Values() (o []Value) {
func (r *Resources) EnabledControllers() (c []string) {
if r.CPU != nil {
c = append(c, "cpu")
c = append(c, "cpuset")
if r.CPU.Cpus != "" || r.CPU.Mems != "" {
c = append(c, "cpuset")
}
}
if r.Memory != nil {
c = append(c, "memory")
@@ -144,20 +144,11 @@ func (c *Value) write(path string, perm os.FileMode) error {
return ErrInvalidFormat
}
// Retry writes on EINTR; see:
// https://github.com/golang/go/issues/38033
for {
err := os.WriteFile(
filepath.Join(path, c.filename),
data,
perm,
)
if err == nil {
return nil
} else if !errors.Is(err, syscall.EINTR) {
return err
}
}
return os.WriteFile(
filepath.Join(path, c.filename),
data,
perm,
)
}
func writeValues(path string, values []Value) error {
@@ -196,13 +187,35 @@ func NewManager(mountpoint string, group string, resources *Resources) (*Manager
return &m, nil
}
func LoadManager(mountpoint string, group string) (*Manager, error) {
type InitConfig struct {
mountpoint string
}
type InitOpts func(c *InitConfig) error
// WithMountpoint sets the unified mountpoint. The default path is /sys/fs/cgroup.
func WithMountpoint(path string) InitOpts {
return func(c *InitConfig) error {
c.mountpoint = path
return nil
}
}
// Load a cgroup.
func Load(group string, opts ...InitOpts) (*Manager, error) {
c := InitConfig{mountpoint: defaultCgroup2Path}
for _, opt := range opts {
if err := opt(&c); err != nil {
return nil, err
}
}
if err := VerifyGroupPath(group); err != nil {
return nil, err
}
path := filepath.Join(mountpoint, group)
path := filepath.Join(c.mountpoint, group)
return &Manager{
unifiedMountpoint: mountpoint,
unifiedMountpoint: c.mountpoint,
path: path,
}, nil
}
@@ -224,6 +237,35 @@ func setResources(path string, resources *Resources) error {
return nil
}
// CgroupType represents the types a cgroup can be.
type CgroupType string
const (
Domain CgroupType = "domain"
Threaded CgroupType = "threaded"
)
func (c *Manager) GetType() (CgroupType, error) {
val, err := os.ReadFile(filepath.Join(c.path, typeFile))
if err != nil {
return "", err
}
trimmed := strings.TrimSpace(string(val))
return CgroupType(trimmed), nil
}
func (c *Manager) SetType(cgType CgroupType) error {
// NOTE: We could abort if cgType != Threaded here as currently
// it's not possible to revert back to domain, but not sure
// it's worth being that opinionated, especially if that may
// ever change.
v := Value{
filename: typeFile,
value: string(cgType),
}
return writeValues(c.path, []Value{v})
}
func (c *Manager) RootControllers() ([]string, error) {
b, err := os.ReadFile(filepath.Join(c.unifiedMountpoint, controllersFile))
if err != nil {
@@ -344,6 +386,86 @@ func (c *Manager) AddThread(tid uint64) error {
return writeValues(c.path, []Value{v})
}
// Kill will try to forcibly exit all of the processes in the cgroup. This is
// equivalent to sending a SIGKILL to every process. On kernels 5.14 and greater
// this will use the cgroup.kill file, on anything that doesn't have the cgroup.kill
// file, a manual process of freezing -> sending a SIGKILL to every process -> thawing
// will be used.
func (c *Manager) Kill() error {
v := Value{
filename: killFile,
value: "1",
}
err := writeValues(c.path, []Value{v})
if err == nil {
return nil
}
logrus.Warnf("falling back to slower kill implementation: %s", err)
// Fallback to slow method.
return c.fallbackKill()
}
// fallbackKill is a slower fallback to the more modern (kernels 5.14+)
// approach of writing to the cgroup.kill file. This is heavily pulled
// from runc's same approach (in signalAllProcesses), with the only differences
// being this is just tailored to the API exposed in this library, and we don't
// need to care about signals other than SIGKILL.
//
// https://github.com/opencontainers/runc/blob/8da0a0b5675764feaaaaad466f6567a9983fcd08/libcontainer/init_linux.go#L523-L529
func (c *Manager) fallbackKill() error {
if err := c.Freeze(); err != nil {
logrus.Warn(err)
}
pids, err := c.Procs(true)
if err != nil {
if err := c.Thaw(); err != nil {
logrus.Warn(err)
}
return err
}
var procs []*os.Process
for _, pid := range pids {
p, err := os.FindProcess(int(pid))
if err != nil {
logrus.Warn(err)
continue
}
procs = append(procs, p)
if err := p.Signal(unix.SIGKILL); err != nil {
logrus.Warn(err)
}
}
if err := c.Thaw(); err != nil {
logrus.Warn(err)
}
subreaper, err := getSubreaper()
if err != nil {
// The error here means that PR_GET_CHILD_SUBREAPER is not
// supported because this code might run on a kernel older
// than 3.4. We don't want to throw an error in that case,
// and we simplify things, considering there is no subreaper
// set.
subreaper = 0
}
for _, p := range procs {
// In case a subreaper has been setup, this code must not
// wait for the process. Otherwise, we cannot be sure the
// current process will be reaped by the subreaper, while
// the subreaper might be waiting for this process in order
// to retrieve its exit code.
if subreaper == 0 {
if _, err := p.Wait(); err != nil {
if !errors.Is(err, unix.ECHILD) {
logrus.Warnf("wait on pid %d failed: %s", p.Pid, err)
}
}
}
}
return nil
}
func (c *Manager) Delete() error {
// kernel prevents cgroups with running process from being removed, check the tree is empty
processes, err := c.Procs(true)
@@ -398,17 +520,15 @@ func (c *Manager) MoveTo(destination *Manager) error {
return nil
}
var singleValueFiles = []string{
"pids.current",
"pids.max",
}
func (c *Manager) Stat() (*stats.Metrics, error) {
controllers, err := c.Controllers()
if err != nil {
return nil, err
}
out := make(map[string]interface{})
// Sizing this avoids an allocation to increase the map at runtime;
// currently the default bucket size is 8 and we put 40+ elements
// in it so we'd always end up allocating.
out := make(map[string]uint64, 50)
for _, controller := range controllers {
switch controller {
case "cpu", "memory":
@@ -420,66 +540,58 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
}
}
}
for _, name := range singleValueFiles {
if err := readSingleFile(c.path, name, out); err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
}
memoryEvents := make(map[string]interface{})
memoryEvents := make(map[string]uint64)
if err := readKVStatsFile(c.path, "memory.events", memoryEvents); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
}
var metrics stats.Metrics
var metrics stats.Metrics
metrics.Pids = &stats.PidsStat{
Current: getPidValue("pids.current", out),
Limit: getPidValue("pids.max", out),
Current: getStatFileContentUint64(filepath.Join(c.path, "pids.current")),
Limit: getStatFileContentUint64(filepath.Join(c.path, "pids.max")),
}
metrics.CPU = &stats.CPUStat{
UsageUsec: getUint64Value("usage_usec", out),
UserUsec: getUint64Value("user_usec", out),
SystemUsec: getUint64Value("system_usec", out),
NrPeriods: getUint64Value("nr_periods", out),
NrThrottled: getUint64Value("nr_throttled", out),
ThrottledUsec: getUint64Value("throttled_usec", out),
UsageUsec: out["usage_usec"],
UserUsec: out["user_usec"],
SystemUsec: out["system_usec"],
NrPeriods: out["nr_periods"],
NrThrottled: out["nr_throttled"],
ThrottledUsec: out["throttled_usec"],
}
metrics.Memory = &stats.MemoryStat{
Anon: getUint64Value("anon", out),
File: getUint64Value("file", out),
KernelStack: getUint64Value("kernel_stack", out),
Slab: getUint64Value("slab", out),
Sock: getUint64Value("sock", out),
Shmem: getUint64Value("shmem", out),
FileMapped: getUint64Value("file_mapped", out),
FileDirty: getUint64Value("file_dirty", out),
FileWriteback: getUint64Value("file_writeback", out),
AnonThp: getUint64Value("anon_thp", out),
InactiveAnon: getUint64Value("inactive_anon", out),
ActiveAnon: getUint64Value("active_anon", out),
InactiveFile: getUint64Value("inactive_file", out),
ActiveFile: getUint64Value("active_file", out),
Unevictable: getUint64Value("unevictable", out),
SlabReclaimable: getUint64Value("slab_reclaimable", out),
SlabUnreclaimable: getUint64Value("slab_unreclaimable", out),
Pgfault: getUint64Value("pgfault", out),
Pgmajfault: getUint64Value("pgmajfault", out),
WorkingsetRefault: getUint64Value("workingset_refault", out),
WorkingsetActivate: getUint64Value("workingset_activate", out),
WorkingsetNodereclaim: getUint64Value("workingset_nodereclaim", out),
Pgrefill: getUint64Value("pgrefill", out),
Pgscan: getUint64Value("pgscan", out),
Pgsteal: getUint64Value("pgsteal", out),
Pgactivate: getUint64Value("pgactivate", out),
Pgdeactivate: getUint64Value("pgdeactivate", out),
Pglazyfree: getUint64Value("pglazyfree", out),
Pglazyfreed: getUint64Value("pglazyfreed", out),
ThpFaultAlloc: getUint64Value("thp_fault_alloc", out),
ThpCollapseAlloc: getUint64Value("thp_collapse_alloc", out),
Anon: out["anon"],
File: out["file"],
KernelStack: out["kernel_stack"],
Slab: out["slab"],
Sock: out["sock"],
Shmem: out["shmem"],
FileMapped: out["file_mapped"],
FileDirty: out["file_dirty"],
FileWriteback: out["file_writeback"],
AnonThp: out["anon_thp"],
InactiveAnon: out["inactive_anon"],
ActiveAnon: out["active_anon"],
InactiveFile: out["inactive_file"],
ActiveFile: out["active_file"],
Unevictable: out["unevictable"],
SlabReclaimable: out["slab_reclaimable"],
SlabUnreclaimable: out["slab_unreclaimable"],
Pgfault: out["pgfault"],
Pgmajfault: out["pgmajfault"],
WorkingsetRefault: out["workingset_refault"],
WorkingsetActivate: out["workingset_activate"],
WorkingsetNodereclaim: out["workingset_nodereclaim"],
Pgrefill: out["pgrefill"],
Pgscan: out["pgscan"],
Pgsteal: out["pgsteal"],
Pgactivate: out["pgactivate"],
Pgdeactivate: out["pgdeactivate"],
Pglazyfree: out["pglazyfree"],
Pglazyfreed: out["pglazyfreed"],
ThpFaultAlloc: out["thp_fault_alloc"],
ThpCollapseAlloc: out["thp_collapse_alloc"],
Usage: getStatFileContentUint64(filepath.Join(c.path, "memory.current")),
UsageLimit: getStatFileContentUint64(filepath.Join(c.path, "memory.max")),
SwapUsage: getStatFileContentUint64(filepath.Join(c.path, "memory.swap.current")),
@@ -487,11 +599,11 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
}
if len(memoryEvents) > 0 {
metrics.MemoryEvents = &stats.MemoryEvents{
Low: getUint64Value("low", memoryEvents),
High: getUint64Value("high", memoryEvents),
Max: getUint64Value("max", memoryEvents),
Oom: getUint64Value("oom", memoryEvents),
OomKill: getUint64Value("oom_kill", memoryEvents),
Low: memoryEvents["low"],
High: memoryEvents["high"],
Max: memoryEvents["max"],
Oom: memoryEvents["oom"],
OomKill: memoryEvents["oom_kill"],
}
}
metrics.Io = &stats.IOStat{Usage: readIoStats(c.path)}
@@ -504,56 +616,7 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
return &metrics, nil
}
func getUint64Value(key string, out map[string]interface{}) uint64 {
v, ok := out[key]
if !ok {
return 0
}
switch t := v.(type) {
case uint64:
return t
}
return 0
}
func getPidValue(key string, out map[string]interface{}) uint64 {
v, ok := out[key]
if !ok {
return 0
}
switch t := v.(type) {
case uint64:
return t
case string:
if t == "max" {
return math.MaxUint64
}
}
return 0
}
func readSingleFile(path string, file string, out map[string]interface{}) error {
f, err := os.Open(filepath.Join(path, file))
if err != nil {
return err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return err
}
s := strings.TrimSpace(string(data))
v, err := parseUint(s, 10, 64)
if err != nil {
// if we cannot parse as a uint, parse as a string
out[file] = s
return nil
}
out[file] = v
return nil
}
func readKVStatsFile(path string, file string, out map[string]interface{}) error {
func readKVStatsFile(path string, file string, out map[string]uint64) error {
f, err := os.Open(filepath.Join(path, file))
if err != nil {
return err
@@ -598,16 +661,12 @@ func (c *Manager) freeze(path string, state State) error {
func (c *Manager) isCgroupEmpty() bool {
// In case of any error we return true so that we exit and don't leak resources
out := make(map[string]interface{})
out := make(map[string]uint64)
if err := readKVStatsFile(c.path, "cgroup.events", out); err != nil {
return true
}
if v, ok := out["populated"]; ok {
populated, ok := v.(uint64)
if !ok {
return true
}
return populated == 0
return v == 0
}
return true
}
@@ -615,19 +674,19 @@ func (c *Manager) isCgroupEmpty() bool {
// MemoryEventFD returns inotify file descriptor and 'memory.events' inotify watch descriptor
func (c *Manager) MemoryEventFD() (int, uint32, error) {
fpath := filepath.Join(c.path, "memory.events")
fd, err := syscall.InotifyInit()
fd, err := unix.InotifyInit()
if err != nil {
return 0, 0, errors.New("failed to create inotify fd")
}
wd, err := syscall.InotifyAddWatch(fd, fpath, unix.IN_MODIFY)
wd, err := unix.InotifyAddWatch(fd, fpath, unix.IN_MODIFY)
if err != nil {
syscall.Close(fd)
unix.Close(fd)
return 0, 0, fmt.Errorf("failed to add inotify watch for %q: %w", fpath, err)
}
// monitor to detect process exit/cgroup deletion
evpath := filepath.Join(c.path, "cgroup.events")
if _, err = syscall.InotifyAddWatch(fd, evpath, unix.IN_MODIFY); err != nil {
syscall.Close(fd)
if _, err = unix.InotifyAddWatch(fd, evpath, unix.IN_MODIFY); err != nil {
unix.Close(fd)
return 0, 0, fmt.Errorf("failed to add inotify watch for %q: %w", evpath, err)
}
@@ -642,41 +701,6 @@ func (c *Manager) EventChan() (<-chan Event, <-chan error) {
return ec, errCh
}
func parseMemoryEvents(out map[string]interface{}) (Event, error) {
e := Event{}
if v, ok := out["high"]; ok {
e.High, ok = v.(uint64)
if !ok {
return Event{}, fmt.Errorf("cannot convert high to uint64: %+v", v)
}
}
if v, ok := out["low"]; ok {
e.Low, ok = v.(uint64)
if !ok {
return Event{}, fmt.Errorf("cannot convert low to uint64: %+v", v)
}
}
if v, ok := out["max"]; ok {
e.Max, ok = v.(uint64)
if !ok {
return Event{}, fmt.Errorf("cannot convert max to uint64: %+v", v)
}
}
if v, ok := out["oom"]; ok {
e.OOM, ok = v.(uint64)
if !ok {
return Event{}, fmt.Errorf("cannot convert oom to uint64: %+v", v)
}
}
if v, ok := out["oom_kill"]; ok {
e.OOMKill, ok = v.(uint64)
if !ok {
return Event{}, fmt.Errorf("cannot convert oom_kill to uint64: %+v", v)
}
}
return e, nil
}
func (c *Manager) waitForEvents(ec chan<- Event, errCh chan<- error) {
defer close(errCh)
@@ -685,17 +709,17 @@ func (c *Manager) waitForEvents(ec chan<- Event, errCh chan<- error) {
errCh <- err
return
}
defer syscall.Close(fd)
defer unix.Close(fd)
for {
buffer := make([]byte, syscall.SizeofInotifyEvent*10)
bytesRead, err := syscall.Read(fd, buffer)
buffer := make([]byte, unix.SizeofInotifyEvent*10)
bytesRead, err := unix.Read(fd, buffer)
if err != nil {
errCh <- err
return
}
if bytesRead >= syscall.SizeofInotifyEvent {
out := make(map[string]interface{})
if bytesRead >= unix.SizeofInotifyEvent {
out := make(map[string]uint64)
if err := readKVStatsFile(c.path, "memory.events", out); err != nil {
// When cgroup is deleted read may return -ENODEV instead of -ENOENT from open.
if _, statErr := os.Lstat(filepath.Join(c.path, "memory.events")); !os.IsNotExist(statErr) {
@@ -703,12 +727,13 @@ func (c *Manager) waitForEvents(ec chan<- Event, errCh chan<- error) {
}
return
}
e, err := parseMemoryEvents(out)
if err != nil {
errCh <- err
return
ec <- Event{
Low: out["low"],
High: out["high"],
Max: out["max"],
OOM: out["oom"],
OOMKill: out["oom_kill"],
}
ec <- e
if c.isCgroupEmpty() {
return
}
@@ -724,7 +749,7 @@ func setDevices(path string, devices []specs.LinuxDeviceCgroup) error {
if err != nil {
return err
}
dirFD, err := unix.Open(path, unix.O_DIRECTORY|unix.O_RDONLY|unix.O_CLOEXEC, 0600)
dirFD, err := unix.Open(path, unix.O_DIRECTORY|unix.O_RDONLY|unix.O_CLOEXEC, 0o600)
if err != nil {
return fmt.Errorf("cannot get dir FD for %s", path)
}
@@ -741,7 +766,8 @@ func setDevices(path string, devices []specs.LinuxDeviceCgroup) error {
// the reason this is necessary is because the "-" character has a special meaning in
// systemd slice. For example, when creating a slice called "my-group-112233.slice",
// systemd will create a hierarchy like this:
// /sys/fs/cgroup/my.slice/my-group.slice/my-group-112233.slice
//
// /sys/fs/cgroup/my.slice/my-group.slice/my-group-112233.slice
func getSystemdFullPath(slice, group string) string {
return filepath.Join(defaultCgroup2Path, dashesToPath(slice), dashesToPath(group))
}
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
type Memory struct {
Swap *int64
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"fmt"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import "strconv"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"fmt"
@@ -14,7 +14,7 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"os"
+17
View File
@@ -0,0 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stats
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,6 @@
file {
name: "github.com/containerd/cgroups/v2/stats/metrics.proto"
name: "github.com/containerd/cgroups/cgroup2/stats/metrics.proto"
package: "io.containerd.cgroups.v2"
dependency: "gogoproto/gogo.proto"
message_type {
name: "Metrics"
field {
@@ -18,9 +17,6 @@ file {
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".io.containerd.cgroups.v2.CPUStat"
options {
65004: "CPU"
}
json_name: "cpu"
}
field {
@@ -535,5 +531,8 @@ file {
json_name: "pagesize"
}
}
options {
go_package: "github.com/containerd/cgroups/cgroup2/stats"
}
syntax: "proto3"
}
@@ -2,11 +2,11 @@ syntax = "proto3";
package io.containerd.cgroups.v2;
import "gogoproto/gogo.proto";
option go_package = "github.com/containerd/cgroups/cgroup2/stats";
message Metrics {
PidsStat pids = 1;
CPUStat cpu = 2 [(gogoproto.customname) = "CPU"];
CPUStat cpu = 2;
MemoryStat memory = 4;
RdmaStat rdma = 5;
IOStat io = 6;
@@ -14,10 +14,11 @@
limitations under the License.
*/
package v2
package cgroup2
import (
"bufio"
"errors"
"fmt"
"io"
"math"
@@ -25,19 +26,22 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unsafe"
"github.com/containerd/cgroups/v2/stats"
"github.com/containerd/cgroups/v3/cgroup2/stats"
"github.com/godbus/dbus/v5"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const (
cgroupProcs = "cgroup.procs"
cgroupThreads = "cgroup.threads"
defaultDirPerm = 0755
defaultDirPerm = 0o755
)
// defaultFilePerm is a var so that the test framework can change the filemode
@@ -90,19 +94,13 @@ func parseCgroupProcsFile(path string) ([]uint64, error) {
return out, nil
}
func parseKV(raw string) (string, interface{}, error) {
func parseKV(raw string) (string, uint64, error) {
parts := strings.Fields(raw)
switch len(parts) {
case 2:
v, err := parseUint(parts[1], 10, 64)
if err != nil {
// if we cannot parse as a uint, parse as a string
return parts[0], parts[1], nil
}
return parts[0], v, nil
default:
if len(parts) != 2 {
return "", 0, ErrInvalidFormat
}
v, err := parseUint(parts[1], 10, 64)
return parts[0], v, err
}
func parseUint(s string, base, bitSize int) (uint64, error) {
@@ -134,9 +132,7 @@ func parseCgroupFile(path string) (string, error) {
}
func parseCgroupFromReader(r io.Reader) (string, error) {
var (
s = bufio.NewScanner(r)
)
s := bufio.NewScanner(r)
for s.Scan() {
var (
text = s.Text()
@@ -242,18 +238,28 @@ func ToResources(spec *specs.LinuxResources) *Resources {
// Gets uint64 parsed content of single value cgroup stat file
func getStatFileContentUint64(filePath string) uint64 {
contents, err := os.ReadFile(filePath)
f, err := os.Open(filePath)
if err != nil {
return 0
}
trimmed := strings.TrimSpace(string(contents))
defer f.Close()
// We expect an unsigned 64 bit integer, or a "max" string
// in some cases.
buf := make([]byte, 32)
n, err := f.Read(buf)
if err != nil {
return 0
}
trimmed := strings.TrimSpace(string(buf[:n]))
if trimmed == "max" {
return math.MaxUint64
}
res, err := parseUint(trimmed, 10, 64)
if err != nil {
logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), filePath)
logrus.Errorf("unable to parse %q as a uint from Cgroup file %q", trimmed, filePath)
return res
}
@@ -383,54 +389,100 @@ func systemdUnitFromPath(path string) string {
}
func readHugeTlbStats(path string) []*stats.HugeTlbStat {
var usage = []*stats.HugeTlbStat{}
var keyUsage = make(map[string]*stats.HugeTlbStat)
f, err := os.Open(path)
if err != nil {
return usage
}
files, err := f.Readdir(-1)
f.Close()
if err != nil {
return usage
}
for _, file := range files {
if strings.Contains(file.Name(), "hugetlb") &&
(strings.HasSuffix(file.Name(), "max") || strings.HasSuffix(file.Name(), "current")) {
var hugeTlb *stats.HugeTlbStat
var ok bool
fileName := strings.Split(file.Name(), ".")
pageSize := fileName[1]
if hugeTlb, ok = keyUsage[pageSize]; !ok {
hugeTlb = &stats.HugeTlbStat{}
}
hugeTlb.Pagesize = pageSize
out, err := os.ReadFile(filepath.Join(path, file.Name()))
if err != nil {
continue
}
var value uint64
stringVal := strings.TrimSpace(string(out))
if stringVal == "max" {
value = math.MaxUint64
} else {
value, err = strconv.ParseUint(stringVal, 10, 64)
}
if err != nil {
continue
}
switch fileName[2] {
case "max":
hugeTlb.Max = value
case "current":
hugeTlb.Current = value
}
keyUsage[pageSize] = hugeTlb
hpSizes := hugePageSizes()
usage := make([]*stats.HugeTlbStat, len(hpSizes))
for idx, pagesize := range hpSizes {
usage[idx] = &stats.HugeTlbStat{
Max: getStatFileContentUint64(filepath.Join(path, "hugetlb."+pagesize+".max")),
Current: getStatFileContentUint64(filepath.Join(path, "hugetlb."+pagesize+".current")),
Pagesize: pagesize,
}
}
for _, entry := range keyUsage {
usage = append(usage, entry)
}
return usage
}
var (
hPageSizes []string
initHPSOnce sync.Once
)
// The following idea and implementation is taken pretty much line for line from
// runc. Because the hugetlb files are well known, and the only variable thrown in
// the mix is what huge page sizes you have on your host, this lends itself well
// to doing the work to find the files present once, and then re-using this. This
// saves a os.Readdirnames(0) call to search for hugeltb files on every `manager.Stat`
// call.
// https://github.com/opencontainers/runc/blob/3a2c0c2565644d8a7e0f1dd594a060b21fa96cf1/libcontainer/cgroups/utils.go#L301
func hugePageSizes() []string {
initHPSOnce.Do(func() {
dir, err := os.OpenFile("/sys/kernel/mm/hugepages", unix.O_DIRECTORY|unix.O_RDONLY, 0)
if err != nil {
return
}
files, err := dir.Readdirnames(0)
dir.Close()
if err != nil {
return
}
hPageSizes, err = getHugePageSizeFromFilenames(files)
if err != nil {
logrus.Warnf("hugePageSizes: %s", err)
}
})
return hPageSizes
}
func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {
pageSizes := make([]string, 0, len(fileNames))
var warn error
for _, file := range fileNames {
// example: hugepages-1048576kB
val := strings.TrimPrefix(file, "hugepages-")
if len(val) == len(file) {
// Unexpected file name: no prefix found, ignore it.
continue
}
// In all known versions of Linux up to 6.3 the suffix is always
// "kB". If we find something else, produce an error but keep going.
eLen := len(val) - 2
val = strings.TrimSuffix(val, "kB")
if len(val) != eLen {
// Highly unlikely.
if warn == nil {
warn = errors.New(file + `: invalid suffix (expected "kB")`)
}
continue
}
size, err := strconv.Atoi(val)
if err != nil {
// Highly unlikely.
if warn == nil {
warn = fmt.Errorf("%s: %w", file, err)
}
continue
}
// Model after https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/hugetlb_cgroup.c?id=eff48ddeab782e35e58ccc8853f7386bbae9dec4#n574
// but in our case the size is in KB already.
if size >= (1 << 20) {
val = strconv.Itoa(size>>20) + "GB"
} else if size >= (1 << 10) {
val = strconv.Itoa(size>>10) + "MB"
} else {
val += "KB"
}
pageSizes = append(pageSizes, val)
}
return pageSizes, warn
}
func getSubreaper() (int, error) {
var i uintptr
if err := unix.Prctl(unix.PR_GET_CHILD_SUBREAPER, uintptr(unsafe.Pointer(&i)), 0, 0, 0); err != nil {
return -1, err
}
return int(i), nil
}
+150
View File
@@ -0,0 +1,150 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cgroups
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"golang.org/x/sys/unix"
)
var (
nsOnce sync.Once
inUserNS bool
checkMode sync.Once
cgMode CGMode
)
const unifiedMountpoint = "/sys/fs/cgroup"
// CGMode is the cgroups mode of the host system
type CGMode int
const (
// Unavailable cgroup mountpoint
Unavailable CGMode = iota
// Legacy cgroups v1
Legacy
// Hybrid with cgroups v1 and v2 controllers mounted
Hybrid
// Unified with only cgroups v2 mounted
Unified
)
// Mode returns the cgroups mode running on the host
func Mode() CGMode {
checkMode.Do(func() {
var st unix.Statfs_t
if err := unix.Statfs(unifiedMountpoint, &st); err != nil {
cgMode = Unavailable
return
}
switch st.Type {
case unix.CGROUP2_SUPER_MAGIC:
cgMode = Unified
default:
cgMode = Legacy
if err := unix.Statfs(filepath.Join(unifiedMountpoint, "unified"), &st); err != nil {
return
}
if st.Type == unix.CGROUP2_SUPER_MAGIC {
cgMode = Hybrid
}
}
})
return cgMode
}
// RunningInUserNS detects whether we are currently running in a user namespace.
// Copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}
// ParseCgroupFileUnified returns legacy subsystem paths as the first value,
// and returns the unified path as the second value.
func ParseCgroupFileUnified(path string) (map[string]string, string, error) {
f, err := os.Open(path)
if err != nil {
return nil, "", err
}
defer f.Close()
return ParseCgroupFromReaderUnified(f)
}
// ParseCgroupFromReaderUnified returns legacy subsystem paths as the first value,
// and returns the unified path as the second value.
func ParseCgroupFromReaderUnified(r io.Reader) (map[string]string, string, error) {
var (
cgroups = make(map[string]string)
unified = ""
s = bufio.NewScanner(r)
)
for s.Scan() {
var (
text = s.Text()
parts = strings.SplitN(text, ":", 3)
)
if len(parts) < 3 {
return nil, unified, fmt.Errorf("invalid cgroup entry: %q", text)
}
for _, subs := range strings.Split(parts[1], ",") {
if subs == "" {
unified = parts[2]
} else {
cgroups[subs] = parts[2]
}
}
}
if err := s.Err(); err != nil {
return nil, unified, err
}
return cgroups, unified, nil
}