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  }