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