From 592653d62b0b78a656cc12426fe6f1099db367fc Mon Sep 17 00:00:00 2001 From: Kamil Dziedzic Date: Wed, 17 Jan 2018 08:03:32 +0100 Subject: [PATCH] PMM-1920: Standard Metrics (#15) * PMM-1920: Standard Metrics * try to fix port allocation * not sure why tests sometimes fail, let's try increase timeouts * fix path * forgot about ssl:/ * fix passing port * PMM-1920 Expose standard metrics as HR only. --- mysqld_exporter.go | 36 ++++++------- mysqld_exporter_test.go | 116 +++++++++++++++++++++++++++++++--------- 2 files changed, 109 insertions(+), 43 deletions(-) diff --git a/mysqld_exporter.go b/mysqld_exporter.go index e4e3aa0e..c493e726 100644 --- a/mysqld_exporter.go +++ b/mysqld_exporter.go @@ -793,25 +793,25 @@ func main() { // https mux := http.NewServeMux() - reg := prometheus.NewRegistry() - reg.MustRegister(NewExporter(dsn)) - handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + registryHr := prometheus.NewRegistry() + registryHr.MustRegister(NewExporter(dsn)) + handler := promhttp.HandlerFor(prometheus.Gatherers{prometheus.DefaultGatherer, registryHr}, promhttp.HandlerOpts{}) if cfg.User != "" && cfg.Password != "" { handler = &basicAuthHandler{handler: handler.ServeHTTP, user: cfg.User, password: cfg.Password} } mux.Handle(*metricPath+"-hr", handler) - reg = prometheus.NewRegistry() - reg.MustRegister(NewExporterMr(dsn)) - handler = promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + registryMr := prometheus.NewRegistry() + registryMr.MustRegister(NewExporterMr(dsn)) + handler = promhttp.HandlerFor(registryMr, promhttp.HandlerOpts{}) if cfg.User != "" && cfg.Password != "" { handler = &basicAuthHandler{handler: handler.ServeHTTP, user: cfg.User, password: cfg.Password} } mux.Handle(*metricPath+"-mr", handler) - reg = prometheus.NewRegistry() - reg.MustRegister(NewExporterLr(dsn)) - handler = promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + registryLr := prometheus.NewRegistry() + registryLr.MustRegister(NewExporterLr(dsn)) + handler = promhttp.HandlerFor(registryLr, promhttp.HandlerOpts{}) if cfg.User != "" && cfg.Password != "" { handler = &basicAuthHandler{handler: handler.ServeHTTP, user: cfg.User, password: cfg.Password} } @@ -841,25 +841,25 @@ func main() { log.Fatal(srv.ListenAndServeTLS(*sslCertFile, *sslKeyFile)) } else { // http - reg := prometheus.NewRegistry() - reg.MustRegister(NewExporter(dsn)) - handler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + registryHr := prometheus.NewRegistry() + registryHr.MustRegister(NewExporter(dsn)) + handler := promhttp.HandlerFor(prometheus.Gatherers{prometheus.DefaultGatherer, registryHr}, promhttp.HandlerOpts{}) if cfg.User != "" && cfg.Password != "" { handler = &basicAuthHandler{handler: handler.ServeHTTP, user: cfg.User, password: cfg.Password} } http.Handle(*metricPath+"-hr", handler) - reg = prometheus.NewRegistry() - reg.MustRegister(NewExporterMr(dsn)) - handler = promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + registryMr := prometheus.NewRegistry() + registryMr.MustRegister(NewExporterMr(dsn)) + handler = promhttp.HandlerFor(registryMr, promhttp.HandlerOpts{}) if cfg.User != "" && cfg.Password != "" { handler = &basicAuthHandler{handler: handler.ServeHTTP, user: cfg.User, password: cfg.Password} } http.Handle(*metricPath+"-mr", handler) - reg = prometheus.NewRegistry() - reg.MustRegister(NewExporterLr(dsn)) - handler = promhttp.HandlerFor(reg, promhttp.HandlerOpts{}) + registryLr := prometheus.NewRegistry() + registryLr.MustRegister(NewExporterLr(dsn)) + handler = promhttp.HandlerFor(registryLr, promhttp.HandlerOpts{}) if cfg.User != "" && cfg.Password != "" { handler = &basicAuthHandler{handler: handler.ServeHTTP, user: cfg.User, password: cfg.Password} } diff --git a/mysqld_exporter_test.go b/mysqld_exporter_test.go index 5c9ccd7b..812a1816 100644 --- a/mysqld_exporter_test.go +++ b/mysqld_exporter_test.go @@ -145,7 +145,8 @@ func TestGetMySQLVersion(t *testing.T) { } type binData struct { - bin string + bin string + port int } func TestBin(t *testing.T) { @@ -189,17 +190,22 @@ func TestBin(t *testing.T) { t.Fatalf("Failed to build: %s", err) } - data := binData{ - bin: bin, - } tests := []func(*testing.T, binData){ testLandingPage, testVersion, + testDefaultGatherer, } + + portStart := 56000 t.Run(binName, func(t *testing.T) { for _, f := range tests { f := f // capture range variable fName := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() + portStart++ + data := binData{ + bin: bin, + port: portStart, + } t.Run(fName, func(t *testing.T) { t.Parallel() f(t, data) @@ -216,6 +222,7 @@ func testVersion(t *testing.T, data binData) { ctx, data.bin, "--version", + "--web.listen-address", fmt.Sprintf(":%d", data.port), ) b := &bytes.Buffer{} @@ -271,6 +278,7 @@ func testLandingPage(t *testing.T, data binData) { cmd := exec.CommandContext( ctx, data.bin, + "--web.listen-address", fmt.Sprintf(":%d", data.port), ) cmd.Env = append(os.Environ(), "DATA_SOURCE_NAME=127.0.0.1:3306") @@ -281,13 +289,75 @@ func testLandingPage(t *testing.T, data binData) { defer cmd.Process.Kill() // Get the main page, but we need to wait a bit for http server - var resp *http.Response - var err error - for i := 0; i <= 10; i++ { + body, err := get(fmt.Sprintf("http://127.0.0.1:%d", data.port)) + if err != nil { + t.Fatal(err) + } + got := string(body) + + expected := ` +MySQLd 3-in-1 exporter + +

MySQL 3-in-1 exporter

+
  • high-res metrics
  • +
  • medium-res metrics
  • +
  • low-res metrics
  • + + +` + if got != expected { + t.Fatalf("got '%s' but expected '%s'", got, expected) + } +} + +func testDefaultGatherer(t *testing.T, data binData) { + metricPath := "/metrics" + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + cmd := exec.CommandContext( + ctx, + data.bin, + "--web.telemetry-path", metricPath, + "--web.listen-address", fmt.Sprintf(":%d", data.port), + ) + cmd.Env = append(os.Environ(), "DATA_SOURCE_NAME=127.0.0.1:3306") + + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + defer cmd.Wait() + defer cmd.Process.Kill() + + const resolution = "hr" + body, err := get(fmt.Sprintf("http://127.0.0.1:%d%s-%s", data.port, metricPath, resolution)) + if err != nil { + t.Fatalf("unable to get metrics for '%s' resolution: %s", resolution, err) + } + got := string(body) + + metricsPrefixes := []string{ + "go_gc_duration_seconds", + "go_goroutines", + "go_memstats", + } + + for _, prefix := range metricsPrefixes { + if !strings.Contains(got, prefix) { + t.Fatalf("no metric starting with %s in resolution %s", prefix, resolution) + } + } +} + +func get(urlToGet string) (body []byte, err error) { + tries := 60 + + // Get data, but we need to wait a bit for http server + for i := 0; i <= tries; i++ { // Try to get main page - resp, err = http.Get("http://127.0.0.1:9104") + body, err = getBody(urlToGet) if err == nil { - break + return body, err } // If there is a syscall.ECONNREFUSED error (web server not available) then retry @@ -302,27 +372,23 @@ func testLandingPage(t *testing.T, data binData) { } } - t.Fatalf("%#v", err) + return nil, err + } + + return nil, fmt.Errorf("failed to GET %s: %s", urlToGet, err) +} + +func getBody(urlToGet string) ([]byte, error) { + resp, err := http.Get(urlToGet) + if err != nil { + return nil, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - t.Fatal(err) + return nil, err } - got := string(body) - expected := ` -MySQLd 3-in-1 exporter - -

    MySQL 3-in-1 exporter

    -
  • high-res metrics
  • -
  • medium-res metrics
  • -
  • low-res metrics
  • - - -` - if got != expected { - t.Fatalf("got '%s' but expected '%s'", got, expected) - } + return body, nil }