@@ -1658,7 +1658,10 @@ func addAndResetCounts(hot, cold *histogramCounts) {
16581658type  nativeExemplars  struct  {
16591659sync.Mutex 
16601660
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+ 
16621665exemplars  []* dto.Exemplar 
16631666}
16641667
@@ -1673,6 +1676,7 @@ func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars {
16731676
16741677if  maxCount  <  0  {
16751678maxCount  =  0 
1679+ ttl  =  - 1 
16761680}
16771681
16781682return  nativeExemplars {
@@ -1682,20 +1686,18 @@ func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars {
16821686}
16831687
16841688func  (n  * nativeExemplars ) addExemplar (e  * dto.Exemplar ) {
1685- if  cap ( n . exemplars )  ==  0  {
1689+ if  n . ttl  ==  - 1  {
16861690return 
16871691}
16881692
16891693n .Lock ()
16901694defer  n .Unlock ()
16911695
1692- // The index where to insert the new exemplar. 
1693- var  nIdx  int  =  - 1 
1694- 
16951696// When the number of exemplars has not yet exceeded or 
16961697// is equal to cap(n.exemplars), then 
16971698// insert the new exemplar directly. 
16981699if  len (n .exemplars ) <  cap (n .exemplars ) {
1700+ var  nIdx  int 
16991701for  nIdx  =  0 ; nIdx  <  len (n .exemplars ); nIdx ++  {
17001702if  * e .Value  <  * n .exemplars [nIdx ].Value  {
17011703break 
@@ -1705,17 +1707,46 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
17051707return 
17061708}
17071709
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+ 
17081718// When the number of exemplars exceeds the limit, remove one exemplar. 
17091719var  (
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. 
17191750)
17201751
17211752for  i , exemplar  :=  range  n .exemplars  {
@@ -1726,7 +1757,7 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
17261757}
17271758
17281759// 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  {
17301761nIdx  =  i 
17311762}
17321763
@@ -1738,11 +1769,13 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
17381769}
17391770diff  :=  math .Abs (cLog  -  pLog )
17401771if  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. 
17411774md  =  diff 
17421775if  n .exemplars [i ].Timestamp .AsTime ().Before (n .exemplars [i - 1 ].Timestamp .AsTime ()) {
1743- mdIdx  =  i 
1776+ rIdx  =  i 
17441777} else  {
1745- mdIdx  =  i  -  1 
1778+ rIdx  =  i  -  1 
17461779}
17471780}
17481781
@@ -1753,8 +1786,12 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
17531786if  nIdx  ==  - 1  {
17541787nIdx  =  len (n .exemplars )
17551788}
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)) 
17561792
17571793if  otIdx  !=  - 1  &&  e .Timestamp .AsTime ().Sub (ot ) >  n .ttl  {
1794+ // If the oldest exemplar has expired, then replace it with the new exemplar. 
17581795rIdx  =  otIdx 
17591796} else  {
17601797// In the previous for loop, when calculating the closest pair of exemplars, 
@@ -1764,23 +1801,26 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
17641801if  nIdx  >  0  {
17651802diff  :=  math .Abs (elog  -  math .Log (n .exemplars [nIdx - 1 ].GetValue ()))
17661803if  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. 
17671809md  =  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 
17721811}
17731812}
17741813if  nIdx  <  len (n .exemplars ) {
17751814diff  :=  math .Abs (math .Log (n .exemplars [nIdx ].GetValue ()) -  elog )
17761815if  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 
17811822}
17821823}
1783- rIdx  =  mdIdx 
17841824}
17851825
17861826// Adjust the slice according to rIdx and nIdx. 
0 commit comments