github.com/minio/madmin-go@v1.7.5/cgroup/linux.go (about) 1 //go:build linux 2 // +build linux 3 4 // 5 // MinIO Object Storage (c) 2022 MinIO, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 // See the License for the specific language governing permissions and 17 // limitations under the License. 18 // 19 20 // Package cgroup implements parsing for all the cgroup 21 // categories and functionality in a simple way. 22 package cgroup 23 24 import ( 25 "bufio" 26 "bytes" 27 "fmt" 28 "io" 29 "io/ioutil" 30 "os" 31 "os/exec" 32 "path/filepath" 33 "strconv" 34 "strings" 35 ) 36 37 // DO NOT EDIT following constants are chosen defaults for any kernel 38 // after 3.x, please open a GitHub issue https://github.com/minio/madmin-go/issues 39 // and discuss first if you wish to change this. 40 const ( 41 // Default string for looking for kernel memory param. 42 memoryLimitKernelParam = "memory.limit_in_bytes" 43 44 // Points to sys path memory path. 45 cgroupMemSysPath = "/sys/fs/cgroup/memory" 46 47 // Default docker prefix. 48 dockerPrefixName = "/docker/" 49 50 // Proc controller group path. 51 cgroupFileTemplate = "/proc/%d/cgroup" 52 ) 53 54 // CGEntries - represents all the entries in a process cgroup file 55 // at /proc/<pid>/cgroup as key value pairs. 56 type CGEntries map[string]string 57 58 // GetEntries reads and parses all the cgroup entries for a given process. 59 func GetEntries(pid int) (CGEntries, error) { 60 r, err := os.Open(fmt.Sprintf(cgroupFileTemplate, pid)) 61 if err != nil { 62 return nil, err 63 } 64 defer r.Close() 65 return parseProcCGroup(r) 66 } 67 68 // parseProcCGroup - cgroups are always in the following 69 // format once enabled you need to know the pid of the 70 // application you are looking for so that the the 71 // following parsing logic only parses the file located 72 // at /proc/<pid>/cgroup. 73 // 74 // CGROUP entries id, component and path are always in 75 // the following format. “ID:COMPONENT:PATH“ 76 // 77 // Following code block parses this information and 78 // returns a procCGroup which is a parsed list of all 79 // the line by line entires from /proc/<pid>/cgroup. 80 func parseProcCGroup(r io.Reader) (CGEntries, error) { 81 cgEntries := CGEntries{} 82 83 // Start reading cgroup categories line by line 84 // and process them into procCGroup structure. 85 scanner := bufio.NewScanner(r) 86 for scanner.Scan() { 87 line := scanner.Text() 88 89 tokens := strings.SplitN(line, ":", 3) 90 if len(tokens) < 3 { 91 continue 92 } 93 94 name, path := tokens[1], tokens[2] 95 for _, token := range strings.Split(name, ",") { 96 name = strings.TrimPrefix(token, "name=") 97 cgEntries[name] = path 98 } 99 } 100 101 // Return upon any error while reading the cgroup categories. 102 if err := scanner.Err(); err != nil { 103 return nil, err 104 } 105 106 return cgEntries, nil 107 } 108 109 // Fetch value of the cgroup kernel param from the cgroup manager, 110 // if cgroup manager is configured we should just rely on `cgm` cli 111 // to fetch all the values for us. 112 func getManagerKernValue(cname, path, kernParam string) (limit uint64, err error) { 113 cmd := exec.Command("cgm", "getvalue", cname, path, kernParam) 114 var out bytes.Buffer 115 cmd.Stdout = &out 116 if err = cmd.Run(); err != nil { 117 return 0, err 118 } 119 120 // Parse the cgm output. 121 limit, err = strconv.ParseUint(strings.TrimSpace(out.String()), 10, 64) 122 return limit, err 123 } 124 125 // Get cgroup memory limit file path. 126 func getMemoryLimitFilePath(cgPath string) string { 127 path := cgroupMemSysPath 128 129 // Docker generates weird cgroup paths that don't 130 // really exist on the file system. 131 // 132 // For example on regular Linux OS : 133 // `/user.slice/user-1000.slice/session-1.scope` 134 // 135 // But they exist as a bind mount on Docker and 136 // are not accessible : `/docker/<hash>` 137 // 138 // We we will just ignore if there is `/docker` in the 139 // path ignore and fall back to : 140 // `/sys/fs/cgroup/memory/memory.limit_in_bytes` 141 if !strings.HasPrefix(cgPath, dockerPrefixName) { 142 path = filepath.Join(path, cgPath) 143 } 144 145 // Final path. 146 return filepath.Join(path, memoryLimitKernelParam) 147 } 148 149 // GetMemoryLimit - Fetches cgroup memory limit either from 150 // a file path at '/sys/fs/cgroup/memory', if path fails then 151 // fallback to querying cgroup manager. 152 func GetMemoryLimit(pid int) (limit uint64, err error) { 153 var cg CGEntries 154 cg, err = GetEntries(pid) 155 if err != nil { 156 return 0, err 157 } 158 159 path := cg["memory"] 160 161 limit, err = getManagerKernValue("memory", path, memoryLimitKernelParam) 162 if err != nil { 163 164 // Upon any failure returned from `cgm`, on some systems cgm 165 // might not be installed. We fallback to using the the sysfs 166 // path instead to lookup memory limits. 167 var b []byte 168 b, err = ioutil.ReadFile(getMemoryLimitFilePath(path)) 169 if err != nil { 170 return 0, err 171 } 172 173 limit, err = strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64) 174 } 175 176 return limit, err 177 }