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  }