mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Merge 9eaa7cda7a
into 677efa4678
This commit is contained in:
commit
9dd0c5f683
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/google/pprof/profile"
|
||||
"github.com/prometheus/client_golang/api"
|
||||
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil/promlint"
|
||||
"github.com/prometheus/common/expfmt"
|
||||
|
@ -162,7 +163,7 @@ func main() {
|
|||
agentMode := checkConfigCmd.Flag("agent", "Check config file for Prometheus in Agent mode.").Bool()
|
||||
|
||||
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")
|
||||
queryCmdFmt := queryCmd.Flag("format", "Output format of the query: promql, json, unittest. Unittest output is experimental.").Short('o').Default("promql").Enum("promql", "json", "unittest")
|
||||
queryCmd.Flag("http.config.file", "HTTP client configuration file for promtool to connect to Prometheus.").PlaceHolder("<filename>").ExistingFileVar(&httpConfigFilePath)
|
||||
|
||||
queryInstantCmd := queryCmd.Command("instant", "Run instant query.")
|
||||
|
@ -310,12 +311,31 @@ func main() {
|
|||
|
||||
parsedCmd := kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||
|
||||
queryRange := v1.Range{}
|
||||
var err error
|
||||
|
||||
switch parsedCmd {
|
||||
case queryInstantCmd.FullCommand():
|
||||
queryRange, err = newQueryRange(*queryInstantTime, "", time.Second)
|
||||
case queryRangeCmd.FullCommand():
|
||||
queryRange, err = newQueryRange(*queryRangeBegin, *queryRangeEnd, *queryRangeStep)
|
||||
case querySeriesCmd.FullCommand():
|
||||
queryRange, err = newQueryRange(*querySeriesBegin, *querySeriesEnd, 0)
|
||||
case queryLabelsCmd.FullCommand():
|
||||
queryRange, err = newQueryRange(*queryLabelsBegin, *queryLabelsEnd, 0)
|
||||
}
|
||||
if err != nil {
|
||||
kingpin.Fatalf("Failed to parse query range: %v", err)
|
||||
}
|
||||
|
||||
var p printer
|
||||
switch *queryCmdFmt {
|
||||
case "json":
|
||||
p = &jsonPrinter{}
|
||||
case "promql":
|
||||
p = &promqlPrinter{}
|
||||
case "unittest":
|
||||
p = &unittestPrinter{Step: model.Duration(queryRange.Step)}
|
||||
}
|
||||
|
||||
if httpConfigFilePath != "" {
|
||||
|
@ -372,13 +392,13 @@ func main() {
|
|||
os.Exit(PushMetrics(remoteWriteURL, httpRoundTripper, *pushMetricsHeaders, *pushMetricsTimeout, *pushMetricsLabels, *metricFiles...))
|
||||
|
||||
case queryInstantCmd.FullCommand():
|
||||
os.Exit(QueryInstant(serverURL, httpRoundTripper, *queryInstantExpr, *queryInstantTime, p))
|
||||
os.Exit(QueryInstant(serverURL, httpRoundTripper, *queryInstantExpr, queryRange, p))
|
||||
|
||||
case queryRangeCmd.FullCommand():
|
||||
os.Exit(QueryRange(serverURL, httpRoundTripper, *queryRangeHeaders, *queryRangeExpr, *queryRangeBegin, *queryRangeEnd, *queryRangeStep, p))
|
||||
os.Exit(QueryRange(serverURL, httpRoundTripper, *queryRangeHeaders, *queryRangeExpr, queryRange, p))
|
||||
|
||||
case querySeriesCmd.FullCommand():
|
||||
os.Exit(QuerySeries(serverURL, httpRoundTripper, *querySeriesMatch, *querySeriesBegin, *querySeriesEnd, p))
|
||||
os.Exit(QuerySeries(serverURL, httpRoundTripper, *querySeriesMatch, queryRange, p))
|
||||
|
||||
case debugPprofCmd.FullCommand():
|
||||
os.Exit(debugPprof(*debugPprofServer))
|
||||
|
@ -390,7 +410,7 @@ func main() {
|
|||
os.Exit(debugAll(*debugAllServer))
|
||||
|
||||
case queryLabelsCmd.FullCommand():
|
||||
os.Exit(QueryLabels(serverURL, httpRoundTripper, *queryLabelsMatch, *queryLabelsName, *queryLabelsBegin, *queryLabelsEnd, p))
|
||||
os.Exit(QueryLabels(serverURL, httpRoundTripper, *queryLabelsMatch, *queryLabelsName, queryRange, p))
|
||||
|
||||
case testRulesCmd.FullCommand():
|
||||
results := io.Discard
|
||||
|
@ -1214,6 +1234,64 @@ func (j *jsonPrinter) printLabelValues(v model.LabelValues) {
|
|||
json.NewEncoder(os.Stdout).Encode(v)
|
||||
}
|
||||
|
||||
type unittestPrinter struct {
|
||||
Step model.Duration
|
||||
}
|
||||
|
||||
func (u *unittestPrinter) printValue(v model.Value) {
|
||||
samples := make(map[string][]string)
|
||||
|
||||
switch modelType := v.(type) {
|
||||
case model.Vector:
|
||||
|
||||
for _, samplePair := range modelType {
|
||||
metricName := samplePair.Metric.String()
|
||||
|
||||
if _, ok := samples[metricName]; !ok {
|
||||
samples[metricName] = []string{}
|
||||
}
|
||||
|
||||
if samplePair.Histogram != nil {
|
||||
panic("histograms are not supported")
|
||||
}
|
||||
|
||||
samples[metricName] = append(samples[metricName], strconv.FormatFloat(float64(samplePair.Value), 'f', -1, 64))
|
||||
}
|
||||
|
||||
case model.Matrix:
|
||||
for _, stream := range modelType {
|
||||
metricName := stream.Metric.String()
|
||||
|
||||
if _, ok := samples[metricName]; !ok {
|
||||
samples[metricName] = []string{}
|
||||
}
|
||||
|
||||
if len(stream.Histograms) > 0 {
|
||||
panic("histograms are not supported")
|
||||
}
|
||||
|
||||
for _, samplePair := range stream.Values {
|
||||
samples[metricName] = append(samples[metricName], strconv.FormatFloat(float64(samplePair.Value), 'f', -1, 64))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputSeries := make([]series, 0, len(samples))
|
||||
|
||||
for metric, value := range samples {
|
||||
inputSeries = append(inputSeries, series{Series: metric, Values: strings.Join(value, " ")})
|
||||
}
|
||||
|
||||
if err := yaml.NewEncoder(os.Stdout).Encode(unitTestFile{Tests: []testGroup{{Interval: u.Step, InputSeries: inputSeries}}}); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(failureExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unittestPrinter) printSeries(v []model.LabelSet) {}
|
||||
|
||||
func (u *unittestPrinter) printLabelValues(v model.LabelValues) {}
|
||||
|
||||
// importRules backfills recording rules from the files provided. The output are blocks of data
|
||||
// at the outputDir location.
|
||||
func importRules(url *url.URL, roundTripper http.RoundTripper, start, end, outputDir string, evalInterval, maxBlockDuration time.Duration, files ...string) error {
|
||||
|
|
|
@ -68,14 +68,17 @@ func TestQueryRange(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
p := &promqlPrinter{}
|
||||
exitCode := QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", "0", "300", 0, p)
|
||||
qr, err := newQueryRange("0", "300", 0)
|
||||
require.NoError(t, err)
|
||||
exitCode := QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", qr, p)
|
||||
require.Equal(t, "/api/v1/query_range", getRequest().URL.Path)
|
||||
form := getRequest().Form
|
||||
require.Equal(t, "up", form.Get("query"))
|
||||
require.Equal(t, "1", form.Get("step"))
|
||||
require.Equal(t, 0, exitCode)
|
||||
|
||||
exitCode = QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", "0", "300", 10*time.Millisecond, p)
|
||||
qr, err = newQueryRange("0", "300", 10*time.Millisecond)
|
||||
exitCode = QueryRange(urlObject, http.DefaultTransport, map[string]string{}, "up", qr, p)
|
||||
require.Equal(t, "/api/v1/query_range", getRequest().URL.Path)
|
||||
form = getRequest().Form
|
||||
require.Equal(t, "up", form.Get("query"))
|
||||
|
@ -92,7 +95,9 @@ func TestQueryInstant(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
p := &promqlPrinter{}
|
||||
exitCode := QueryInstant(urlObject, http.DefaultTransport, "up", "300", p)
|
||||
qr, err := newQueryRange("300", "", time.Second)
|
||||
require.NoError(t, err)
|
||||
exitCode := QueryInstant(urlObject, http.DefaultTransport, "up", qr, p)
|
||||
require.Equal(t, "/api/v1/query", getRequest().URL.Path)
|
||||
form := getRequest().Form
|
||||
require.Equal(t, "up", form.Get("query"))
|
||||
|
|
|
@ -61,25 +61,20 @@ func newAPI(url *url.URL, roundTripper http.RoundTripper, headers map[string]str
|
|||
}
|
||||
|
||||
// QueryInstant performs an instant query against a Prometheus server.
|
||||
func QueryInstant(url *url.URL, roundTripper http.RoundTripper, query, evalTime string, p printer) int {
|
||||
func QueryInstant(url *url.URL, roundTripper http.RoundTripper, query string, qr v1.Range, p printer) int {
|
||||
api, err := newAPI(url, roundTripper, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error creating API client:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
eTime := time.Now()
|
||||
if evalTime != "" {
|
||||
eTime, err = parseTime(evalTime)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error parsing evaluation time:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
if qr.Start.Equal(minTime) {
|
||||
qr.Start = time.Now()
|
||||
}
|
||||
|
||||
// Run query against client.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
val, _, err := api.Query(ctx, query, eTime) // Ignoring warnings for now.
|
||||
val, _, err := api.Query(ctx, query, qr.Start) // Ignoring warnings for now.
|
||||
cancel()
|
||||
if err != nil {
|
||||
return handleAPIError(err)
|
||||
|
@ -91,50 +86,24 @@ func QueryInstant(url *url.URL, roundTripper http.RoundTripper, query, evalTime
|
|||
}
|
||||
|
||||
// QueryRange performs a range query against a Prometheus server.
|
||||
func QueryRange(url *url.URL, roundTripper http.RoundTripper, headers map[string]string, query, start, end string, step time.Duration, p printer) int {
|
||||
func QueryRange(url *url.URL, roundTripper http.RoundTripper, headers map[string]string, query string, qr v1.Range, p printer) int {
|
||||
api, err := newAPI(url, roundTripper, headers)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error creating API client:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
var stime, etime time.Time
|
||||
|
||||
if end == "" {
|
||||
etime = time.Now()
|
||||
} else {
|
||||
etime, err = parseTime(end)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error parsing end time:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
if qr.End.Equal(maxTime) {
|
||||
qr.End = time.Now()
|
||||
}
|
||||
|
||||
if start == "" {
|
||||
stime = etime.Add(-5 * time.Minute)
|
||||
} else {
|
||||
stime, err = parseTime(start)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error parsing start time:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
}
|
||||
|
||||
if !stime.Before(etime) {
|
||||
fmt.Fprintln(os.Stderr, "start time is not before end time")
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
if step == 0 {
|
||||
resolution := math.Max(math.Floor(etime.Sub(stime).Seconds()/250), 1)
|
||||
// Convert seconds to nanoseconds such that time.Duration parses correctly.
|
||||
step = time.Duration(resolution) * time.Second
|
||||
if qr.Start.Equal(minTime) {
|
||||
qr.Start = qr.End.Add(-5 * time.Minute)
|
||||
}
|
||||
|
||||
// Run query against client.
|
||||
r := v1.Range{Start: stime, End: etime, Step: step}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
val, _, err := api.QueryRange(ctx, query, r) // Ignoring warnings for now.
|
||||
val, _, err := api.QueryRange(ctx, query, qr) // Ignoring warnings for now.
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
|
@ -146,22 +115,16 @@ func QueryRange(url *url.URL, roundTripper http.RoundTripper, headers map[string
|
|||
}
|
||||
|
||||
// QuerySeries queries for a series against a Prometheus server.
|
||||
func QuerySeries(url *url.URL, roundTripper http.RoundTripper, matchers []string, start, end string, p printer) int {
|
||||
func QuerySeries(url *url.URL, roundTripper http.RoundTripper, matchers []string, qr v1.Range, p printer) int {
|
||||
api, err := newAPI(url, roundTripper, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error creating API client:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
stime, etime, err := parseStartTimeAndEndTime(start, end)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
// Run query against client.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
val, _, err := api.Series(ctx, matchers, stime, etime) // Ignoring warnings for now.
|
||||
val, _, err := api.Series(ctx, matchers, qr.Start, qr.End) // Ignoring warnings for now.
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
|
@ -173,22 +136,16 @@ func QuerySeries(url *url.URL, roundTripper http.RoundTripper, matchers []string
|
|||
}
|
||||
|
||||
// QueryLabels queries for label values against a Prometheus server.
|
||||
func QueryLabels(url *url.URL, roundTripper http.RoundTripper, matchers []string, name, start, end string, p printer) int {
|
||||
func QueryLabels(url *url.URL, roundTripper http.RoundTripper, matchers []string, name string, qr v1.Range, p printer) int {
|
||||
api, err := newAPI(url, roundTripper, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error creating API client:", err)
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
stime, etime, err := parseStartTimeAndEndTime(start, end)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return failureExitCode
|
||||
}
|
||||
|
||||
// Run query against client.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
val, warn, err := api.LabelValues(ctx, name, matchers, stime, etime)
|
||||
val, warn, err := api.LabelValues(ctx, name, matchers, qr.Start, qr.End)
|
||||
cancel()
|
||||
|
||||
for _, v := range warn {
|
||||
|
@ -213,12 +170,31 @@ func handleAPIError(err error) int {
|
|||
return failureExitCode
|
||||
}
|
||||
|
||||
func newQueryRange(start, end string, step time.Duration) (v1.Range, error) {
|
||||
qr := v1.Range{}
|
||||
var err error
|
||||
qr.Start, qr.End, err = parseStartTimeAndEndTime(start, end)
|
||||
|
||||
if !qr.Start.Before(qr.End) {
|
||||
return qr, errors.New("start time is not before end time")
|
||||
}
|
||||
|
||||
if step == 0 {
|
||||
resolution := math.Max(math.Floor(qr.End.Sub(qr.Start).Seconds()/250), 1)
|
||||
// Convert seconds to nanoseconds such that time.Duration parses correctly.
|
||||
step = time.Duration(resolution) * time.Second
|
||||
}
|
||||
|
||||
qr.Step = step
|
||||
|
||||
return qr, err
|
||||
}
|
||||
|
||||
var minTime = time.Now().Add(-9999 * time.Hour)
|
||||
var maxTime = time.Now().Add(9999 * time.Hour)
|
||||
|
||||
func parseStartTimeAndEndTime(start, end string) (time.Time, time.Time, error) {
|
||||
var (
|
||||
minTime = time.Now().Add(-9999 * time.Hour)
|
||||
maxTime = time.Now().Add(9999 * time.Hour)
|
||||
err error
|
||||
)
|
||||
var err error
|
||||
|
||||
stime := minTime
|
||||
etime := maxTime
|
||||
|
|
|
@ -215,7 +215,7 @@ Run query against a Prometheus server.
|
|||
|
||||
| Flag | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| <code class="text-nowrap">-o</code>, <code class="text-nowrap">--format</code> | Output format of the query. | `promql` |
|
||||
| <code class="text-nowrap">-o</code>, <code class="text-nowrap">--format</code> | Output format of the query: promql, json, unittest. Unittest output is experimental. | `promql` |
|
||||
| <code class="text-nowrap">--http.config.file</code> | HTTP client configuration file for promtool to connect to Prometheus. | |
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue