Skip to content

Commit 89f23d4

Browse files
authored
add support for uint64 bucket element type for summary. (#858)
1 parent f3e7189 commit 89f23d4

File tree

4 files changed

+77
-41
lines changed

4 files changed

+77
-41
lines changed

include/ylt/metric/summary.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ YLT_REFL(json_summary_t, name, help, type, labels_name, quantiles_key, metrics);
3737
class summary_t : public static_metric {
3838
public:
3939
summary_t(std::string name, std::string help, std::vector<double> quantiles,
40-
std::chrono::seconds max_age = std::chrono::seconds{60})
40+
std::chrono::seconds max_age = std::chrono::seconds{0})
4141
: static_metric(MetricType::Summary, std::move(name), std::move(help)),
4242
quantiles_(std::move(quantiles)),
4343
impl_(quantiles_,
@@ -133,14 +133,16 @@ class summary_t : public static_metric {
133133

134134
private:
135135
std::vector<double> quantiles_;
136-
ylt::metric::detail::summary_impl<> impl_;
136+
ylt::metric::detail::summary_impl<uint64_t> impl_;
137137
};
138138

139139
template <size_t N>
140140
class basic_dynamic_summary
141-
: public dynamic_metric_impl<ylt::metric::detail::summary_impl<>, N> {
141+
: public dynamic_metric_impl<ylt::metric::detail::summary_impl<uint32_t>,
142+
N> {
142143
private:
143-
using Base = dynamic_metric_impl<ylt::metric::detail::summary_impl<>, N>;
144+
using Base =
145+
dynamic_metric_impl<ylt::metric::detail::summary_impl<uint32_t>, N>;
144146

145147
public:
146148
basic_dynamic_summary(

include/ylt/metric/summary_impl.hpp

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@
77
#include <iterator>
88
#include <limits>
99
#include <memory>
10+
#include <type_traits>
1011
#include <vector>
1112

1213
namespace ylt::metric::detail {
1314

14-
template <std::size_t frac_bit = 6>
15+
template <typename uint_type, std::size_t frac_bit = 6>
1516
class summary_impl {
17+
static_assert(sizeof(uint_type) >= 4);
18+
static_assert(std::is_unsigned_v<uint_type>);
1619
constexpr static uint32_t decode_impl(uint16_t float16_value) {
1720
float16_value <<= (8 - frac_bit);
1821
uint32_t sign = float16_value >> 15;
@@ -57,7 +60,8 @@ class summary_impl {
5760
static constexpr float float16_max = (1ull << 63) * 2.0f; // 2^64
5861

5962
static uint16_t encode(float flt) {
60-
unsigned int& fltInt32 = *(unsigned int*)&flt;
63+
static_assert(sizeof(float) == 4);
64+
uint32_t& fltInt32 = *(uint32_t*)&flt;
6165
if (std::abs(flt) >= float16_max || std::isnan(flt)) {
6266
flt = (fltInt32 & 0x8000'0000) ? (-float16_max) : (float16_max);
6367
}
@@ -88,9 +92,9 @@ class summary_impl {
8892

8993
struct data_t {
9094
static constexpr size_t piece_size = bucket_size / piece_cnt;
91-
using piece_t = std::array<std::atomic<uint32_t>, piece_size>;
95+
using piece_t = std::array<std::atomic<uint_type>, piece_size>;
9296

93-
std::atomic<uint32_t>& operator[](std::size_t index) {
97+
std::atomic<uint_type>& operator[](std::size_t index) {
9498
piece_t* piece = arr[index / piece_size];
9599
if (piece == nullptr) {
96100
auto ptr = new piece_t{};
@@ -122,7 +126,7 @@ class summary_impl {
122126
}
123127
template <bool inc_order>
124128
void stat_impl(uint64_t& count,
125-
std::vector<std::pair<int16_t, uint32_t>>& result, int i) {
129+
std::vector<std::pair<int16_t, uint_type>>& result, int i) {
126130
auto piece = arr[i].load(std::memory_order_relaxed);
127131
if (piece) {
128132
if constexpr (inc_order) {
@@ -146,7 +150,7 @@ class summary_impl {
146150
}
147151
}
148152
void stat(uint64_t& count,
149-
std::vector<std::pair<int16_t, uint32_t>>& result) {
153+
std::vector<std::pair<int16_t, uint_type>>& result) {
150154
for (int i = piece_cnt - 1; i >= piece_cnt / 2; --i) {
151155
stat_impl<false>(count, result, i);
152156
}
@@ -182,36 +186,38 @@ class summary_impl {
182186
static inline const unsigned long ms_count =
183187
std::chrono::steady_clock::duration{std::chrono::milliseconds{1}}.count();
184188

185-
constexpr static unsigned int near_uint32_max = 4290000000U;
189+
constexpr static uint32_t near_uint32_max = 4290000000U;
186190

187191
void increase(data_t& arr, uint16_t pos) {
188-
if (arr[pos].fetch_add(1, std::memory_order::relaxed) >
189-
near_uint32_max) /*no overflow*/ [[likely]] {
190-
arr[pos].fetch_sub(1, std::memory_order::relaxed);
191-
int upper = (pos < bucket_size / 2) ? (bucket_size / 2) : (bucket_size);
192-
int lower = (pos < bucket_size / 2) ? (0) : (bucket_size / 2);
193-
for (int delta = 1, lim = (std::max)(upper - pos, pos - lower + 1);
194-
delta < lim; ++delta) {
195-
if (pos + delta < upper) {
196-
if (arr[pos + delta].fetch_add(1, std::memory_order::relaxed) <=
197-
near_uint32_max) {
198-
break;
192+
auto res = arr[pos].fetch_add(1, std::memory_order::relaxed);
193+
if constexpr (std::is_same_v<uint_type, uint32_t>) {
194+
if (res > near_uint32_max) /*no overflow*/ [[likely]] {
195+
arr[pos].fetch_sub(1, std::memory_order::relaxed);
196+
int upper = (pos < bucket_size / 2) ? (bucket_size / 2) : (bucket_size);
197+
int lower = (pos < bucket_size / 2) ? (0) : (bucket_size / 2);
198+
for (int delta = 1, lim = (std::max)(upper - pos, pos - lower + 1);
199+
delta < lim; ++delta) {
200+
if (pos + delta < upper) {
201+
if (arr[pos + delta].fetch_add(1, std::memory_order::relaxed) <=
202+
near_uint32_max) {
203+
break;
204+
}
205+
arr[pos + delta].fetch_sub(1, std::memory_order::relaxed);
199206
}
200-
arr[pos + delta].fetch_sub(1, std::memory_order::relaxed);
201-
}
202-
if (pos - delta >= lower) {
203-
if (arr[pos - delta].fetch_add(1, std::memory_order::relaxed) <=
204-
near_uint32_max) {
205-
break;
207+
if (pos - delta >= lower) {
208+
if (arr[pos - delta].fetch_add(1, std::memory_order::relaxed) <=
209+
near_uint32_max) {
210+
break;
211+
}
212+
arr[pos - delta].fetch_sub(1, std::memory_order::relaxed);
206213
}
207-
arr[pos - delta].fetch_sub(1, std::memory_order::relaxed);
208214
}
209215
}
210216
}
211217
}
212218

213219
struct data_copy_t {
214-
std::vector<std::pair<int16_t, uint32_t>> arr[2];
220+
std::vector<std::pair<int16_t, uint_type>> arr[2];
215221
int index[2] = {}, smaller_one;
216222
void init() {
217223
if (arr[0][0] <= arr[1][0]) {
@@ -231,7 +237,7 @@ class summary_impl {
231237
}
232238
}
233239
int16_t value() { return arr[smaller_one][index[smaller_one]].first; }
234-
uint32_t count() { return arr[smaller_one][index[smaller_one]].second; }
240+
uint_type count() { return arr[smaller_one][index[smaller_one]].second; }
235241
};
236242

237243
public:
@@ -304,6 +310,9 @@ class summary_impl {
304310
e = 1;
305311
}
306312
auto target_count = std::min<double>(e * count, count);
313+
if (e == 0) {
314+
target_count = std::min(uint64_t{1}, count);
315+
}
307316
while (true) {
308317
if (target_count <= count_now) [[unlikely]] {
309318
result.push_back(v);

src/metric/tests/test_metric.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -925,15 +925,15 @@ TEST_CASE("test summary with illegal quantities") {
925925
CHECK(str.find("test_summary_sum") != std::string::npos);
926926
CHECK(str.find("test_summary{quantile=\"") != std::string::npos);
927927
CHECK(result[0] < 0);
928-
CHECK(result[1] < 0);
928+
CHECK(result[1] == 0);
929929
CHECK(result[result.size() - 1] > result[result.size() - 2]);
930930

931931
#ifdef CINATRA_ENABLE_METRIC_JSON
932932
std::string str_json;
933933
summary.serialize_to_json(str_json);
934934
std::cout << str_json << "\n";
935935
std::cout << str_json.size() << std::endl;
936-
CHECK(str_json.size() == 233);
936+
CHECK(str_json.size() == 222);
937937
#endif
938938
}
939939

@@ -969,7 +969,7 @@ TEST_CASE("test summary with many quantities") {
969969
summary.serialize_to_json(str_json);
970970
std::cout << str_json << "\n";
971971
std::cout << str_json.size() << std::endl;
972-
CHECK(str_json.size() == 8868);
972+
CHECK(str_json.size() == 8857);
973973
#endif
974974
}
975975

@@ -1998,6 +1998,24 @@ TEST_CASE("test remove label value") {
19981998
CHECK(!counter.has_label_value(std::vector<std::string>{}));
19991999
}
20002000

2001+
TEST_CASE("test static summary with 0 and 1 quantiles") {
2002+
{
2003+
ylt::metric::summary_t s("test", "help", {0, 1});
2004+
for (uint64_t i = 0; i < 100ull; ++i) {
2005+
s.observe(1);
2006+
}
2007+
auto result = s.get_rates();
2008+
CHECK(result[0] == 1);
2009+
CHECK(result[1] == 1);
2010+
}
2011+
{
2012+
ylt::metric::summary_t s("test", "help", {0, 1});
2013+
auto result = s.get_rates();
2014+
CHECK(result[0] == 0);
2015+
CHECK(result[1] == 0);
2016+
}
2017+
}
2018+
20012019
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)
20022020
int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }
20032021
DOCTEST_MSVC_SUPPRESS_WARNING_POP

website/docs/zh/metric/metric_introduction.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# metric 介绍
22
metric 用于统计应用程序的各种指标,这些指标被用于系统见识和警报,常见的指标类型有四种:Counter、Gauge、Histogram和Summary,这些指标遵循[Prometheus](https://hulining.gitbook.io/prometheus/introduction)的数据格式。yalantinglibs提供了一系列高性能且线程安全的统计工具。
33

4+
metric 包括4种指标类型:
5+
- couter:只会增加的指标;
6+
- gauge:可以增加或减少的指标,它派生于counter;
7+
- histogram:直方图,初始化的时候需要设置桶(bucket);
8+
- summary:分位数指标,初始化的时候需要设置桶和误差;
9+
10+
411
## Counter 计数器类型
512
Counter是一个累计类型的数据指标,它代表单调递增的计数器,其值只能在重新启动时增加或重置为 0。例如,您可以使用计数器来表示已响应的请求数,已完成或出错的任务数。
613

@@ -71,11 +78,7 @@ prometheus_tsdb_wal_fsync_duration_seconds_count 216
7178
```
7279
7380
# 概述
74-
metric 包括4种指标类型:
75-
- couter:只会增加的指标;
76-
- gauge:可以增加或减少的指标,它派生于counter;
77-
- histogram:直方图,初始化的时候需要设置桶(bucket);
78-
- summary:分位数指标,初始化的时候需要设置桶和误差;
81+
7982
8083
# label
8184
@@ -453,7 +456,11 @@ std::vector<std::shared_ptr<counter_t>> get_bucket_counts();
453456
// 序列化
454457
void serialize(std::string& str);
455458
```
456-
## 例子
459+
460+
461+
## 例子
462+
463+
457464
```cpp
458465
histogram_t h("test", "help", {5.0, 10.0, 20.0, 50.0, 100.0});
459466
h.observe(23);
@@ -548,7 +555,7 @@ summary每次写入的数据会被映射到一个14位的浮点数中,其指
548555
549556
### 大量重复数字导致的误差
550557
551-
为节省内存空间,summary内部的每个桶仅能存储一个32位数字,因此,在一个过期时间周期内同一个数字被插入超过2^32次后,为了避免溢出,新的数字会被插入到与该数字临近的桶(相差约1%)中,这可能导致一定误差。
558+
为节省内存空间,dynamic summary内部的每个桶仅能存储一个32位数字,因此,在一个过期时间周期内同一个数字被插入超过2^32次后,为了避免溢出,新的数字会被插入到与该数字临近的桶(相差约1%)中,这可能导致一定误差。非daynamic的summary 不会有这个问题,因为他内部使用的是64位,不可能出现溢出
552559
553560
### 过期时间误差
554561

0 commit comments

Comments
 (0)