github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/process/process_linux.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  // Package process provides functions for inspecting processes.
    22  package process
    23  
    24  import (
    25  	"fmt"
    26  	"os"
    27  	"syscall"
    28  	"time"
    29  )
    30  
    31  const (
    32  	// syscallBatchSize controls the number of syscalls to perform before
    33  	// triggering a sleep.
    34  	syscallBatchSize                           = 10
    35  	defaultSyscallBatchDurationSleepMultiplier = 10
    36  )
    37  
    38  var (
    39  	dotBytes       = []byte(".")
    40  	doubleDotBytes = []byte("..")
    41  )
    42  
    43  // numFDsSlow returns the number of file descriptors for a given process.
    44  // This is a reference implementation that can be used to compare against for
    45  // correctness.
    46  func numFDsSlow(pid int) (int, error) {
    47  	statPath := fmt.Sprintf("/proc/%d/fd", pid)
    48  	d, err := os.Open(statPath)
    49  	if err != nil {
    50  		return 0, err
    51  	}
    52  	fnames, err := d.Readdirnames(-1)
    53  	d.Close()
    54  	return len(fnames), err
    55  }
    56  
    57  // NumFDs returns the number of file descriptors for a given process.
    58  // This is an optimized implementation that avoids allocations as much as
    59  // possible. In terms of wall-clock time it is not much faster than
    60  // NumFDsReference due to the fact that the syscall overhead dominates,
    61  // however, it produces significantly less garbage.
    62  func NumFDs(pid int) (int, error) {
    63  	// Multiplier of zero means no throttling.
    64  	return NumFDsWithBatchSleep(pid, 0)
    65  }
    66  
    67  // NumFDsWithBatchSleep is the same as NumFDs but it throttles itself to prevent excessive
    68  // CPU usages for processes with a lot of file descriptors.
    69  //
    70  // batchDurationSleepMultiplier is the multiplier by which the amount of time spent performing
    71  // a single batch of syscalls will be multiplied by to determine the amount of time that the
    72  // function will spend sleeping.
    73  //
    74  // For example, if performing syscallBatchSize syscalls takes 500 nanoseconds and
    75  // batchDurationSleepMultiplier is 10 then the function will sleep for ~500 * 10 nanoseconds
    76  // inbetween batches.
    77  //
    78  // In other words, a batchDurationSleepMultiplier will cause the function to take approximately
    79  // 10x longer but require 10x less CPU utilization at any given moment in time.
    80  func NumFDsWithBatchSleep(pid int, batchDurationSleepMultiplier float64) (int, error) {
    81  	statPath := fmt.Sprintf("/proc/%d/fd", pid)
    82  	d, err := os.Open(statPath)
    83  	if err != nil {
    84  		return 0, err
    85  	}
    86  	defer d.Close()
    87  
    88  	var (
    89  		b         = make([]byte, 4096)
    90  		count     = 0
    91  		lastSleep = time.Now()
    92  	)
    93  	for i := 0; ; i++ {
    94  		if i%syscallBatchSize == 0 && i != 0 {
    95  			// Throttle loop to prevent execssive CPU usage.
    96  			syscallBatchCompletionDuration := time.Now().Sub(lastSleep)
    97  			timeToSleep := time.Duration(float64(syscallBatchCompletionDuration) * batchDurationSleepMultiplier)
    98  			if timeToSleep > 0 {
    99  				time.Sleep(timeToSleep)
   100  			}
   101  			lastSleep = time.Now()
   102  		}
   103  
   104  		n, err := syscall.ReadDirent(int(d.Fd()), b)
   105  		if err != nil {
   106  			return 0, err
   107  		}
   108  		if n <= 0 {
   109  			break
   110  		}
   111  
   112  		_, numDirs := countDirent(b[:n])
   113  		count += numDirs
   114  	}
   115  
   116  	return count, nil
   117  }
   118  
   119  // NumFDsWithDefaultBatchSleep is the same as NumFDsWithBatchSleep except it uses the default value
   120  // for the batchSleepDurationMultiplier.
   121  func NumFDsWithDefaultBatchSleep(pid int) (int, error) {
   122  	return NumFDsWithBatchSleep(pid, defaultSyscallBatchDurationSleepMultiplier)
   123  }