@@ -875,3 +875,350 @@ func TestGetLe(t *testing.T) {
875875 }
876876 }
877877}
878+
879+ func TestHistogramCreatedTimestamp (t * testing.T ) {
880+ now := time .Now ()
881+
882+ histogram := NewHistogram (HistogramOpts {
883+ Name : "test" ,
884+ Help : "test help" ,
885+ Buckets : []float64 {1 , 2 , 3 , 4 },
886+ now : func () time.Time { return now },
887+ })
888+
889+ var metric dto.Metric
890+ if err := histogram .Write (& metric ); err != nil {
891+ t .Fatal (err )
892+ }
893+
894+ if metric .Histogram .CreatedTimestamp .AsTime ().Unix () != now .Unix () {
895+ t .Errorf ("expected created timestamp %d, got %d" , now .Unix (), metric .Histogram .CreatedTimestamp .AsTime ().Unix ())
896+ }
897+ }
898+
899+ func TestHistogramVecCreatedTimestamp (t * testing.T ) {
900+ now := time .Now ()
901+
902+ histogramVec := NewHistogramVec (HistogramOpts {
903+ Name : "test" ,
904+ Help : "test help" ,
905+ Buckets : []float64 {1 , 2 , 3 , 4 },
906+ now : func () time.Time { return now },
907+ }, []string {"label" })
908+ histogram := histogramVec .WithLabelValues ("value" ).(Histogram )
909+
910+ var metric dto.Metric
911+ if err := histogram .Write (& metric ); err != nil {
912+ t .Fatal (err )
913+ }
914+
915+ if metric .Histogram .CreatedTimestamp .AsTime ().Unix () != now .Unix () {
916+ t .Errorf ("expected created timestamp %d, got %d" , now .Unix (), metric .Histogram .CreatedTimestamp .AsTime ().Unix ())
917+ }
918+ }
919+
920+ func TestHistogramVecCreatedTimestampWithDeletes (t * testing.T ) {
921+ now := time .Now ()
922+
923+ histogramVec := NewHistogramVec (HistogramOpts {
924+ Name : "test" ,
925+ Help : "test help" ,
926+ Buckets : []float64 {1 , 2 , 3 , 4 },
927+ now : func () time.Time { return now },
928+ }, []string {"label" })
929+
930+ // First use of "With" should populate CT.
931+ histogramVec .WithLabelValues ("1" )
932+ expected := map [string ]time.Time {"1" : now }
933+
934+ now = now .Add (1 * time .Hour )
935+ expectCTsForMetricVecValues (t , histogramVec .MetricVec , dto .MetricType_HISTOGRAM , expected )
936+
937+ // Two more labels at different times.
938+ histogramVec .WithLabelValues ("2" )
939+ expected ["2" ] = now
940+
941+ now = now .Add (1 * time .Hour )
942+
943+ histogramVec .WithLabelValues ("3" )
944+ expected ["3" ] = now
945+
946+ now = now .Add (1 * time .Hour )
947+ expectCTsForMetricVecValues (t , histogramVec .MetricVec , dto .MetricType_HISTOGRAM , expected )
948+
949+ // Recreate metric instance should reset created timestamp to now.
950+ histogramVec .DeleteLabelValues ("1" )
951+ histogramVec .WithLabelValues ("1" )
952+ expected ["1" ] = now
953+
954+ now = now .Add (1 * time .Hour )
955+ expectCTsForMetricVecValues (t , histogramVec .MetricVec , dto .MetricType_HISTOGRAM , expected )
956+ }
957+
958+ func TestNewConstHistogramWithCreatedTimestamp (t * testing.T ) {
959+ metricDesc := NewDesc (
960+ "sample_value" ,
961+ "sample value" ,
962+ nil ,
963+ nil ,
964+ )
965+ buckets := map [float64 ]uint64 {25 : 100 , 50 : 200 }
966+ createdTs := time .Unix (1719670764 , 123 )
967+
968+ h , err := NewConstHistogramWithCreatedTimestamp (metricDesc , 100 , 200 , buckets , createdTs )
969+ if err != nil {
970+ t .Fatal (err )
971+ }
972+
973+ var metric dto.Metric
974+ if err := h .Write (& metric ); err != nil {
975+ t .Fatal (err )
976+ }
977+
978+ if metric .Histogram .CreatedTimestamp .AsTime ().UnixMicro () != createdTs .UnixMicro () {
979+ t .Errorf ("Expected created timestamp %v, got %v" , createdTs , & metric .Histogram .CreatedTimestamp )
980+ }
981+ }
982+
983+ func TestNativeHistogramExemplar (t * testing.T ) {
984+ // Test the histogram with positive NativeHistogramExemplarTTL and NativeHistogramMaxExemplars
985+ h := NewHistogram (HistogramOpts {
986+ Name : "test" ,
987+ Help : "test help" ,
988+ Buckets : []float64 {1 , 2 , 3 , 4 },
989+ NativeHistogramBucketFactor : 1.1 ,
990+ NativeHistogramMaxExemplars : 3 ,
991+ NativeHistogramExemplarTTL : 10 * time .Second ,
992+ }).(* histogram )
993+
994+ tcs := []struct {
995+ name string
996+ addFunc func (* histogram )
997+ expectedValues []float64
998+ }{
999+ {
1000+ name : "add exemplars to the limit" ,
1001+ addFunc : func (h * histogram ) {
1002+ h .ObserveWithExemplar (1 , Labels {"id" : "1" })
1003+ h .ObserveWithExemplar (3 , Labels {"id" : "1" })
1004+ h .ObserveWithExemplar (5 , Labels {"id" : "1" })
1005+ },
1006+ expectedValues : []float64 {1 , 3 , 5 },
1007+ },
1008+ {
1009+ name : "remove exemplar in closest pair, the removed index equals to inserted index" ,
1010+ addFunc : func (h * histogram ) {
1011+ h .ObserveWithExemplar (4 , Labels {"id" : "1" })
1012+ },
1013+ expectedValues : []float64 {1 , 3 , 4 },
1014+ },
1015+ {
1016+ name : "remove exemplar in closest pair, the removed index is bigger than inserted index" ,
1017+ addFunc : func (h * histogram ) {
1018+ h .ObserveWithExemplar (0 , Labels {"id" : "1" })
1019+ },
1020+ expectedValues : []float64 {0 , 1 , 4 },
1021+ },
1022+ {
1023+ name : "remove exemplar with oldest timestamp, the removed index is smaller than inserted index" ,
1024+ addFunc : func (h * histogram ) {
1025+ h .now = func () time.Time { return time .Now ().Add (time .Second * 11 ) }
1026+ h .ObserveWithExemplar (6 , Labels {"id" : "1" })
1027+ },
1028+ expectedValues : []float64 {0 , 4 , 6 },
1029+ },
1030+ }
1031+
1032+ for _ , tc := range tcs {
1033+ t .Run (tc .name , func (t * testing.T ) {
1034+ tc .addFunc (h )
1035+ compareNativeExemplarValues (t , h .nativeExemplars .exemplars , tc .expectedValues )
1036+ })
1037+ }
1038+
1039+ // Test the histogram with negative NativeHistogramExemplarTTL
1040+ h = NewHistogram (HistogramOpts {
1041+ Name : "test" ,
1042+ Help : "test help" ,
1043+ Buckets : []float64 {1 , 2 , 3 , 4 },
1044+ NativeHistogramBucketFactor : 1.1 ,
1045+ NativeHistogramMaxExemplars : 3 ,
1046+ NativeHistogramExemplarTTL : - 1 * time .Second ,
1047+ }).(* histogram )
1048+
1049+ tcs = []struct {
1050+ name string
1051+ addFunc func (* histogram )
1052+ expectedValues []float64
1053+ }{
1054+ {
1055+ name : "add exemplars to the limit" ,
1056+ addFunc : func (h * histogram ) {
1057+ h .ObserveWithExemplar (1 , Labels {"id" : "1" })
1058+ h .ObserveWithExemplar (3 , Labels {"id" : "1" })
1059+ h .ObserveWithExemplar (5 , Labels {"id" : "1" })
1060+ },
1061+ expectedValues : []float64 {1 , 3 , 5 },
1062+ },
1063+ {
1064+ name : "remove exemplar with oldest timestamp, the removed index is smaller than inserted index" ,
1065+ addFunc : func (h * histogram ) {
1066+ h .ObserveWithExemplar (4 , Labels {"id" : "1" })
1067+ },
1068+ expectedValues : []float64 {3 , 4 , 5 },
1069+ },
1070+ {
1071+ name : "remove exemplar with oldest timestamp, the removed index equals to inserted index" ,
1072+ addFunc : func (h * histogram ) {
1073+ h .ObserveWithExemplar (0 , Labels {"id" : "1" })
1074+ },
1075+ expectedValues : []float64 {0 , 4 , 5 },
1076+ },
1077+ {
1078+ name : "remove exemplar with oldest timestamp, the removed index is bigger than inserted index" ,
1079+ addFunc : func (h * histogram ) {
1080+ h .ObserveWithExemplar (3 , Labels {"id" : "1" })
1081+ },
1082+ expectedValues : []float64 {0 , 3 , 4 },
1083+ },
1084+ }
1085+
1086+ for _ , tc := range tcs {
1087+ t .Run (tc .name , func (t * testing.T ) {
1088+ tc .addFunc (h )
1089+ compareNativeExemplarValues (t , h .nativeExemplars .exemplars , tc .expectedValues )
1090+ })
1091+ }
1092+
1093+ // Test the histogram with negative NativeHistogramMaxExemplars
1094+ h = NewHistogram (HistogramOpts {
1095+ Name : "test" ,
1096+ Help : "test help" ,
1097+ Buckets : []float64 {1 , 2 , 3 , 4 },
1098+ NativeHistogramBucketFactor : 1.1 ,
1099+ NativeHistogramMaxExemplars : - 1 ,
1100+ NativeHistogramExemplarTTL : - 1 * time .Second ,
1101+ }).(* histogram )
1102+
1103+ tcs = []struct {
1104+ name string
1105+ addFunc func (* histogram )
1106+ expectedValues []float64
1107+ }{
1108+ {
1109+ name : "add exemplars to the limit, but no effect" ,
1110+ addFunc : func (h * histogram ) {
1111+ h .ObserveWithExemplar (1 , Labels {"id" : "1" })
1112+ h .ObserveWithExemplar (3 , Labels {"id" : "1" })
1113+ h .ObserveWithExemplar (5 , Labels {"id" : "1" })
1114+ },
1115+ expectedValues : []float64 {},
1116+ },
1117+ }
1118+
1119+ for _ , tc := range tcs {
1120+ t .Run (tc .name , func (t * testing.T ) {
1121+ tc .addFunc (h )
1122+ compareNativeExemplarValues (t , h .nativeExemplars .exemplars , tc .expectedValues )
1123+ })
1124+ }
1125+ }
1126+
1127+ func compareNativeExemplarValues (t * testing.T , exps []* dto.Exemplar , values []float64 ) {
1128+ if len (exps ) != len (values ) {
1129+ t .Errorf ("the count of exemplars is not %d" , len (values ))
1130+ }
1131+ for i , e := range exps {
1132+ if e .GetValue () != values [i ] {
1133+ t .Errorf ("the %dth exemplar value %v is not as expected: %v" , i , e .GetValue (), values [i ])
1134+ }
1135+ }
1136+ }
1137+
1138+ var resultFindBucket int
1139+
1140+ func benchmarkFindBucket (b * testing.B , l int ) {
1141+ h := & histogram {upperBounds : make ([]float64 , l )}
1142+ for i := range h .upperBounds {
1143+ h .upperBounds [i ] = float64 (i )
1144+ }
1145+ v := float64 (l / 2 )
1146+
1147+ b .ResetTimer ()
1148+ for i := 0 ; i < b .N ; i ++ {
1149+ resultFindBucket = h .FindBucket (v )
1150+ }
1151+ }
1152+
1153+ func BenchmarkFindBucketShort (b * testing.B ) {
1154+ benchmarkFindBucket (b , 20 )
1155+ }
1156+
1157+ func BenchmarkFindBucketMid (b * testing.B ) {
1158+ benchmarkFindBucket (b , 40 )
1159+ }
1160+
1161+ func BenchmarkFindBucketLarge (b * testing.B ) {
1162+ benchmarkFindBucket (b , 100 )
1163+ }
1164+
1165+ func BenchmarkFindBucketHuge (b * testing.B ) {
1166+ benchmarkFindBucket (b , 500 )
1167+ }
1168+
1169+ func BenchmarkFindBucketInf (b * testing.B ) {
1170+ h := & histogram {upperBounds : make ([]float64 , 500 )}
1171+ for i := range h .upperBounds {
1172+ h .upperBounds [i ] = float64 (i )
1173+ }
1174+ v := 1000.5
1175+
1176+ b .ResetTimer ()
1177+ for i := 0 ; i < b .N ; i ++ {
1178+ resultFindBucket = h .FindBucket (v )
1179+ }
1180+ }
1181+
1182+ func BenchmarkFindBucketLow (b * testing.B ) {
1183+ h := & histogram {upperBounds : make ([]float64 , 500 )}
1184+ for i := range h .upperBounds {
1185+ h .upperBounds [i ] = float64 (i )
1186+ }
1187+ v := - 1.1
1188+
1189+ b .ResetTimer ()
1190+ for i := 0 ; i < b .N ; i ++ {
1191+ resultFindBucket = h .FindBucket (v )
1192+ }
1193+ }
1194+
1195+ func TestFindBucket (t * testing.T ) {
1196+ smallHistogram := & histogram {upperBounds : []float64 {1 , 2 , 3 , 4 , 5 }}
1197+ largeHistogram := & histogram {upperBounds : make ([]float64 , 50 )}
1198+ for i := range largeHistogram .upperBounds {
1199+ largeHistogram .upperBounds [i ] = float64 (i )
1200+ }
1201+
1202+ tests := []struct {
1203+ h * histogram
1204+ v float64
1205+ expected int
1206+ }{
1207+ {smallHistogram , - 1 , 0 },
1208+ {smallHistogram , 0.5 , 0 },
1209+ {smallHistogram , 2.5 , 2 },
1210+ {smallHistogram , 5.5 , 5 },
1211+ {largeHistogram , - 1 , 0 },
1212+ {largeHistogram , 25.5 , 26 },
1213+ {largeHistogram , 49.5 , 50 },
1214+ {largeHistogram , 50.5 , 50 },
1215+ {largeHistogram , 5000.5 , 50 },
1216+ }
1217+
1218+ for _ , tt := range tests {
1219+ result := tt .h .FindBucket (tt .v )
1220+ if result != tt .expected {
1221+ t .Errorf ("findBucket(%v) = %d; expected %d" , tt .v , result , tt .expected )
1222+ }
1223+ }
1224+ }
0 commit comments