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