Skip to content

Commit 494ccce

Browse files
committed
add native histogram exemplar support
Signed-off-by: Ziqi Zhao <[email protected]>
1 parent 7882668 commit 494ccce

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

prometheus/histogram.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,8 @@ type HistogramOpts struct {
472472
NativeHistogramMaxBucketNumber uint32
473473
NativeHistogramMinResetDuration time.Duration
474474
NativeHistogramMaxZeroThreshold float64
475+
NativeHistogramMaxExemplarCount uint32
476+
NativeHistogramExemplarTTL time.Duration
475477

476478
// now is for testing purposes, by default it's time.Now.
477479
now func() time.Time
@@ -556,6 +558,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
556558
h.nativeHistogramZeroThreshold = DefNativeHistogramZeroThreshold
557559
} // Leave h.nativeHistogramZeroThreshold at 0 otherwise.
558560
h.nativeHistogramSchema = pickSchema(opts.NativeHistogramBucketFactor)
561+
h.nativeExemplars = newNativeExemplars(opts.NativeHistogramExemplarTTL, opts.NativeHistogramMaxExemplarCount)
559562
}
560563
for i, upperBound := range h.upperBounds {
561564
if i < len(h.upperBounds)-1 {
@@ -732,6 +735,8 @@ type histogram struct {
732735

733736
// afterFunc is for testing purposes, by default it's time.AfterFunc.
734737
afterFunc func(time.Duration, func()) *time.Timer
738+
739+
nativeExemplars nativeExemplars
735740
}
736741

737742
func (h *histogram) Desc() *Desc {
@@ -821,6 +826,8 @@ func (h *histogram) Write(out *dto.Metric) error {
821826
Length: proto.Uint32(0),
822827
}}
823828
}
829+
830+
his.Exemplars = append(his.Exemplars, h.nativeExemplars.exemplars...)
824831
}
825832
addAndResetCounts(hotCounts, coldCounts)
826833
return nil
@@ -1102,6 +1109,10 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
11021109
panic(err)
11031110
}
11041111
h.exemplars[bucket].Store(e)
1112+
doSparse := h.nativeHistogramSchema > math.MinInt32 && !math.IsNaN(v)
1113+
if doSparse {
1114+
h.nativeExemplars.addExemplar(e)
1115+
}
11051116
}
11061117

11071118
// HistogramVec is a Collector that bundles a set of Histograms that all share the
@@ -1575,3 +1586,58 @@ func addAndResetCounts(hot, cold *histogramCounts) {
15751586
atomic.AddUint64(&hot.nativeHistogramZeroBucket, atomic.LoadUint64(&cold.nativeHistogramZeroBucket))
15761587
atomic.StoreUint64(&cold.nativeHistogramZeroBucket, 0)
15771588
}
1589+
1590+
type nativeExemplars struct {
1591+
nativeHistogramExemplarTTL time.Duration
1592+
nativeHistogramMaxExemplarCount uint32
1593+
1594+
exemplars []*dto.Exemplar
1595+
1596+
lock sync.Mutex
1597+
}
1598+
1599+
func newNativeExemplars(ttl time.Duration, count uint32) nativeExemplars {
1600+
return nativeExemplars{
1601+
nativeHistogramExemplarTTL: ttl,
1602+
nativeHistogramMaxExemplarCount: count,
1603+
exemplars: make([]*dto.Exemplar, 0),
1604+
lock: sync.Mutex{},
1605+
}
1606+
}
1607+
1608+
func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1609+
n.lock.Lock()
1610+
defer n.lock.Unlock()
1611+
1612+
elogarithm := math.Log(e.GetValue())
1613+
if len(n.exemplars) == int(n.nativeHistogramMaxExemplarCount) {
1614+
// check if oldestIndex is beyond TTL,
1615+
// if so, find the oldest exemplar, and nearest exemplar
1616+
oldestTimestamp := time.Now()
1617+
oldestIndex := -1
1618+
nearestValue := -1.0
1619+
nearestIndex := -1
1620+
1621+
for i, exemplar := range n.exemplars {
1622+
if exemplar.Timestamp.AsTime().Before(oldestTimestamp) {
1623+
oldestTimestamp = exemplar.Timestamp.AsTime()
1624+
oldestIndex = i
1625+
}
1626+
logarithm := math.Log(exemplar.GetValue())
1627+
if nearestValue == -1 || math.Abs(elogarithm-logarithm) < nearestValue {
1628+
fmt.Printf("gap: %f", math.Abs(elogarithm-logarithm))
1629+
nearestValue = math.Abs(elogarithm - logarithm)
1630+
nearestIndex = i
1631+
}
1632+
}
1633+
1634+
if oldestIndex != -1 && time.Since(oldestTimestamp) > n.nativeHistogramExemplarTTL {
1635+
n.exemplars[oldestIndex] = e
1636+
} else {
1637+
n.exemplars[nearestIndex] = e
1638+
}
1639+
return
1640+
}
1641+
1642+
n.exemplars = append(n.exemplars, e)
1643+
}

