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