@@ -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