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()
|
||||
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()
|
||||
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.")
|
||||
importHumanReadable := importCmd.Flag("human-readable", "Print human readable values.").Short('r').Bool()
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -720,11 +720,21 @@ func dumpSamples(ctx context.Context, path string, mint, maxt int64, match strin
|
|||
}
|
||||
defer q.Close()
|
||||
|
||||
matchers, err := parser.ParseMetricSelector(match)
|
||||
matcherSets, err := parser.ParseMetricSelectors(match)
|
||||
if err != nil {
|
||||
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() {
|
||||
series := ss.At()
|
||||
|
|
|
@ -14,9 +14,18 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
func TestGenerateBucket(t *testing.T) {
|
||||
|
@ -41,3 +50,101 @@ func TestGenerateBucket(t *testing.T) {
|
|||
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">--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
|
||||
}
|
||||
|
||||
// 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.
|
||||
type SequenceValue struct {
|
||||
Value float64
|
||||
|
|
|
@ -1848,13 +1848,9 @@ func parseDuration(s string) (time.Duration, error) {
|
|||
}
|
||||
|
||||
func parseMatchersParam(matchers []string) ([][]*labels.Matcher, error) {
|
||||
var matcherSets [][]*labels.Matcher
|
||||
for _, s := range matchers {
|
||||
matchers, err := parser.ParseMetricSelector(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matcherSets = append(matcherSets, matchers)
|
||||
matcherSets, err := parser.ParseMetricSelectors(matchers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
OUTER:
|
||||
|
|
|
@ -65,14 +65,10 @@ func (h *Handler) federation(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
var matcherSets [][]*labels.Matcher
|
||||
for _, s := range req.Form["match[]"] {
|
||||
matchers, err := parser.ParseMetricSelector(s)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
matcherSets = append(matcherSets, matchers)
|
||||
matcherSets, err := parser.ParseMetricSelectors(req.Form["match[]"])
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
Loading…
Reference in New Issue