mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 05:04:05 -08:00
promtool: allow setting multiple matchers to "promtool tsdb dump" command. (#13296)
Conditions are ANDed inside the same matcher but matchers are ORed Including unit tests for "promtool tsdb dump". Refactor some matchers scraping utils. Signed-off-by: machine424 <ayoubmrini424@gmail.com>
This commit is contained in:
parent
17920623e7
commit
ace9c8a3da
|
@ -236,7 +236,7 @@ func main() {
|
||||||
dumpPath := tsdbDumpCmd.Arg("db path", "Database path (default is "+defaultDBPath+").").Default(defaultDBPath).String()
|
dumpPath := tsdbDumpCmd.Arg("db path", "Database path (default is "+defaultDBPath+").").Default(defaultDBPath).String()
|
||||||
dumpMinTime := tsdbDumpCmd.Flag("min-time", "Minimum timestamp to dump.").Default(strconv.FormatInt(math.MinInt64, 10)).Int64()
|
dumpMinTime := tsdbDumpCmd.Flag("min-time", "Minimum timestamp to dump.").Default(strconv.FormatInt(math.MinInt64, 10)).Int64()
|
||||||
dumpMaxTime := tsdbDumpCmd.Flag("max-time", "Maximum timestamp to dump.").Default(strconv.FormatInt(math.MaxInt64, 10)).Int64()
|
dumpMaxTime := tsdbDumpCmd.Flag("max-time", "Maximum timestamp to dump.").Default(strconv.FormatInt(math.MaxInt64, 10)).Int64()
|
||||||
dumpMatch := tsdbDumpCmd.Flag("match", "Series selector.").Default("{__name__=~'(?s:.*)'}").String()
|
dumpMatch := tsdbDumpCmd.Flag("match", "Series selector. Can be specified multiple times.").Default("{__name__=~'(?s:.*)'}").Strings()
|
||||||
|
|
||||||
importCmd := tsdbCmd.Command("create-blocks-from", "[Experimental] Import samples from input and produce TSDB blocks. Please refer to the storage docs for more details.")
|
importCmd := tsdbCmd.Command("create-blocks-from", "[Experimental] Import samples from input and produce TSDB blocks. Please refer to the storage docs for more details.")
|
||||||
importHumanReadable := importCmd.Flag("human-readable", "Print human readable values.").Short('r').Bool()
|
importHumanReadable := importCmd.Flag("human-readable", "Print human readable values.").Short('r').Bool()
|
||||||
|
|
15
cmd/promtool/testdata/dump-test-1.prom
vendored
Normal file
15
cmd/promtool/testdata/dump-test-1.prom
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{__name__="heavy_metric", foo="bar"} 5 0
|
||||||
|
{__name__="heavy_metric", foo="bar"} 4 60000
|
||||||
|
{__name__="heavy_metric", foo="bar"} 3 120000
|
||||||
|
{__name__="heavy_metric", foo="bar"} 2 180000
|
||||||
|
{__name__="heavy_metric", foo="bar"} 1 240000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 5 0
|
||||||
|
{__name__="heavy_metric", foo="foo"} 4 60000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 3 120000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 2 180000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 1 240000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 1 0
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 2 60000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 3 120000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 4 180000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 5 240000
|
10
cmd/promtool/testdata/dump-test-2.prom
vendored
Normal file
10
cmd/promtool/testdata/dump-test-2.prom
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{__name__="heavy_metric", foo="foo"} 5 0
|
||||||
|
{__name__="heavy_metric", foo="foo"} 4 60000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 3 120000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 2 180000
|
||||||
|
{__name__="heavy_metric", foo="foo"} 1 240000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 1 0
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 2 60000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 3 120000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 4 180000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 5 240000
|
2
cmd/promtool/testdata/dump-test-3.prom
vendored
Normal file
2
cmd/promtool/testdata/dump-test-3.prom
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 2 60000
|
||||||
|
{__name__="metric", baz="abc", foo="bar"} 3 120000
|
|
@ -706,7 +706,7 @@ func analyzeCompaction(ctx context.Context, block tsdb.BlockReader, indexr tsdb.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpSamples(ctx context.Context, path string, mint, maxt int64, match string) (err error) {
|
func dumpSamples(ctx context.Context, path string, mint, maxt int64, match []string) (err error) {
|
||||||
db, err := tsdb.OpenDBReadOnly(path, nil)
|
db, err := tsdb.OpenDBReadOnly(path, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -720,11 +720,21 @@ func dumpSamples(ctx context.Context, path string, mint, maxt int64, match strin
|
||||||
}
|
}
|
||||||
defer q.Close()
|
defer q.Close()
|
||||||
|
|
||||||
matchers, err := parser.ParseMetricSelector(match)
|
matcherSets, err := parser.ParseMetricSelectors(match)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ss := q.Select(ctx, false, nil, matchers...)
|
|
||||||
|
var ss storage.SeriesSet
|
||||||
|
if len(matcherSets) > 1 {
|
||||||
|
var sets []storage.SeriesSet
|
||||||
|
for _, mset := range matcherSets {
|
||||||
|
sets = append(sets, q.Select(ctx, true, nil, mset...))
|
||||||
|
}
|
||||||
|
ss = storage.NewMergeSeriesSet(sets, storage.ChainedSeriesMerge)
|
||||||
|
} else {
|
||||||
|
ss = q.Select(ctx, false, nil, matcherSets[0]...)
|
||||||
|
}
|
||||||
|
|
||||||
for ss.Next() {
|
for ss.Next() {
|
||||||
series := ss.At()
|
series := ss.At()
|
||||||
|
|
|
@ -14,9 +14,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/prometheus/prometheus/promql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateBucket(t *testing.T) {
|
func TestGenerateBucket(t *testing.T) {
|
||||||
|
@ -41,3 +50,101 @@ func TestGenerateBucket(t *testing.T) {
|
||||||
require.Equal(t, tc.step, step)
|
require.Equal(t, tc.step, step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDumpedSamples dumps samples and returns them.
|
||||||
|
func getDumpedSamples(t *testing.T, path string, mint, maxt int64, match []string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
oldStdout := os.Stdout
|
||||||
|
r, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
err := dumpSamples(
|
||||||
|
context.Background(),
|
||||||
|
path,
|
||||||
|
mint,
|
||||||
|
maxt,
|
||||||
|
match,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
os.Stdout = oldStdout
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
io.Copy(&buf, r)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTSDBDump(t *testing.T) {
|
||||||
|
storage := promql.LoadedStorage(t, `
|
||||||
|
load 1m
|
||||||
|
metric{foo="bar", baz="abc"} 1 2 3 4 5
|
||||||
|
heavy_metric{foo="bar"} 5 4 3 2 1
|
||||||
|
heavy_metric{foo="foo"} 5 4 3 2 1
|
||||||
|
`)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
mint int64
|
||||||
|
maxt int64
|
||||||
|
match []string
|
||||||
|
expectedDump string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default match",
|
||||||
|
mint: math.MinInt64,
|
||||||
|
maxt: math.MaxInt64,
|
||||||
|
match: []string{"{__name__=~'(?s:.*)'}"},
|
||||||
|
expectedDump: "testdata/dump-test-1.prom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same matcher twice",
|
||||||
|
mint: math.MinInt64,
|
||||||
|
maxt: math.MaxInt64,
|
||||||
|
match: []string{"{foo=~'.+'}", "{foo=~'.+'}"},
|
||||||
|
expectedDump: "testdata/dump-test-1.prom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no duplication",
|
||||||
|
mint: math.MinInt64,
|
||||||
|
maxt: math.MaxInt64,
|
||||||
|
match: []string{"{__name__=~'(?s:.*)'}", "{baz='abc'}"},
|
||||||
|
expectedDump: "testdata/dump-test-1.prom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "well merged",
|
||||||
|
mint: math.MinInt64,
|
||||||
|
maxt: math.MaxInt64,
|
||||||
|
match: []string{"{__name__='heavy_metric'}", "{baz='abc'}"},
|
||||||
|
expectedDump: "testdata/dump-test-1.prom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi matchers",
|
||||||
|
mint: math.MinInt64,
|
||||||
|
maxt: math.MaxInt64,
|
||||||
|
match: []string{"{__name__='heavy_metric',foo='foo'}", "{__name__='metric'}"},
|
||||||
|
expectedDump: "testdata/dump-test-2.prom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with reduced mint and maxt",
|
||||||
|
mint: int64(60000),
|
||||||
|
maxt: int64(120000),
|
||||||
|
match: []string{"{__name__='metric'}"},
|
||||||
|
expectedDump: "testdata/dump-test-3.prom",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
dumpedMetrics := getDumpedSamples(t, storage.Dir(), tt.mint, tt.maxt, tt.match)
|
||||||
|
expectedMetrics, err := os.ReadFile(tt.expectedDump)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if strings.Contains(runtime.GOOS, "windows") {
|
||||||
|
// We use "/n" while dumping on windows as well.
|
||||||
|
expectedMetrics = bytes.ReplaceAll(expectedMetrics, []byte("\r\n"), []byte("\n"))
|
||||||
|
}
|
||||||
|
// even though in case of one matcher samples are not sorted, the order in the cases above should stay the same.
|
||||||
|
require.Equal(t, string(expectedMetrics), dumpedMetrics)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -567,7 +567,7 @@ Dump samples from a TSDB.
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| <code class="text-nowrap">--min-time</code> | Minimum timestamp to dump. | `-9223372036854775808` |
|
| <code class="text-nowrap">--min-time</code> | Minimum timestamp to dump. | `-9223372036854775808` |
|
||||||
| <code class="text-nowrap">--max-time</code> | Maximum timestamp to dump. | `9223372036854775807` |
|
| <code class="text-nowrap">--max-time</code> | Maximum timestamp to dump. | `9223372036854775807` |
|
||||||
| <code class="text-nowrap">--match</code> | Series selector. | `{__name__=~'(?s:.*)'}` |
|
| <code class="text-nowrap">--match</code> | Series selector. Can be specified multiple times. | `{__name__=~'(?s:.*)'}` |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -208,6 +208,20 @@ func ParseMetricSelector(input string) (m []*labels.Matcher, err error) {
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseMetricSelectors parses a list of provided textual metric selectors into lists of
|
||||||
|
// label matchers.
|
||||||
|
func ParseMetricSelectors(matchers []string) (m [][]*labels.Matcher, err error) {
|
||||||
|
var matcherSets [][]*labels.Matcher
|
||||||
|
for _, s := range matchers {
|
||||||
|
matchers, err := ParseMetricSelector(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
matcherSets = append(matcherSets, matchers)
|
||||||
|
}
|
||||||
|
return matcherSets, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SequenceValue is an omittable value in a sequence of time series values.
|
// SequenceValue is an omittable value in a sequence of time series values.
|
||||||
type SequenceValue struct {
|
type SequenceValue struct {
|
||||||
Value float64
|
Value float64
|
||||||
|
|
|
@ -1848,13 +1848,9 @@ func parseDuration(s string) (time.Duration, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMatchersParam(matchers []string) ([][]*labels.Matcher, error) {
|
func parseMatchersParam(matchers []string) ([][]*labels.Matcher, error) {
|
||||||
var matcherSets [][]*labels.Matcher
|
matcherSets, err := parser.ParseMetricSelectors(matchers)
|
||||||
for _, s := range matchers {
|
if err != nil {
|
||||||
matchers, err := parser.ParseMetricSelector(s)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
matcherSets = append(matcherSets, matchers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OUTER:
|
OUTER:
|
||||||
|
|
|
@ -65,14 +65,10 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var matcherSets [][]*labels.Matcher
|
matcherSets, err := parser.ParseMetricSelectors(req.Form["match[]"])
|
||||||
for _, s := range req.Form["match[]"] {
|
if err != nil {
|
||||||
matchers, err := parser.ParseMetricSelector(s)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
if err != nil {
|
return
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
matcherSets = append(matcherSets, matchers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
Loading…
Reference in a new issue