/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 |