storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/cgroup/linux.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 * MinIO Cloud Storage, (C) 2017 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/minio/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 var 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 114 cmd := exec.Command("cgm", "getvalue", cname, path, kernParam) 115 var out bytes.Buffer 116 cmd.Stdout = &out 117 if err = cmd.Run(); err != nil { 118 return 0, err 119 } 120 121 // Parse the cgm output. 122 limit, err = strconv.ParseUint(strings.TrimSpace(out.String()), 10, 64) 123 return limit, err 124 } 125 126 // Get cgroup memory limit file path. 127 func getMemoryLimitFilePath(cgPath string) string { 128 path := cgroupMemSysPath 129 130 // Docker generates weird cgroup paths that don't 131 // really exist on the file system. 132 // 133 // For example on regular Linux OS : 134 // `/user.slice/user-1000.slice/session-1.scope` 135 // 136 // But they exist as a bind mount on Docker and 137 // are not accessible : `/docker/<hash>` 138 // 139 // We we will just ignore if there is `/docker` in the 140 // path ignore and fall back to : 141 // `/sys/fs/cgroup/memory/memory.limit_in_bytes` 142 if !strings.HasPrefix(cgPath, dockerPrefixName) { 143 path = filepath.Join(path, cgPath) 144 } 145 146 // Final path. 147 return filepath.Join(path, memoryLimitKernelParam) 148 } 149 150 // GetMemoryLimit - Fetches cgroup memory limit either from 151 // a file path at '/sys/fs/cgroup/memory', if path fails then 152 // fallback to querying cgroup manager. 153 func GetMemoryLimit(pid int) (limit uint64, err error) { 154 var cg CGEntries 155 cg, err = GetEntries(pid) 156 if err != nil { 157 return 0, err 158 } 159 160 path := cg["memory"] 161 162 limit, err = getManagerKernValue("memory", path, memoryLimitKernelParam) 163 if err != nil { 164 165 // Upon any failure returned from `cgm`, on some systems cgm 166 // might not be installed. We fallback to using the the sysfs 167 // path instead to lookup memory limits. 168 var b []byte 169 b, err = ioutil.ReadFile(getMemoryLimitFilePath(path)) 170 if err != nil { 171 return 0, err 172 } 173 174 limit, err = strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64) 175 } 176 177 return limit, err 178 }