From a71f4bbcff338d246f0b6e7d1e427eabeb46b11c Mon Sep 17 00:00:00 2001 From: Kamil Dziedzic Date: Thu, 1 Feb 2018 18:33:24 +0100 Subject: [PATCH] Broken landing page, doesn't include metric path. (#264) * Broken landing page, doesn't include metric path. * Fix landing page. --- collector/perf_schema_file_instances_test.go | 2 +- mysqld_exporter.go | 22 +-- mysqld_exporter_test.go | 169 +++++++++++++++++++ 3 files changed, 181 insertions(+), 12 deletions(-) diff --git a/collector/perf_schema_file_instances_test.go b/collector/perf_schema_file_instances_test.go index 9b14c95..932fdb7 100644 --- a/collector/perf_schema_file_instances_test.go +++ b/collector/perf_schema_file_instances_test.go @@ -1,9 +1,9 @@ package collector import ( + "fmt" "testing" - "fmt" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/smartystreets/goconvey/convey" diff --git a/mysqld_exporter.go b/mysqld_exporter.go index c03355c..07532f0 100644 --- a/mysqld_exporter.go +++ b/mysqld_exporter.go @@ -132,17 +132,6 @@ var ( dsn string ) -// landingPage contains the HTML served at '/'. -// TODO: Make this nicer and more informative. -var landingPage = []byte(` -MySQLd exporter - -

MySQLd exporter

-

Metrics

- - -`) - func parseMycnf(config interface{}) (string, error) { var dsn string cfg, err := ini.Load(config) @@ -235,6 +224,17 @@ func main() { kingpin.HelpFlag.Short('h') kingpin.Parse() + // landingPage contains the HTML served at '/'. + // TODO: Make this nicer and more informative. + var landingPage = []byte(` +MySQLd exporter + +

MySQLd exporter

+

Metrics

+ + +`) + log.Infoln("Starting mysqld_exporter", version.Info()) log.Infoln("Build context", version.BuildContext()) diff --git a/mysqld_exporter_test.go b/mysqld_exporter_test.go index be99683..5156e76 100644 --- a/mysqld_exporter_test.go +++ b/mysqld_exporter_test.go @@ -1,7 +1,20 @@ package main import ( + "context" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "os/exec" + "reflect" + "runtime" + "strings" + "syscall" "testing" + "time" "github.com/smartystreets/goconvey/convey" ) @@ -98,3 +111,159 @@ func TestParseMycnf(t *testing.T) { }) }) } + +// bin stores information about path of executable and attached port +type bin struct { + path string + port int +} + +// TestBin builds, runs and tests binary. +func TestBin(t *testing.T) { + var err error + binName := "mysqld_exporter" + + binDir, err := ioutil.TempDir("/tmp", binName+"-test-bindir-") + if err != nil { + t.Fatal(err) + } + defer func() { + err := os.RemoveAll(binDir) + if err != nil { + t.Fatal(err) + } + }() + + importpath := "github.com/prometheus/mysqld_exporter/vendor/github.com/prometheus/common" + path := binDir + "/" + binName + xVariables := map[string]string{ + importpath + "/version.Version": "gotest-version", + importpath + "/version.Branch": "gotest-branch", + importpath + "/version.Revision": "gotest-revision", + } + var ldflags []string + for x, value := range xVariables { + ldflags = append(ldflags, fmt.Sprintf("-X %s=%s", x, value)) + } + cmd := exec.Command( + "go", + "build", + "-o", + path, + "-ldflags", + strings.Join(ldflags, " "), + ) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + t.Fatalf("Failed to build: %s", err) + } + + tests := []func(*testing.T, bin){ + testLandingPage, + } + + 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 := bin{ + path: path, + port: portStart, + } + t.Run(fName, func(t *testing.T) { + t.Parallel() + f(t, data) + }) + } + }) +} + +func testLandingPage(t *testing.T, data bin) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // Run exporter. + cmd := exec.CommandContext( + ctx, + data.path, + "--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() + + // Get the main page. + urlToGet := fmt.Sprintf("http://127.0.0.1:%d", data.port) + body, err := waitForBody(urlToGet) + if err != nil { + t.Fatal(err) + } + got := string(body) + + expected := ` +MySQLd exporter + +

MySQLd exporter

+

Metrics

+ + +` + if got != expected { + t.Fatalf("got '%s' but expected '%s'", got, expected) + } +} + +// waitForBody is a helper function which makes http calls until http server is up +// and then returns body of the successful call. +func waitForBody(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 web page. + body, err = getBody(urlToGet) + if err == nil { + return body, err + } + + // If there is a syscall.ECONNREFUSED error (web server not available) then retry. + if urlError, ok := err.(*url.Error); ok { + if opError, ok := urlError.Err.(*net.OpError); ok { + if osSyscallError, ok := opError.Err.(*os.SyscallError); ok { + if osSyscallError.Err == syscall.ECONNREFUSED { + time.Sleep(1 * time.Second) + continue + } + } + } + } + + // There was an error, and it wasn't syscall.ECONNREFUSED. + return nil, err + } + + return nil, fmt.Errorf("failed to GET %s after %d tries: %s", urlToGet, tries, err) +} + +// getBody is a helper function which retrieves http body from given address. +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 { + return nil, err + } + + return body, nil +}