From 1e793a1c524179e1d4503ccda1a29dc95360e10e Mon Sep 17 00:00:00 2001 From: Michael Baird Date: Mon, 12 Jun 2017 13:16:19 -0500 Subject: [PATCH] Count open file handles as a StatsD metric. --- server.go | 31 +++++++++++++++++++++++++++++++ server/server_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/server.go b/server.go index f9719c563..7f2027f10 100644 --- a/server.go +++ b/server.go @@ -24,8 +24,10 @@ import ( "net/http" "net/url" "os" + "os/exec" "runtime" "strconv" + "strings" "sync" "time" @@ -486,6 +488,9 @@ func (s *Server) monitorRuntime() { // Record the number of go routines s.Holder.Stats.Gauge("goroutines", float64(runtime.NumGoroutine()), 1.0) + // Open File handles + s.Holder.Stats.Gauge("OpenFiles", float64(CountOpenFiles()), 1.0) + // Runtime memory metrics runtime.ReadMemStats(&m) s.Holder.Stats.Gauge("HeapAlloc", float64(m.HeapAlloc), 1.0) @@ -496,6 +501,32 @@ func (s *Server) monitorRuntime() { } } +// CountOpenFiles on opperating systems that support lsof +func CountOpenFiles() int { + count := 0 + + switch runtime.GOOS { + case "darwin": + fallthrough + case "linux": + fallthrough + case "unix": + fallthrough + case "freebsd": + out, err := exec.Command("/bin/sh", "-c", fmt.Sprintf("lsof -p %v", os.Getpid())).Output() + if err != nil { + log.Fatal(err) + } + lines := strings.Split(string(out), "\n") + count = len(lines) - 1 + case "windows": + // TODO: count open file handles on windows + default: + + } + return count +} + // StatusHandler specifies two methods which an object must implement to share // state in the cluster. These are used by the GossipNodeSet to implement the // LocalState and MergeRemoteState methods of memberlist.Delegate diff --git a/server/server_test.go b/server/server_test.go index cd82ba007..f1ee11b23 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -25,7 +25,9 @@ import ( "net" "net/http" "os" + "path/filepath" "reflect" + "runtime" "sort" "strconv" "strings" @@ -372,6 +374,42 @@ path = "/path/to/plugins" } } +// tempMkdir makes a temporary directory +func tempMkdir(t *testing.T) string { + dir, err := ioutil.TempDir("", "pilosatemp") + if err != nil { + t.Fatalf("failed to create test directory: %s", err) + } + return dir +} + +// Ensure the file handle count is working +func TestCountOpenFiles(t *testing.T) { + // Windows is not supported yet + supported := []string{"darwin", "linux", "unix", "freebsd"} + sort.Strings(supported) + i := sort.Search(len(supported), + func(i int) bool { return supported[i] >= runtime.GOOS }) + if i == len(supported) { + return + } + + // Create directory store temp file + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + count := pilosa.CountOpenFiles() + testFile := filepath.Join(testDir, "test.txt") + _, err := os.Create(testFile) + if err != nil { + t.Fatalf("create test file failed: %s", err) + } + + if pilosa.CountOpenFiles() < count+1 { + t.Error("Invalid open file handle count") + } +} + // Ensure program can send/receive broadcast messages. func TestMain_SendReceiveMessage(t *testing.T) { m0 := MustRunMain()