From 6a69471bc2924c1b57e813cb2f0294a5f68df807 Mon Sep 17 00:00:00 2001 From: stuart nelson Date: Wed, 14 Nov 2018 18:40:07 +0100 Subject: [PATCH] [promtool] Support writing output as json (#4848) * Support writing output as json Oftentimes I'll want to execute something based on the output from promtool, and supporting json makes it easy to pull out values with a supporting tool such as jq. Signed-off-by: stuart nelson --- cmd/promtool/main.go | 77 ++++++++++++++++++++++++++++++--------- cmd/promtool/main_test.go | 5 ++- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go index 16198eb1cd..6033219933 100644 --- a/cmd/promtool/main.go +++ b/cmd/promtool/main.go @@ -15,6 +15,7 @@ package main import ( "context" + "encoding/json" "fmt" "math" "net/url" @@ -29,6 +30,7 @@ import ( "github.com/prometheus/client_golang/api" "github.com/prometheus/client_golang/api/prometheus/v1" config_util "github.com/prometheus/common/config" + "github.com/prometheus/common/model" "github.com/prometheus/common/version" "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/pkg/rulefmt" @@ -57,6 +59,7 @@ func main() { checkMetricsCmd := checkCmd.Command("metrics", checkMetricsUsage) queryCmd := app.Command("query", "Run query against a Prometheus server.") + queryCmdFmt := queryCmd.Flag("format", "Output format of the query.").Short('o').Default("promql").Enum("promql", "json") queryInstantCmd := queryCmd.Command("instant", "Run instant query.") queryServer := queryInstantCmd.Arg("server", "Prometheus server to query.").Required().String() queryExpr := queryInstantCmd.Arg("expr", "PromQL query expression.").Required().String() @@ -93,7 +96,17 @@ func main() { "The unit test file.", ).Required().ExistingFiles() - switch kingpin.MustParse(app.Parse(os.Args[1:])) { + parsedCmd := kingpin.MustParse(app.Parse(os.Args[1:])) + + var p printer + switch *queryCmdFmt { + case "json": + p = &jsonPrinter{} + case "promql": + p = &promqlPrinter{} + } + + switch parsedCmd { case checkConfigCmd.FullCommand(): os.Exit(CheckConfig(*configFiles...)) @@ -104,13 +117,13 @@ func main() { os.Exit(CheckMetrics()) case queryInstantCmd.FullCommand(): - os.Exit(QueryInstant(*queryServer, *queryExpr)) + os.Exit(QueryInstant(*queryServer, *queryExpr, p)) case queryRangeCmd.FullCommand(): - os.Exit(QueryRange(*queryRangeServer, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep)) + os.Exit(QueryRange(*queryRangeServer, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep, p)) case querySeriesCmd.FullCommand(): - os.Exit(QuerySeries(*querySeriesServer, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd)) + os.Exit(QuerySeries(*querySeriesServer, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd, p)) case debugPprofCmd.FullCommand(): os.Exit(debugPprof(*debugPprofServer)) @@ -122,7 +135,7 @@ func main() { os.Exit(debugAll(*debugAllServer)) case queryLabelsCmd.FullCommand(): - os.Exit(QueryLabels(*queryLabelsServer, *queryLabelsName)) + os.Exit(QueryLabels(*queryLabelsServer, *queryLabelsName, p)) case testRulesCmd.FullCommand(): os.Exit(RulesUnitTest(*testRulesFiles...)) @@ -316,7 +329,7 @@ func CheckMetrics() int { } // QueryInstant performs an instant query against a Prometheus server. -func QueryInstant(url string, query string) int { +func QueryInstant(url, query string, p printer) int { config := api.Config{ Address: url, } @@ -339,13 +352,13 @@ func QueryInstant(url string, query string) int { return 1 } - fmt.Println(val.String()) + p.printValue(val) return 0 } // QueryRange performs a range query against a Prometheus server. -func QueryRange(url, query, start, end string, step time.Duration) int { +func QueryRange(url, query, start, end string, step time.Duration, p printer) int { config := api.Config{ Address: url, } @@ -400,12 +413,12 @@ func QueryRange(url, query, start, end string, step time.Duration) int { return 1 } - fmt.Println(val.String()) + p.printValue(val) return 0 } // QuerySeries queries for a series against a Prometheus server. -func QuerySeries(url *url.URL, matchers []string, start string, end string) int { +func QuerySeries(url *url.URL, matchers []string, start, end string, p printer) int { config := api.Config{ Address: url.String(), } @@ -454,14 +467,12 @@ func QuerySeries(url *url.URL, matchers []string, start string, end string) int return 1 } - for _, v := range val { - fmt.Println(v) - } + p.printSeries(val) return 0 } // QueryLabels queries for label values against a Prometheus server. -func QueryLabels(url *url.URL, name string) int { +func QueryLabels(url *url.URL, name string, p printer) int { config := api.Config{ Address: url.String(), } @@ -484,9 +495,7 @@ func QueryLabels(url *url.URL, name string) int { return 1 } - for _, v := range val { - fmt.Println(v) - } + p.printLabelValues(val) return 0 } @@ -557,3 +566,37 @@ func debugAll(url string) int { } return w.Write() } + +type printer interface { + printValue(v model.Value) + printSeries(v []model.LabelSet) + printLabelValues(v model.LabelValues) +} + +type promqlPrinter struct{} + +func (p *promqlPrinter) printValue(v model.Value) { + fmt.Println(v) +} +func (p *promqlPrinter) printSeries(val []model.LabelSet) { + for _, v := range val { + fmt.Println(v) + } +} +func (j *promqlPrinter) printLabelValues(val model.LabelValues) { + for _, v := range val { + fmt.Println(v) + } +} + +type jsonPrinter struct{} + +func (j *jsonPrinter) printValue(v model.Value) { + json.NewEncoder(os.Stdout).Encode(v) +} +func (j *jsonPrinter) printSeries(v []model.LabelSet) { + json.NewEncoder(os.Stdout).Encode(v) +} +func (j *jsonPrinter) printLabelValues(v model.LabelValues) { + json.NewEncoder(os.Stdout).Encode(v) +} diff --git a/cmd/promtool/main_test.go b/cmd/promtool/main_test.go index ae71a3f63f..8824fba7be 100644 --- a/cmd/promtool/main_test.go +++ b/cmd/promtool/main_test.go @@ -26,7 +26,8 @@ func TestQueryRange(t *testing.T) { s, getURL := mockServer(200, `{"status": "success", "data": {"resultType": "matrix", "result": []}}`) defer s.Close() - exitCode := QueryRange(s.URL, "up", "0", "300", 0) + p := &promqlPrinter{} + exitCode := QueryRange(s.URL, "up", "0", "300", 0, p) expectedPath := "/api/v1/query_range" if getURL().Path != expectedPath { t.Errorf("unexpected URL path %s (wanted %s)", getURL().Path, expectedPath) @@ -43,7 +44,7 @@ func TestQueryRange(t *testing.T) { t.Error() } - exitCode = QueryRange(s.URL, "up", "0", "300", 10*time.Millisecond) + exitCode = QueryRange(s.URL, "up", "0", "300", 10*time.Millisecond, p) if getURL().Path != expectedPath { t.Errorf("unexpected URL path %s (wanted %s)", getURL().Path, expectedPath) }