#pragma once #include #include #include #include #include namespace tw::net { template struct AverageOp { uint32_t count; T sum; AverageOp() : count(0), sum{} { } void add(const T value) { sum += value; count++; } T result() const { return count == 0 ? 0 : sum / count; } }; template struct SumOp { T sum; void add(const T value) { sum += value; } T result() const { return sum; } }; template, typename Clock = std::chrono::steady_clock> class BucketMetric { T m_min, m_max; std::vector m_metric; std::vector m_bucket_idx; Operation m_op; uint32_t m_offset; uint32_t m_right, m_left; std::string m_format; const uint32_t get_bucket(Clock::time_point time_point) const { return std::chrono::floor(time_point).time_since_epoch().count() - m_offset; } public: BucketMetric(std::string format, uint32_t size) : m_metric(size), m_bucket_idx(size), m_right(0), m_left(0), m_offset(0), m_format(format) { m_offset = get_bucket(Clock::now()); } const T max() const { return m_max; } const T min() const { return m_min; } const std::string& format() const { return m_format; } size_t max_size() const { return m_metric.size(); } void push(T value) { auto time = Clock::now(); size_t bucket = get_bucket(time) % m_metric.size(); size_t idx = get_bucket(time); // set result to correct bucket if(m_right != bucket) { m_metric[m_right] = m_op.result(); m_min = std::min(m_min, m_op.result()); m_max = std::max(m_max, m_op.result()); m_bucket_idx[m_right] = idx++; m_right++; m_op = {}; } // reset all buckets until the required one for(; m_right != bucket; m_right = (m_right + 1) % m_metric.size()) { m_metric[m_right] = {}; m_bucket_idx[m_right] = idx++; if(m_right == m_left) { m_left = (m_left + 1) % m_metric.size(); } } m_op.add(value); } const size_t get_size() const { return m_right - m_left + (m_left > m_right ? m_metric.size() : 0); } const T get(uint32_t idx) const { if(idx > get_size()) { throw std::invalid_argument("`idx` cannot be higher than buffer size"); } return m_metric[m_left + idx].result(); } std::span get_head() { return std::span(m_metric).subspan(m_left, (m_right > m_left ? m_right : m_metric.size())); } std::span get_head_timeline() { return std::span(m_bucket_idx).subspan(m_left, (m_right > m_left ? m_right : m_bucket_idx.size())); } std::span get_tail() { if(m_right > m_left) { return std::span(); } return std::span(m_metric).subspan(0, m_right); } std::span get_tail_timeline() { if(m_right > m_left) { return std::span(); } return std::span(m_bucket_idx).subspan(0, m_right); } }; }