@@ -82,6 +82,12 @@ class string_view {
8282
8383#endif
8484
85+ enum { TL_MAX_TINY_STRING_LEN = 253 };
86+
87+ inline string_view shorten (string_view x) {
88+ return string_view{x.data (), std::min (x.size (), static_cast <size_t >(TL_MAX_TINY_STRING_LEN ))};
89+ }
90+
8591namespace wyhash {
8692// https://github.com/wangyi-fudan/wyhash
8793
@@ -409,7 +415,6 @@ class TransportUDPBase {
409415 TL_INT_SIZE = 4 ,
410416 TL_LONG_SIZE = 8 ,
411417 TL_DOUBLE_SIZE = 8 ,
412- TL_MAX_TINY_STRING_LEN = 253 ,
413418 TL_BIG_STRING_LEN = 0xffffff ,
414419 TL_BIG_STRING_MARKER = 0xfe ,
415420 TL_STATSHOUSE_METRICS_BATCH_TAG = 0x56580239 ,
@@ -474,7 +479,6 @@ class TransportUDPBase {
474479 std::memcpy (&v64, &v, sizeof (v));
475480 return v64;
476481 }
477- static string_view shorten (string_view x) { return string_view{x.data (), std::min<size_t >(x.size (), TL_MAX_TINY_STRING_LEN )}; }
478482// Trim is VERY costly, 2x slowdown even when no actual trim performed. We do not want to punish good guys, and for bad guys
479483// we have 'err_header_too_big' usage meta metric
480484// static char * pack_string_trim(char * begin, const char * end, const char *str, size_t len) {
@@ -1366,9 +1370,27 @@ class Registry {
13661370 std::function<int (const char *)> logger;
13671371 bool incremental_flush_disabled{false };
13681372 bool sampling_disabled{false };
1373+ std::unordered_map<uint64_t , int > metric_sample_prob; // use add_metric_sample_prob
1374+
1375+ void add_metric_sample_prob (string_view metric_name, int keep_percent) {
1376+ const string_view sn = shorten (metric_name);
1377+ if (sn.size () == 0 ) {
1378+ return ;
1379+ }
1380+ const uint64_t h = wyhash::wyhash (sn.data (), sn.size (), 0 , wyhash::_wyp);
1381+ int p = keep_percent;
1382+ if (p < 0 ) {
1383+ p = 0 ;
1384+ }
1385+ if (p > 100 ) {
1386+ p = 100 ;
1387+ }
1388+ metric_sample_prob[h] = p;
1389+ }
13691390 };
13701391 explicit Registry (const options &o)
1371- : max_bucket_size{o.max_bucket_size }
1392+ : metric_sample_prob(o.metric_sample_prob)
1393+ , max_bucket_size{o.max_bucket_size }
13721394 , time_external{0 }
13731395 , metrics_logging_enabled{false }
13741396 , sampling_disabled{o.sampling_disabled }
@@ -1594,6 +1616,11 @@ class Registry {
15941616 }
15951617 explicit operator bool () const { return ptr.get () != nullptr ; }
15961618 bool write_count (double count, uint32_t timestamp = 0 ) const {
1619+ const double m = registry->metric_sample_prob_mult (ptr->key );
1620+ if (m <= 0 ) {
1621+ return true ;
1622+ }
1623+ count *= m;
15971624 registry->log_count (ptr->key , count, timestamp);
15981625 if (timestamp) {
15991626 std::lock_guard<std::mutex> transport_lock{registry->transport_mu };
@@ -1613,6 +1640,14 @@ class Registry {
16131640 return write_values (&value, 1 , 1 , timestamp);
16141641 }
16151642 bool write_values (const double *values, size_t values_count, double count = 0 , uint32_t timestamp = 0 ) const {
1643+ const double m = registry->metric_sample_prob_mult (ptr->key );
1644+ if (m <= 0 ) {
1645+ return true ;
1646+ }
1647+ if (count == 0 ) {
1648+ count = 1 ;
1649+ }
1650+ count *= m;
16161651 registry->log_values (ptr->key , values, values_count, count, timestamp);
16171652 auto sampling = count != 0 && count != values_count;
16181653 if (sampling || timestamp || values_count >= registry->max_bucket_size .load (std::memory_order_relaxed)) {
@@ -1633,6 +1668,11 @@ class Registry {
16331668 return write_unique (&value, 1 , 1 , timestamp);
16341669 }
16351670 bool write_unique (const uint64_t *values, size_t values_count, double count, uint32_t timestamp = 0 ) const {
1671+ const double m = registry->metric_sample_prob_mult (ptr->key );
1672+ if (m <= 0 ) {
1673+ return true ;
1674+ }
1675+ count *= m;
16361676 registry->log_unique (ptr->key , values, values_count, count, timestamp);
16371677 auto sampling = count != 0 && count != values_count;
16381678 if (sampling || timestamp || values_count >= registry->max_bucket_size .load (std::memory_order_relaxed)) {
@@ -1699,6 +1739,11 @@ class Registry {
16991739 return WaterlevelMetricRef{registry, key};
17001740 }
17011741 bool write_count (double count, uint32_t timestamp = 0 ) const {
1742+ const double m = registry->metric_sample_prob_mult (key);
1743+ if (m <= 0 ) {
1744+ return true ;
1745+ }
1746+ count *= m;
17021747 registry->log_count (key, count, timestamp);
17031748 if (timestamp) {
17041749 std::lock_guard<std::mutex> transport_lock{registry->transport_mu };
@@ -1711,6 +1756,14 @@ class Registry {
17111756 return write_values (&value, 1 , 1 , timestamp);
17121757 }
17131758 bool write_values (const double *values, size_t values_count, double count = 0 , uint32_t timestamp = 0 ) const {
1759+ const double m = registry->metric_sample_prob_mult (key);
1760+ if (m <= 0 ) {
1761+ return true ;
1762+ }
1763+ if (count == 0 ) {
1764+ count = 1 ;
1765+ }
1766+ count *= m;
17141767 registry->log_values (key, values, values_count, count, timestamp);
17151768 auto sampling = count != 0 && count != values_count;
17161769 if (sampling || timestamp || values_count >= registry->max_bucket_size .load (std::memory_order_relaxed)) {
@@ -1724,6 +1777,11 @@ class Registry {
17241777 return write_unique (&value, 1 , 1 , timestamp);
17251778 }
17261779 bool write_unique (const uint64_t *values, size_t values_count, double count, uint32_t timestamp = 0 ) const {
1780+ const double m = registry->metric_sample_prob_mult (key);
1781+ if (m <= 0 ) {
1782+ return true ;
1783+ }
1784+ count *= m;
17271785 registry->log_unique (key, values, values_count, count, timestamp);
17281786 auto sampling = count != 0 && count != values_count;
17291787 if (sampling || timestamp || values_count >= registry->max_bucket_size .load (std::memory_order_relaxed)) {
@@ -2006,6 +2064,27 @@ class Registry {
20062064 uint32_t t{time_external};
20072065 return t != 0 ? t : static_cast <uint32_t >(std::time (nullptr ));
20082066 }
2067+ double metric_sample_prob_mult (const TransportUDPBase::MetricBuilder &key) const {
2068+ if (metric_sample_prob.empty ()) {
2069+ return 1 ;
2070+ }
2071+ const string_view name = key.metric_name ();
2072+ const uint64_t h = wyhash::wyhash (name.data (), name.size (), 0 , wyhash::_wyp);
2073+ const auto it = metric_sample_prob.find (h);
2074+ if (it == metric_sample_prob.end ()) {
2075+ return 1 ;
2076+ }
2077+ int p = it->second ;
2078+ thread_local uint64_t seed = 0 ;
2079+ if (!seed) {
2080+ seed = static_cast <uint64_t >(std::random_device{}()) | 1ull ;
2081+ }
2082+ const int roll = static_cast <int >(wyhash::wy2u0k (wyhash::wyrand (&seed), 100 ));
2083+ if (roll >= p) {
2084+ return 0 ;
2085+ }
2086+ return 100.0 / static_cast <double >(p);
2087+ }
20092088 inline void log_count (const TransportUDPBase::MetricBuilder &key, double count, uint32_t timestamp) const {
20102089 if (!metrics_logging_enabled.load (std::memory_order_relaxed)) return ;
20112090 log_message_writer w{};
@@ -2146,6 +2225,7 @@ class Registry {
21462225 std::atomic<size_t > freelist_size;
21472226 std::atomic<size_t > bucket_count;
21482227 };
2228+ std::unordered_map<uint64_t , int > metric_sample_prob;
21492229 std::mutex mu;
21502230 mutable std::mutex transport_mu;
21512231 std::atomic<size_t > max_bucket_size;
0 commit comments