@@ -102,6 +102,12 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
102
102
// cgroup v2 is always hierarchical.
103
103
stats .MemoryStats .UseHierarchy = true
104
104
105
+ pagesByNUMA , err := getPageUsageByNUMAV2 (dirPath )
106
+ if err != nil {
107
+ return err
108
+ }
109
+ stats .MemoryStats .PageUsageByNUMA = pagesByNUMA
110
+
105
111
memoryUsage , err := getMemoryDataV2 (dirPath , "" )
106
112
if err != nil {
107
113
if errors .Is (err , unix .ENOENT ) && dirPath == UnifiedMountpoint {
@@ -124,7 +130,9 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
124
130
swapUsage .Limit += memoryUsage .Limit
125
131
}
126
132
stats .MemoryStats .SwapUsage = swapUsage
127
-
133
+ if stats .MemoryStats .PageUsageByNUMA .Hierarchical .Total .Total != 0 {
134
+ stats .MemoryStats .UseHierarchy = true
135
+ }
128
136
return nil
129
137
}
130
138
@@ -219,3 +227,147 @@ func statsFromMeminfo(stats *cgroups.Stats) error {
219
227
220
228
return nil
221
229
}
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