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 }