Skip to content

Commit 82dc9bd

Browse files
committed
Add referenced bytes metric for containers
see: https://github.com/brendangregg/wss#wsspl-referenced-page-flag Signed-off-by: Katarzyna Kujawa <[email protected]>
1 parent b1312d3 commit 82dc9bd

21 files changed

+384
-4
lines changed

cmd/cadvisor.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ var (
8787
container.ProcessSchedulerMetrics: struct{}{},
8888
container.ProcessMetrics: struct{}{},
8989
container.HugetlbUsageMetrics: struct{}{},
90+
container.ReferencedMemoryMetrics: struct{}{},
9091
}}
9192

9293
// List of metrics that can be ignored.
@@ -101,6 +102,7 @@ var (
101102
container.ProcessSchedulerMetrics: struct{}{},
102103
container.ProcessMetrics: struct{}{},
103104
container.HugetlbUsageMetrics: struct{}{},
105+
container.ReferencedMemoryMetrics: struct{}{},
104106
}
105107
)
106108

@@ -132,7 +134,7 @@ func (ml *metricSetValue) Set(value string) error {
132134
}
133135

134136
func init() {
135-
flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'diskIO', 'network', 'tcp', 'udp', 'percpu', 'sched', 'process', 'hugetlb'.")
137+
flag.Var(&ignoreMetrics, "disable_metrics", "comma-separated list of `metrics` to be disabled. Options are 'disk', 'diskIO', 'network', 'tcp', 'udp', 'percpu', 'sched', 'process', 'hugetlb', 'referenced'.")
136138

137139
// Default logging verbosity to V(2)
138140
flag.Set("v", "2")

cmd/cadvisor_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ func TestUdpMetricsAreDisabledByDefault(t *testing.T) {
4040
assert.True(t, ignoreMetrics.Has(container.NetworkUdpUsageMetrics))
4141
}
4242

43+
func TestReferencedMemoryMetricsIsDisabledByDefault(t *testing.T) {
44+
assert.True(t, ignoreMetrics.Has(container.ReferencedMemoryMetrics))
45+
flag.Parse()
46+
assert.True(t, ignoreMetrics.Has(container.ReferencedMemoryMetrics))
47+
}
48+
4349
func TestIgnoreMetrics(t *testing.T) {
4450
tests := []struct {
4551
value string
@@ -86,6 +92,7 @@ func TestToIncludedMetrics(t *testing.T) {
8692
container.AppMetrics: struct{}{},
8793
container.HugetlbUsageMetrics: struct{}{},
8894
container.PerfMetrics: struct{}{},
95+
container.ReferencedMemoryMetrics: struct{}{},
8996
},
9097
container.AllMetrics,
9198
{},

container/factory.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const (
5959
ProcessMetrics MetricKind = "process"
6060
HugetlbUsageMetrics MetricKind = "hugetlb"
6161
PerfMetrics MetricKind = "perf_event"
62+
ReferencedMemoryMetrics MetricKind = "referenced_memory"
6263
)
6364

6465
// AllMetrics represents all kinds of metrics that cAdvisor supported.
@@ -79,6 +80,7 @@ var AllMetrics = MetricSet{
7980
AppMetrics: struct{}{},
8081
HugetlbUsageMetrics: struct{}{},
8182
PerfMetrics: struct{}{},
83+
ReferencedMemoryMetrics: struct{}{},
8284
}
8385

8486
func (mk MetricKind) String() string {

container/libcontainer/handler.go

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package libcontainer
1717
import (
1818
"bufio"
1919
"encoding/json"
20+
"flag"
2021
"fmt"
2122
"io"
2223
"io/ioutil"
@@ -38,16 +39,27 @@ import (
3839
"k8s.io/klog/v2"
3940
)
4041

42+
var (
43+
whitelistedUlimits = [...]string{"max_open_files"}
44+
referencedResetInterval = flag.Uint64("referenced_reset_interval", 0,
45+
"Reset interval for referenced bytes (container_referenced_bytes metric), number of measurement cycles after which referenced bytes are cleared, if set to 0 referenced bytes are never cleared (default: 0)")
46+
47+
smapsFilePathPattern = "/proc/%d/smaps"
48+
clearRefsFilePathPattern = "/proc/%d/clear_refs"
49+
50+
referencedRegexp = regexp.MustCompile(`Referenced:\s*([0-9]+)\s*kB`)
51+
isDigitRegExp = regexp.MustCompile("\\d+")
52+
)
53+
4154
type Handler struct {
4255
cgroupManager cgroups.Manager
4356
rootFs string
4457
pid int
4558
includedMetrics container.MetricSet
4659
pidMetricsCache map[int]*info.CpuSchedstat
60+
cycles uint64
4761
}
4862

49-
var whitelistedUlimits = [...]string{"max_open_files"}
50-
5163
func NewHandler(cgroupManager cgroups.Manager, rootFs string, pid int, includedMetrics container.MetricSet) *Handler {
5264
return &Handler{
5365
cgroupManager: cgroupManager,
@@ -81,6 +93,19 @@ func (h *Handler) GetStats() (*info.ContainerStats, error) {
8193
}
8294
}
8395

96+
if h.includedMetrics.Has(container.ReferencedMemoryMetrics) {
97+
h.cycles++
98+
pids, err := h.cgroupManager.GetPids()
99+
if err != nil {
100+
klog.V(4).Infof("Could not get PIDs for container %d: %v", h.pid, err)
101+
} else {
102+
stats.Referenced, err = referencedBytesStat(pids, h.cycles, *referencedResetInterval)
103+
if err != nil {
104+
klog.V(4).Infof("Unable to get referenced bytes: %v", err)
105+
}
106+
}
107+
}
108+
84109
// If we know the pid then get network stats from /proc/<pid>/net/dev
85110
if h.pid == 0 {
86111
return stats, nil
@@ -318,6 +343,92 @@ func schedulerStatsFromProcs(rootFs string, pids []int, pidMetricsCache map[int]
318343
return schedstats, nil
319344
}
320345

346+
// referencedBytesStat gets and clears referenced bytes
347+
// see: https://github.com/brendangregg/wss#wsspl-referenced-page-flag
348+
func referencedBytesStat(pids []int, cycles uint64, resetInterval uint64) (uint64, error) {
349+
referencedKBytes, err := getReferencedKBytes(pids)
350+
if err != nil {
351+
return uint64(0), err
352+
}
353+
354+
err = clearReferencedBytes(pids, cycles, resetInterval)
355+
if err != nil {
356+
return uint64(0), err
357+
}
358+
return referencedKBytes * 1024, nil
359+
}
360+
361+
func getReferencedKBytes(pids []int) (uint64, error) {
362+
referencedKBytes := uint64(0)
363+
readSmapsContent := false
364+
foundMatch := false
365+
for _, pid := range pids {
366+
smapsFilePath := fmt.Sprintf(smapsFilePathPattern, pid)
367+
smapsContent, err := ioutil.ReadFile(smapsFilePath)
368+
if err != nil {
369+
klog.V(5).Infof("Cannot read %s file, err: %s", smapsFilePath, err)
370+
if os.IsNotExist(err) {
371+
continue //smaps file does not exists for all PIDs
372+
}
373+
return 0, err
374+
}
375+
readSmapsContent = true
376+
377+
allMatches := referencedRegexp.FindAllSubmatch(smapsContent, -1)
378+
if len(allMatches) == 0 {
379+
klog.V(5).Infof("Not found any information about referenced bytes in %s file", smapsFilePath)
380+
continue // referenced bytes may not exist in smaps file
381+
}
382+
383+
for _, matches := range allMatches {
384+
if len(matches) != 2 {
385+
return 0, fmt.Errorf("failed to match regexp in output: %s", string(smapsContent))
386+
}
387+
foundMatch = true
388+
referenced, err := strconv.ParseUint(string(matches[1]), 10, 64)
389+
if err != nil {
390+
return 0, err
391+
}
392+
referencedKBytes += referenced
393+
}
394+
}
395+
396+
if len(pids) != 0 {
397+
if !readSmapsContent {
398+
klog.Warningf("Cannot read smaps files for any PID from %s", "CONTAINER")
399+
} else if !foundMatch {
400+
klog.Warningf("Not found any information about referenced bytes in smaps files for any PID from %s", "CONTAINER")
401+
}
402+
}
403+
return referencedKBytes, nil
404+
}
405+
406+
func clearReferencedBytes(pids []int, cycles uint64, resetInterval uint64) error {
407+
if resetInterval == 0 {
408+
return nil
409+
}
410+
411+
if cycles%resetInterval == 0 {
412+
for _, pid := range pids {
413+
clearRefsFilePath := fmt.Sprintf(clearRefsFilePathPattern, pid)
414+
clerRefsFile, err := os.OpenFile(clearRefsFilePath, os.O_WRONLY, 0644)
415+
if err != nil {
416+
// clear_refs file may not exist for all PIDs
417+
continue
418+
}
419+
_, err = clerRefsFile.WriteString("1\n")
420+
if err != nil {
421+
return err
422+
}
423+
err = clerRefsFile.Close()
424+
if err != nil {
425+
return err
426+
}
427+
}
428+
}
429+
return nil
430+
}
431+
321432
func networkStatsFromProc(rootFs string, pid int) ([]info.InterfaceStats, error) {
322433
netStatsFile := path.Join(rootFs, "proc", strconv.Itoa(pid), "/net/dev")
323434

container/libcontainer/handler_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
info "github.com/google/cadvisor/info/v1"
2323
"github.com/opencontainers/runc/libcontainer/cgroups"
2424
"github.com/opencontainers/runc/libcontainer/system"
25+
"github.com/stretchr/testify/assert"
2526
)
2627

2728
func TestScanInterfaceStats(t *testing.T) {
@@ -215,3 +216,87 @@ func TestParseLimitsFile(t *testing.T) {
215216
}
216217
}
217218
}
219+
220+
func TestReferencedBytesStat(t *testing.T) {
221+
//overwrite package variables
222+
smapsFilePathPattern = "testdata/smaps%d"
223+
clearRefsFilePathPattern = "testdata/clear_refs%d"
224+
225+
pids := []int{4, 6, 8}
226+
stat, err := referencedBytesStat(pids, 1, 3)
227+
assert.Nil(t, err)
228+
assert.Equal(t, uint64(416*1024), stat)
229+
230+
clearRefsFiles := []string{
231+
"testdata/clear_refs4",
232+
"testdata/clear_refs6",
233+
"testdata/clear_refs8"}
234+
235+
//check if clear_refs files have proper values
236+
assert.Equal(t, "0\n", getFileContent(t, clearRefsFiles[0]))
237+
assert.Equal(t, "0\n", getFileContent(t, clearRefsFiles[1]))
238+
assert.Equal(t, "0\n", getFileContent(t, clearRefsFiles[2]))
239+
}
240+
241+
func TestReferencedBytesStatWhenNeverCleared(t *testing.T) {
242+
//overwrite package variables
243+
smapsFilePathPattern = "testdata/smaps%d"
244+
clearRefsFilePathPattern = "testdata/clear_refs%d"
245+
246+
pids := []int{4, 6, 8}
247+
stat, err := referencedBytesStat(pids, 1, 0)
248+
assert.Nil(t, err)
249+
assert.Equal(t, uint64(416*1024), stat)
250+
251+
clearRefsFiles := []string{
252+
"testdata/clear_refs4",
253+
"testdata/clear_refs6",
254+
"testdata/clear_refs8"}
255+
256+
//check if clear_refs files have proper values
257+
assert.Equal(t, "0\n", getFileContent(t, clearRefsFiles[0]))
258+
assert.Equal(t, "0\n", getFileContent(t, clearRefsFiles[1]))
259+
assert.Equal(t, "0\n", getFileContent(t, clearRefsFiles[2]))
260+
}
261+
262+
func TestReferencedBytesStatWhenResetIsNeeded(t *testing.T) {
263+
//overwrite package variables
264+
smapsFilePathPattern = "testdata/smaps%d"
265+
clearRefsFilePathPattern = "testdata/clear_refs%d"
266+
267+
pids := []int{4, 6, 8}
268+
stat, err := referencedBytesStat(pids, 1, 1)
269+
assert.Nil(t, err)
270+
assert.Equal(t, uint64(416*1024), stat)
271+
272+
clearRefsFiles := []string{
273+
"testdata/clear_refs4",
274+
"testdata/clear_refs6",
275+
"testdata/clear_refs8"}
276+
277+
//check if clear_refs files have proper values
278+
assert.Equal(t, "1\n", getFileContent(t, clearRefsFiles[0]))
279+
assert.Equal(t, "1\n", getFileContent(t, clearRefsFiles[1]))
280+
assert.Equal(t, "1\n", getFileContent(t, clearRefsFiles[2]))
281+
282+
clearTestData(t, clearRefsFiles)
283+
}
284+
285+
func TestGetReferencedKBytesWhenSmapsMissing(t *testing.T) {
286+
//overwrite package variable
287+
smapsFilePathPattern = "testdata/smaps%d"
288+
289+
pids := []int{10}
290+
referenced, err := getReferencedKBytes(pids)
291+
assert.Nil(t, err)
292+
assert.Equal(t, uint64(0), referenced)
293+
}
294+
295+
func TestClearReferencedBytesWhenClearRefsMissing(t *testing.T) {
296+
//overwrite package variable
297+
clearRefsFilePathPattern = "testdata/clear_refs%d"
298+
299+
pids := []int{10}
300+
err := clearReferencedBytes(pids, 0, 1)
301+
assert.Nil(t, err)
302+
}

container/libcontainer/helpers_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ package libcontainer
1616

1717
import (
1818
"fmt"
19+
"io/ioutil"
1920
"path/filepath"
2021
"reflect"
2122
"sort"
2223
"strings"
2324
"testing"
2425

2526
"github.com/opencontainers/runc/libcontainer/cgroups"
27+
"github.com/stretchr/testify/assert"
2628
)
2729

2830
var defaultCgroupSubsystems = []string{
@@ -130,3 +132,16 @@ func assertCgroupSubsystemsEqual(t *testing.T, expected, actual CgroupSubsystems
130132
t.Fatalf("%s Expected %v == %v", message, expected.Mounts, actual.Mounts)
131133
}
132134
}
135+
136+
func getFileContent(t *testing.T, filePath string) string {
137+
fileContent, err := ioutil.ReadFile(filePath)
138+
assert.Nil(t, err)
139+
return string(fileContent)
140+
}
141+
142+
func clearTestData(t *testing.T, clearRefsPaths []string) {
143+
for _, clearRefsPath := range clearRefsPaths {
144+
err := ioutil.WriteFile(clearRefsPath, []byte("0\n"), 0644)
145+
assert.Nil(t, err)
146+
}
147+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0

0 commit comments

Comments
 (0)