| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | // Copyright 2019 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 promql | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-01-28 06:29:44 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2019-10-10 08:06:53 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-13 07:58:27 +08:00
										 |  |  | 	"github.com/grafana/regexp" | 
					
						
							| 
									
										
										
										
											2020-10-29 17:43:23 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestQueryLogging(t *testing.T) { | 
					
						
							|  |  |  | 	fileAsBytes := make([]byte, 4096) | 
					
						
							|  |  |  | 	queryLogger := ActiveQueryTracker{ | 
					
						
							| 
									
										
										
										
											2024-09-11 04:32:03 +08:00
										 |  |  | 		mmappedFile:  fileAsBytes, | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 		logger:       nil, | 
					
						
							|  |  |  | 		getNextIndex: make(chan int, 4), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	queryLogger.generateIndices(4) | 
					
						
							|  |  |  | 	veryLongString := "MassiveQueryThatNeverEndsAndExceedsTwoHundredBytesWhichIsTheSizeOfEntrySizeAndShouldThusBeTruncatedAndIamJustGoingToRepeatTheSameCharactersAgainProbablyBecauseWeAreStillOnlyHalfWayDoneOrMaybeNotOrMaybeMassiveQueryThatNeverEndsAndExceedsTwoHundredBytesWhichIsTheSizeOfEntrySizeAndShouldThusBeTruncatedAndIamJustGoingToRepeatTheSameCharactersAgainProbablyBecauseWeAreStillOnlyHalfWayDoneOrMaybeNotOrMaybeMassiveQueryThatNeverEndsAndExceedsTwoHundredBytesWhichIsTheSizeOfEntrySizeAndShouldThusBeTruncatedAndIamJustGoingToRepeatTheSameCharactersAgainProbablyBecauseWeAreStillOnlyHalfWayDoneOrMaybeNotOrMaybeMassiveQueryThatNeverEndsAndExceedsTwoHundredBytesWhichIsTheSizeOfEntrySizeAndShouldThusBeTruncatedAndIamJustGoingToRepeatTheSameCharactersAgainProbablyBecauseWeAreStillOnlyHalfWayDoneOrMaybeNotOrMaybeMassiveQueryThatNeverEndsAndExceedsTwoHundredBytesWhichIsTheSizeOfEntrySizeAndShouldThusBeTruncatedAndIamJustGoingToRepeatTheSameCharactersAgainProbablyBecauseWeAreStillOnlyHalfWayDoneOrMaybeNotOrMaybe" | 
					
						
							|  |  |  | 	queries := []string{ | 
					
						
							|  |  |  | 		"TestQuery", | 
					
						
							|  |  |  | 		veryLongString, | 
					
						
							|  |  |  | 		"", | 
					
						
							|  |  |  | 		"SpecialCharQuery{host=\"2132132\", id=123123}", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	want := []string{ | 
					
						
							|  |  |  | 		`^{"query":"TestQuery","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 		`^{"query":"` + trimStringByBytes(veryLongString, entrySize-40) + `","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 		`^{"query":"","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 		`^{"query":"SpecialCharQuery{host=\\"2132132\\", id=123123}","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check for inserts of queries.
 | 
					
						
							| 
									
										
										
										
											2025-08-27 20:38:54 +08:00
										 |  |  | 	for i := range 4 { | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 		start := 1 + i*entrySize | 
					
						
							|  |  |  | 		end := start + entrySize | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-28 06:29:44 +08:00
										 |  |  | 		queryLogger.Insert(context.Background(), queries[i]) | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		have := string(fileAsBytes[start:end]) | 
					
						
							| 
									
										
										
										
											2021-09-14 03:19:20 +08:00
										 |  |  | 		require.True(t, regexp.MustCompile(want[i]).MatchString(have), | 
					
						
							|  |  |  | 			"Query not written correctly: %s", queries[i]) | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if all queries have been deleted.
 | 
					
						
							| 
									
										
										
										
											2025-08-27 20:38:54 +08:00
										 |  |  | 	for i := range 4 { | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 		queryLogger.Delete(1 + i*entrySize) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-14 03:19:20 +08:00
										 |  |  | 	require.True(t, regexp.MustCompile(`^\x00+$`).Match(fileAsBytes[1:1+entrySize*4]), | 
					
						
							|  |  |  | 		"All queries not deleted properly. Want only null bytes \\x00") | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestIndexReuse(t *testing.T) { | 
					
						
							|  |  |  | 	queryBytes := make([]byte, 1+3*entrySize) | 
					
						
							|  |  |  | 	queryLogger := ActiveQueryTracker{ | 
					
						
							| 
									
										
										
										
											2024-09-11 04:32:03 +08:00
										 |  |  | 		mmappedFile:  queryBytes, | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 		logger:       nil, | 
					
						
							|  |  |  | 		getNextIndex: make(chan int, 3), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	queryLogger.generateIndices(3) | 
					
						
							| 
									
										
										
										
											2020-01-28 06:29:44 +08:00
										 |  |  | 	queryLogger.Insert(context.Background(), "TestQuery1") | 
					
						
							|  |  |  | 	queryLogger.Insert(context.Background(), "TestQuery2") | 
					
						
							|  |  |  | 	queryLogger.Insert(context.Background(), "TestQuery3") | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	queryLogger.Delete(1 + entrySize) | 
					
						
							|  |  |  | 	queryLogger.Delete(1) | 
					
						
							|  |  |  | 	newQuery2 := "ThisShouldBeInsertedAtIndex2" | 
					
						
							|  |  |  | 	newQuery1 := "ThisShouldBeInsertedAtIndex1" | 
					
						
							| 
									
										
										
										
											2020-01-28 06:29:44 +08:00
										 |  |  | 	queryLogger.Insert(context.Background(), newQuery2) | 
					
						
							|  |  |  | 	queryLogger.Insert(context.Background(), newQuery1) | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	want := []string{ | 
					
						
							|  |  |  | 		`^{"query":"ThisShouldBeInsertedAtIndex1","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 		`^{"query":"ThisShouldBeInsertedAtIndex2","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 		`^{"query":"TestQuery3","timestamp_sec":\d+}\x00*,$`, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check all bytes and verify new query was inserted at index 2
 | 
					
						
							| 
									
										
										
										
											2025-08-27 20:38:54 +08:00
										 |  |  | 	for i := range 3 { | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 		start := 1 + i*entrySize | 
					
						
							|  |  |  | 		end := start + entrySize | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		have := queryBytes[start:end] | 
					
						
							| 
									
										
										
										
											2021-09-14 03:19:20 +08:00
										 |  |  | 		require.True(t, regexp.MustCompile(want[i]).Match(have), | 
					
						
							|  |  |  | 			"Index not reused properly.") | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestMMapFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	dir := t.TempDir() | 
					
						
							| 
									
										
										
										
											2024-09-11 04:32:03 +08:00
										 |  |  | 	fpath := filepath.Join(dir, "mmappedFile") | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	const data = "ab" | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-11 04:32:03 +08:00
										 |  |  | 	fileAsBytes, closer, err := getMMappedFile(fpath, 2, nil) | 
					
						
							| 
									
										
										
										
											2020-10-29 17:43:23 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	copy(fileAsBytes, data) | 
					
						
							|  |  |  | 	require.NoError(t, closer.Close()) | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	f, err := os.Open(fpath) | 
					
						
							| 
									
										
										
										
											2020-10-29 17:43:23 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		_ = f.Close() | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	bytes := make([]byte, 4) | 
					
						
							|  |  |  | 	n, err := f.Read(bytes) | 
					
						
							| 
									
										
										
										
											2021-09-14 03:19:20 +08:00
										 |  |  | 	require.NoError(t, err, "Unexpected error while reading file.") | 
					
						
							| 
									
										
										
										
											2024-04-29 22:16:51 +08:00
										 |  |  | 	require.Equal(t, 2, n) | 
					
						
							|  |  |  | 	require.Equal(t, []byte(data), bytes[:2], "Mmap failed") | 
					
						
							| 
									
										
										
										
											2019-07-31 23:12:43 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-18 07:21:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-10 05:11:39 +08:00
										 |  |  | func TestParseBrokenJSON(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2019-10-18 07:21:58 +08:00
										 |  |  | 	for _, tc := range []struct { | 
					
						
							|  |  |  | 		b []byte | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ok  bool | 
					
						
							|  |  |  | 		out string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			b: []byte(""), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			b: []byte("\x00\x00"), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			b: []byte("\x00[\x00"), | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			b:   []byte("\x00[]\x00"), | 
					
						
							|  |  |  | 			ok:  true, | 
					
						
							|  |  |  | 			out: "[]", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			b:   []byte("[\"up == 0\",\"rate(http_requests[2w]\"]\x00\x00\x00"), | 
					
						
							|  |  |  | 			ok:  true, | 
					
						
							|  |  |  | 			out: "[\"up == 0\",\"rate(http_requests[2w]\"]", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} { | 
					
						
							|  |  |  | 		t.Run("", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2020-08-02 16:48:57 +08:00
										 |  |  | 			out, ok := parseBrokenJSON(tc.b) | 
					
						
							| 
									
										
										
										
											2021-09-14 03:19:20 +08:00
										 |  |  | 			require.Equal(t, tc.ok, ok) | 
					
						
							|  |  |  | 			if ok { | 
					
						
							|  |  |  | 				require.Equal(t, tc.out, out) | 
					
						
							| 
									
										
										
										
											2019-10-18 07:21:58 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |