diff --git a/client/app/scripts/utils/string-utils.js b/client/app/scripts/utils/string-utils.js index c1ca3eda1..1364c67fc 100644 --- a/client/app/scripts/utils/string-utils.js +++ b/client/app/scripts/utils/string-utils.js @@ -10,6 +10,13 @@ const formatters = { return formatters.metric(obj.value, obj.suffix); }, + integer(value) { + if (value < 1100 && value >= 0) { + return Number(value).toFixed(0); + } + return formatLargeValue(value); + }, + number(value) { if (value < 1100 && value >= 0) { return Number(value).toFixed(2); diff --git a/probe/process/reporter.go b/probe/process/reporter.go index a5d7ce5c9..15042cb3d 100644 --- a/probe/process/reporter.go +++ b/probe/process/reporter.go @@ -9,13 +9,14 @@ import ( // We use these keys in node metadata const ( - PID = "pid" - Name = "name" - PPID = "ppid" - Cmdline = "cmdline" - Threads = "threads" - CPUUsage = "process_cpu_usage_percent" - MemoryUsage = "process_memory_usage_bytes" + PID = "pid" + Name = "name" + PPID = "ppid" + Cmdline = "cmdline" + Threads = "threads" + CPUUsage = "process_cpu_usage_percent" + MemoryUsage = "process_memory_usage_bytes" + OpenFilesCount = "open_files_count" ) // Reporter generates Reports containing the Process topology. @@ -84,6 +85,7 @@ func (r *Reporter) processTopology() (report.Topology, error) { } node = node.WithMetric(MemoryUsage, report.MakeMetric().Add(now, float64(p.RSSBytes))) + node = node.WithMetric(OpenFilesCount, report.MakeMetric().Add(now, float64(p.OpenFilesCount))) t.AddNode(nodeID, node) }) diff --git a/probe/process/walker.go b/probe/process/walker.go index 31e0d445d..5b1bc4869 100644 --- a/probe/process/walker.go +++ b/probe/process/walker.go @@ -4,12 +4,13 @@ import "sync" // Process represents a single process. type Process struct { - PID, PPID int - Name string - Cmdline string - Threads int - Jiffies uint64 - RSSBytes uint64 + PID, PPID int + Name string + Cmdline string + Threads int + Jiffies uint64 + RSSBytes uint64 + OpenFilesCount int } // Walker is something that walks the /proc directory diff --git a/probe/process/walker_linux.go b/probe/process/walker_linux.go index d3c5538dc..80622bdbb 100644 --- a/probe/process/walker_linux.go +++ b/probe/process/walker_linux.go @@ -83,6 +83,11 @@ func (w *walker) Walk(f func(Process, Process)) error { continue } + openFiles, err := fs.ReadDirNames(path.Join(w.procRoot, filename, "fd")) + if err != nil { + continue + } + cmdline, name := "", "(unknown)" if cmdlineBuf, err := cachedReadFile(path.Join(w.procRoot, filename, "cmdline")); err == nil { // like proc, treat name as the first element of command line @@ -96,13 +101,14 @@ func (w *walker) Walk(f func(Process, Process)) error { } f(Process{ - PID: pid, - PPID: ppid, - Name: name, - Cmdline: cmdline, - Threads: threads, - Jiffies: jiffies, - RSSBytes: rss, + PID: pid, + PPID: ppid, + Name: name, + Cmdline: cmdline, + Threads: threads, + Jiffies: jiffies, + RSSBytes: rss, + OpenFilesCount: len(openFiles), }, Process{}) } diff --git a/probe/process/walker_linux_test.go b/probe/process/walker_linux_test.go index d9ea0c06a..11656e39e 100644 --- a/probe/process/walker_linux_test.go +++ b/probe/process/walker_linux_test.go @@ -21,6 +21,7 @@ var mockFS = fs.Dir("", FName: "stat", FContents: "3 na R 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0", }, + fs.Dir("fd", fs.File{FName: "0"}, fs.File{FName: "1"}, fs.File{FName: "2"}), ), fs.Dir("2", fs.File{ @@ -31,6 +32,7 @@ var mockFS = fs.Dir("", FName: "stat", FContents: "2 na R 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0", }, + fs.Dir("fd", fs.File{FName: "1"}, fs.File{FName: "2"}), ), fs.Dir("4", fs.File{ @@ -41,6 +43,7 @@ var mockFS = fs.Dir("", FName: "stat", FContents: "4 na R 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0", }, + fs.Dir("fd", fs.File{FName: "0"}), ), fs.Dir("notapid"), fs.Dir("1", @@ -52,6 +55,7 @@ var mockFS = fs.Dir("", FName: "stat", FContents: "1 na R 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0", }, + fs.Dir("fd"), ), ), ) @@ -61,10 +65,10 @@ func TestWalker(t *testing.T) { defer fs_hook.Restore() want := map[int]process.Process{ - 3: {PID: 3, PPID: 2, Name: "curl", Cmdline: "curl google.com", Threads: 1}, - 2: {PID: 2, PPID: 1, Name: "bash", Cmdline: "bash", Threads: 1}, - 4: {PID: 4, PPID: 3, Name: "apache", Cmdline: "apache", Threads: 1}, - 1: {PID: 1, PPID: 0, Name: "init", Cmdline: "init", Threads: 1}, + 3: {PID: 3, PPID: 2, Name: "curl", Cmdline: "curl google.com", Threads: 1, OpenFilesCount: 3}, + 2: {PID: 2, PPID: 1, Name: "bash", Cmdline: "bash", Threads: 1, OpenFilesCount: 2}, + 4: {PID: 4, PPID: 3, Name: "apache", Cmdline: "apache", Threads: 1, OpenFilesCount: 1}, + 1: {PID: 1, PPID: 0, Name: "init", Cmdline: "init", Threads: 1, OpenFilesCount: 0}, } have := map[int]process.Process{} diff --git a/render/detailed/labels.go b/render/detailed/labels.go index bf46bb578..0a5fd4735 100644 --- a/render/detailed/labels.go +++ b/render/detailed/labels.go @@ -42,6 +42,7 @@ var labels = map[string]string{ process.CPUUsage: "CPU", process.Cmdline: "Command", process.MemoryUsage: "Memory", + process.OpenFilesCount: "Open Files", process.PID: "PID", process.PPID: "Parent PID", process.Threads: "# Threads", diff --git a/render/detailed/metrics.go b/render/detailed/metrics.go index f84d2bf00..4d764f4ad 100644 --- a/render/detailed/metrics.go +++ b/render/detailed/metrics.go @@ -13,6 +13,7 @@ import ( const ( defaultFormat = "" filesizeFormat = "filesize" + integerFormat = "integer" percentFormat = "percent" ) @@ -20,6 +21,7 @@ var ( processNodeMetrics = []MetricRow{ {ID: process.CPUUsage, Format: percentFormat}, {ID: process.MemoryUsage, Format: filesizeFormat}, + {ID: process.OpenFilesCount, Format: integerFormat}, } containerNodeMetrics = []MetricRow{ {ID: docker.CPUTotalUsage, Format: percentFormat},