github.com/livekit/protocol@v1.39.3/utils/hwstats/memory_linux.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  
    17  package hwstats
    18  
    19  import (
    20  	"os"
    21  	"strconv"
    22  
    23  	"github.com/livekit/protocol/logger"
    24  )
    25  
    26  const (
    27  	memUsagePathV1 = "/sys/fs/cgroup/memory/memory.usage_in_bytes"
    28  	memLimitPathV1 = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
    29  
    30  	memCurrentPathV2 = "/sys/fs/cgroup/memory.current"
    31  	memMaxPathV2     = "/sys/fs/cgroup/memory.max"
    32  )
    33  
    34  type memInfoGetter interface {
    35  	getMemory() (uint64, uint64, error)
    36  }
    37  
    38  type cgroupMemoryGetter struct {
    39  	cg     memInfoGetter
    40  	osStat *osStatMemoryGetter
    41  }
    42  
    43  func newPlatformMemoryGetter() (platformMemoryGetter, error) {
    44  	// backup getter when max is not available,
    45  	// will get from /proc/meminfo which will be node level max though
    46  	osStat, err := newOSStatMemoryGetter()
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	// probe for the cgroup version
    52  	var cg memInfoGetter
    53  	for k, v := range map[string]func(osStat *osStatMemoryGetter) memInfoGetter{
    54  		memUsagePathV1:   newMemInfoGetterV1,
    55  		memCurrentPathV2: newMemInfoGetterV2,
    56  	} {
    57  		e, err := fileExists(k)
    58  		if err != nil {
    59  			return nil, err
    60  		}
    61  		if e {
    62  			cg = v(osStat)
    63  			break
    64  		}
    65  	}
    66  	if cg == nil {
    67  		logger.Infow("failed reading cgroup specific memory stats, falling back to system wide implementation")
    68  		return osStat, nil
    69  	}
    70  
    71  	return &cgroupMemoryGetter{
    72  		cg:     cg,
    73  		osStat: osStat,
    74  	}, nil
    75  }
    76  
    77  func (c *cgroupMemoryGetter) getMemory() (uint64, uint64, error) {
    78  	return c.cg.getMemory()
    79  }
    80  
    81  // -----------------------------------------
    82  
    83  type memInfoGetterV1 struct {
    84  	osStat *osStatMemoryGetter
    85  }
    86  
    87  func newMemInfoGetterV1(osStat *osStatMemoryGetter) memInfoGetter {
    88  	return &memInfoGetterV1{
    89  		osStat: osStat,
    90  	}
    91  }
    92  
    93  func (cg *memInfoGetterV1) getMemory() (uint64, uint64, error) {
    94  	usage, err := readValueFromFile(memUsagePathV1)
    95  	if err != nil {
    96  		return 0, 0, err
    97  	}
    98  
    99  	total, err := readValueFromFile(memLimitPathV1)
   100  	if err != nil {
   101  		return 0, 0, err
   102  	}
   103  
   104  	// fallback if limit from cgroup is more than physical available memory
   105  	// when limit is not set explicitly, it could be very high
   106  	usage1, total1, err := cg.osStat.getMemory()
   107  	if err != nil {
   108  		return 0, 0, err
   109  	}
   110  	if total > total1 {
   111  		return usage1, total1, nil
   112  	}
   113  
   114  	return usage, total, nil
   115  }
   116  
   117  // --------------------------------------------
   118  
   119  type memInfoGetterV2 struct {
   120  	osStat *osStatMemoryGetter
   121  }
   122  
   123  func newMemInfoGetterV2(osStat *osStatMemoryGetter) memInfoGetter {
   124  	return &memInfoGetterV2{
   125  		osStat: osStat,
   126  	}
   127  }
   128  
   129  func (cg *memInfoGetterV2) getMemory() (uint64, uint64, error) {
   130  	usage, err := readValueFromFile(memCurrentPathV2)
   131  	if err != nil {
   132  		return 0, 0, err
   133  	}
   134  
   135  	total, err := readValueFromFile(memMaxPathV2)
   136  	if err != nil {
   137  		// when memory limit not set, it has the string "max"
   138  		usage, total, err = cg.osStat.getMemory()
   139  		if err != nil {
   140  			return 0, 0, err
   141  		}
   142  	}
   143  
   144  	return usage, total, nil
   145  }
   146  
   147  // ---------------------------------
   148  
   149  func readValueFromFile(file string) (uint64, error) {
   150  	b, err := os.ReadFile(file)
   151  	if err != nil {
   152  		return 0, err
   153  	}
   154  
   155  	// Skip the trailing EOL
   156  	return strconv.ParseUint(string(b[:len(b)-1]), 10, 64)
   157  }