Skip to content

Commit e270c0b

Browse files
committed
add numa_stat for cgroup v2
Signed-off-by: victoryang00 <[email protected]>
1 parent b338acc commit e270c0b

File tree

4 files changed

+582
-1
lines changed

4 files changed

+582
-1
lines changed

libcontainer/cgroups/fs2/fs2.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313

1414
type parseError = fscommon.ParseError
1515

16+
func malformedLine(path, file, line string) error {
17+
return &parseError{Path: path, File: file, Err: fmt.Errorf("malformed line: %s", line)}
18+
}
19+
1620
type Manager struct {
1721
config *configs.Cgroup
1822
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"

libcontainer/cgroups/fs2/memory.go

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
102102
// cgroup v2 is always hierarchical.
103103
stats.MemoryStats.UseHierarchy = true
104104

105+
pagesByNUMA, err := getPageUsageByNUMAV2(dirPath)
106+
if err != nil {
107+
return err
108+
}
109+
stats.MemoryStats.PageUsageByNUMA = pagesByNUMA
110+
105111
memoryUsage, err := getMemoryDataV2(dirPath, "")
106112
if err != nil {
107113
if errors.Is(err, unix.ENOENT) && dirPath == UnifiedMountpoint {
@@ -124,7 +130,9 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
124130
swapUsage.Limit += memoryUsage.Limit
125131
}
126132
stats.MemoryStats.SwapUsage = swapUsage
127-
133+
if stats.MemoryStats.PageUsageByNUMA.Hierarchical.Total.Total != 0 {
134+
stats.MemoryStats.UseHierarchy = true
135+
}
128136
return nil
129137
}
130138

@@ -219,3 +227,147 @@ func statsFromMeminfo(stats *cgroups.Stats) error {
219227

220228
return nil
221229
}
230+
231+
func getPageUsageByNUMAV2(path string) (cgroups.PageUsageByNUMA, error) {
232+
const (
233+
maxColumns = math.MaxUint8 + 1
234+
file = "memory.numa_stat"
235+
)
236+
stats := cgroups.PageUsageByNUMA{}
237+
238+
fd, err := cgroups.OpenFile(path, file, os.O_RDONLY)
239+
if os.IsNotExist(err) {
240+
return stats, nil
241+
} else if err != nil {
242+
return stats, err
243+
}
244+
defer fd.Close()
245+
246+
// https://docs.kernel.org/admin-guide/cgroup-v2.html.
247+
// anon N0=<> N1=<> # The Anon page size in byte which equals to page_num * page_size.
248+
// file N0=<> N1=0 # The File page size in byte which equals to file_mmaped_page_num * page_size.
249+
// kernel_stack N0=<> N1=0 # The Kernel's stack occupation.
250+
// pagetables N0=<> N1=0 # The total number of pagetable entry been occupied.
251+
// sec_pagetables N0=<> N1=<>
252+
// shmem N0=<> N1=<>
253+
// file_mapped N0=<> N1=<> # file page breakdown.
254+
// file_dirty N0=<> N1=<> # file page breakdown.
255+
// file_writeback N0=<> N1=<> # file page breakdown.
256+
// swapcached N0=<> N1=<>
257+
// anon_thp N0=<> N1=<> # The transparent huge page occupation.
258+
// file_thp N0=<> N1=<> # The transparent huge page occupation.
259+
// shmem_thp N0=<> N1=<> # The transparent huge page occupation.
260+
// inactive_anon N0=<> N1=<>
261+
// active_anon N0=<> N1=<>
262+
// inactive_file N0=<> N1=<>
263+
// active_file N0=<> N1=<>
264+
// unevictable N0=<> N1=<>
265+
// slab_reclaimable N0=<> N1=<>
266+
// slab_unreclaimable N0=<> N1=<>
267+
// workingset_refault_anon N0=<> N1=<>
268+
// workingset_refault_file N0=<> N1=<>
269+
// workingset_activate_anon N0=<> N1=<>
270+
// workingset_activate_file N0=<> N1=<>
271+
// workingset_restore_anon N0=<> N1=<>
272+
// workingset_restore_file N0=<> N1=<>
273+
// workingset_nodereclaim N0=<> N1=<>
274+
275+
scanner := bufio.NewScanner(fd)
276+
for scanner.Scan() {
277+
var field *cgroups.PageStats
278+
279+
line := scanner.Text()
280+
columns := strings.SplitN(line, " ", maxColumns)
281+
for i, column := range columns {
282+
byNode := strings.SplitN(column, "=", 2)
283+
key := byNode[0]
284+
if i == 0 { // First column: key is name, val is total.
285+
field = getNUMAFieldV2(&stats, key)
286+
if field == nil { // unknown field (new kernel?)
287+
break
288+
}
289+
field.Nodes = map[uint8]uint64{}
290+
} else { // Subsequent columns: key is N<id>, val is usage.
291+
if len(byNode) != 2 {
292+
// This is definitely an error.
293+
return stats, malformedLine(path, file, line)
294+
}
295+
val := byNode[1]
296+
if len(key) < 2 || key[0] != 'N' {
297+
// This is definitely an error.
298+
return stats, malformedLine(path, file, line)
299+
}
300+
301+
n, err := strconv.ParseUint(key[1:], 10, 8)
302+
if err != nil {
303+
return stats, &parseError{Path: path, File: file, Err: err}
304+
}
305+
306+
usage, err := strconv.ParseUint(val, 10, 64)
307+
if err != nil {
308+
return stats, &parseError{Path: path, File: file, Err: err}
309+
}
310+
field.Nodes[uint8(n)] += usage
311+
field.Total += usage
312+
}
313+
314+
}
315+
stats.Total.Total = stats.File.Total + stats.Anon.Total
316+
stats.Total.Nodes = map[uint8]uint64{}
317+
for k, v := range stats.File.Nodes {
318+
stats.Total.Nodes[k] = v + stats.Anon.Nodes[k]
319+
}
320+
}
321+
if err := scanner.Err(); err != nil {
322+
return cgroups.PageUsageByNUMA{}, &parseError{Path: path, File: file, Err: err}
323+
}
324+
325+
files, err := os.ReadDir(path)
326+
if err != nil {
327+
return stats, err
328+
}
329+
// hierarchical stats in subdirectory
330+
for _, file := range files {
331+
if file.IsDir() {
332+
statTmp, err := getPageUsageByNUMAV2(path + "/" + file.Name())
333+
if err != nil {
334+
return stats, err
335+
}
336+
if stats.Hierarchical.Total.Total == 0 {
337+
stats.Hierarchical.Total.Nodes = map[uint8]uint64{}
338+
stats.Hierarchical.Anon.Nodes = map[uint8]uint64{}
339+
stats.Hierarchical.File.Nodes = map[uint8]uint64{}
340+
stats.Hierarchical.Unevictable.Nodes = map[uint8]uint64{}
341+
}
342+
stats.Hierarchical.Total.Total += statTmp.Total.Total
343+
stats.Hierarchical.Anon.Total += statTmp.Anon.Total
344+
stats.Hierarchical.File.Total += statTmp.File.Total
345+
stats.Hierarchical.Unevictable.Total += statTmp.Unevictable.Total
346+
for k, v := range statTmp.Total.Nodes {
347+
stats.Hierarchical.Total.Nodes[k] += v
348+
}
349+
for k, v := range statTmp.Anon.Nodes {
350+
stats.Hierarchical.Anon.Nodes[k] += v
351+
}
352+
for k, v := range statTmp.File.Nodes {
353+
stats.Hierarchical.File.Nodes[k] += v
354+
}
355+
for k, v := range statTmp.Unevictable.Nodes {
356+
stats.Hierarchical.Unevictable.Nodes[k] += v
357+
}
358+
}
359+
}
360+
return stats, nil
361+
}
362+
363+
func getNUMAFieldV2(stats *cgroups.PageUsageByNUMA, name string) *cgroups.PageStats {
364+
switch name {
365+
case "anon":
366+
return &stats.Anon
367+
case "file":
368+
return &stats.File
369+
case "unevictable":
370+
return &stats.Unevictable
371+
}
372+
return nil
373+
}

0 commit comments

Comments
 (0)