prometheus/histogram_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,3 +1271,91 @@ func TestHistogramVecCreatedTimestampWithDeletes(t *testing.T) {
12711271
now = now.Add(1 * time.Hour)
12721272
expectCTsForMetricVecValues(t, histogramVec.MetricVec, dto.MetricType_HISTOGRAM, expected)
12731273
}
1274+
1275+
func TestNativeHistogramExemplar(t *testing.T) {
1276+
histogram := NewHistogram(HistogramOpts{
1277+
Name: "test",
1278+
Help: "test help",
1279+
Buckets: []float64{1, 2, 3, 4},
1280+
NativeHistogramBucketFactor: 1.1,
1281+
NativeHistogramMaxExemplarCount: 3,
1282+
NativeHistogramExemplarTTL: 10 * time.Second,
1283+
}).(*histogram)
1284+
1285+
// expectedExemplars := []*dto.Exemplar{
1286+
// {
1287+
// Label: []*dto.LabelPair{
1288+
// {Name: proto.String("id"), Value: proto.String("1")},
1289+
// },
1290+
// Value: proto.Float64(1),
1291+
// },
1292+
// {
1293+
// Label: []*dto.LabelPair{
1294+
// {Name: proto.String("id"), Value: proto.String("2")},
1295+
// },
1296+
// Value: proto.Float64(3),
1297+
// },
1298+
// {
1299+
// Label: []*dto.LabelPair{
1300+
// {Name: proto.String("id"), Value: proto.String("3")},
1301+
// },
1302+
// Value: proto.Float64(5),
1303+
// },
1304+
// }
1305+
1306+
histogram.ObserveWithExemplar(1, Labels{"id": "1"})
1307+
histogram.ObserveWithExemplar(3, Labels{"id": "1"})
1308+
histogram.ObserveWithExemplar(5, Labels{"id": "1"})
1309+
1310+
if len(histogram.nativeExemplars.exemplars) != 3 {
1311+
t.Errorf("the count of exemplars is not 3")
1312+
}
1313+
1314+
expectedValues := map[float64]struct{}{
1315+
1: {},
1316+
3: {},
1317+
5: {},
1318+
}
1319+
1320+
for _, e := range histogram.nativeExemplars.exemplars {
1321+
if _, ok := expectedValues[e.GetValue()]; !ok {
1322+
t.Errorf("the value is not in expected value")
1323+
}
1324+
}
1325+
1326+
histogram.ObserveWithExemplar(4, Labels{"id": "1"})
1327+
1328+
if len(histogram.nativeExemplars.exemplars) != 3 {
1329+
t.Errorf("the count of exemplars is not 3")
1330+
}
1331+
1332+
expectedValues = map[float64]struct{}{
1333+
1: {},
1334+
3: {},
1335+
4: {},
1336+
}
1337+
1338+
for _, e := range histogram.nativeExemplars.exemplars {
1339+
if _, ok := expectedValues[e.GetValue()]; !ok {
1340+
t.Errorf("the value is not in expected value")
1341+
}
1342+
}
1343+
1344+
time.Sleep(10 * time.Second)
1345+
histogram.ObserveWithExemplar(6, Labels{"id": "1"})
1346+
1347+
if len(histogram.nativeExemplars.exemplars) != 3 {
1348+
t.Errorf("the count of exemplars is not 3")
1349+
}
1350+
1351+
expectedValues = map[float64]struct{}{
1352+
6: {},
1353+
3: {},
1354+
4: {},
1355+
}
1356+
for _, e := range histogram.nativeExemplars.exemplars {
1357+
if _, ok := expectedValues[e.GetValue()]; !ok {
1358+
t.Errorf("the value is not in expected value")
1359+
}
1360+
}
1361+
}

0 commit comments

Comments
 (0)