@@ -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 {
@@ -219,3 +225,108 @@ func statsFromMeminfo(stats *cgroups.Stats) error {
219
225
220
226
return nil
221
227
}
228
+
229
+ func getPageUsageByNUMAV2 (path string ) (cgroups.PageUsageByNUMA , error ) {
230
+ const (
231
+ maxColumns = math .MaxUint8 + 1
232
+ file = "memory.numa_stat"
233
+ )
234
+ stats := cgroups.PageUsageByNUMA {}
235
+
236
+ fd , err := cgroups .OpenFile (path , file , os .O_RDONLY )
237
+ if os .IsNotExist (err ) {
238
+ return stats , nil
239
+ } else if err != nil {
240
+ return stats , err
241
+ }
242
+ defer fd .Close ()
243
+
244
+ // anon N0=139022336 N1=2760704
245
+ // file N0=449581056 N1=0
246
+ // kernel_stack N0=3670016 N1=0
247
+ // pagetables N0=4116480 N1=0
248
+ // sec_pagetables N0=0 N1=0
249
+ // shmem N0=0 N1=0
250
+ // file_mapped N0=55029760 N1=0
251
+ // file_dirty N0=0 N1=0
252
+ // file_writeback N0=0 N1=0
253
+ // swapcached N0=0 N1=0
254
+ // anon_thp N0=0 N1=0
255
+ // file_thp N0=0 N1=0
256
+ // shmem_thp N0=0 N1=0
257
+ // inactive_anon N0=138956800 N1=2752512
258
+ // active_anon N0=65536 N1=8192
259
+ // inactive_file N0=14770176 N1=0
260
+ // active_file N0=434810880 N1=0
261
+ // unevictable N0=0 N1=0
262
+ // slab_reclaimable N0=2358224 N1=11088
263
+ // slab_unreclaimable N0=2672352 N1=544144
264
+ // workingset_refault_anon N0=0 N1=0
265
+ // workingset_refault_file N0=0 N1=0
266
+ // workingset_activate_anon N0=0 N1=0
267
+ // workingset_activate_file N0=0 N1=0
268
+ // workingset_restore_anon N0=0 N1=0
269
+ // workingset_restore_file N0=0 N1=0
270
+ // workingset_nodereclaim N0=0 N1=0
271
+
272
+ scanner := bufio .NewScanner (fd )
273
+ for scanner .Scan () {
274
+ var field * cgroups.PageStats
275
+
276
+ line := scanner .Text ()
277
+ columns := strings .SplitN (line , " " , maxColumns )
278
+ for i , column := range columns {
279
+ byNode := strings .SplitN (column , "=" , 2 )
280
+ key := byNode [0 ]
281
+ if i == 0 { // First column: key is name, val is total.
282
+ field = getNUMAFieldV2 (& stats , key )
283
+ if field == nil { // unknown field (new kernel?)
284
+ break
285
+ }
286
+ field .Nodes = map [uint8 ]uint64 {}
287
+ } else { // Subsequent columns: key is N<id>, val is usage.
288
+ val := byNode [1 ]
289
+ if len (key ) < 2 || key [0 ] != 'N' {
290
+ // This is definitely an error.
291
+ return stats , malformedLine (path , file , line )
292
+ }
293
+
294
+ n , err := strconv .ParseUint (key [1 :], 10 , 8 )
295
+ if err != nil {
296
+ return stats , & parseError {Path : path , File : file , Err : err }
297
+ }
298
+
299
+ usage , err := strconv .ParseUint (val , 10 , 64 )
300
+ if err != nil {
301
+ return stats , & parseError {Path : path , File : file , Err : err }
302
+ }
303
+ field .Nodes [uint8 (n )] += usage
304
+ field .Total += usage
305
+ }
306
+
307
+ }
308
+ }
309
+ if err := scanner .Err (); err != nil {
310
+ return cgroups.PageUsageByNUMA {}, & parseError {Path : path , File : file , Err : err }
311
+ }
312
+
313
+ return stats , nil
314
+ }
315
+
316
+ func getNUMAFieldV2 (stats * cgroups.PageUsageByNUMA , name string ) * cgroups.PageStats {
317
+ switch name {
318
+ case "pagetables" :
319
+ return & stats .Total
320
+ case "anon" :
321
+ return & stats .Anon
322
+ case "file_mapped" :
323
+ return & stats .File
324
+ case "file_dirty" :
325
+ return & stats .File
326
+ case "file_writeback" :
327
+ return & stats .File
328
+ case "unevictable" :
329
+ return & stats .Unevictable
330
+ }
331
+ return nil
332
+ }
0 commit comments