Skip to content

Commit 8858916

Browse files
committed
support psi
Signed-off-by: zouyee <zouyee1989@gmail.com>
1 parent 2f48571 commit 8858916

8 files changed

+678
-244
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ cgutil:
2626
proto:
2727
protobuild --quiet ${PACKAGES}
2828
# Keep them Go-idiomatic and backward-compatible with the gogo/protobuf era.
29-
go-fix-acronym -w -a '(Cpu|Tcp|Rss)' $(shell find cgroup1/stats/ cgroup2/stats/ -name '*.pb.go')
29+
go-fix-acronym -w -a '(Cpu|Tcp|Rss|Psi)' $(shell find cgroup1/stats/ cgroup2/stats/ -name '*.pb.go')

cgroup2/manager.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
559559
NrPeriods: out["nr_periods"],
560560
NrThrottled: out["nr_throttled"],
561561
ThrottledUsec: out["throttled_usec"],
562+
PSI: getStatPSIFromFile(filepath.Join(c.path, "cpu.pressure")),
562563
}
563564
metrics.Memory = &stats.MemoryStat{
564565
Anon: out["anon"],
@@ -598,6 +599,7 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
598599
SwapUsage: getStatFileContentUint64(filepath.Join(c.path, "memory.swap.current")),
599600
SwapLimit: getStatFileContentUint64(filepath.Join(c.path, "memory.swap.max")),
600601
SwapMaxUsage: getStatFileContentUint64(filepath.Join(c.path, "memory.swap.peak")),
602+
PSI: getStatPSIFromFile(filepath.Join(c.path, "memory.pressure")),
601603
}
602604
if len(memoryEvents) > 0 {
603605
metrics.MemoryEvents = &stats.MemoryEvents{
@@ -608,7 +610,10 @@ func (c *Manager) Stat() (*stats.Metrics, error) {
608610
OomKill: memoryEvents["oom_kill"],
609611
}
610612
}
611-
metrics.Io = &stats.IOStat{Usage: readIoStats(c.path)}
613+
metrics.Io = &stats.IOStat{
614+
Usage: readIoStats(c.path),
615+
PSI: getStatPSIFromFile(filepath.Join(c.path, "io.pressure")),
616+
}
612617
metrics.Rdma = &stats.RdmaStat{
613618
Current: rdmaStats(filepath.Join(c.path, "rdma.current")),
614619
Limit: rdmaStats(filepath.Join(c.path, "rdma.max")),

cgroup2/manager_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,39 @@ func TestCgroupType(t *testing.T) {
296296
require.Equal(t, cgType, Threaded)
297297
}
298298

299+
func TestCgroupv2PSIStats(t *testing.T) {
300+
checkCgroupMode(t)
301+
group := "/psi-test-cg"
302+
groupPath := fmt.Sprintf("%s-%d", group, os.Getpid())
303+
res := Resources{}
304+
c, err := NewManager(defaultCgroup2Path, groupPath, &res)
305+
require.NoError(t, err, "failed to init new cgroup manager")
306+
t.Cleanup(func() {
307+
os.Remove(c.path)
308+
})
309+
310+
stats, err := c.Stat()
311+
require.NoError(t, err, "failed to get cgroup stats")
312+
if stats.CPU.PSI == nil || stats.Memory.PSI == nil || stats.Io.PSI == nil {
313+
t.Error("expected psi not nil but got nil")
314+
}
315+
}
316+
317+
func TestSystemdCgroupPSIController(t *testing.T) {
318+
checkCgroupMode(t)
319+
group := fmt.Sprintf("testing-psi-%d.scope", os.Getpid())
320+
pid := os.Getpid()
321+
res := Resources{}
322+
c, err := NewSystemd("", group, pid, &res)
323+
require.NoError(t, err, "failed to init new cgroup systemd manager")
324+
325+
stats, err := c.Stat()
326+
require.NoError(t, err, "failed to get cgroup stats")
327+
if stats.CPU.PSI == nil || stats.Memory.PSI == nil || stats.Io.PSI == nil {
328+
t.Error("expected psi not nil but got nil")
329+
}
330+
}
331+
299332
func BenchmarkStat(b *testing.B) {
300333
checkCgroupMode(b)
301334
group := "/stat-test-cg"

cgroup2/stats/metrics.pb.go

+447-242
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cgroup2/stats/metrics.pb.txt

+74
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,56 @@ file {
6060
json_name: "memoryEvents"
6161
}
6262
}
63+
message_type {
64+
name: "PSIData"
65+
field {
66+
name: "avg10"
67+
number: 1
68+
label: LABEL_OPTIONAL
69+
type: TYPE_DOUBLE
70+
json_name: "avg10"
71+
}
72+
field {
73+
name: "avg60"
74+
number: 2
75+
label: LABEL_OPTIONAL
76+
type: TYPE_DOUBLE
77+
json_name: "avg60"
78+
}
79+
field {
80+
name: "avg300"
81+
number: 3
82+
label: LABEL_OPTIONAL
83+
type: TYPE_DOUBLE
84+
json_name: "avg300"
85+
}
86+
field {
87+
name: "total"
88+
number: 4
89+
label: LABEL_OPTIONAL
90+
type: TYPE_UINT64
91+
json_name: "total"
92+
}
93+
}
94+
message_type {
95+
name: "PSIStats"
96+
field {
97+
name: "some"
98+
number: 1
99+
label: LABEL_OPTIONAL
100+
type: TYPE_MESSAGE
101+
type_name: ".io.containerd.cgroups.v2.PSIData"
102+
json_name: "some"
103+
}
104+
field {
105+
name: "full"
106+
number: 2
107+
label: LABEL_OPTIONAL
108+
type: TYPE_MESSAGE
109+
type_name: ".io.containerd.cgroups.v2.PSIData"
110+
json_name: "full"
111+
}
112+
}
63113
message_type {
64114
name: "PidsStat"
65115
field {
@@ -121,6 +171,14 @@ file {
121171
type: TYPE_UINT64
122172
json_name: "throttledUsec"
123173
}
174+
field {
175+
name: "psi"
176+
number: 7
177+
label: LABEL_OPTIONAL
178+
type: TYPE_MESSAGE
179+
type_name: ".io.containerd.cgroups.v2.PSIStats"
180+
json_name: "psi"
181+
}
124182
}
125183
message_type {
126184
name: "MemoryStat"
@@ -383,6 +441,14 @@ file {
383441
type: TYPE_UINT64
384442
json_name: "swapMaxUsage"
385443
}
444+
field {
445+
name: "psi"
446+
number: 38
447+
label: LABEL_OPTIONAL
448+
type: TYPE_MESSAGE
449+
type_name: ".io.containerd.cgroups.v2.PSIStats"
450+
json_name: "psi"
451+
}
386452
}
387453
message_type {
388454
name: "MemoryEvents"
@@ -475,6 +541,14 @@ file {
475541
type_name: ".io.containerd.cgroups.v2.IOEntry"
476542
json_name: "usage"
477543
}
544+
field {
545+
name: "psi"
546+
number: 2
547+
label: LABEL_OPTIONAL
548+
type: TYPE_MESSAGE
549+
type_name: ".io.containerd.cgroups.v2.PSIStats"
550+
json_name: "psi"
551+
}
478552
}
479553
message_type {
480554
name: "IOEntry"

cgroup2/stats/metrics.proto

+15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ message Metrics {
1414
MemoryEvents memory_events = 8;
1515
}
1616

17+
message PSIData {
18+
double avg10 = 1;
19+
double avg60 = 2;
20+
double avg300 = 3;
21+
uint64 total = 4;
22+
}
23+
24+
message PSIStats {
25+
PSIData some = 1;
26+
PSIData full = 2;
27+
}
28+
1729
message PidsStat {
1830
uint64 current = 1;
1931
uint64 limit = 2;
@@ -26,6 +38,7 @@ message CPUStat {
2638
uint64 nr_periods = 4;
2739
uint64 nr_throttled = 5;
2840
uint64 throttled_usec = 6;
41+
PSIStats psi = 7;
2942
}
3043

3144
message MemoryStat {
@@ -66,6 +79,7 @@ message MemoryStat {
6679
uint64 swap_limit = 35;
6780
uint64 max_usage = 36;
6881
uint64 swap_max_usage = 37;
82+
PSIStats psi = 38;
6983
}
7084

7185
message MemoryEvents {
@@ -89,6 +103,7 @@ message RdmaEntry {
89103

90104
message IOStat {
91105
repeated IOEntry usage = 1;
106+
PSIStats psi = 2;
92107
}
93108

94109
message IOEntry {

cgroup2/utils.go

+68
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,74 @@ func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {
479479
return pageSizes, warn
480480
}
481481

482+
func getStatPSIFromFile(path string) *stats.PSIStats {
483+
f, err := os.Open(path)
484+
if err != nil {
485+
return nil
486+
}
487+
defer f.Close()
488+
489+
psistats := &stats.PSIStats{}
490+
sc := bufio.NewScanner(f)
491+
for sc.Scan() {
492+
parts := strings.Fields(sc.Text())
493+
var pv *stats.PSIData
494+
switch parts[0] {
495+
case "some":
496+
psistats.Some = &stats.PSIData{}
497+
pv = psistats.Some
498+
case "full":
499+
psistats.Full = &stats.PSIData{}
500+
pv = psistats.Full
501+
}
502+
if pv != nil {
503+
err = parsePSIData(parts[1:], pv)
504+
if err != nil {
505+
logrus.Errorf("failed to read file %s: %v", path, err)
506+
return nil
507+
}
508+
}
509+
}
510+
511+
if err := sc.Err(); err != nil {
512+
logrus.Errorf("unable to parse PSI data: %v", err)
513+
return nil
514+
}
515+
return psistats
516+
}
517+
518+
func parsePSIData(psi []string, data *stats.PSIData) error {
519+
for _, f := range psi {
520+
kv := strings.SplitN(f, "=", 2)
521+
if len(kv) != 2 {
522+
return fmt.Errorf("invalid PSI data: %q", f)
523+
}
524+
var pv *float64
525+
switch kv[0] {
526+
case "avg10":
527+
pv = &data.Avg10
528+
case "avg60":
529+
pv = &data.Avg60
530+
case "avg300":
531+
pv = &data.Avg300
532+
case "total":
533+
v, err := strconv.ParseUint(kv[1], 10, 64)
534+
if err != nil {
535+
return fmt.Errorf("invalid %s PSI value: %w", kv[0], err)
536+
}
537+
data.Total = v
538+
}
539+
if pv != nil {
540+
v, err := strconv.ParseFloat(kv[1], 64)
541+
if err != nil {
542+
return fmt.Errorf("invalid %s PSI value: %w", kv[0], err)
543+
}
544+
*pv = v
545+
}
546+
}
547+
return nil
548+
}
549+
482550
func getSubreaper() (int, error) {
483551
var i uintptr
484552
if err := unix.Prctl(unix.PR_GET_CHILD_SUBREAPER, uintptr(unsafe.Pointer(&i)), 0, 0, 0); err != nil {

cgroup2/utils_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
package cgroup2
1818

1919
import (
20+
"os"
21+
"path/filepath"
2022
"strings"
2123
"testing"
2224

25+
"github.com/containerd/cgroups/v3/cgroup2/stats"
26+
2327
"github.com/opencontainers/runtime-spec/specs-go"
2428
"github.com/stretchr/testify/assert"
2529
)
@@ -41,6 +45,36 @@ func TestParseCgroupFromReader(t *testing.T) {
4145
}
4246
}
4347

48+
func TestParseStatCPUPSI(t *testing.T) {
49+
const examplePSIData = `some avg10=1.71 avg60=2.36 avg300=2.57 total=230548833
50+
full avg10=1.00 avg60=1.01 avg300=1.00 total=157622356`
51+
52+
fakeCgroupDir := t.TempDir()
53+
statPath := filepath.Join(fakeCgroupDir, "cpu.pressure")
54+
55+
if err := os.WriteFile(statPath, []byte(examplePSIData), 0o644); err != nil {
56+
t.Fatal(err)
57+
}
58+
59+
st := getStatPSIFromFile(filepath.Join(fakeCgroupDir, "cpu.pressure"))
60+
expected := stats.PSIStats{
61+
Some: &stats.PSIData{
62+
Avg10: 1.71,
63+
Avg60: 2.36,
64+
Avg300: 2.57,
65+
Total: 230548833,
66+
},
67+
Full: &stats.PSIData{
68+
Avg10: 1.00,
69+
Avg60: 1.01,
70+
Avg300: 1.00,
71+
Total: 157622356,
72+
},
73+
}
74+
assert.Equal(t, &st.Some, &expected.Some)
75+
assert.Equal(t, &st.Full, &expected.Full)
76+
}
77+
4478
func TestToResources(t *testing.T) {
4579
var (
4680
quota int64 = 8000

0 commit comments

Comments
 (0)