aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--node_exporter.go10
-rw-r--r--node_exporter_test.go132
2 files changed, 93 insertions, 49 deletions
diff --git a/node_exporter.go b/node_exporter.go
index 1a505cf..1028ed8 100644
--- a/node_exporter.go
+++ b/node_exporter.go
@@ -158,10 +158,16 @@ func main() {
158 log.Infof(" - %s", n) 158 log.Infof(" - %s", n)
159 } 159 }
160 160
161 prometheus.MustRegister(NodeCollector{collectors: collectors}) 161 if err := prometheus.Register(NodeCollector{collectors: collectors}); err != nil {
162 log.Fatalf("Couldn't register collector: %s", err)
163 }
162 handler := promhttp.HandlerFor(prometheus.DefaultGatherer, 164 handler := promhttp.HandlerFor(prometheus.DefaultGatherer,
163 promhttp.HandlerOpts{ErrorLog: log.NewErrorLogger()}) 165 promhttp.HandlerOpts{
166 ErrorLog: log.NewErrorLogger(),
167 ErrorHandling: promhttp.ContinueOnError,
168 })
164 169
170 // TODO(ts): Remove deprecated and problematic InstrumentHandler usage.
165 http.Handle(*metricsPath, prometheus.InstrumentHandler("prometheus", handler)) 171 http.Handle(*metricsPath, prometheus.InstrumentHandler("prometheus", handler))
166 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 172 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
167 w.Write([]byte(`<html> 173 w.Write([]byte(`<html>
diff --git a/node_exporter_test.go b/node_exporter_test.go
index 4ff0caa..d1830ec 100644
--- a/node_exporter_test.go
+++ b/node_exporter_test.go
@@ -2,21 +2,23 @@ package main
2 2
3import ( 3import (
4 "fmt" 4 "fmt"
5 "io/ioutil"
5 "net/http" 6 "net/http"
6 "os" 7 "os"
7 "os/exec" 8 "os/exec"
9 "path/filepath"
8 "testing" 10 "testing"
9 "time" 11 "time"
10 12
11 "github.com/prometheus/procfs" 13 "github.com/prometheus/procfs"
12) 14)
13 15
14func TestFileDescriptorLeak(t *testing.T) { 16const (
15 const ( 17 binary = "./node_exporter"
16 binary = "./node_exporter" 18 address = "localhost:19100"
17 address = "localhost:9100" 19)
18 )
19 20
21func TestFileDescriptorLeak(t *testing.T) {
20 if _, err := os.Stat(binary); err != nil { 22 if _, err := os.Stat(binary); err != nil {
21 t.Skipf("node_exporter binary not available, try to run `make build` first: %s", err) 23 t.Skipf("node_exporter binary not available, try to run `make build` first: %s", err)
22 } 24 }
@@ -24,75 +26,111 @@ func TestFileDescriptorLeak(t *testing.T) {
24 t.Skipf("proc filesystem is not available, but currently required to read number of open file descriptors: %s", err) 26 t.Skipf("proc filesystem is not available, but currently required to read number of open file descriptors: %s", err)
25 } 27 }
26 28
27 errc := make(chan error)
28 exporter := exec.Command(binary, "-web.listen-address", address) 29 exporter := exec.Command(binary, "-web.listen-address", address)
29 go func() { 30 test := func(pid int) error {
30 if err := exporter.Run(); err != nil { 31 if err := queryExporter(address); err != nil {
31 errc <- fmt.Errorf("execution of node_exporter failed: %s", err) 32 return err
32 } else {
33 errc <- nil
34 }
35 }()
36
37 select {
38 case err := <-errc:
39 t.Fatal(err)
40 case <-time.After(100 * time.Millisecond):
41 }
42
43 go func(pid int, url string) {
44 if err := queryExporter(url); err != nil {
45 errc <- err
46 return
47 } 33 }
48 proc, err := procfs.NewProc(pid) 34 proc, err := procfs.NewProc(pid)
49 if err != nil { 35 if err != nil {
50 errc <- err 36 return err
51 return
52 } 37 }
53 fdsBefore, err := proc.FileDescriptors() 38 fdsBefore, err := proc.FileDescriptors()
54 if err != nil { 39 if err != nil {
55 errc <- err 40 return err
56 return
57 } 41 }
58 for i := 0; i < 5; i++ { 42 for i := 0; i < 5; i++ {
59 if err := queryExporter(url); err != nil { 43 if err := queryExporter(address); err != nil {
60 errc <- err 44 return err
61 return
62 } 45 }
63 } 46 }
64 fdsAfter, err := proc.FileDescriptors() 47 fdsAfter, err := proc.FileDescriptors()
65 if err != nil { 48 if err != nil {
66 errc <- err 49 return err
67 return
68 } 50 }
69 if want, have := len(fdsBefore), len(fdsAfter); want != have { 51 if want, have := len(fdsBefore), len(fdsAfter); want != have {
70 errc <- fmt.Errorf("want %d open file descriptors after metrics scrape, have %d", want, have) 52 return fmt.Errorf("want %d open file descriptors after metrics scrape, have %d", want, have)
71 } 53 }
72 errc <- nil 54 return nil
73 }(exporter.Process.Pid, fmt.Sprintf("http://%s/metrics", address)) 55 }
74 56
75 select { 57 if err := runCommandAndTests(exporter, test); err != nil {
76 case err := <-errc: 58 t.Error(err)
77 if exporter.Process != nil {
78 exporter.Process.Kill()
79 }
80 if err != nil {
81 t.Fatal(err)
82 }
83 } 59 }
84} 60}
85 61
86func queryExporter(url string) error { 62func TestHandlingOfDuplicatedMetrics(t *testing.T) {
87 resp, err := http.Get(url) 63 if _, err := os.Stat(binary); err != nil {
64 t.Skipf("node_exporter binary not available, try to run `make build` first: %s", err)
65 }
66
67 dir, err := ioutil.TempDir("", "node-exporter")
68 if err != nil {
69 t.Fatal(err)
70 }
71 defer os.RemoveAll(dir)
72
73 content := []byte("dummy_metric 1\n")
74 if err := ioutil.WriteFile(filepath.Join(dir, "a.prom"), content, 0600); err != nil {
75 t.Fatal(err)
76 }
77 if err := ioutil.WriteFile(filepath.Join(dir, "b.prom"), content, 0600); err != nil {
78 t.Fatal(err)
79 }
80
81 exporter := exec.Command(binary, "-web.listen-address", address, "-collector.textfile.directory", dir)
82 test := func(_ int) error {
83 return queryExporter(address)
84 }
85
86 if err := runCommandAndTests(exporter, test); err != nil {
87 t.Error(err)
88 }
89}
90
91func queryExporter(address string) error {
92 resp, err := http.Get(fmt.Sprintf("http://%s/metrics", address))
88 if err != nil { 93 if err != nil {
89 return err 94 return err
90 } 95 }
91 if err := resp.Body.Close(); err != nil { 96 if err := resp.Body.Close(); err != nil {
92 return err 97 return err
93 } 98 }
94 if want, have := resp.StatusCode, http.StatusOK; want != have { 99 if want, have := http.StatusOK, resp.StatusCode; want != have {
95 return fmt.Errorf("want /metrics status code %d, have %d", want, have) 100 return fmt.Errorf("want /metrics status code %d, have %d", want, have)
96 } 101 }
97 return nil 102 return nil
98} 103}
104
105func runCommandAndTests(cmd *exec.Cmd, fn func(pid int) error) error {
106 errc := make(chan error)
107 go func() {
108 if err := cmd.Run(); err != nil {
109 errc <- fmt.Errorf("execution of command failed: %s", err)
110 } else {
111 errc <- nil
112 }
113 }()
114
115 // Allow the process to start before running any tests.
116 select {
117 case err := <-errc:
118 return err
119 case <-time.After(100 * time.Millisecond):
120 }
121
122 go func(pid int) {
123 errc <- fn(pid)
124 }(cmd.Process.Pid)
125
126 select {
127 case err := <-errc:
128 if cmd.Process != nil {
129 cmd.Process.Kill()
130 }
131 if err != nil {
132 return err
133 }
134 }
135 return nil
136}