@@ -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
@@ -1673,6 +1676,7 @@ func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars {
1673
1676
1674
1677
if maxCount < 0 {
1675
1678
maxCount = 0
1679
+ ttl = - 1
1676
1680
}
1677
1681
1678
1682
return nativeExemplars {
@@ -1682,20 +1686,18 @@ func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars {
1682
1686
}
1683
1687
1684
1688
func (n * nativeExemplars ) addExemplar (e * dto.Exemplar ) {
1685
- if cap ( n . exemplars ) == 0 {
1689
+ if n . ttl == - 1 {
1686
1690
return
1687
1691
}
1688
1692
1689
1693
n .Lock ()
1690
1694
defer n .Unlock ()
1691
1695
1692
- // The index where to insert the new exemplar.
1693
- var nIdx int = - 1
1694
-
1695
1696
// When the number of exemplars has not yet exceeded or
1696
1697
// is equal to cap(n.exemplars), then
1697
1698
// insert the new exemplar directly.
1698
1699
if len (n .exemplars ) < cap (n .exemplars ) {
1700
+ var nIdx int
1699
1701
for nIdx = 0 ; nIdx < len (n .exemplars ); nIdx ++ {
1700
1702
if * e .Value < * n .exemplars [nIdx ].Value {
1701
1703
break
@@ -1705,17 +1707,46 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1705
1707
return
1706
1708
}
1707
1709
1710
+ if len (n .exemplars ) == 1 {
1711
+ // When the number of exemplars is 1, then
1712
+ // replace the existing exemplar with the new exemplar.
1713
+ n .exemplars [0 ] = e
1714
+ return
1715
+ }
1716
+ // From this point on, the number of exemplars is greater than 1.
1717
+
1708
1718
// When the number of exemplars exceeds the limit, remove one exemplar.
1709
1719
var (
1710
- rIdx int // The index where to remove the old exemplar.
1711
-
1712
- ot = time .Now () // Oldest timestamp seen.
1713
- otIdx = - 1 // Index of the exemplar with the oldest timestamp.
1714
-
1715
- md = - 1.0 // Logarithm of the delta of the closest pair of exemplars.
1716
- mdIdx = - 1 // Index of the older exemplar within the closest pair.
1717
- cLog float64 // Logarithm of the current exemplar.
1718
- pLog float64 // Logarithm of the previous 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
1748
+ cLog float64 // Logarithm of the current exemplar.
1749
+ pLog float64 // Logarithm of the previous exemplar.
1719
1750
)
1720
1751
1721
1752
for i , exemplar := range n .exemplars {
@@ -1726,7 +1757,7 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1726
1757
}
1727
1758
1728
1759
// Find the index at which to insert new the exemplar.
1729
- if * e .Value <= * exemplar .Value && nIdx == - 1 {
1760
+ if nIdx == - 1 && * e .Value <= * exemplar .Value {
1730
1761
nIdx = i
1731
1762
}
1732
1763
@@ -1738,11 +1769,13 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1738
1769
}
1739
1770
diff := math .Abs (cLog - pLog )
1740
1771
if md == - 1 || diff < md {
1772
+ // The closest exemplar pair is at index: i-1, i.
1773
+ // Choose the exemplar with the older timestamp for replacement.
1741
1774
md = diff
1742
1775
if n .exemplars [i ].Timestamp .AsTime ().Before (n .exemplars [i - 1 ].Timestamp .AsTime ()) {
1743
- mdIdx = i
1776
+ rIdx = i
1744
1777
} else {
1745
- mdIdx = i - 1
1778
+ rIdx = i - 1
1746
1779
}
1747
1780
}
1748
1781
@@ -1753,8 +1786,12 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1753
1786
if nIdx == - 1 {
1754
1787
nIdx = len (n .exemplars )
1755
1788
}
1789
+ // Here, we have the following relationships:
1790
+ // n.exemplars[nIdx-1].Value < e.Value (if nIdx > 0)
1791
+ // e.Value <= n.exemplars[nIdx].Value (if nIdx < len(n.exemplars))
1756
1792
1757
1793
if otIdx != - 1 && e .Timestamp .AsTime ().Sub (ot ) > n .ttl {
1794
+ // If the oldest exemplar has expired, then replace it with the new exemplar.
1758
1795
rIdx = otIdx
1759
1796
} else {
1760
1797
// In the previous for loop, when calculating the closest pair of exemplars,
@@ -1764,23 +1801,26 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
1764
1801
if nIdx > 0 {
1765
1802
diff := math .Abs (elog - math .Log (n .exemplars [nIdx - 1 ].GetValue ()))
1766
1803
if diff < md {
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.
1767
1809
md = diff
1768
- mdIdx = nIdx
1769
- if n .exemplars [nIdx - 1 ].Timestamp .AsTime ().Before (e .Timestamp .AsTime ()) {
1770
- mdIdx = nIdx - 1
1771
- }
1810
+ rIdx = nIdx - 1
1772
1811
}
1773
1812
}
1774
1813
if nIdx < len (n .exemplars ) {
1775
1814
diff := math .Abs (math .Log (n .exemplars [nIdx ].GetValue ()) - elog )
1776
1815
if diff < md {
1777
- mdIdx = nIdx
1778
- if n .exemplars [nIdx ].Timestamp .AsTime ().Before (e .Timestamp .AsTime ()) {
1779
- mdIdx = nIdx
1780
- }
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.
1821
+ rIdx = nIdx
1781
1822
}
1782
1823
}
1783
- rIdx = mdIdx
1784
1824
}
1785
1825
1786
1826
// Adjust the slice according to rIdx and nIdx.
0 commit comments