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 }