/src/brpc/src/bvar/detail/percentile.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | // Date: 2015/09/15 10:44:17 |
19 | | |
20 | | #ifndef BVAR_DETAIL_PERCENTILE_H |
21 | | #define BVAR_DETAIL_PERCENTILE_H |
22 | | |
23 | | #include <string.h> // memset memcmp |
24 | | #include <stdint.h> // uint32_t |
25 | | #include <limits> // std::numeric_limits |
26 | | #include <ostream> // std::ostream |
27 | | #include <algorithm> // std::sort |
28 | | #include <math.h> // ceil |
29 | | #include "butil/macros.h" // ARRAY_SIZE |
30 | | #include "bvar/reducer.h" // Reducer |
31 | | #include "bvar/window.h" // Window |
32 | | #include "bvar/detail/combiner.h" // AgentCombiner |
33 | | #include "bvar/detail/sampler.h" // ReducerSampler |
34 | | #include "butil/fast_rand.h" |
35 | | #if WITH_BABYLON_COUNTER |
36 | | #include "babylon/concurrent/counter.h" |
37 | | #endif // WITH_BABYLON_COUNTER |
38 | | |
39 | | namespace bvar { |
40 | | namespace detail { |
41 | | |
42 | | // Round of expectation of a rational number |a/b| to a natural number. |
43 | 0 | inline unsigned long round_of_expectation(unsigned long a, unsigned long b) { |
44 | 0 | if (BAIDU_UNLIKELY(b == 0)) { |
45 | 0 | return 0; |
46 | 0 | } |
47 | 0 | return a / b + (butil::fast_rand_less_than(b) < a % b); |
48 | 0 | } |
49 | | |
50 | | // Storing latencies inside a interval. |
51 | | template <size_t SAMPLE_SIZE> |
52 | | class PercentileInterval { |
53 | | public: |
54 | | PercentileInterval() |
55 | 0 | : _num_added(0) |
56 | 0 | , _sorted(false) |
57 | 0 | , _num_samples(0) { |
58 | 0 | } Unexecuted instantiation: bvar::detail::PercentileInterval<254ul>::PercentileInterval() Unexecuted instantiation: bvar::detail::PercentileInterval<1022ul>::PercentileInterval() Unexecuted instantiation: bvar::detail::PercentileInterval<30ul>::PercentileInterval() |
59 | | |
60 | | // Get index-th sample in ascending order. |
61 | 0 | uint32_t get_sample_at(size_t index) { |
62 | 0 | const size_t saved_num = _num_samples; |
63 | 0 | if (index >= saved_num) { |
64 | 0 | if (saved_num == 0) { |
65 | 0 | return 0; |
66 | 0 | } |
67 | 0 | index = saved_num - 1; |
68 | 0 | } |
69 | 0 | if (!_sorted) { |
70 | 0 | std::sort(_samples, _samples + saved_num); |
71 | 0 | _sorted = true; |
72 | 0 | } |
73 | 0 | CHECK_EQ(saved_num, _num_samples) << "You must call get_number() on" |
74 | 0 | " a unchanging PercentileInterval"; |
75 | 0 | return _samples[index]; |
76 | 0 | } |
77 | | |
78 | | // Add samples of another interval. This function tries to make each |
79 | | // sample in merged _samples has (approximately) equal probability to |
80 | | // remain. |
81 | | // This method is invoked when merging ThreadLocalPercentileSamples in to |
82 | | // GlobalPercentileSamples |
83 | | template <size_t size2> |
84 | 0 | void merge(const PercentileInterval<size2> &rhs) { |
85 | 0 | if (rhs._num_added == 0) { |
86 | 0 | return; |
87 | 0 | } |
88 | 0 | BAIDU_CASSERT(SAMPLE_SIZE >= size2, |
89 | 0 | must_merge_small_interval_into_larger_one_currently); |
90 | 0 | CHECK_EQ(rhs._num_samples, rhs._num_added); |
91 | | // Assume that the probability of each sample in |this| is a0/b0 and |
92 | | // the probability of each sample in |rhs| is a1/b1. |
93 | | // We are going to randomly pick some samples from |this| and |rhs| to |
94 | | // satisfy the constraint that each sample stands for the probability |
95 | | // of |
96 | | // * 1 (SAMPLE_SIZE >= |b0 + b1|), which indicates that no sample |
97 | | // has been dropped |
98 | | // * SAMPLE_SIZE / |b0 + b1| (SAMPLE_SIZE < |b0 + b1|) |
99 | | // So we should keep |b0*SAMPLE_SIZE/(b0+b1)| from |this| |
100 | | // |b1*SAMPLE_SIZE/(b0+b1)| from |rhs|. |
101 | 0 | if (_num_added + rhs._num_added <= SAMPLE_SIZE) { |
102 | | // No sample should be dropped |
103 | 0 | CHECK_EQ(_num_samples, _num_added) |
104 | 0 | << "_num_added=" << _num_added |
105 | 0 | << " rhs._num_added=" << rhs._num_added |
106 | 0 | << " _num_samples=" << _num_samples |
107 | 0 | << " rhs._num_samples=" << rhs._num_samples |
108 | 0 | << " SAMPLE_SIZE=" << SAMPLE_SIZE |
109 | 0 | << " size2=" << size2; |
110 | 0 | memcpy(_samples + _num_samples, rhs._samples, |
111 | 0 | sizeof(_samples[0]) * rhs._num_samples); |
112 | 0 | _num_samples += rhs._num_samples; |
113 | 0 | } else { |
114 | | // |num_remain| must be less than _num_samples: |
115 | | // if _num_added = _num_samples: |
116 | | // SAMPLE_SIZE / (_num_added + rhs._num_added) < 1 so that |
117 | | // num_remain < _num_added = _num_samples |
118 | | // otherwise: |
119 | | // _num_samples = SAMPLE_SIZE; |
120 | | // _num_added / (_num_added + rhs._num_added) < 1 so that |
121 | | // num_remain < SAMPLE_SIZE = _num_added |
122 | 0 | size_t num_remain = round_of_expectation( |
123 | 0 | _num_added * SAMPLE_SIZE, _num_added + rhs._num_added); |
124 | 0 | CHECK_LE(num_remain, _num_samples); |
125 | | // Randomly drop samples of this |
126 | 0 | for (size_t i = _num_samples; i > num_remain; --i) { |
127 | 0 | _samples[butil::fast_rand_less_than(i)] = _samples[i - 1]; |
128 | 0 | } |
129 | 0 | const size_t num_remain_from_rhs = SAMPLE_SIZE - num_remain; |
130 | 0 | CHECK_LE(num_remain_from_rhs, rhs._num_samples); |
131 | | // Have to copy data from rhs to shuffle since it's const |
132 | 0 | DEFINE_SMALL_ARRAY(uint32_t, tmp, rhs._num_samples, 64); |
133 | 0 | memcpy(tmp, rhs._samples, sizeof(uint32_t) * rhs._num_samples); |
134 | 0 | for (size_t i = 0; i < num_remain_from_rhs; ++i) { |
135 | 0 | const int index = butil::fast_rand_less_than(rhs._num_samples - i); |
136 | 0 | _samples[num_remain++] = tmp[index]; |
137 | 0 | tmp[index] = tmp[rhs._num_samples - i - 1]; |
138 | 0 | } |
139 | 0 | _num_samples = num_remain; |
140 | 0 | CHECK_EQ(_num_samples, SAMPLE_SIZE); |
141 | 0 | } |
142 | 0 | _num_added += rhs._num_added; |
143 | 0 | } Unexecuted instantiation: void bvar::detail::PercentileInterval<254ul>::merge<254ul>(bvar::detail::PercentileInterval<254ul> const&) Unexecuted instantiation: void bvar::detail::PercentileInterval<254ul>::merge<30ul>(bvar::detail::PercentileInterval<30ul> const&) |
144 | | |
145 | | #if WITH_BABYLON_COUNTER |
146 | | size_t merge(const babylon::ConcurrentSampler::SampleBucket& bucket) { |
147 | | auto num_added = bucket.record_num.load(std::memory_order_acquire); |
148 | | if (num_added == 0) { |
149 | | return 0; |
150 | | } |
151 | | auto num_samples = std::min(num_added, static_cast<uint32_t>(bucket.capacity)); |
152 | | // If there is space, deposit directly. |
153 | | if (_num_samples + num_samples <= SAMPLE_SIZE) { |
154 | | __builtin_memcpy(_samples + _num_samples, bucket.data, |
155 | | sizeof(uint32_t) * num_samples); |
156 | | _num_samples += num_samples; |
157 | | } else { |
158 | | // Sample probability weighting. |
159 | | float ratio = static_cast<float>(num_samples) / num_added; |
160 | | // Try to deposit directly first. |
161 | | if (_num_samples < SAMPLE_SIZE) { |
162 | | auto copy_size = SAMPLE_SIZE - _num_samples; |
163 | | num_samples -= copy_size; |
164 | | __builtin_memcpy(_samples + _num_samples, |
165 | | bucket.data + num_samples, sizeof(uint32_t) * copy_size); |
166 | | } |
167 | | // The remaining samples are stored according to probability. |
168 | | for (size_t i = 0; i < num_samples; ++i) { |
169 | | auto index = butil::fast_rand() % |
170 | | static_cast<uint64_t>((_num_added + i) * ratio + 1); |
171 | | if (index < SAMPLE_SIZE) { |
172 | | _samples[index] = bucket.data[i]; |
173 | | } |
174 | | } |
175 | | _num_samples = SAMPLE_SIZE; |
176 | | } |
177 | | _num_added += num_added; |
178 | | return num_added; |
179 | | } |
180 | | #endif // WITH_BABYLON_COUNTER |
181 | | |
182 | | // Randomly pick n samples from mutable_rhs to |this| |
183 | | template <size_t size2> |
184 | 0 | void merge_with_expectation(PercentileInterval<size2>& mutable_rhs, size_t n) { |
185 | 0 | CHECK(n <= mutable_rhs._num_samples); |
186 | 0 | _num_added += mutable_rhs._num_added; |
187 | 0 | if (_num_samples + n <= SAMPLE_SIZE && n == mutable_rhs._num_samples) { |
188 | 0 | memcpy(_samples + _num_samples, mutable_rhs._samples, sizeof(_samples[0]) * n); |
189 | 0 | _num_samples += n; |
190 | 0 | return; |
191 | 0 | } |
192 | 0 | for (size_t i = 0; i < n; ++i) { |
193 | 0 | size_t index = butil::fast_rand_less_than(mutable_rhs._num_samples - i); |
194 | 0 | if (_num_samples < SAMPLE_SIZE) { |
195 | 0 | _samples[_num_samples++] = mutable_rhs._samples[index]; |
196 | 0 | } else { |
197 | 0 | _samples[butil::fast_rand_less_than(_num_samples)] |
198 | 0 | = mutable_rhs._samples[index]; |
199 | 0 | } |
200 | 0 | std::swap(mutable_rhs._samples[index], |
201 | 0 | mutable_rhs._samples[mutable_rhs._num_samples - i - 1]); |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | // Add an unsigned 32-bit latency (what percentile actually accepts) to a |
206 | | // non-full interval, which is invoked by Percentile::operator<< to add a |
207 | | // sample into the ThreadLocalPercentileSamples. |
208 | | // Returns true if the input was stored. |
209 | 0 | bool add32(uint32_t x) { |
210 | 0 | if (BAIDU_UNLIKELY(_num_samples >= SAMPLE_SIZE)) { |
211 | 0 | LOG(ERROR) << "This interval was full"; |
212 | 0 | return false; |
213 | 0 | } |
214 | 0 | ++_num_added; |
215 | 0 | _samples[_num_samples++] = x; |
216 | 0 | return true; |
217 | 0 | } |
218 | | |
219 | | // Add a signed latency. |
220 | 0 | bool add64(int64_t x) { |
221 | 0 | if (x >= 0) { |
222 | 0 | return add32((uint32_t)x); |
223 | 0 | } |
224 | 0 | return false; |
225 | 0 | } |
226 | | |
227 | | // Remove all samples inside. |
228 | 0 | void clear() { |
229 | 0 | _num_added = 0; |
230 | 0 | _sorted = false; |
231 | 0 | _num_samples = 0; |
232 | 0 | } Unexecuted instantiation: bvar::detail::PercentileInterval<254ul>::clear() Unexecuted instantiation: bvar::detail::PercentileInterval<1022ul>::clear() Unexecuted instantiation: bvar::detail::PercentileInterval<30ul>::clear() |
233 | | |
234 | | // True if no more room for new samples. |
235 | 0 | bool full() const { return _num_samples == SAMPLE_SIZE; } |
236 | | |
237 | | // True if there's no samples. |
238 | 0 | bool empty() const { return !_num_samples; }Unexecuted instantiation: bvar::detail::PercentileInterval<254ul>::empty() const Unexecuted instantiation: bvar::detail::PercentileInterval<30ul>::empty() const |
239 | | |
240 | | // #samples ever added by calling add*() |
241 | 0 | uint32_t added_count() const { return _num_added; }Unexecuted instantiation: bvar::detail::PercentileInterval<254ul>::added_count() const Unexecuted instantiation: bvar::detail::PercentileInterval<1022ul>::added_count() const Unexecuted instantiation: bvar::detail::PercentileInterval<30ul>::added_count() const |
242 | | |
243 | | // #samples stored. |
244 | 0 | uint32_t sample_count() const { return _num_samples; }Unexecuted instantiation: bvar::detail::PercentileInterval<254ul>::sample_count() const Unexecuted instantiation: bvar::detail::PercentileInterval<1022ul>::sample_count() const |
245 | | |
246 | | // For debuggin. |
247 | 0 | void describe(std::ostream &os) const { |
248 | 0 | os << "(num_added=" << added_count() << ")["; |
249 | 0 | for (size_t j = 0; j < _num_samples; ++j) { |
250 | 0 | os << ' ' << _samples[j]; |
251 | 0 | } |
252 | 0 | os << " ]"; |
253 | 0 | } |
254 | | |
255 | | // True if two PercentileInterval are exactly same, namely same # of added and |
256 | | // same samples, mainly for debuggin. |
257 | | bool operator==(const PercentileInterval& rhs) const { |
258 | | return (_num_added == rhs._num_added && |
259 | | _num_samples == rhs._num_samples && |
260 | | memcmp(_samples, rhs._samples, _num_samples * sizeof(uint32_t)) == 0); |
261 | | } |
262 | | |
263 | | private: |
264 | | template <size_t size2> friend class PercentileInterval; |
265 | | BAIDU_CASSERT(SAMPLE_SIZE <= 65536, SAMPLE_SIZE_must_be_16bit); |
266 | | |
267 | | uint32_t _num_added; |
268 | | bool _sorted; |
269 | | uint16_t _num_samples; |
270 | | uint32_t _samples[SAMPLE_SIZE]; |
271 | | }; |
272 | | |
273 | | static const size_t NUM_INTERVALS = 32; |
274 | | |
275 | | // This declartion is a must for gcc 3.4 |
276 | | class AddLatency; |
277 | | |
278 | | // Group of PercentileIntervals. |
279 | | template <size_t SAMPLE_SIZE_IN> |
280 | | class PercentileSamples { |
281 | | public: |
282 | | #if !WITH_BABYLON_COUNTER |
283 | | friend class AddLatency; |
284 | | #endif // WITH_BABYLON_COUNTER |
285 | | |
286 | | static const size_t SAMPLE_SIZE = SAMPLE_SIZE_IN; |
287 | | |
288 | 0 | PercentileSamples() { |
289 | 0 | memset(this, 0, sizeof(*this)); |
290 | 0 | } Unexecuted instantiation: bvar::detail::PercentileSamples<254ul>::PercentileSamples() Unexecuted instantiation: bvar::detail::PercentileSamples<1022ul>::PercentileSamples() Unexecuted instantiation: bvar::detail::PercentileSamples<30ul>::PercentileSamples() |
291 | | |
292 | 0 | ~PercentileSamples() { |
293 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
294 | 0 | if (_intervals[i]) { |
295 | 0 | delete _intervals[i]; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } Unexecuted instantiation: bvar::detail::PercentileSamples<254ul>::~PercentileSamples() Unexecuted instantiation: bvar::detail::PercentileSamples<1022ul>::~PercentileSamples() Unexecuted instantiation: bvar::detail::PercentileSamples<30ul>::~PercentileSamples() |
299 | | |
300 | | // Copy-construct from another PercentileSamples. |
301 | | // Copy/assigning happen at per-second scale. should be OK. |
302 | 0 | PercentileSamples(const PercentileSamples& rhs) { |
303 | 0 | _num_added = rhs._num_added; |
304 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
305 | 0 | if (rhs._intervals[i] && !rhs._intervals[i]->empty()) { |
306 | 0 | _intervals[i] = new PercentileInterval<SAMPLE_SIZE>(*rhs._intervals[i]); |
307 | 0 | } else { |
308 | 0 | _intervals[i] = NULL; |
309 | 0 | } |
310 | 0 | } |
311 | 0 | } Unexecuted instantiation: bvar::detail::PercentileSamples<254ul>::PercentileSamples(bvar::detail::PercentileSamples<254ul> const&) Unexecuted instantiation: bvar::detail::PercentileSamples<30ul>::PercentileSamples(bvar::detail::PercentileSamples<30ul> const&) |
312 | | |
313 | | // Assign from another PercentileSamples. |
314 | | // Notice that we keep empty _intervals to avoid future allocations. |
315 | 0 | void operator=(const PercentileSamples& rhs) { |
316 | 0 | _num_added = rhs._num_added; |
317 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
318 | 0 | if (rhs._intervals[i] && !rhs._intervals[i]->empty()) { |
319 | 0 | get_interval_at(i) = *rhs._intervals[i]; |
320 | 0 | } else if (_intervals[i]) { |
321 | 0 | _intervals[i]->clear(); |
322 | 0 | } |
323 | 0 | } |
324 | 0 | } Unexecuted instantiation: bvar::detail::PercentileSamples<254ul>::operator=(bvar::detail::PercentileSamples<254ul> const&) Unexecuted instantiation: bvar::detail::PercentileSamples<30ul>::operator=(bvar::detail::PercentileSamples<30ul> const&) |
325 | | |
326 | | // Get the `ratio'-ile value. E.g. 0.99 means 99%-ile value. |
327 | | // Since we store samples in different intervals internally. We first |
328 | | // address the interval by multiplying ratio with _num_added, then |
329 | | // find the sample inside the interval. We've tested an alternative |
330 | | // method that store all samples together w/o any intervals (or in another |
331 | | // word, only one interval), the method is much simpler but is not as |
332 | | // stable as current impl. CDF plotted by the method changes dramatically |
333 | | // from seconds to seconds. It seems that separating intervals probably |
334 | | // keep more long-tail values. |
335 | 0 | uint32_t get_number(double ratio) { |
336 | 0 | size_t n = (size_t)ceil(ratio * _num_added); |
337 | 0 | if (n > _num_added) { |
338 | 0 | n = _num_added; |
339 | 0 | } else if (n == 0) { |
340 | 0 | return 0; |
341 | 0 | } |
342 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
343 | 0 | if (_intervals[i] == NULL) { |
344 | 0 | continue; |
345 | 0 | } |
346 | 0 | PercentileInterval<SAMPLE_SIZE>& invl = *_intervals[i]; |
347 | 0 | if (n <= invl.added_count()) { |
348 | 0 | size_t sample_n = n * invl.sample_count() / invl.added_count(); |
349 | 0 | size_t sample_index = (sample_n ? sample_n - 1 : 0); |
350 | 0 | return invl.get_sample_at(sample_index); |
351 | 0 | } |
352 | 0 | n -= invl.added_count(); |
353 | 0 | } |
354 | 0 | CHECK(false) << "Can't reach here"; |
355 | 0 | return std::numeric_limits<uint32_t>::max(); |
356 | 0 | } |
357 | | |
358 | | // Add samples in another PercentileSamples. |
359 | | template <size_t size2> |
360 | 0 | void merge(const PercentileSamples<size2> &rhs) { |
361 | 0 | _num_added += rhs._num_added; |
362 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
363 | 0 | if (rhs._intervals[i] && !rhs._intervals[i]->empty()) { |
364 | 0 | get_interval_at(i).merge(*rhs._intervals[i]); |
365 | 0 | } |
366 | 0 | } |
367 | 0 | } Unexecuted instantiation: void bvar::detail::PercentileSamples<254ul>::merge<254ul>(bvar::detail::PercentileSamples<254ul> const&) Unexecuted instantiation: void bvar::detail::PercentileSamples<254ul>::merge<30ul>(bvar::detail::PercentileSamples<30ul> const&) |
368 | | |
369 | | #if WITH_BABYLON_COUNTER |
370 | | void merge(const babylon::ConcurrentSampler::SampleBucket& bucket, size_t index) { |
371 | | _num_added += get_interval_at(index).merge(bucket); |
372 | | } |
373 | | #endif // WITH_BABYLON_COUNTER |
374 | | |
375 | | // Combine multiple into a single PercentileSamples |
376 | | template <typename Iterator> |
377 | 0 | void combine_of(const Iterator& begin, const Iterator& end) { |
378 | 0 | if (_num_added) { |
379 | | // Very unlikely |
380 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
381 | 0 | if (_intervals[i]) { |
382 | 0 | _intervals[i]->clear(); |
383 | 0 | } |
384 | 0 | } |
385 | 0 | _num_added = 0; |
386 | 0 | } |
387 | |
|
388 | 0 | for (Iterator iter = begin; iter != end; ++iter) { |
389 | 0 | _num_added += iter->_num_added; |
390 | 0 | } |
391 | | |
392 | | // Calculate probabilities for each interval |
393 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
394 | 0 | size_t total = 0; |
395 | 0 | size_t total_sample=0; |
396 | 0 | for (Iterator iter = begin; iter != end; ++iter) { |
397 | 0 | if (iter->_intervals[i]) { |
398 | 0 | total += iter->_intervals[i]->added_count(); |
399 | 0 | total_sample += iter->_intervals[i]->sample_count(); |
400 | 0 | } |
401 | 0 | } |
402 | 0 | if (total == 0) { |
403 | | // Empty interval |
404 | 0 | continue; |
405 | 0 | } |
406 | | |
407 | | |
408 | | // Consider that sub interval took |a| samples out of |b| totally, |
409 | | // each sample won the probability of |a/b| according to the |
410 | | // algorithm of add32(), now we should pick some samples into the |
411 | | // combined interval that satisfied each sample has the |
412 | | // probability of |SAMPLE_SIZE/total|, so each sample has the |
413 | | // probability of |(SAMPLE_SIZE*b)/(a*total) to remain and the |
414 | | // expected number of samples in this interval is |
415 | | // |(SAMPLE_SIZE*b)/total| |
416 | 0 | for (Iterator iter = begin; iter != end; ++iter) { |
417 | 0 | if (!iter->_intervals[i] || iter->_intervals[i]->empty()) { |
418 | 0 | continue; |
419 | 0 | } |
420 | 0 | typename butil::add_reference<BAIDU_TYPEOF(*(iter->_intervals[i]))>::type |
421 | 0 | invl = *(iter->_intervals[i]); |
422 | 0 | if (total <= SAMPLE_SIZE) { |
423 | 0 | get_interval_at(i).merge_with_expectation( |
424 | 0 | invl, invl.sample_count()); |
425 | 0 | continue; |
426 | 0 | } |
427 | | // Each |
428 | 0 | const size_t b = invl.added_count(); |
429 | 0 | const size_t remain = std::min( |
430 | 0 | round_of_expectation(b * SAMPLE_SIZE, total), |
431 | 0 | (size_t)invl.sample_count()); |
432 | 0 | get_interval_at(i).merge_with_expectation(invl, remain); |
433 | 0 | } |
434 | 0 | } |
435 | 0 | } |
436 | | |
437 | | // For debuggin. |
438 | 0 | void describe(std::ostream &os) const { |
439 | 0 | os << this << "{num_added=" << _num_added; |
440 | 0 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
441 | 0 | if (_intervals[i] && !_intervals[i]->empty()) { |
442 | 0 | os << " interval[" << i << "]="; |
443 | 0 | _intervals[i]->describe(os); |
444 | 0 | } |
445 | 0 | } |
446 | 0 | os << '}'; |
447 | 0 | } |
448 | | |
449 | | // True if intervals of two PercentileSamples are exactly same. |
450 | | bool operator==(const PercentileSamples& rhs) const { |
451 | | for (size_t i = 0; i < NUM_INTERVALS; ++i) { |
452 | | if (_intervals != rhs._intervals[i]) { |
453 | | return false; |
454 | | } |
455 | | } |
456 | | return true; |
457 | | } |
458 | | |
459 | | private: |
460 | | template <size_t size1> friend class PercentileSamples; |
461 | | |
462 | | // Get/create interval on-demand. |
463 | 0 | PercentileInterval<SAMPLE_SIZE>& get_interval_at(size_t index) { |
464 | 0 | if (_intervals[index] == NULL) { |
465 | 0 | _intervals[index] = new PercentileInterval<SAMPLE_SIZE>; |
466 | 0 | } |
467 | 0 | return *_intervals[index]; |
468 | 0 | } Unexecuted instantiation: bvar::detail::PercentileSamples<254ul>::get_interval_at(unsigned long) Unexecuted instantiation: bvar::detail::PercentileSamples<1022ul>::get_interval_at(unsigned long) Unexecuted instantiation: bvar::detail::PercentileSamples<30ul>::get_interval_at(unsigned long) |
469 | | // sum of _num_added of all intervals. we update this value after any |
470 | | // changes to intervals inside to make it O(1)-time accessible. |
471 | | size_t _num_added; |
472 | | PercentileInterval<SAMPLE_SIZE>* _intervals[NUM_INTERVALS]; |
473 | | }; |
474 | | |
475 | | template <size_t sz> const size_t PercentileSamples<sz>::SAMPLE_SIZE; |
476 | | |
477 | | template <size_t size> |
478 | | std::ostream &operator<<(std::ostream &os, const PercentileInterval<size> &p) { |
479 | | p.describe(os); |
480 | | return os; |
481 | | } |
482 | | |
483 | | template <size_t size> |
484 | 0 | std::ostream &operator<<(std::ostream &os, const PercentileSamples<size> &p) { |
485 | 0 | p.describe(os); |
486 | 0 | return os; |
487 | 0 | } |
488 | | |
489 | | // NOTE: we intentionally minus 2 uint32_t from sample-size to make the struct |
490 | | // size be power of 2 and more friendly to memory allocators. |
491 | | typedef PercentileSamples<254> GlobalPercentileSamples; |
492 | | typedef PercentileSamples<30> ThreadLocalPercentileSamples; |
493 | | |
494 | | namespace detail { |
495 | | struct AddPercentileSamples { |
496 | | template <size_t size1, size_t size2> |
497 | | void operator()(PercentileSamples<size1> &b1, |
498 | 0 | const PercentileSamples<size2> &b2) const { |
499 | 0 | b1.merge(b2); |
500 | 0 | } Unexecuted instantiation: void bvar::detail::detail::AddPercentileSamples::operator()<254ul, 254ul>(bvar::detail::PercentileSamples<254ul>&, bvar::detail::PercentileSamples<254ul> const&) const Unexecuted instantiation: void bvar::detail::detail::AddPercentileSamples::operator()<254ul, 30ul>(bvar::detail::PercentileSamples<254ul>&, bvar::detail::PercentileSamples<30ul> const&) const |
501 | | }; |
502 | | } // namespace detail |
503 | | |
504 | | // A specialized reducer for finding the percentile of latencies. |
505 | | // NOTE: DON'T use it directly, use LatencyRecorder instead. |
506 | | #if !WITH_BABYLON_COUNTER |
507 | | class Percentile { |
508 | | public: |
509 | | typedef GlobalPercentileSamples value_type; |
510 | | typedef ReducerSampler<Percentile, GlobalPercentileSamples, |
511 | | detail::AddPercentileSamples, VoidOp>sampler_type; |
512 | | typedef AgentCombiner <GlobalPercentileSamples, |
513 | | ThreadLocalPercentileSamples, |
514 | | detail::AddPercentileSamples> combiner_type; |
515 | | typedef combiner_type::self_shared_type shared_combiner_type; |
516 | | typedef combiner_type::Agent agent_type; |
517 | | |
518 | | Percentile(); |
519 | | ~Percentile(); |
520 | | |
521 | 0 | detail::AddPercentileSamples op() const { |
522 | 0 | return detail::AddPercentileSamples(); |
523 | 0 | } |
524 | 0 | VoidOp inv_op() const { return VoidOp(); } |
525 | | |
526 | | // The sampler for windows over percentile. |
527 | 0 | sampler_type* get_sampler() { |
528 | 0 | if (NULL == _sampler) { |
529 | 0 | _sampler = new sampler_type(this); |
530 | 0 | _sampler->schedule(); |
531 | 0 | } |
532 | 0 | return _sampler; |
533 | 0 | } |
534 | | |
535 | | value_type reset(); |
536 | | |
537 | | value_type get_value() const; |
538 | | |
539 | | Percentile& operator<<(int64_t latency); |
540 | | |
541 | 0 | bool valid() const { return _combiner != NULL && _combiner->valid(); } |
542 | | |
543 | | // This name is useful for warning negative latencies in operator<< |
544 | 0 | void set_debug_name(const butil::StringPiece& name) { |
545 | 0 | _debug_name.assign(name.data(), name.size()); |
546 | 0 | } |
547 | | |
548 | | private: |
549 | | DISALLOW_COPY_AND_ASSIGN(Percentile); |
550 | | |
551 | | shared_combiner_type _combiner; |
552 | | sampler_type* _sampler; |
553 | | std::string _debug_name; |
554 | | }; |
555 | | #else |
556 | | class Percentile { |
557 | | public: |
558 | | typedef GlobalPercentileSamples value_type; |
559 | | typedef detail::AddPercentileSamples AddPercentileSamples; |
560 | | typedef AddPercentileSamples Op; |
561 | | typedef VoidOp InvOp; |
562 | | typedef ReducerSampler<Percentile, value_type, Op, InvOp> sampler_type; |
563 | | |
564 | | Percentile() = default; |
565 | | DISALLOW_COPY_AND_MOVE(Percentile); |
566 | | ~Percentile() noexcept { |
567 | | if (NULL != _sampler) { |
568 | | _sampler->destroy(); |
569 | | } |
570 | | } |
571 | | |
572 | | Op op() const { return Op(); } |
573 | | InvOp inv_op() const { return InvOp(); } |
574 | | |
575 | | sampler_type* get_sampler() { |
576 | | if (NULL == _sampler) { |
577 | | _sampler = new sampler_type(this); |
578 | | _sampler->schedule(); |
579 | | } |
580 | | return _sampler; |
581 | | } |
582 | | |
583 | | value_type reset(); |
584 | | |
585 | | value_type get_value() const { |
586 | | LOG_EVERY_SECOND(ERROR) << "Percentile should never call this get_value()"; |
587 | | return value_type(); |
588 | | } |
589 | | |
590 | | Percentile& operator<<(int64_t value); |
591 | | |
592 | | bool valid() const { return true; } |
593 | | |
594 | | // This name is useful for warning negative latencies in operator<< |
595 | | void set_debug_name(const butil::StringPiece& name) { |
596 | | _debug_name.assign(name.data(), name.size()); |
597 | | } |
598 | | |
599 | | private: |
600 | | babylon::ConcurrentSampler _concurrent_sampler; |
601 | | sampler_type* _sampler{NULL}; |
602 | | std::string _debug_name; |
603 | | }; |
604 | | #endif // WITH_BABYLON_COUNTER |
605 | | |
606 | | } // namespace detail |
607 | | } // namespace bvar |
608 | | |
609 | | #endif //BVAR_DETAIL_PERCENTILE_H |