| 
									
										
										
										
											2024-06-06 23:17:13 +08:00
										 |  |  | // Copyright 2015 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 ( | 
					
						
							| 
									
										
										
										
											2024-07-26 15:49:57 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2024-06-06 23:17:13 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/prometheus/prometheus/model/histogram" | 
					
						
							|  |  |  | 	"github.com/prometheus/prometheus/model/labels" | 
					
						
							| 
									
										
										
										
											2024-07-26 15:49:57 +08:00
										 |  |  | 	"github.com/prometheus/prometheus/model/value" | 
					
						
							| 
									
										
										
										
											2024-06-06 23:17:13 +08:00
										 |  |  | 	"github.com/prometheus/prometheus/tsdb/chunkenc" | 
					
						
							|  |  |  | 	"github.com/prometheus/prometheus/tsdb/tsdbutil" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestHistogramStatsDecoding(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-07-26 15:49:57 +08:00
										 |  |  | 	cases := []struct { | 
					
						
							|  |  |  | 		name          string | 
					
						
							|  |  |  | 		histograms    []*histogram.Histogram | 
					
						
							|  |  |  | 		expectedHints []histogram.CounterResetHint | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "unknown counter reset triggers detection", | 
					
						
							|  |  |  | 			histograms: []*histogram.Histogram{ | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(0, histogram.NotCounterReset), | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(2, histogram.CounterReset), | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expectedHints: []histogram.CounterResetHint{ | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 				histogram.CounterReset, | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "stale sample before unknown reset hint", | 
					
						
							|  |  |  | 			histograms: []*histogram.Histogram{ | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(0, histogram.NotCounterReset), | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 				{Sum: math.Float64frombits(value.StaleNaN)}, | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expectedHints: []histogram.CounterResetHint{ | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 				histogram.UnknownCounterReset, | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-07-29 20:53:32 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name: "unknown counter reset at the beginning", | 
					
						
							|  |  |  | 			histograms: []*histogram.Histogram{ | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expectedHints: []histogram.CounterResetHint{ | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "detect real counter reset", | 
					
						
							|  |  |  | 			histograms: []*histogram.Histogram{ | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expectedHints: []histogram.CounterResetHint{ | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 				histogram.CounterReset, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "detect real counter reset after stale NaN", | 
					
						
							|  |  |  | 			histograms: []*histogram.Histogram{ | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(2, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 				{Sum: math.Float64frombits(value.StaleNaN)}, | 
					
						
							|  |  |  | 				tsdbutil.GenerateTestHistogramWithHint(1, histogram.UnknownCounterReset), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			expectedHints: []histogram.CounterResetHint{ | 
					
						
							|  |  |  | 				histogram.NotCounterReset, | 
					
						
							|  |  |  | 				histogram.UnknownCounterReset, | 
					
						
							|  |  |  | 				histogram.CounterReset, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-06-06 23:17:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-26 15:49:57 +08:00
										 |  |  | 	for _, tc := range cases { | 
					
						
							|  |  |  | 		t.Run(tc.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			t.Run("histogram_stats", func(t *testing.T) { | 
					
						
							|  |  |  | 				decodedStats := make([]*histogram.Histogram, 0) | 
					
						
							|  |  |  | 				statsIterator := NewHistogramStatsIterator(newHistogramSeries(tc.histograms).Iterator(nil)) | 
					
						
							|  |  |  | 				for statsIterator.Next() != chunkenc.ValNone { | 
					
						
							|  |  |  | 					_, h := statsIterator.AtHistogram(nil) | 
					
						
							|  |  |  | 					decodedStats = append(decodedStats, h) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				for i := 0; i < len(tc.histograms); i++ { | 
					
						
							|  |  |  | 					require.Equal(t, tc.expectedHints[i], decodedStats[i].CounterResetHint, fmt.Sprintf("mismatch in counter reset hint for histogram %d", i)) | 
					
						
							|  |  |  | 					h := tc.histograms[i] | 
					
						
							|  |  |  | 					if value.IsStaleNaN(h.Sum) { | 
					
						
							|  |  |  | 						require.True(t, value.IsStaleNaN(decodedStats[i].Sum)) | 
					
						
							|  |  |  | 						require.Equal(t, uint64(0), decodedStats[i].Count) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						require.Equal(t, tc.histograms[i].Count, decodedStats[i].Count) | 
					
						
							|  |  |  | 						require.Equal(t, tc.histograms[i].Sum, decodedStats[i].Sum) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			t.Run("float_histogram_stats", func(t *testing.T) { | 
					
						
							|  |  |  | 				decodedStats := make([]*histogram.FloatHistogram, 0) | 
					
						
							|  |  |  | 				statsIterator := NewHistogramStatsIterator(newHistogramSeries(tc.histograms).Iterator(nil)) | 
					
						
							|  |  |  | 				for statsIterator.Next() != chunkenc.ValNone { | 
					
						
							|  |  |  | 					_, h := statsIterator.AtFloatHistogram(nil) | 
					
						
							|  |  |  | 					decodedStats = append(decodedStats, h) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				for i := 0; i < len(tc.histograms); i++ { | 
					
						
							|  |  |  | 					require.Equal(t, tc.expectedHints[i], decodedStats[i].CounterResetHint) | 
					
						
							|  |  |  | 					fh := tc.histograms[i].ToFloat(nil) | 
					
						
							|  |  |  | 					if value.IsStaleNaN(fh.Sum) { | 
					
						
							|  |  |  | 						require.True(t, value.IsStaleNaN(decodedStats[i].Sum)) | 
					
						
							|  |  |  | 						require.Equal(t, float64(0), decodedStats[i].Count) | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						require.Equal(t, fh.Count, decodedStats[i].Count) | 
					
						
							|  |  |  | 						require.Equal(t, fh.Sum, decodedStats[i].Sum) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2024-06-06 23:17:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type histogramSeries struct { | 
					
						
							|  |  |  | 	histograms []*histogram.Histogram | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newHistogramSeries(histograms []*histogram.Histogram) *histogramSeries { | 
					
						
							|  |  |  | 	return &histogramSeries{ | 
					
						
							|  |  |  | 		histograms: histograms, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m histogramSeries) Labels() labels.Labels { return labels.EmptyLabels() } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (m histogramSeries) Iterator(_ chunkenc.Iterator) chunkenc.Iterator { | 
					
						
							|  |  |  | 	return &histogramIterator{ | 
					
						
							|  |  |  | 		i:          -1, | 
					
						
							|  |  |  | 		histograms: m.histograms, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type histogramIterator struct { | 
					
						
							|  |  |  | 	i          int | 
					
						
							|  |  |  | 	histograms []*histogram.Histogram | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) Next() chunkenc.ValueType { | 
					
						
							|  |  |  | 	h.i++ | 
					
						
							|  |  |  | 	if h.i < len(h.histograms) { | 
					
						
							|  |  |  | 		return chunkenc.ValHistogram | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return chunkenc.ValNone | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) Seek(t int64) chunkenc.ValueType { panic("not implemented") } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) At() (int64, float64) { panic("not implemented") } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) AtHistogram(_ *histogram.Histogram) (int64, *histogram.Histogram) { | 
					
						
							|  |  |  | 	return 0, h.histograms[h.i] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) AtFloatHistogram(_ *histogram.FloatHistogram) (int64, *histogram.FloatHistogram) { | 
					
						
							|  |  |  | 	return 0, h.histograms[h.i].ToFloat(nil) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) AtT() int64 { return 0 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (h *histogramIterator) Err() error { return nil } |