From 5bb3d4929724dea53796848096cc1c8c8061d96b Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 12 Feb 2016 17:05:54 +0000 Subject: [PATCH 1/2] gather file descriptors as process metric --- client/app/scripts/utils/string-utils.js | 7 +++++++ probe/process/reporter.go | 16 ++++++++------- probe/process/walker.go | 13 ++++++------ probe/process/walker_linux.go | 25 +++++++++++++++++------- probe/process/walker_linux_test.go | 12 ++++++++---- render/detailed/labels.go | 1 + render/detailed/metrics.go | 2 ++ 7 files changed, 52 insertions(+), 24 deletions(-) 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..77f860949 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" + FileDescriptors = "file_descriptors" ) // 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(FileDescriptors, report.MakeMetric().Add(now, float64(p.FileDescriptors))) t.AddNode(nodeID, node) }) diff --git a/probe/process/walker.go b/probe/process/walker.go index 31e0d445d..1eac4ab7a 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 + FileDescriptors 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..8f98b3d42 100644 --- a/probe/process/walker_linux.go +++ b/probe/process/walker_linux.go @@ -62,6 +62,11 @@ func readStats(path string) (ppid, threads int, jiffies, rss uint64, err error) return } +func readFileDescriptors(path string) (int, error) { + names, err := fs.ReadDirNames(path) + return len(names), err +} + // Walk walks the supplied directory (expecting it to look like /proc) // and marshalls the files into instances of Process, which it then // passes one-by-one to the supplied function. Walk is only made public @@ -83,6 +88,11 @@ func (w *walker) Walk(f func(Process, Process)) error { continue } + fileDescriptors, err := readFileDescriptors(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 +106,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, + FileDescriptors: fileDescriptors, }, Process{}) } diff --git a/probe/process/walker_linux_test.go b/probe/process/walker_linux_test.go index d9ea0c06a..d946e1f21 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, FileDescriptors: 3}, + 2: {PID: 2, PPID: 1, Name: "bash", Cmdline: "bash", Threads: 1, FileDescriptors: 2}, + 4: {PID: 4, PPID: 3, Name: "apache", Cmdline: "apache", Threads: 1, FileDescriptors: 1}, + 1: {PID: 1, PPID: 0, Name: "init", Cmdline: "init", Threads: 1, FileDescriptors: 0}, } have := map[int]process.Process{} diff --git a/render/detailed/labels.go b/render/detailed/labels.go index bf46bb578..8475ba5d5 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.FileDescriptors: "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..913928330 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.FileDescriptors, Format: integerFormat}, } containerNodeMetrics = []MetricRow{ {ID: docker.CPUTotalUsage, Format: percentFormat}, From 44dada25f20ca8e9dd63123fb0d620395c11bd04 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 16 Feb 2016 10:37:25 +0000 Subject: [PATCH 2/2] Review Feedback --- probe/process/reporter.go | 18 +++++++++--------- probe/process/walker.go | 14 +++++++------- probe/process/walker_linux.go | 23 +++++++++-------------- probe/process/walker_linux_test.go | 8 ++++---- render/detailed/labels.go | 2 +- render/detailed/metrics.go | 2 +- 6 files changed, 31 insertions(+), 36 deletions(-) diff --git a/probe/process/reporter.go b/probe/process/reporter.go index 77f860949..15042cb3d 100644 --- a/probe/process/reporter.go +++ b/probe/process/reporter.go @@ -9,14 +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" - FileDescriptors = "file_descriptors" + 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. @@ -85,7 +85,7 @@ func (r *Reporter) processTopology() (report.Topology, error) { } node = node.WithMetric(MemoryUsage, report.MakeMetric().Add(now, float64(p.RSSBytes))) - node = node.WithMetric(FileDescriptors, report.MakeMetric().Add(now, float64(p.FileDescriptors))) + 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 1eac4ab7a..5b1bc4869 100644 --- a/probe/process/walker.go +++ b/probe/process/walker.go @@ -4,13 +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 - FileDescriptors int + 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 8f98b3d42..80622bdbb 100644 --- a/probe/process/walker_linux.go +++ b/probe/process/walker_linux.go @@ -62,11 +62,6 @@ func readStats(path string) (ppid, threads int, jiffies, rss uint64, err error) return } -func readFileDescriptors(path string) (int, error) { - names, err := fs.ReadDirNames(path) - return len(names), err -} - // Walk walks the supplied directory (expecting it to look like /proc) // and marshalls the files into instances of Process, which it then // passes one-by-one to the supplied function. Walk is only made public @@ -88,7 +83,7 @@ func (w *walker) Walk(f func(Process, Process)) error { continue } - fileDescriptors, err := readFileDescriptors(path.Join(w.procRoot, filename, "fd")) + openFiles, err := fs.ReadDirNames(path.Join(w.procRoot, filename, "fd")) if err != nil { continue } @@ -106,14 +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, - FileDescriptors: fileDescriptors, + 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 d946e1f21..11656e39e 100644 --- a/probe/process/walker_linux_test.go +++ b/probe/process/walker_linux_test.go @@ -65,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, FileDescriptors: 3}, - 2: {PID: 2, PPID: 1, Name: "bash", Cmdline: "bash", Threads: 1, FileDescriptors: 2}, - 4: {PID: 4, PPID: 3, Name: "apache", Cmdline: "apache", Threads: 1, FileDescriptors: 1}, - 1: {PID: 1, PPID: 0, Name: "init", Cmdline: "init", Threads: 1, FileDescriptors: 0}, + 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 8475ba5d5..0a5fd4735 100644 --- a/render/detailed/labels.go +++ b/render/detailed/labels.go @@ -42,7 +42,7 @@ var labels = map[string]string{ process.CPUUsage: "CPU", process.Cmdline: "Command", process.MemoryUsage: "Memory", - process.FileDescriptors: "Open Files", + 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 913928330..4d764f4ad 100644 --- a/render/detailed/metrics.go +++ b/render/detailed/metrics.go @@ -21,7 +21,7 @@ var ( processNodeMetrics = []MetricRow{ {ID: process.CPUUsage, Format: percentFormat}, {ID: process.MemoryUsage, Format: filesizeFormat}, - {ID: process.FileDescriptors, Format: integerFormat}, + {ID: process.OpenFilesCount, Format: integerFormat}, } containerNodeMetrics = []MetricRow{ {ID: docker.CPUTotalUsage, Format: percentFormat},