Skip to content

Commit abd0b19

Browse files
authored
Merge pull request #98 from mariomac/ignoremem
Allows ignoring memory modules
2 parents 484059a + ad1c4b9 commit abd0b19

File tree

2 files changed

+191
-4
lines changed

2 files changed

+191
-4
lines changed

memory.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,43 @@ import (
3232
"golang.org/x/sys/unix"
3333
)
3434

35-
func NewMemory(root string) *memoryController {
36-
return &memoryController{
37-
root: filepath.Join(root, string(Memory)),
35+
// NewMemory returns a Memory controller given the root folder of cgroups.
36+
// It may optionally accept other configuration options, such as IgnoreModules(...)
37+
func NewMemory(root string, options ...func(*memoryController)) *memoryController {
38+
mc := &memoryController{
39+
root: filepath.Join(root, string(Memory)),
40+
ignored: map[string]struct{}{},
41+
}
42+
for _, opt := range options {
43+
opt(mc)
44+
}
45+
return mc
46+
}
47+
48+
// IgnoreModules configure the memory controller to not read memory metrics for some
49+
// module names (e.g. passing "memsw" would avoid all the memory.memsw.* entries)
50+
func IgnoreModules(names ...string) func(*memoryController) {
51+
return func(mc *memoryController) {
52+
for _, name := range names {
53+
mc.ignored[name] = struct{}{}
54+
}
55+
}
56+
}
57+
58+
// OptionalSwap allows the memory controller to not fail if cgroups is not accounting
59+
// Swap memory (there are no memory.memsw.* entries)
60+
func OptionalSwap() func(*memoryController) {
61+
return func(mc *memoryController) {
62+
_, err := os.Stat(filepath.Join(mc.root, "memory.memsw.usage_in_bytes"))
63+
if os.IsNotExist(err) {
64+
mc.ignored["memsw"] = struct{}{}
65+
}
3866
}
3967
}
4068

4169
type memoryController struct {
42-
root string
70+
root string
71+
ignored map[string]struct{}
4372
}
4473

4574
func (m *memoryController) Name() Name {
@@ -133,6 +162,9 @@ func (m *memoryController) Stat(path string, stats *v1.Metrics) error {
133162
entry: stats.Memory.KernelTCP,
134163
},
135164
} {
165+
if _, ok := m.ignored[t.module]; ok {
166+
continue
167+
}
136168
for _, tt := range []struct {
137169
name string
138170
value *uint64

memory_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
package cgroups
1818

1919
import (
20+
"fmt"
21+
"io/ioutil"
22+
"os"
23+
"path"
2024
"strings"
2125
"testing"
2226

@@ -106,3 +110,154 @@ func TestParseMemoryStats(t *testing.T) {
106110
}
107111
}
108112
}
113+
114+
func TestMemoryController_Stat(t *testing.T) {
115+
// GIVEN a cgroups folder with all the memory metrics
116+
modules := []string{"", "memsw", "kmem", "kmem.tcp"}
117+
metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
118+
tmpRoot := buildMemoryMetrics(t, modules, metrics)
119+
120+
// WHEN the memory controller reads the metrics stats
121+
mc := NewMemory(tmpRoot)
122+
stats := v1.Metrics{}
123+
if err := mc.Stat("", &stats); err != nil {
124+
t.Errorf("can't get stats: %v", err)
125+
}
126+
127+
// THEN all the memory stats have been completely loaded in memory
128+
checkMemoryStatIsComplete(t, stats.Memory)
129+
}
130+
131+
func TestMemoryController_Stat_IgnoreModules(t *testing.T) {
132+
// GIVEN a cgroups folder that accounts for all the metrics BUT swap memory
133+
modules := []string{"", "kmem", "kmem.tcp"}
134+
metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
135+
tmpRoot := buildMemoryMetrics(t, modules, metrics)
136+
137+
// WHEN the memory controller explicitly ignores memsw module and reads the data
138+
mc := NewMemory(tmpRoot, IgnoreModules("memsw"))
139+
stats := v1.Metrics{}
140+
if err := mc.Stat("", &stats); err != nil {
141+
t.Errorf("can't get stats: %v", err)
142+
}
143+
144+
// THEN the swap memory stats are not loaded but all the other memory metrics are
145+
checkMemoryStatHasNoSwap(t, stats.Memory)
146+
}
147+
148+
func TestMemoryController_Stat_OptionalSwap_HasSwap(t *testing.T) {
149+
// GIVEN a cgroups folder with all the memory metrics
150+
modules := []string{"", "memsw", "kmem", "kmem.tcp"}
151+
metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
152+
tmpRoot := buildMemoryMetrics(t, modules, metrics)
153+
154+
// WHEN a memory controller that ignores swap only if it is missing reads stats
155+
mc := NewMemory(tmpRoot, OptionalSwap())
156+
stats := v1.Metrics{}
157+
if err := mc.Stat("", &stats); err != nil {
158+
t.Errorf("can't get stats: %v", err)
159+
}
160+
161+
// THEN all the memory stats have been completely loaded in memory
162+
checkMemoryStatIsComplete(t, stats.Memory)
163+
}
164+
165+
func TestMemoryController_Stat_OptionalSwap_NoSwap(t *testing.T) {
166+
// GIVEN a cgroups folder that accounts for all the metrics BUT swap memory
167+
modules := []string{"", "kmem", "kmem.tcp"}
168+
metrics := []string{"usage_in_bytes", "max_usage_in_bytes", "failcnt", "limit_in_bytes"}
169+
tmpRoot := buildMemoryMetrics(t, modules, metrics)
170+
171+
// WHEN a memory controller that ignores swap only if it is missing reads stats
172+
mc := NewMemory(tmpRoot, OptionalSwap())
173+
stats := v1.Metrics{}
174+
if err := mc.Stat("", &stats); err != nil {
175+
t.Errorf("can't get stats: %v", err)
176+
}
177+
178+
// THEN the swap memory stats are not loaded but all the other memory metrics are
179+
checkMemoryStatHasNoSwap(t, stats.Memory)
180+
}
181+
182+
func checkMemoryStatIsComplete(t *testing.T, mem *v1.MemoryStat) {
183+
index := []uint64{
184+
mem.Usage.Usage,
185+
mem.Usage.Max,
186+
mem.Usage.Failcnt,
187+
mem.Usage.Limit,
188+
mem.Swap.Usage,
189+
mem.Swap.Max,
190+
mem.Swap.Failcnt,
191+
mem.Swap.Limit,
192+
mem.Kernel.Usage,
193+
mem.Kernel.Max,
194+
mem.Kernel.Failcnt,
195+
mem.Kernel.Limit,
196+
mem.KernelTCP.Usage,
197+
mem.KernelTCP.Max,
198+
mem.KernelTCP.Failcnt,
199+
mem.KernelTCP.Limit,
200+
}
201+
for i, v := range index {
202+
if v != uint64(i) {
203+
t.Errorf("expected value at index %d to be %d but received %d", i, i, v)
204+
}
205+
}
206+
}
207+
208+
func checkMemoryStatHasNoSwap(t *testing.T, mem *v1.MemoryStat) {
209+
if mem.Swap.Usage != 0 || mem.Swap.Limit != 0 ||
210+
mem.Swap.Max != 0 || mem.Swap.Failcnt != 0 {
211+
t.Errorf("swap memory should have been ignored. Got: %+v", mem.Swap)
212+
}
213+
index := []uint64{
214+
mem.Usage.Usage,
215+
mem.Usage.Max,
216+
mem.Usage.Failcnt,
217+
mem.Usage.Limit,
218+
mem.Kernel.Usage,
219+
mem.Kernel.Max,
220+
mem.Kernel.Failcnt,
221+
mem.Kernel.Limit,
222+
mem.KernelTCP.Usage,
223+
mem.KernelTCP.Max,
224+
mem.KernelTCP.Failcnt,
225+
mem.KernelTCP.Limit,
226+
}
227+
for i, v := range index {
228+
if v != uint64(i) {
229+
t.Errorf("expected value at index %d to be %d but received %d", i, i, v)
230+
}
231+
}
232+
}
233+
234+
// buildMemoryMetrics creates fake cgroups memory entries in a temporary dir. Returns the fake cgroups root
235+
func buildMemoryMetrics(t *testing.T, modules []string, metrics []string) string {
236+
tmpRoot, err := ioutil.TempDir("", "memtests")
237+
if err != nil {
238+
t.Fatal(err)
239+
}
240+
tmpDir := path.Join(tmpRoot, string(Memory))
241+
if err := os.MkdirAll(tmpDir, defaultDirPerm); err != nil {
242+
t.Fatal(err)
243+
}
244+
if err := ioutil.WriteFile(path.Join(tmpDir, "memory.stat"), []byte(memoryData), defaultFilePerm); err != nil {
245+
t.Fatal(err)
246+
}
247+
cnt := 0
248+
for _, mod := range modules {
249+
for _, metric := range metrics {
250+
var fileName string
251+
if mod == "" {
252+
fileName = path.Join(tmpDir, strings.Join([]string{"memory", metric}, "."))
253+
} else {
254+
fileName = path.Join(tmpDir, strings.Join([]string{"memory", mod, metric}, "."))
255+
}
256+
if err := ioutil.WriteFile(fileName, []byte(fmt.Sprintln(cnt)), defaultFilePerm); err != nil {
257+
t.Fatal(err)
258+
}
259+
cnt++
260+
}
261+
}
262+
return tmpRoot
263+
}

0 commit comments

Comments
 (0)