@@ -18,13 +18,15 @@ package cgroup2
1818
1919import (
2020 "bufio"
21+ "errors"
2122 "fmt"
2223 "io"
2324 "math"
2425 "os"
2526 "path/filepath"
2627 "strconv"
2728 "strings"
29+ "sync"
2830 "time"
2931 "unsafe"
3032
@@ -236,18 +238,28 @@ func ToResources(spec *specs.LinuxResources) *Resources {
236238
237239// Gets uint64 parsed content of single value cgroup stat file
238240func getStatFileContentUint64 (filePath string ) uint64 {
239- contents , err := os .ReadFile (filePath )
241+ f , err := os .Open (filePath )
240242 if err != nil {
241243 return 0
242244 }
243- trimmed := strings .TrimSpace (string (contents ))
245+ defer f .Close ()
246+
247+ // We expect an unsigned 64 bit integer, or a "max" string
248+ // in some cases.
249+ buf := make ([]byte , 32 )
250+ n , err := f .Read (buf )
251+ if err != nil {
252+ return 0
253+ }
254+
255+ trimmed := strings .TrimSpace (string (buf [:n ]))
244256 if trimmed == "max" {
245257 return math .MaxUint64
246258 }
247259
248260 res , err := parseUint (trimmed , 10 , 64 )
249261 if err != nil {
250- logrus .Errorf ("unable to parse %q as a uint from Cgroup file %q" , string ( contents ) , filePath )
262+ logrus .Errorf ("unable to parse %q as a uint from Cgroup file %q" , trimmed , filePath )
251263 return res
252264 }
253265
@@ -377,56 +389,94 @@ func systemdUnitFromPath(path string) string {
377389}
378390
379391func readHugeTlbStats (path string ) []* stats.HugeTlbStat {
380- usage := []* stats.HugeTlbStat {}
381- keyUsage := make (map [string ]* stats.HugeTlbStat )
382- f , err := os .Open (path )
383- if err != nil {
384- return usage
385- }
386- files , err := f .Readdir (- 1 )
387- f .Close ()
388- if err != nil {
389- return usage
392+ hpSizes := hugePageSizes ()
393+ usage := make ([]* stats.HugeTlbStat , len (hpSizes ))
394+ for idx , pagesize := range hpSizes {
395+ usage [idx ] = & stats.HugeTlbStat {
396+ Max : getStatFileContentUint64 (filepath .Join (path , "hugetlb." + pagesize + ".max" )),
397+ Current : getStatFileContentUint64 (filepath .Join (path , "hugetlb." + pagesize + ".current" )),
398+ Pagesize : pagesize ,
399+ }
390400 }
401+ return usage
402+ }
391403
392- for _ , file := range files {
393- if strings .Contains (file .Name (), "hugetlb" ) &&
394- (strings .HasSuffix (file .Name (), "max" ) || strings .HasSuffix (file .Name (), "current" )) {
395- var hugeTlb * stats.HugeTlbStat
396- var ok bool
397- fileName := strings .Split (file .Name (), "." )
398- pageSize := fileName [1 ]
399- if hugeTlb , ok = keyUsage [pageSize ]; ! ok {
400- hugeTlb = & stats.HugeTlbStat {}
401- }
402- hugeTlb .Pagesize = pageSize
403- out , err := os .ReadFile (filepath .Join (path , file .Name ()))
404- if err != nil {
405- continue
406- }
407- var value uint64
408- stringVal := strings .TrimSpace (string (out ))
409- if stringVal == "max" {
410- value = math .MaxUint64
411- } else {
412- value , err = strconv .ParseUint (stringVal , 10 , 64 )
413- }
414- if err != nil {
415- continue
404+ var (
405+ hPageSizes []string
406+ initHPSOnce sync.Once
407+ )
408+
409+ // The following idea and implementation is taken pretty much line for line from
410+ // runc. Because the hugetlb files are well known, and the only variable thrown in
411+ // the mix is what huge page sizes you have on your host, this lends itself well
412+ // to doing the work to find the files present once, and then re-using this. This
413+ // saves a os.Readdirnames(0) call to search for hugeltb files on every `manager.Stat`
414+ // call.
415+ // https://github.com/opencontainers/runc/blob/3a2c0c2565644d8a7e0f1dd594a060b21fa96cf1/libcontainer/cgroups/utils.go#L301
416+ func hugePageSizes () []string {
417+ initHPSOnce .Do (func () {
418+ dir , err := os .OpenFile ("/sys/kernel/mm/hugepages" , unix .O_DIRECTORY | unix .O_RDONLY , 0 )
419+ if err != nil {
420+ return
421+ }
422+ files , err := dir .Readdirnames (0 )
423+ dir .Close ()
424+ if err != nil {
425+ return
426+ }
427+
428+ hPageSizes , err = getHugePageSizeFromFilenames (files )
429+ if err != nil {
430+ logrus .Warnf ("hugePageSizes: %s" , err )
431+ }
432+ })
433+
434+ return hPageSizes
435+ }
436+
437+ func getHugePageSizeFromFilenames (fileNames []string ) ([]string , error ) {
438+ pageSizes := make ([]string , 0 , len (fileNames ))
439+ var warn error
440+
441+ for _ , file := range fileNames {
442+ // example: hugepages-1048576kB
443+ val := strings .TrimPrefix (file , "hugepages-" )
444+ if len (val ) == len (file ) {
445+ // Unexpected file name: no prefix found, ignore it.
446+ continue
447+ }
448+ // In all known versions of Linux up to 6.3 the suffix is always
449+ // "kB". If we find something else, produce an error but keep going.
450+ eLen := len (val ) - 2
451+ val = strings .TrimSuffix (val , "kB" )
452+ if len (val ) != eLen {
453+ // Highly unlikely.
454+ if warn == nil {
455+ warn = errors .New (file + `: invalid suffix (expected "kB")` )
416456 }
417- switch fileName [2 ] {
418- case "max" :
419- hugeTlb .Max = value
420- case "current" :
421- hugeTlb .Current = value
457+ continue
458+ }
459+ size , err := strconv .Atoi (val )
460+ if err != nil {
461+ // Highly unlikely.
462+ if warn == nil {
463+ warn = fmt .Errorf ("%s: %w" , file , err )
422464 }
423- keyUsage [ pageSize ] = hugeTlb
465+ continue
424466 }
467+ // Model after https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/hugetlb_cgroup.c?id=eff48ddeab782e35e58ccc8853f7386bbae9dec4#n574
468+ // but in our case the size is in KB already.
469+ if size >= (1 << 20 ) {
470+ val = strconv .Itoa (size >> 20 ) + "GB"
471+ } else if size >= (1 << 10 ) {
472+ val = strconv .Itoa (size >> 10 ) + "MB"
473+ } else {
474+ val += "KB"
475+ }
476+ pageSizes = append (pageSizes , val )
425477 }
426- for _ , entry := range keyUsage {
427- usage = append (usage , entry )
428- }
429- return usage
478+
479+ return pageSizes , warn
430480}
431481
432482func getSubreaper () (int , error ) {
0 commit comments