diff --git a/process_metrics_linux.go b/process_metrics_linux.go index 4442992..ab8260b 100644 --- a/process_metrics_linux.go +++ b/process_metrics_linux.go @@ -1,11 +1,16 @@ package metrics import ( + "bufio" "bytes" "fmt" "io" "io/ioutil" "log" + "os" + "regexp" + "strconv" + "strings" "time" ) @@ -81,3 +86,75 @@ func writeProcessMetrics(w io.Writer) { } var startTimeSeconds = time.Now().Unix() + +const fdReadChunkSize = 512 + +func WriteFDMetrics(w io.Writer){ + totalOpenFDs, err := getOpenFDsCount("/proc/self/fd") + if err != nil { + log.Printf("ERROR: %v",err) + return + } + maxOpenFDs, err := getMaxFilesLimit("/proc/self/limits") + if err != nil { + log.Printf("ERROR: %v",err) + return + } + fmt.Fprintf(w,"process_max_fds %d\n",maxOpenFDs) + fmt.Fprintf(w,"process_open_fds %d\n",totalOpenFDs) +} + + +func getOpenFDsCount(path string)(uint64,error){ + f, err := os.Open(path) + if err != nil { + return 0,fmt.Errorf("cannot open process fd path: %q, err: %v",path,err) + } + defer f.Close() + var totalOpenFDs uint64 + for { + names, err := f.Readdirnames(fdReadChunkSize) + if err == io.EOF{ + break + } + if err != nil { + return 0, fmt.Errorf("unexpected error at readdirnames: %v",err) + } + totalOpenFDs += uint64(len(names)) + } + return totalOpenFDs, nil +} + +var limitsRe = regexp.MustCompile(`(Max \w+\s{0,1}?\w*\s{0,1}\w*)\s{2,}(\w+)\s+(\w+)`) + +func getMaxFilesLimit(path string)(uint64,error){ + f, err := os.Open(path) + if err != nil { + return 0,fmt.Errorf("cannot open path: %q for max files limit, err: %w",path,err) + } + defer f.Close() + scan := bufio.NewScanner(f) + // skip first line + scan.Scan() + for scan.Scan(){ + text := scan.Text() + if !strings.HasPrefix(text,"Max open files"){ + continue + } + items := limitsRe.FindStringSubmatch(text) + if len(items) != 4 { + return 0,fmt.Errorf("unxpected fields num for limits file, want: %d, got: %d, line: %q",4,len(items),text) + } + // use soft limit. + limit := items[2] + if limit == "unlimited"{ + return 18446744073709551615,nil + } + limitUint, err := strconv.ParseUint(limit,10,64) + if err != nil { + return 0, fmt.Errorf("cannot parse limit: %q as uint64: %w",limit,err) + } + return limitUint,nil + } + return 0, fmt.Errorf("max open files limit wasn't found") +} diff --git a/process_metrics_linux_test.go b/process_metrics_linux_test.go new file mode 100644 index 0000000..b3ea502 --- /dev/null +++ b/process_metrics_linux_test.go @@ -0,0 +1,37 @@ +package metrics + +import "testing" + +func TestGetMaxFilesLimit(t *testing.T){ + f := func(want uint64,path string, wantErr bool) { + t.Helper() + got, err := getMaxFilesLimit(path) + if err != nil && !wantErr { + t.Fatalf("unexpected error: %v",err) + } + if got != want{ + t.Fatalf("unexpected result: %d, want: %d at getMaxFilesLimit",got,want) + } + + } + f(1024,"testdata/limits",false) + f(0,"testdata/bad_path",true) + f(0,"testdata/limits_bad",true) +} + + +func TestGetOpenFDsCount(t *testing.T){ + f := func(want uint64,path string, wantErr bool) { + t.Helper() + got, err := getOpenFDsCount(path) + if (err != nil && !wantErr)|| (err == nil && wantErr) { + t.Fatalf("unexpected error: %v",err) + } + if got != want{ + t.Fatalf("unexpected result: %d, want: %d at getOpenFDsCount",got,want) + } + } + f(5,"testdata/fd/",false) + f(0,"testdata/fd/0",true) + f(0,"testdata/limits",true) +} diff --git a/testdata/fd/0 b/testdata/fd/0 new file mode 100644 index 0000000..e69de29 diff --git a/testdata/fd/10 b/testdata/fd/10 new file mode 100644 index 0000000..e69de29 diff --git a/testdata/fd/2 b/testdata/fd/2 new file mode 100644 index 0000000..e69de29 diff --git a/testdata/fd/3 b/testdata/fd/3 new file mode 100644 index 0000000..e69de29 diff --git a/testdata/fd/5 b/testdata/fd/5 new file mode 100644 index 0000000..e69de29 diff --git a/testdata/limits b/testdata/limits new file mode 100644 index 0000000..fb520d3 --- /dev/null +++ b/testdata/limits @@ -0,0 +1,17 @@ +Limit Soft Limit Hard Limit Units +Max cpu time unlimited unlimited seconds +Max file size unlimited unlimited bytes +Max data size unlimited unlimited bytes +Max stack size 8388608 unlimited bytes +Max core file size 0 unlimited bytes +Max resident set unlimited unlimited bytes +Max processes 127458 127458 processes +Max open files 1024 1048576 files +Max locked memory 67108864 67108864 bytes +Max address space unlimited unlimited bytes +Max file locks unlimited unlimited locks +Max pending signals 127458 127458 signals +Max msgqueue size 819200 819200 bytes +Max nice priority 0 0 +Max realtime priority 0 0 +Max realtime timeout unlimited unlimited us \ No newline at end of file diff --git a/testdata/limits_bad b/testdata/limits_bad new file mode 100644 index 0000000..d54162d --- /dev/null +++ b/testdata/limits_bad @@ -0,0 +1 @@ +Limit Soft Limit Hard Limit Units \ No newline at end of file