Skip to content

Commit 076b5e0

Browse files
authored
Merge pull request #387 from dcantah/cg2-stats-filter
Cg2: Add the ability to filter stats
2 parents 31da8b0 + 9293fbb commit 076b5e0

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

@@ -559,41 +575,62 @@ func (c *Manager) MoveTo(destination *Manager) error {
559575
return nil
560576
}
561577

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

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

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

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

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

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

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

596-
metrics.Hugetlb = readHugeTlbStats(c.path)
631+
if mask&StatHugetlb != 0 {
632+
metrics.Hugetlb = readHugeTlbStats(c.path)
633+
}
597634

598635
return &metrics, nil
599636
}

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)