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