| 
									
										
										
										
											2023-10-15 02:34:50 +08:00
										 |  |  | // Copyright 2017 The Prometheus Authors
 | 
					
						
							|  |  |  | // Licensed under the Apache License, Version 2.0 (the "License");
 | 
					
						
							|  |  |  | // you may not use this file except in compliance with the License.
 | 
					
						
							|  |  |  | // You may obtain a copy of the License at
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Unless required by applicable law or agreed to in writing, software
 | 
					
						
							|  |  |  | // distributed under the License is distributed on an "AS IS" BASIS,
 | 
					
						
							|  |  |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					
						
							|  |  |  | // See the License for the specific language governing permissions and
 | 
					
						
							|  |  |  | // limitations under the License.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"math" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2024-06-27 19:47:31 +08:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2023-10-15 02:34:50 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2023-10-15 02:34:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-29 17:48:24 +08:00
										 |  |  | 	"github.com/prometheus/prometheus/promql/promqltest" | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 	"github.com/prometheus/prometheus/tsdb" | 
					
						
							| 
									
										
										
										
											2023-10-15 02:34:50 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestGenerateBucket(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-11-09 00:08:27 +08:00
										 |  |  | 	t.Parallel() | 
					
						
							| 
									
										
										
										
											2023-10-15 02:34:50 +08:00
										 |  |  | 	tcs := []struct { | 
					
						
							|  |  |  | 		min, max         int | 
					
						
							|  |  |  | 		start, end, step int | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			min:   101, | 
					
						
							|  |  |  | 			max:   141, | 
					
						
							|  |  |  | 			start: 100, | 
					
						
							|  |  |  | 			end:   150, | 
					
						
							|  |  |  | 			step:  10, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tc := range tcs { | 
					
						
							|  |  |  | 		start, end, step := generateBucket(tc.min, tc.max) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		require.Equal(t, tc.start, start) | 
					
						
							|  |  |  | 		require.Equal(t, tc.end, end) | 
					
						
							|  |  |  | 		require.Equal(t, tc.step, step) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // getDumpedSamples dumps samples and returns them.
 | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | func getDumpedSamples(t *testing.T, databasePath, sandboxDirRoot string, mint, maxt int64, match []string, formatter SeriesSetFormatter) string { | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	oldStdout := os.Stdout | 
					
						
							|  |  |  | 	r, w, _ := os.Pipe() | 
					
						
							|  |  |  | 	os.Stdout = w | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err := dumpSamples( | 
					
						
							|  |  |  | 		context.Background(), | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 		databasePath, | 
					
						
							|  |  |  | 		sandboxDirRoot, | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 		mint, | 
					
						
							|  |  |  | 		maxt, | 
					
						
							|  |  |  | 		match, | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 		formatter, | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 	) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	w.Close() | 
					
						
							|  |  |  | 	os.Stdout = oldStdout | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var buf bytes.Buffer | 
					
						
							|  |  |  | 	io.Copy(&buf, r) | 
					
						
							|  |  |  | 	return buf.String() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | func normalizeNewLine(b []byte) []byte { | 
					
						
							|  |  |  | 	if strings.Contains(runtime.GOOS, "windows") { | 
					
						
							|  |  |  | 		// We use "/n" while dumping on windows as well.
 | 
					
						
							|  |  |  | 		return bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n")) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return b | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | func TestTSDBDump(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-04-29 17:48:24 +08:00
										 |  |  | 	storage := promqltest.LoadedStorage(t, ` | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 		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 | 
					
						
							|  |  |  | 	`) | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 	t.Cleanup(func() { storage.Close() }) | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	tests := []struct { | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 		name           string | 
					
						
							|  |  |  | 		mint           int64 | 
					
						
							|  |  |  | 		maxt           int64 | 
					
						
							|  |  |  | 		sandboxDirRoot string | 
					
						
							|  |  |  | 		match          []string | 
					
						
							|  |  |  | 		expectedDump   string | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:         "default match", | 
					
						
							|  |  |  | 			mint:         math.MinInt64, | 
					
						
							|  |  |  | 			maxt:         math.MaxInt64, | 
					
						
							|  |  |  | 			match:        []string{"{__name__=~'(?s:.*)'}"}, | 
					
						
							|  |  |  | 			expectedDump: "testdata/dump-test-1.prom", | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:           "default match with sandbox dir root set", | 
					
						
							|  |  |  | 			mint:           math.MinInt64, | 
					
						
							|  |  |  | 			maxt:           math.MaxInt64, | 
					
						
							|  |  |  | 			sandboxDirRoot: t.TempDir(), | 
					
						
							|  |  |  | 			match:          []string{"{__name__=~'(?s:.*)'}"}, | 
					
						
							|  |  |  | 			expectedDump:   "testdata/dump-test-1.prom", | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			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) { | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 			dumpedMetrics := getDumpedSamples(t, storage.Dir(), tt.sandboxDirRoot, tt.mint, tt.maxt, tt.match, formatSeriesSet) | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 			expectedMetrics, err := os.ReadFile(tt.expectedDump) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 			expectedMetrics = normalizeNewLine(expectedMetrics) | 
					
						
							| 
									
										
										
										
											2024-06-27 19:47:31 +08:00
										 |  |  | 			// Sort both, because Prometheus does not guarantee the output order.
 | 
					
						
							|  |  |  | 			require.Equal(t, sortLines(string(expectedMetrics)), sortLines(dumpedMetrics)) | 
					
						
							| 
									
										
										
										
											2024-01-15 18:29:53 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-27 19:47:31 +08:00
										 |  |  | func sortLines(buf string) string { | 
					
						
							|  |  |  | 	lines := strings.Split(buf, "\n") | 
					
						
							|  |  |  | 	slices.Sort(lines) | 
					
						
							|  |  |  | 	return strings.Join(lines, "\n") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | func TestTSDBDumpOpenMetrics(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-04-29 17:48:24 +08:00
										 |  |  | 	storage := promqltest.LoadedStorage(t, ` | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 		load 1m | 
					
						
							|  |  |  | 			my_counter{foo="bar", baz="abc"} 1 2 3 4 5 | 
					
						
							|  |  |  | 			my_gauge{bar="foo", abc="baz"} 9 8 0 4 7 | 
					
						
							|  |  |  | 	`) | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 	t.Cleanup(func() { storage.Close() }) | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name           string | 
					
						
							|  |  |  | 		sandboxDirRoot string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "default match", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:           "default match with sandbox dir root set", | 
					
						
							|  |  |  | 			sandboxDirRoot: t.TempDir(), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, tt := range tests { | 
					
						
							|  |  |  | 		t.Run(tt.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			expectedMetrics, err := os.ReadFile("testdata/dump-openmetrics-test.prom") | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			expectedMetrics = normalizeNewLine(expectedMetrics) | 
					
						
							|  |  |  | 			dumpedMetrics := getDumpedSamples(t, storage.Dir(), tt.sandboxDirRoot, math.MinInt64, math.MaxInt64, []string{"{__name__=~'(?s:.*)'}"}, formatSeriesSetOpenMetrics) | 
					
						
							|  |  |  | 			require.Equal(t, sortLines(string(expectedMetrics)), sortLines(dumpedMetrics)) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestTSDBDumpOpenMetricsRoundTrip(t *testing.T) { | 
					
						
							|  |  |  | 	initialMetrics, err := os.ReadFile("testdata/dump-openmetrics-roundtrip-test.prom") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	initialMetrics = normalizeNewLine(initialMetrics) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dbDir := t.TempDir() | 
					
						
							|  |  |  | 	// Import samples from OM format
 | 
					
						
							| 
									
										
										
										
											2024-08-28 10:12:24 +08:00
										 |  |  | 	err = backfill(5000, initialMetrics, dbDir, false, false, 2*time.Hour, map[string]string{}) | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	db, err := tsdb.Open(dbDir, nil, nil, tsdb.DefaultOptions(), nil) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		require.NoError(t, db.Close()) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Dump the blocks into OM format
 | 
					
						
							| 
									
										
										
										
											2024-08-07 22:53:47 +08:00
										 |  |  | 	dumpedMetrics := getDumpedSamples(t, dbDir, "", math.MinInt64, math.MaxInt64, []string{"{__name__=~'(?s:.*)'}"}, formatSeriesSetOpenMetrics) | 
					
						
							| 
									
										
										
										
											2024-01-25 06:22:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Should get back the initial metrics.
 | 
					
						
							|  |  |  | 	require.Equal(t, string(initialMetrics), dumpedMetrics) | 
					
						
							|  |  |  | } |