Coverage Report

Created: 2026-06-11 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/trafficserver/include/tsutil/Histogram.h
Line
Count
Source
1
/** @file
2
 *
3
 * Fast small foot print histogram support.
4
5
  @section license License
6
7
  Licensed to the Apache Software Foundation (ASF) under one
8
  or more contributor license agreements.  See the NOTICE file
9
  distributed with this work for additional information
10
  regarding copyright ownership.  The ASF licenses this file
11
  to you under the Apache License, Version 2.0 (the
12
  "License"); you may not use this file except in compliance
13
  with the License.  You may obtain a copy of the License at
14
15
      http://www.apache.org/licenses/LICENSE-2.0
16
17
  Unless required by applicable law or agreed to in writing, software
18
  distributed under the License is distributed on an "AS IS" BASIS,
19
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
  See the License for the specific language governing permissions and
21
  limitations under the License.
22
 */
23
24
#pragma once
25
26
#include <cstdint>
27
#include <array>
28
29
namespace ts
30
{
31
/** Small fast histogram.
32
 *
33
 * This is a stepped logarithmic histogram. Each range is twice the size of the previous range. Each range is divided into equal
34
 * sized spans, with a bucket for each span. The ranges and spans are defined by the @a R and @a S template parameters. There is
35
 * an underflow range for values less than @c 2^S. There is a range for each power of 2 from @c 2^S to @c 2^(S+R-1) and overflow
36
 * bucket for values greater than or equal to @c 2^(R+S-1).
37
 *
38
 * This can also been seen as having a range for each bit from @c S to @c S+R-1. The bucket is determined by the most significant
39
 * bit of the value. If it is past @c S+R-1 then it is put in the overflow bucket. If the MSB is less than @c S then it is put in
40
 * a bucket in the underflow range, values <tt>0 .. (2^S)-1</tt>. For normal ranges, the range is determined by the bit index and
41
 * the next @c S bits are used as an index into the buckets for that range. Note this is the same for the underflow range which is
42
 * used when the MSB is in the first @c S bits.
43
 *
44
 * For example, if @a S is 2 then the buckets are (where @c U is an underflow bucket)
45
 * <tt>0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28, ...</tt> <- Sample value
46
 * <tt>U U U U 0 0 0 0 1  1  1  1  2  2  2  2  ...</tt> <- Range
47
 *
48
 * To keep data relevant there is a decay mechanism will divides all of the bucket counts by 2. If done periodically this creates
49
 * an exponential decay of sample data, which is less susceptible to timing issues. Instances can be summed so that parallel
50
 * instances can be kept on different threads without locking and then combined.
51
 *
52
 * @tparam R Bits for the overall range of the histogram.
53
 * @tparam S Bits used for spans inside a range.
54
 *
55
 */
56
template <auto R, auto S> class Histogram
57
{
58
  using self_type = Histogram; ///< Self reference type.
59
60
public:
61
  /// Type used for internal calculations.
62
  using raw_type = uint64_t;
63
  /// Number of bits to use for the base range.
64
  static constexpr raw_type N_RANGE_BITS = R;
65
  /// Number of bits to split each base range in to span buckets.
66
  static constexpr raw_type N_SPAN_BITS = S;
67
  /// Number of buckets per span.
68
  static constexpr raw_type N_SPAN_BUCKETS = static_cast<raw_type>(1) << N_SPAN_BITS;
69
  /// Mask to extract the local bucket index from a sample.
70
  static constexpr raw_type SPAN_MASK = (static_cast<raw_type>(1) << N_SPAN_BITS) - 1;
71
  /// Initial mask to find the MSB in the sample.
72
  static constexpr raw_type MSB_MASK = static_cast<raw_type>(1) << (N_RANGE_BITS + N_SPAN_BITS - 1);
73
  /// Total number of buckets - 1 for overflow and an extra range for less than @c UNDERFLOW_BOUND
74
  static constexpr raw_type N_BUCKETS = ((N_RANGE_BITS + 1) * N_SPAN_BUCKETS) + 1;
75
  /// Samples less than this go in the underflow range.
76
  static constexpr raw_type UNDERFLOW_BOUND = static_cast<raw_type>(1) << N_SPAN_BITS;
77
  /// Sample equal or greater than this  go in the overflow bucket.
78
  static constexpr raw_type OVERFLOW_BOUND = static_cast<raw_type>(1) << (N_RANGE_BITS + N_SPAN_BITS);
79
80
  /** Add @sample to the histogram.
81
   *
82
   * @param sample Value to add.
83
   * @return @a this
84
   */
85
  self_type &operator()(raw_type sample);
86
87
  /** Decrease all values by a factor of 2.
88
   *
89
   * @return @a this
90
   */
91
  self_type &decay();
92
93
  /** Get bucket count.
94
   *
95
   * @param idx Index of the bucket.
96
   * @return Count in the bucket.
97
   */
98
  raw_type operator[](unsigned idx);
99
100
  /** Minimum value for samples in bucket.
101
   *
102
   * @param idx Index of the bucket.
103
   * @return The smallest sample value that will increment the bucket.
104
   */
105
  static raw_type min_for_bucket(unsigned idx);
106
107
  /** Add counts from another histogram.
108
   *
109
   * @param that Source histogram.
110
   * @return @a this
111
   *
112
   * The buckets are added in parallel.
113
   */
114
  self_type &operator+=(self_type const &that);
115
116
protected:
117
  /// The buckets.
118
  std::array<raw_type, N_BUCKETS> _bucket = {0};
119
};
120
121
/// @cond INTERNAL_DETAIL
122
template <auto R, auto S>
123
auto
124
Histogram<R, S>::operator[](unsigned int idx) -> raw_type
125
0
{
126
0
  return _bucket[idx];
127
0
}
128
129
template <auto R, auto S>
130
auto
131
Histogram<R, S>::operator+=(self_type const &that) -> self_type &
132
0
{
133
0
  auto dst = _bucket.data();
134
0
  auto src = that._bucket.data();
135
0
  for (raw_type idx = 0; idx < N_BUCKETS; ++idx) {
136
0
    *dst++ += *src++;
137
0
  }
138
0
  return *this;
139
0
}
140
141
template <auto R, auto S>
142
auto
143
Histogram<R, S>::operator()(raw_type sample) -> self_type &
144
0
{
145
0
  int idx = N_BUCKETS - 1; // index of overflow bucket
146
0
  if (sample < UNDERFLOW_BOUND) {
147
0
    idx = sample;                       // sample -> bucket is identity in the underflow range.
148
0
  } else if (sample < OVERFLOW_BOUND) { // not overflow bucket.
149
0
    idx       -= N_SPAN_BUCKETS;        // bottom bucket in the range.
150
0
    auto mask  = MSB_MASK;              // Mask to probe for bit set.
151
    // Shift needed after finding the MSB to put the span bits in the LSBs.
152
0
    unsigned normalize_shift_count = N_RANGE_BITS - 1;
153
    // Walk the mask bit down until the MSB is found. Each span bumps down the bucket index
154
    // and the shift for the span bits. An MSB will be found because @a sample >= @c UNDERFLOW_BOUND
155
    // The MSB is not before @c MSB_MASK because @a sample < @c UPPER_BOUND
156
0
    while (0 == (sample & mask)) {
157
0
      mask >>= 1;
158
0
      --normalize_shift_count;
159
0
      idx -= N_SPAN_BUCKETS;
160
0
    }
161
0
    idx += (sample >> normalize_shift_count) & SPAN_MASK;
162
0
  } // else idx remains the overflow bucket.
163
0
  ++_bucket[idx];
164
0
  return *this;
165
0
}
166
167
template <auto R, auto S>
168
auto
169
Histogram<R, S>::min_for_bucket(unsigned idx) -> raw_type
170
66
{
171
66
  auto     range     = idx / N_SPAN_BUCKETS;
172
66
  raw_type base      = 0; // minimum value for the range (not span!).
173
66
  raw_type span_size = 1; // for @a range 0 or 1
174
66
  if (range > 0) {
175
58
    base = 1 << (range + N_SPAN_BITS - 1);
176
58
    if (range > 1) { // at @a range == 1 this would be 0, which is wrong.
177
50
      span_size = base >> N_SPAN_BITS;
178
50
    }
179
58
  }
180
66
  return base + span_size * (idx & SPAN_MASK);
181
66
}
182
183
template <auto R, auto S>
184
auto
185
Histogram<R, S>::decay() -> self_type &
186
0
{
187
0
  for (auto &v : _bucket) {
188
0
    v >>= 1;
189
0
  }
190
0
  return *this;
191
0
}
192
193
/// @endcond
194
195
} // namespace ts