@@ -1658,7 +1658,10 @@ func addAndResetCounts(hot, cold *histogramCounts) {
1658
1658
type nativeExemplars struct {
1659
1659
sync.Mutex
1660
1660
1661
- ttl time.Duration
1661
+ // Time-to-live for exemplars, it is set to -1 if exemplars are disabled, that is NativeHistogramMaxExemplars is below 0.
1662
+ // The ttl is used on insertion to remove an exemplar that is older than ttl, if present.
1663
+ ttl time.Duration
1664
+
1662
1665
exemplars []* dto.Exemplar
1663
1666
}
1664
1667
@@ -1690,13 +1693,11 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1690
1693
n .Lock ()
1691
1694
defer n .Unlock ()
1692
1695
1693
- // The index where to insert the new exemplar.
1694
- var nIdx int = - 1
1695
-
1696
1696
// When the number of exemplars has not yet exceeded or
1697
1697
// is equal to cap(n.exemplars), then
1698
1698
// insert the new exemplar directly.
1699
1699
if len (n .exemplars ) < cap (n .exemplars ) {
1700
+ var nIdx int
1700
1701
for nIdx = 0 ; nIdx < len (n .exemplars ); nIdx ++ {
1701
1702
if * e .Value < * n .exemplars [nIdx ].Value {
1702
1703
break
@@ -1716,11 +1717,34 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1716
1717
1717
1718
// When the number of exemplars exceeds the limit, remove one exemplar.
1718
1719
var (
1719
- ot = time .Now () // Oldest timestamp seen.
1720
- otIdx = - 1 // Index of the exemplar with the oldest timestamp.
1721
-
1722
- md = - 1.0 // Logarithm of the delta of the closest pair of exemplars.
1723
- rIdx = - 1 // Index of the older exemplar within the closest pair and where we need to insert the new exemplar.
1720
+ ot = time.Time {} // Oldest timestamp seen. Initial value doesn't matter as we replace it due to otIdx == -1 in the loop.
1721
+ otIdx = - 1 // Index of the exemplar with the oldest timestamp.
1722
+
1723
+ md = - 1.0 // Logarithm of the delta of the closest pair of exemplars.
1724
+
1725
+ // The insertion point of the new exemplar in the exemplars slice after insertion.
1726
+ // This is calculated purely based on the order of the exemplars by value.
1727
+ // nIdx == len(n.exemplars) means the new exemplar is to be inserted after the end.
1728
+ nIdx = - 1
1729
+
1730
+ // rIdx is ultimately the index for the exemplar that we are replacing with the new exemplar.
1731
+ // The aim is to keep a good spread of exemplars by value and not let them bunch up too much.
1732
+ // It is calculated in 3 steps:
1733
+ // 1. First we set rIdx to the index of the older exemplar within the closest pair by value.
1734
+ // That is the following will be true (on log scale):
1735
+ // either the exemplar pair on index (rIdx-1, rIdx) or (rIdx, rIdx+1) will have
1736
+ // the closest values to each other from all pairs.
1737
+ // For example, suppose the values are distributed like this:
1738
+ // |-----------x-------------x----------------x----x-----|
1739
+ // ^--rIdx as this is older.
1740
+ // Or like this:
1741
+ // |-----------x-------------x----------------x----x-----|
1742
+ // ^--rIdx as this is older.
1743
+ // 2. If there is an exemplar that expired, then we simple reset rIdx to that index.
1744
+ // 3. We check if by inserting the new exemplar we would create a closer pair at
1745
+ // (nIdx-1, nIdx) or (nIdx, nIdx+1) and set rIdx to nIdx-1 or nIdx accordingly to
1746
+ // keep the spread of exemplars by value; otherwise we keep rIdx as it is.
1747
+ rIdx = - 1
1724
1748
cLog float64 // Logarithm of the current exemplar.
1725
1749
pLog float64 // Logarithm of the previous exemplar.
1726
1750
)
@@ -1745,7 +1769,7 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1745
1769
}
1746
1770
diff := math .Abs (cLog - pLog )
1747
1771
if md == - 1 || diff < md {
1748
- // The closest exemplar pair is this: |exemplar.[i] - n.exemplars[i-1].Value| is minimal .
1772
+ // The closest exemplar pair is at index: i-1, i .
1749
1773
// Choose the exemplar with the older timestamp for replacement.
1750
1774
md = diff
1751
1775
if n .exemplars [i ].Timestamp .AsTime ().Before (n .exemplars [i - 1 ].Timestamp .AsTime ()) {
@@ -1763,7 +1787,8 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1763
1787
nIdx = len (n .exemplars )
1764
1788
}
1765
1789
// Here, we have the following relationships:
1766
- // n.exemplars[nIdx-1].Value < e.Value <= n.exemplars[nIdx].Value
1790
+ // n.exemplars[nIdx-1].Value < e.Value (if nIdx > 0)
1791
+ // e.Value <= n.exemplars[nIdx].Value (if nIdx < len(n.exemplars))
1767
1792
1768
1793
if otIdx != - 1 && e .Timestamp .AsTime ().Sub (ot ) > n .ttl {
1769
1794
// If the oldest exemplar has expired, then replace it with the new exemplar.
@@ -1776,19 +1801,23 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1776
1801
if nIdx > 0 {
1777
1802
diff := math .Abs (elog - math .Log (n .exemplars [nIdx - 1 ].GetValue ()))
1778
1803
if diff < md {
1779
- // The closest exemplar pair is this: |e.Value - n.exemplars[nIdx-1].Value| is minimal.
1780
- // Assume that the exemplar we are inserting has a newer timestamp. This is not always
1781
- // true, due to concurrency, but it's a good enough approximation.
1804
+ // The value we are about to insert is closer to the previous exemplar at the insertion point than what we calculated before in rIdx.
1805
+ // v--rIdx
1806
+ // |-----------x-n-----------x----------------x----x-----|
1807
+ // nIdx-1--^ ^--new exemplar value
1808
+ // Do not make the spread worse, replace nIdx-1 and not rIdx.
1782
1809
md = diff
1783
1810
rIdx = nIdx - 1
1784
1811
}
1785
1812
}
1786
1813
if nIdx < len (n .exemplars ) {
1787
1814
diff := math .Abs (math .Log (n .exemplars [nIdx ].GetValue ()) - elog )
1788
1815
if diff < md {
1789
- // The closest exemplar pair is this: |n.exemplars[nIdx].Value - e.Value| is minimal.
1790
- // Assume that the exemplar we are inserting has a newer timestamp. This is not always
1791
- // true, due to concurrency, but it's a good enough approximation.
1816
+ // The value we are about to insert is closer to the next exemplar at the insertion point than what we calculated before in rIdx.
1817
+ // v--rIdx
1818
+ // |-----------x-----------n-x----------------x----x-----|
1819
+ // new exemplar value--^ ^--nIdx
1820
+ // Do not make the spread worse, replace nIdx-1 and not rIdx.
1792
1821
rIdx = nIdx
1793
1822
}
1794
1823
}
0 commit comments