Skip to content

Commit 9293fbb

Browse files
committed
Cg2: Add the ability to filter stats
A large user of this library (unsurprisingly) is the containerd daemon, and specifically through the CRI entrypoint. There's quite a few stats we gather that are never bubbled up the chain when requested through that funnel. The stats never used are: MemoryEvents, Rdma, Hugetlb, and IO. We should likely do the same to cg1, but if we want to add it to the Cgroup interface it'd be a breaking change. Signed-off-by: Danny Canter <danny@dcantah.dev>
1 parent 568b349 commit 9293fbb

File tree

2 files changed

+196
-19
lines changed

2 files changed

+196
-19
lines changed

cgroup2/manager.go

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ const (
5151
cpuQuotaPeriodUSecSupportedVersion = 242
5252
)
5353

54+
// StatMask represents which controller stats to collect.
55+
type StatMask uint64
56+
57+
const (
58+
StatPids StatMask = 1 << iota
59+
StatCPU
60+
StatMemory
61+
StatMemoryEvents
62+
StatIO
63+
StatRdma
64+
StatHugetlb
65+
66+
// StatAll collects all available stats (default behavior of Stat).
67+
StatAll = StatPids | StatCPU | StatMemory | StatMemoryEvents | StatIO | StatRdma | StatHugetlb
68+
)
69+
5470
var (
5571
canDelegate bool
5672

@@ -558,41 +574,62 @@ func (c *Manager) MoveTo(destination *Manager) error {
558574
return nil
559575
}
560576

577+
// Stat returns all cgroup stats.
578+
// This is equivalent to calling StatFiltered(StatAll).
561579
func (c *Manager) Stat() (*stats.Metrics, error) {
580+
return c.StatFiltered(StatAll)
581+
}
582+
583+
// StatFiltered returns cgroup stats for the specified controllers.
584+
func (c *Manager) StatFiltered(mask StatMask) (*stats.Metrics, error) {
562585
var metrics stats.Metrics
563586
var err error
564587

565-
metrics.Pids = &stats.PidsStat{
566-
Current: getStatFileContentUint64(filepath.Join(c.path, "pids.current")),
567-
Limit: getStatFileContentUint64(filepath.Join(c.path, "pids.max")),
588+
if mask&StatPids != 0 {
589+
metrics.Pids = &stats.PidsStat{
590+
Current: getStatFileContentUint64(filepath.Join(c.path, "pids.current")),
591+
Limit: getStatFileContentUint64(filepath.Join(c.path, "pids.max")),
592+
}
568593
}
569594

570-
metrics.CPU, err = readCPUStats(c.path)
571-
if err != nil {
572-
return nil, err
595+
if mask&StatCPU != 0 {
596+
metrics.CPU, err = readCPUStats(c.path)
597+
if err != nil {
598+
return nil, err
599+
}
573600
}
574601

575-
metrics.Memory, err = readMemoryStats(c.path)
576-
if err != nil {
577-
return nil, err
602+
if mask&StatMemory != 0 {
603+
metrics.Memory, err = readMemoryStats(c.path)
604+
if err != nil {
605+
return nil, err
606+
}
578607
}
579608

580-
metrics.MemoryEvents, err = readMemoryEvents(c.path)
581-
if err != nil {
582-
return nil, err
609+
if mask&StatMemoryEvents != 0 {
610+
metrics.MemoryEvents, err = readMemoryEvents(c.path)
611+
if err != nil {
612+
return nil, err
613+
}
583614
}
584615

585-
metrics.Io = &stats.IOStat{
586-
Usage: readIoStats(c.path),
587-
PSI: getStatPSIFromFile(filepath.Join(c.path, "io.pressure")),
616+
if mask&StatIO != 0 {
617+
metrics.Io = &stats.IOStat{
618+
Usage: readIoStats(c.path),
619+
PSI: getStatPSIFromFile(filepath.Join(c.path, "io.pressure")),
620+
}
588621
}
589622

590-
metrics.Rdma = &stats.RdmaStat{
591-
Current: rdmaStats(filepath.Join(c.path, "rdma.current")),
592-
Limit: rdmaStats(filepath.Join(c.path, "rdma.max")),
623+
if mask&StatRdma != 0 {
624+
metrics.Rdma = &stats.RdmaStat{
625+
Current: rdmaStats(filepath.Join(c.path, "rdma.current")),
626+
Limit: rdmaStats(filepath.Join(c.path, "rdma.max")),
627+
}
593628
}
594629

595-
metrics.Hugetlb = readHugeTlbStats(c.path)
630+
if mask&StatHugetlb != 0 {
631+
metrics.Hugetlb = readHugeTlbStats(c.path)
632+
}
596633

597634
return &metrics, nil
598635
}

cgroup2/manager_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,146 @@ func BenchmarkStat(b *testing.B) {
463463
}
464464
}
465465

466+
func TestStatFiltered(t *testing.T) {
467+
checkCgroupMode(t)
468+
group := "/stat-filtered-test-cg"
469+
groupPath := fmt.Sprintf("%s-%d", group, os.Getpid())
470+
c, err := NewManager(defaultCgroup2Path, groupPath, &Resources{})
471+
require.NoError(t, err, "failed to init new cgroup manager")
472+
t.Cleanup(func() {
473+
_ = c.Delete()
474+
})
475+
476+
t.Run("StatAll", func(t *testing.T) {
477+
statAll, err := c.StatFiltered(StatAll)
478+
require.NoError(t, err)
479+
480+
assert.NotNil(t, statAll.Pids)
481+
assert.NotNil(t, statAll.CPU)
482+
assert.NotNil(t, statAll.Memory)
483+
assert.NotNil(t, statAll.Io)
484+
assert.NotNil(t, statAll.Rdma)
485+
})
486+
487+
t.Run("CPUOnly", func(t *testing.T) {
488+
stats, err := c.StatFiltered(StatCPU)
489+
require.NoError(t, err)
490+
491+
assert.NotNil(t, stats.CPU, "CPU stats should be populated")
492+
assert.Nil(t, stats.Pids, "Pids stats should be nil")
493+
assert.Nil(t, stats.Memory, "Memory stats should be nil")
494+
assert.Nil(t, stats.MemoryEvents, "MemoryEvents should be nil")
495+
assert.Nil(t, stats.Io, "IO stats should be nil")
496+
assert.Nil(t, stats.Rdma, "RDMA stats should be nil")
497+
assert.Nil(t, stats.Hugetlb, "Hugetlb stats should be nil")
498+
})
499+
500+
t.Run("MemoryOnly", func(t *testing.T) {
501+
stats, err := c.StatFiltered(StatMemory)
502+
require.NoError(t, err)
503+
504+
assert.NotNil(t, stats.Memory, "Memory stats should be populated")
505+
assert.Nil(t, stats.Pids, "Pids stats should be nil")
506+
assert.Nil(t, stats.CPU, "CPU stats should be nil")
507+
assert.Nil(t, stats.MemoryEvents, "MemoryEvents should be nil")
508+
assert.Nil(t, stats.Io, "IO stats should be nil")
509+
})
510+
511+
t.Run("MemoryEventsOnly", func(t *testing.T) {
512+
stats, err := c.StatFiltered(StatMemoryEvents)
513+
require.NoError(t, err)
514+
515+
assert.NotNil(t, stats.MemoryEvents, "MemoryEvents should be populated")
516+
assert.Nil(t, stats.Memory, "Memory stats should be nil")
517+
assert.Nil(t, stats.CPU, "CPU stats should be nil")
518+
})
519+
520+
t.Run("CPUAndMemory", func(t *testing.T) {
521+
stats, err := c.StatFiltered(StatCPU | StatMemory)
522+
require.NoError(t, err)
523+
524+
assert.NotNil(t, stats.CPU, "CPU stats should be populated")
525+
assert.NotNil(t, stats.Memory, "Memory stats should be populated")
526+
assert.Nil(t, stats.Pids, "Pids stats should be nil")
527+
assert.Nil(t, stats.MemoryEvents, "MemoryEvents should be nil")
528+
assert.Nil(t, stats.Io, "IO stats should be nil")
529+
})
530+
531+
t.Run("PidsOnly", func(t *testing.T) {
532+
stats, err := c.StatFiltered(StatPids)
533+
require.NoError(t, err)
534+
535+
assert.NotNil(t, stats.Pids, "Pids stats should be populated")
536+
assert.Nil(t, stats.CPU, "CPU stats should be nil")
537+
assert.Nil(t, stats.Memory, "Memory stats should be nil")
538+
})
539+
540+
t.Run("IOOnly", func(t *testing.T) {
541+
stats, err := c.StatFiltered(StatIO)
542+
require.NoError(t, err)
543+
544+
assert.NotNil(t, stats.Io, "IO stats should be populated")
545+
assert.Nil(t, stats.CPU, "CPU stats should be nil")
546+
assert.Nil(t, stats.Memory, "Memory stats should be nil")
547+
})
548+
549+
t.Run("ZeroMask", func(t *testing.T) {
550+
stats, err := c.StatFiltered(0)
551+
require.NoError(t, err)
552+
553+
assert.Nil(t, stats.Pids, "Pids stats should be nil")
554+
assert.Nil(t, stats.CPU, "CPU stats should be nil")
555+
assert.Nil(t, stats.Memory, "Memory stats should be nil")
556+
assert.Nil(t, stats.MemoryEvents, "MemoryEvents should be nil")
557+
assert.Nil(t, stats.Io, "IO stats should be nil")
558+
assert.Nil(t, stats.Rdma, "RDMA stats should be nil")
559+
assert.Nil(t, stats.Hugetlb, "Hugetlb stats should be nil")
560+
})
561+
}
562+
563+
func BenchmarkStatFiltered(b *testing.B) {
564+
checkCgroupMode(b)
565+
group := "/stat-filtered-bench-cg"
566+
groupPath := fmt.Sprintf("%s-%d", group, os.Getpid())
567+
c, err := NewManager(defaultCgroup2Path, groupPath, &Resources{})
568+
require.NoErrorf(b, err, "failed to init new cgroup manager")
569+
b.Cleanup(func() {
570+
_ = c.Delete()
571+
})
572+
573+
b.Run("StatAll", func(b *testing.B) {
574+
b.ReportAllocs()
575+
for i := 0; i < b.N; i++ {
576+
_, err := c.StatFiltered(StatAll)
577+
require.NoError(b, err)
578+
}
579+
})
580+
581+
b.Run("CPUOnly", func(b *testing.B) {
582+
b.ReportAllocs()
583+
for i := 0; i < b.N; i++ {
584+
_, err := c.StatFiltered(StatCPU)
585+
require.NoError(b, err)
586+
}
587+
})
588+
589+
b.Run("MemoryOnly", func(b *testing.B) {
590+
b.ReportAllocs()
591+
for i := 0; i < b.N; i++ {
592+
_, err := c.StatFiltered(StatMemory)
593+
require.NoError(b, err)
594+
}
595+
})
596+
597+
b.Run("CPUAndMemory", func(b *testing.B) {
598+
b.ReportAllocs()
599+
for i := 0; i < b.N; i++ {
600+
_, err := c.StatFiltered(StatCPU | StatMemory)
601+
require.NoError(b, err)
602+
}
603+
})
604+
}
605+
466606
func toPtr[T any](v T) *T {
467607
return &v
468608
}

0 commit comments

Comments
 (0)