[metric][feat]improve metrics (#692)

This commit is contained in:
qicosmos 2024-06-14 16:55:12 +08:00 committed by GitHub
parent ef012e6327
commit 4f4b010bcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1505 additions and 120 deletions

22
include/ylt/metric.hpp Normal file
View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024, Alibaba Group Holding Limited;
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define CINATRA_ENABLE_METRIC_JSON
#include "metric/gauge.hpp"
#include "metric/histogram.hpp"
#include "metric/metric.hpp"
#include "metric/summary.hpp"
#include "ylt/struct_json/json_writer.h"

View File

@ -4,13 +4,23 @@
#include "metric.hpp"
namespace ylt {
namespace ylt::metric {
enum class op_type_t { INC, DEC, SET };
struct counter_sample {
op_type_t op_type;
std::vector<std::string> labels_value;
double value;
#ifdef CINATRA_ENABLE_METRIC_JSON
struct json_counter_metric_t {
std::unordered_multimap<std::string, std::string> labels;
int64_t value;
};
REFLECTION(json_counter_metric_t, labels, value);
struct json_counter_t {
std::string name;
std::string help;
std::string type;
std::vector<json_counter_metric_t> metrics;
};
REFLECTION(json_counter_t, name, help, type, metrics);
#endif
class counter_t : public metric_t {
public:
@ -23,12 +33,8 @@ class counter_t : public metric_t {
// static labels value, contains a map with atomic value.
counter_t(std::string name, std::string help,
std::map<std::string, std::string> labels)
: metric_t(MetricType::Counter, std::move(name), std::move(help)) {
for (auto &[k, v] : labels) {
labels_name_.push_back(k);
labels_value_.push_back(v);
}
: metric_t(MetricType::Counter, std::move(name), std::move(help),
std::move(labels)) {
atomic_value_map_.emplace(labels_value_, 0);
use_atomic_ = true;
}
@ -56,7 +62,7 @@ class counter_t : public metric_t {
std::map<std::vector<std::string>, double,
std::less<std::vector<std::string>>>
value_map() {
value_map() override {
std::map<std::vector<std::string>, double,
std::less<std::vector<std::string>>>
map;
@ -97,6 +103,44 @@ class counter_t : public metric_t {
}
}
#ifdef CINATRA_ENABLE_METRIC_JSON
void serialize_to_json(std::string &str) override {
std::string s;
if (labels_name_.empty()) {
if (default_lable_value_ == 0) {
return;
}
json_counter_t counter{name_, help_, std::string(metric_name())};
int64_t value = default_lable_value_;
counter.metrics.push_back({{}, value});
iguana::to_json(counter, str);
return;
}
json_counter_t counter{name_, help_, std::string(metric_name())};
if (use_atomic_) {
to_json(counter, atomic_value_map_, str);
}
else {
to_json(counter, value_map_, str);
}
}
template <typename T>
void to_json(json_counter_t &counter, T &map, std::string &str) {
for (auto &[k, v] : map) {
json_counter_metric_t metric;
size_t index = 0;
for (auto &label_value : k) {
metric.labels.emplace(labels_name_[index++], label_value);
}
metric.value = (int64_t)v;
counter.metrics.push_back(std::move(metric));
}
iguana::to_json(counter, str);
}
#endif
void inc(double val = 1) {
if (val < 0) {
throw std::invalid_argument("the value is less than zero");
@ -255,4 +299,4 @@ class counter_t : public metric_t {
std::less<std::vector<std::string>>>
value_map_;
};
} // namespace ylt
} // namespace ylt::metric

View File

@ -5,7 +5,7 @@
// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h
namespace ylt {
namespace ylt::metric {
class CKMSQuantiles {
public:
struct Quantile {
@ -172,4 +172,4 @@ class CKMSQuantiles {
std::array<double, 500> buffer_;
std::size_t buffer_count_;
};
} // namespace ylt
} // namespace ylt::metric

View File

@ -2,7 +2,7 @@
#include "ckms_quantiles.hpp"
// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/time_window_quantiles.h
namespace ylt {
namespace ylt::metric {
class TimeWindowQuantiles {
using Clock = std::chrono::steady_clock;
@ -49,4 +49,4 @@ class TimeWindowQuantiles {
mutable Clock::time_point last_rotation_;
const Clock::duration rotation_interval_;
};
} // namespace ylt
} // namespace ylt::metric

View File

@ -3,7 +3,7 @@
#include "counter.hpp"
namespace ylt {
namespace ylt::metric {
class gauge_t : public counter_t {
public:
gauge_t(std::string name, std::string help)
@ -49,4 +49,4 @@ class gauge_t : public counter_t {
}
}
};
} // namespace ylt
} // namespace ylt::metric

View File

@ -8,7 +8,24 @@
#include "counter.hpp"
#include "metric.hpp"
namespace ylt {
namespace ylt::metric {
#ifdef CINATRA_ENABLE_METRIC_JSON
struct json_histogram_metric_t {
std::map<std::string, std::string> labels;
std::map<double, int64_t> quantiles;
int64_t count;
double sum;
};
REFLECTION(json_histogram_metric_t, labels, quantiles, count, sum);
struct json_histogram_t {
std::string name;
std::string help;
std::string type;
std::vector<json_histogram_metric_t> metrics;
};
REFLECTION(json_histogram_t, name, help, type, metrics);
#endif
class histogram_t : public metric_t {
public:
histogram_t(std::string name, std::string help, std::vector<double> buckets)
@ -25,7 +42,41 @@ class histogram_t : public metric_t {
use_atomic_ = true;
}
histogram_t(std::string name, std::string help, std::vector<double> buckets,
std::vector<std::string> labels_name)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, name, help, labels_name),
sum_(std::make_shared<gauge_t>(name, help, labels_name)) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
for (size_t i = 0; i < buckets.size() + 1; i++) {
bucket_counts_.push_back(
std::make_shared<counter_t>(name, help, labels_name));
}
}
histogram_t(std::string name, std::string help, std::vector<double> buckets,
std::map<std::string, std::string> labels)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, name, help, labels),
sum_(std::make_shared<gauge_t>(name, help, labels)) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
for (size_t i = 0; i < buckets.size() + 1; i++) {
bucket_counts_.push_back(std::make_shared<counter_t>(name, help, labels));
}
use_atomic_ = true;
}
void observe(double value) {
if (!use_atomic_ || !labels_name_.empty()) {
throw std::invalid_argument("not a default label metric");
}
const auto bucket_index = static_cast<std::size_t>(
std::distance(bucket_boundaries_.begin(),
std::lower_bound(bucket_boundaries_.begin(),
@ -34,9 +85,33 @@ class histogram_t : public metric_t {
bucket_counts_[bucket_index]->inc();
}
void observe(const std::vector<std::string> &labels_value, double value) {
if (sum_->labels_name().empty()) {
throw std::invalid_argument("not a label metric");
}
const auto bucket_index = static_cast<std::size_t>(
std::distance(bucket_boundaries_.begin(),
std::lower_bound(bucket_boundaries_.begin(),
bucket_boundaries_.end(), value)));
sum_->inc(labels_value, value);
bucket_counts_[bucket_index]->inc(labels_value);
}
auto get_bucket_counts() { return bucket_counts_; }
void serialize(std::string& str) override {
std::map<std::vector<std::string>, double,
std::less<std::vector<std::string>>>
value_map() override {
return sum_->value_map();
}
void serialize(std::string &str) override {
if (!sum_->labels_name().empty()) {
serialize_with_labels(str);
return;
}
serialize_head(str);
double count = 0;
auto bucket_counts = get_bucket_counts();
@ -68,6 +143,41 @@ class histogram_t : public metric_t {
.append("\n");
}
#ifdef CINATRA_ENABLE_METRIC_JSON
void serialize_to_json(std::string &str) override {
if (!sum_->labels_name().empty()) {
serialize_to_json_with_labels(str);
return;
}
json_histogram_t hist{name_, help_, std::string(metric_name())};
double count = 0;
auto bucket_counts = get_bucket_counts();
json_histogram_metric_t metric{};
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
count += counter->value();
if (i == bucket_boundaries_.size()) {
metric.quantiles.emplace(std::numeric_limits<int>::max(),
(int64_t)count);
}
else {
metric.quantiles.emplace(bucket_boundaries_[i],
(int64_t)counter->value());
}
}
metric.count = (int64_t)count;
metric.sum = sum_->value();
hist.metrics.push_back(std::move(metric));
iguana::to_json(hist, str);
}
#endif
private:
template <class ForwardIterator>
bool is_strict_sorted(ForwardIterator first, ForwardIterator last) {
@ -76,8 +186,102 @@ class histogram_t : public metric_t {
ForwardIterator>::value_type>()) == last;
}
void serialize_with_labels(std::string &str) {
serialize_head(str);
auto bucket_counts = get_bucket_counts();
auto value_map = sum_->value_map();
for (auto &[labels_value, value] : value_map) {
if (value == 0) {
continue;
}
double count = 0;
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
str.append(name_).append("_bucket{");
build_label_string(str, sum_->labels_name(), labels_value);
str.append(",");
if (i == bucket_boundaries_.size()) {
str.append("le=\"").append("+Inf").append("\"} ");
}
else {
str.append("le=\"")
.append(std::to_string(bucket_boundaries_[i]))
.append("\"} ");
}
count += counter->value(labels_value);
str.append(std::to_string(count));
str.append("\n");
}
str.append(name_);
str.append("_sum{");
build_label_string(str, sum_->labels_name(), labels_value);
str.append("} ");
if (type_ == MetricType::Counter) {
str.append(std::to_string((int64_t)value));
}
else {
str.append(std::to_string(value));
}
str.append("\n");
str.append(name_).append("_count{");
build_label_string(str, sum_->labels_name(), labels_value);
str.append("} ");
str.append(std::to_string(count));
str.append("\n");
}
}
#ifdef CINATRA_ENABLE_METRIC_JSON
void serialize_to_json_with_labels(std::string &str) {
json_histogram_t hist{name_, help_, std::string(metric_name())};
auto bucket_counts = get_bucket_counts();
auto value_map = sum_->value_map();
for (auto &[labels_value, value] : value_map) {
if (value == 0) {
continue;
}
size_t count = 0;
json_histogram_metric_t metric{};
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
count += counter->value(labels_value);
if (i == bucket_boundaries_.size()) {
metric.quantiles.emplace(std::numeric_limits<int>::max(),
(int64_t)count);
}
else {
metric.quantiles.emplace(bucket_boundaries_[i],
(int64_t)counter->value(labels_value));
}
}
metric.count = (int64_t)count;
metric.sum = sum_->value(labels_value);
for (size_t i = 0; i < labels_value.size(); i++) {
metric.labels[sum_->labels_name()[i]] = labels_value[i];
}
hist.metrics.push_back(std::move(metric));
}
iguana::to_json(hist, str);
}
#endif
std::vector<double> bucket_boundaries_;
std::vector<std::shared_ptr<counter_t>> bucket_counts_; // readonly
std::shared_ptr<gauge_t> sum_;
};
} // namespace ylt
} // namespace ylt::metric

View File

@ -1,17 +1,33 @@
#pragma once
#include <algorithm>
#include <atomic>
#include <cassert>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
#include "async_simple/coro/Lazy.h"
#include "async_simple/coro/SyncAwait.h"
#include "cinatra/cinatra_log_wrapper.hpp"
namespace ylt {
#ifdef CINATRA_ENABLE_METRIC_JSON
namespace iguana {
template <typename T>
inline char* to_chars_float(T value, char* buffer) {
return buffer + snprintf(buffer, 65, "%g", value);
}
} // namespace iguana
#include <iguana/json_writer.hpp>
#endif
namespace ylt::metric {
enum class MetricType {
Counter,
Gauge,
@ -20,15 +36,32 @@ enum class MetricType {
Nil,
};
struct metric_filter_options {
std::optional<std::regex> name_regex{};
std::optional<std::regex> label_regex{};
bool is_white = true;
};
class metric_t {
public:
metric_t() = default;
metric_t(MetricType type, std::string name, std::string help)
: type_(type), name_(std::move(name)), help_(std::move(help)) {}
metric_t(MetricType type, std::string name, std::string help,
std::vector<std::string> labels_name = {})
: type_(type),
name_(std::move(name)),
help_(std::move(help)),
labels_name_(std::move(labels_name)) {}
std::vector<std::string> labels_name)
: metric_t(type, std::move(name), std::move(help)) {
labels_name_ = std::move(labels_name);
}
metric_t(MetricType type, std::string name, std::string help,
std::map<std::string, std::string> static_labels)
: metric_t(type, std::move(name), std::move(help)) {
static_labels_ = std::move(static_labels);
for (auto& [k, v] : static_labels_) {
labels_name_.push_back(k);
labels_value_.push_back(v);
}
}
virtual ~metric_t() {}
std::string_view name() { return name_; }
@ -55,13 +88,35 @@ class metric_t {
const std::vector<std::string>& labels_name() { return labels_name_; }
const std::map<std::string, std::string>& get_static_labels() {
return static_labels_;
}
virtual std::map<std::vector<std::string>, double,
std::less<std::vector<std::string>>>
value_map() {
return {};
}
virtual void serialize(std::string& str) {}
#ifdef CINATRA_ENABLE_METRIC_JSON
virtual void serialize_to_json(std::string& str) {}
#endif
// only for summary
virtual async_simple::coro::Lazy<void> serialize_async(std::string& out) {
co_return;
}
#ifdef CINATRA_ENABLE_METRIC_JSON
// only for summary
virtual async_simple::coro::Lazy<void> serialize_to_json_async(
std::string& out) {
co_return;
}
#endif
bool is_atomic() const { return use_atomic_; }
template <typename T>
@ -80,6 +135,19 @@ class metric_t {
.append("\n");
}
void build_label_string(std::string& str,
const std::vector<std::string>& label_name,
const std::vector<std::string>& label_value) {
for (size_t i = 0; i < label_name.size(); i++) {
str.append(label_name[i])
.append("=\"")
.append(label_value[i])
.append("\"")
.append(",");
}
str.pop_back();
}
#ifdef __APPLE__
double mac_os_atomic_fetch_add(std::atomic<double>* obj, double arg) {
double v;
@ -101,6 +169,7 @@ class metric_t {
MetricType type_ = MetricType::Nil;
std::string name_;
std::string help_;
std::map<std::string, std::string> static_labels_;
std::vector<std::string> labels_name_; // read only
std::vector<std::string> labels_value_; // read only
bool use_atomic_ = false;
@ -138,12 +207,20 @@ struct metric_manager_t {
return m;
}
static bool register_metric_static(std::shared_ptr<metric_t> metric) {
return register_metric_impl<false>(metric);
}
static bool register_metric_dynamic(std::shared_ptr<metric_t> metric) {
return register_metric_impl<true>(metric);
}
static bool register_metric_static(std::shared_ptr<metric_t> metric) {
return register_metric_impl<false>(metric);
static bool remove_metric_static(const std::string& name) {
return remove_metric_impl<false>(name);
}
static bool remove_metric_dynamic(const std::string& name) {
return remove_metric_impl<true>(name);
}
template <typename... Metrics>
@ -175,30 +252,144 @@ struct metric_manager_t {
return metric_keys_impl<true>();
}
// static labels: {{"method", "GET"}, {"url", "/"}}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_labels_static(
const std::map<std::string, std::string>& labels) {
std::vector<std::shared_ptr<metric_t>> vec;
auto map = metric_map_static();
for (auto& [name, m] : map) {
const auto& static_labels = m->get_static_labels();
if (static_labels == labels) {
vec.push_back(m);
}
}
return vec;
}
// static label: {"method", "GET"}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_label_static(
const std::pair<std::string, std::string>& label) {
std::vector<std::shared_ptr<metric_t>> vec;
auto map = metric_map_static();
for (auto& [name, t] : map) {
const auto& static_labels = t->get_static_labels();
for (const auto& pair : static_labels) {
if (pair.first == label.first && pair.second == label.second) {
vec.push_back(t);
}
}
}
return vec;
}
// labels: {{"method", "POST"}, {"code", "200"}}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_labels_dynamic(
const std::map<std::string, std::string>& labels) {
std::vector<std::shared_ptr<metric_t>> vec;
auto map = metric_map_dynamic();
for (auto& [name, t] : map) {
auto val_map = t->value_map();
auto labels_name = t->labels_name();
for (auto& [k, v] : labels) {
if (auto it = std::find(labels_name.begin(), labels_name.end(), k);
it != labels_name.end()) {
if (auto it = std::find_if(val_map.begin(), val_map.end(),
[label_val = v](auto& pair) {
auto& key = pair.first;
return std::find(key.begin(), key.end(),
label_val) != key.end();
});
it != val_map.end()) {
vec.push_back(t);
}
}
}
}
return vec;
}
template <typename T>
static T* get_metric_static(const std::string& name) {
static std::shared_ptr<T> get_metric_static(const std::string& name) {
auto m = get_metric_impl<false>(name);
if (m == nullptr) {
return nullptr;
}
return m->template as<T>();
return std::dynamic_pointer_cast<T>(m);
}
template <typename T>
static T* get_metric_dynamic(const std::string& name) {
static std::shared_ptr<T> get_metric_dynamic(const std::string& name) {
auto m = get_metric_impl<true>(name);
if (m == nullptr) {
return nullptr;
}
return m->template as<T>();
return std::dynamic_pointer_cast<T>(m);
}
static async_simple::coro::Lazy<std::string> serialize_static() {
return serialize_impl<false>();
static std::string serialize(
const std::vector<std::shared_ptr<metric_t>>& metrics) {
std::string str;
for (auto& m : metrics) {
if (m->metric_type() == MetricType::Summary) {
async_simple::coro::syncAwait(m->serialize_async(str));
}
else {
m->serialize(str);
}
}
return str;
}
static async_simple::coro::Lazy<std::string> serialize_dynamic() {
return serialize_impl<true>();
static std::string serialize_static() { return serialize(collect<false>()); }
static std::string serialize_dynamic() { return serialize(collect<true>()); }
#ifdef CINATRA_ENABLE_METRIC_JSON
static std::string serialize_to_json_static() {
auto metrics = collect<false>();
return serialize_to_json(metrics);
}
static std::string serialize_to_json_dynamic() {
auto metrics = collect<true>();
return serialize_to_json(metrics);
}
static std::string serialize_to_json(
const std::vector<std::shared_ptr<metric_t>>& metrics) {
if (metrics.empty()) {
return "";
}
std::string str;
str.append("[");
for (auto& m : metrics) {
size_t start = str.size();
if (m->metric_type() == MetricType::Summary) {
async_simple::coro::syncAwait(m->serialize_to_json_async(str));
}
else {
m->serialize_to_json(str);
}
if (str.size() > start)
str.append(",");
}
str.back() = ']';
return str;
}
#endif
static std::vector<std::shared_ptr<metric_t>> filter_metrics_static(
const metric_filter_options& options) {
return filter_metrics<false>(options);
}
static std::vector<std::shared_ptr<metric_t>> filter_metrics_dynamic(
const metric_filter_options& options) {
return filter_metrics<true>(options);
}
private:
@ -242,6 +433,12 @@ struct metric_manager_t {
return r;
}
template <bool need_lock>
static size_t remove_metric_impl(const std::string& name) {
auto lock = get_lock<need_lock>();
return metric_map_.erase(name);
}
template <bool need_lock>
static auto metric_map_impl() {
auto lock = get_lock<need_lock>();
@ -289,19 +486,64 @@ struct metric_manager_t {
return metrics;
}
template <bool need_lock = true>
static async_simple::coro::Lazy<std::string> serialize_impl() {
std::string str;
auto metrics = collect<need_lock>();
for (auto& m : metrics) {
if (m->metric_type() == MetricType::Summary) {
co_await m->serialize_async(str);
}
else {
m->serialize(str);
static void filter_by_label_name(
std::vector<std::shared_ptr<metric_t>>& filtered_metrics,
std::shared_ptr<metric_t> m, const metric_filter_options& options,
std::vector<size_t>& indexs, size_t index) {
const auto& labels_name = m->labels_name();
for (auto& label_name : labels_name) {
if (std::regex_match(label_name, *options.label_regex)) {
if (options.is_white) {
filtered_metrics.push_back(m);
}
else {
indexs.push_back(index);
}
}
}
co_return str;
}
template <bool need_lock>
static std::vector<std::shared_ptr<metric_t>> filter_metrics(
const metric_filter_options& options) {
auto metrics = collect<need_lock>();
if (!options.name_regex && !options.label_regex) {
return metrics;
}
std::vector<std::shared_ptr<metric_t>> filtered_metrics;
std::vector<size_t> indexs;
size_t index = 0;
for (auto& m : metrics) {
if (options.name_regex && !options.label_regex) {
if (std::regex_match(std::string(m->name()), *options.name_regex)) {
if (options.is_white) {
filtered_metrics.push_back(m);
}
else {
indexs.push_back(index);
}
}
}
else if (options.label_regex && !options.name_regex) {
filter_by_label_name(filtered_metrics, m, options, indexs, index);
}
else {
if (std::regex_match(std::string(m->name()), *options.name_regex)) {
filter_by_label_name(filtered_metrics, m, options, indexs, index);
}
}
index++;
}
if (!options.is_white) {
for (size_t i : indexs) {
metrics.erase(std::next(metrics.begin(), i));
}
return metrics;
}
return filtered_metrics;
}
static inline std::mutex mtx_;
@ -313,4 +555,4 @@ struct metric_manager_t {
};
using default_metric_manager = metric_manager_t<0>;
} // namespace ylt
} // namespace ylt::metric

View File

@ -6,7 +6,29 @@
#include "ylt/coro_io/coro_io.hpp"
#include "ylt/util/concurrentqueue.h"
namespace ylt {
namespace ylt::metric {
#ifdef CINATRA_ENABLE_METRIC_JSON
struct json_summary_metric_t {
std::map<std::string, std::string> labels;
std::map<double, double> quantiles;
int64_t count;
double sum;
};
REFLECTION(json_summary_metric_t, labels, quantiles, count, sum);
struct json_summary_t {
std::string name;
std::string help;
std::string type;
std::vector<json_summary_metric_t> metrics;
};
REFLECTION(json_summary_t, name, help, type, metrics);
#endif
struct summary_label_sample {
std::vector<std::string> labels_value;
double value;
};
class summary_t : public metric_t {
public:
using Quantiles = std::vector<CKMSQuantiles::Quantile>;
@ -14,22 +36,56 @@ class summary_t : public metric_t {
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5)
: quantiles_{std::move(quantiles)},
metric_t(MetricType::Summary, std::move(name), std::move(help)) {
work_ = std::make_shared<asio::io_context::work>(ctx_);
thd_ = std::thread([this] {
ctx_.run();
});
excutor_ =
std::make_unique<coro_io::ExecutorWrapper<>>(ctx_.get_executor());
block_ = std::make_shared<block_t>();
metric_t(MetricType::Summary, std::move(name), std::move(help)),
max_age_(max_age),
age_buckets_(age_buckets) {
init_executor();
init_block(block_);
block_->quantile_values_ =
std::make_shared<TimeWindowQuantiles>(quantiles_, max_age, age_buckets);
start_timer(block_).via(excutor_.get()).start([](auto &&) {
});
use_atomic_ = true;
}
summary_t(std::string name, std::string help, Quantiles quantiles,
std::vector<std::string> labels_name,
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5)
: quantiles_{std::move(quantiles)},
metric_t(MetricType::Summary, std::move(name), std::move(help),
std::move(labels_name)),
max_age_(max_age),
age_buckets_(age_buckets) {
init_executor();
init_block(labels_block_);
}
summary_t(std::string name, std::string help, Quantiles quantiles,
std::map<std::string, std::string> static_labels,
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5)
: quantiles_{std::move(quantiles)},
metric_t(MetricType::Summary, std::move(name), std::move(help),
std::move(static_labels)),
max_age_(max_age),
age_buckets_(age_buckets) {
init_executor();
init_block(labels_block_);
labels_block_->label_quantile_values_[labels_value_] =
std::make_shared<TimeWindowQuantiles>(quantiles_, max_age, age_buckets);
labels_block_->label_count_.emplace(labels_value_, 0);
labels_block_->label_sum_.emplace(labels_value_, 0);
use_atomic_ = true;
}
~summary_t() {
block_->stop_ = true;
if (block_) {
block_->stop_ = true;
}
if (labels_block_) {
labels_block_->stop_ = true;
}
work_ = nullptr;
thd_.join();
}
@ -42,7 +98,39 @@ class summary_t : public metric_t {
double sum_;
};
void observe(double value) { block_->sample_queue_.enqueue(value); }
struct labels_block_t {
std::atomic<bool> stop_ = false;
moodycamel::ConcurrentQueue<summary_label_sample> sample_queue_;
std::map<std::vector<std::string>, std::shared_ptr<TimeWindowQuantiles>,
std::less<std::vector<std::string>>>
label_quantile_values_;
std::map<std::vector<std::string>, std::uint64_t,
std::less<std::vector<std::string>>>
label_count_;
std::map<std::vector<std::string>, double,
std::less<std::vector<std::string>>>
label_sum_;
};
void observe(double value) {
if (!labels_name_.empty()) {
throw std::invalid_argument("not a default label metric");
}
block_->sample_queue_.enqueue(value);
}
void observe(std::vector<std::string> labels_value, double value) {
if (labels_value.empty()) {
throw std::invalid_argument("not a label metric");
}
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument("not equal with static label");
}
}
labels_block_->sample_queue_.enqueue({std::move(labels_value), value});
}
async_simple::coro::Lazy<std::vector<double>> get_rates(double &sum,
uint64_t &count) {
@ -51,34 +139,86 @@ class summary_t : public metric_t {
co_return std::vector<double>{};
}
co_await coro_io::post([this, &vec, &sum, &count] {
sum = block_->sum_;
count = block_->count_;
for (const auto &quantile : quantiles_) {
vec.push_back(block_->quantile_values_->get(quantile.quantile));
}
});
co_await coro_io::post(
[this, &vec, &sum, &count] {
sum = block_->sum_;
count = block_->count_;
for (const auto &quantile : quantiles_) {
vec.push_back(block_->quantile_values_->get(quantile.quantile));
}
},
excutor_.get());
co_return vec;
}
async_simple::coro::Lazy<std::vector<double>> get_rates(
const std::vector<std::string> &labels_value, double &sum,
uint64_t &count) {
std::vector<double> vec;
if (quantiles_.empty()) {
co_return std::vector<double>{};
}
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument("not equal with static label");
}
}
co_await coro_io::post(
[this, &vec, &sum, &count, &labels_value] {
auto it = labels_block_->label_quantile_values_.find(labels_value);
if (it == labels_block_->label_quantile_values_.end()) {
return;
}
sum = labels_block_->label_sum_[labels_value];
count = labels_block_->label_count_[labels_value];
for (const auto &quantile : quantiles_) {
vec.push_back(it->second->get(quantile.quantile));
}
},
excutor_.get());
co_return vec;
}
std::map<std::vector<std::string>, double,
std::less<std::vector<std::string>>>
value_map() override {
auto ret = async_simple::coro::syncAwait(coro_io::post(
[this] {
return labels_block_->label_sum_;
},
excutor_.get()));
return ret.value();
}
async_simple::coro::Lazy<double> get_sum() {
auto ret = co_await coro_io::post([this] {
return block_->sum_;
});
auto ret = co_await coro_io::post(
[this] {
return block_->sum_;
},
excutor_.get());
co_return ret.value();
}
async_simple::coro::Lazy<uint64_t> get_count() {
auto ret = co_await coro_io::post([this] {
return block_->count_;
});
auto ret = co_await coro_io::post(
[this] {
return block_->count_;
},
excutor_.get());
co_return ret.value();
}
size_t size_approx() { return block_->sample_queue_.size_approx(); }
async_simple::coro::Lazy<void> serialize_async(std::string &str) override {
if (block_ == nullptr) {
co_await serialize_async_with_label(str);
co_return;
}
if (quantiles_.empty()) {
co_return;
}
@ -103,8 +243,55 @@ class summary_t : public metric_t {
.append("\n");
}
#ifdef CINATRA_ENABLE_METRIC_JSON
async_simple::coro::Lazy<void> serialize_to_json_async(
std::string &str) override {
if (block_ == nullptr) {
co_await serialize_to_json_with_label_async(str);
co_return;
}
if (quantiles_.empty()) {
co_return;
}
json_summary_t summary{name_, help_, std::string(metric_name())};
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(sum, count);
json_summary_metric_t metric;
for (size_t i = 0; i < quantiles_.size(); i++) {
metric.quantiles.emplace(quantiles_[i].quantile, rates[i]);
}
metric.sum = sum;
metric.count = count;
summary.metrics.push_back(std::move(metric));
iguana::to_json(summary, str);
}
#endif
private:
async_simple::coro::Lazy<void> start_timer(std::shared_ptr<block_t> block) {
void init_executor() {
work_ = std::make_shared<asio::io_context::work>(ctx_);
thd_ = std::thread([this] {
ctx_.run();
});
excutor_ =
std::make_unique<coro_io::ExecutorWrapper<>>(ctx_.get_executor());
}
template <typename T>
void init_block(std::shared_ptr<T> &block) {
block = std::make_shared<T>();
start(block).via(excutor_.get()).start([](auto &&) {
});
}
async_simple::coro::Lazy<void> start(std::shared_ptr<block_t> block) {
double sample;
size_t count = 1000000;
while (!block->stop_) {
@ -122,18 +309,132 @@ class summary_t : public metric_t {
co_await async_simple::coro::Yield{};
if (block->sample_queue_.size_approx() == 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
co_await coro_io::sleep_for(std::chrono::milliseconds(5),
excutor_.get());
}
}
co_return;
}
async_simple::coro::Lazy<void> start(std::shared_ptr<labels_block_t> block) {
summary_label_sample sample;
size_t count = 1000000;
while (!block->stop_) {
size_t index = 0;
while (block->sample_queue_.try_dequeue(sample)) {
auto &ptr = block->label_quantile_values_[sample.labels_value];
if (ptr == nullptr) {
ptr = std::make_shared<TimeWindowQuantiles>(quantiles_, max_age_,
age_buckets_);
}
ptr->insert(sample.value);
block->label_count_[sample.labels_value] += 1;
block->label_sum_[sample.labels_value] += sample.value;
index++;
if (index == count) {
break;
}
}
co_await async_simple::coro::Yield{};
if (block->sample_queue_.size_approx() == 0) {
co_await coro_io::sleep_for(std::chrono::milliseconds(5),
excutor_.get());
}
}
co_return;
}
async_simple::coro::Lazy<void> serialize_async_with_label(std::string &str) {
if (quantiles_.empty()) {
co_return;
}
serialize_head(str);
auto sum_map = co_await coro_io::post(
[this] {
return labels_block_->label_sum_;
},
excutor_.get());
for (auto &[labels_value, sum_val] : sum_map.value()) {
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(labels_value, sum, count);
for (size_t i = 0; i < quantiles_.size(); i++) {
str.append(name_);
str.append("{");
build_label_string(str, labels_name_, labels_value);
str.append(",");
str.append("quantile=\"");
str.append(std::to_string(quantiles_[i].quantile)).append("\"} ");
str.append(std::to_string(rates[i])).append("\n");
}
str.append(name_).append("_sum ");
str.append("{");
build_label_string(str, labels_name_, labels_value);
str.append("} ");
str.append(std::to_string(sum)).append("\n");
str.append(name_).append("_count ");
str.append("{");
build_label_string(str, labels_name_, labels_value);
str.append("} ");
str.append(std::to_string((uint64_t)count)).append("\n");
}
}
#ifdef CINATRA_ENABLE_METRIC_JSON
async_simple::coro::Lazy<void> serialize_to_json_with_label_async(
std::string &str) {
if (quantiles_.empty()) {
co_return;
}
auto sum_map = co_await coro_io::post(
[this] {
return labels_block_->label_sum_;
},
excutor_.get());
json_summary_t summary{name_, help_, std::string(metric_name())};
for (auto &[labels_value, sum_val] : sum_map.value()) {
json_summary_metric_t metric;
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(labels_value, sum, count);
metric.count = count;
metric.sum = sum;
for (size_t i = 0; i < quantiles_.size(); i++) {
for (size_t i = 0; i < labels_value.size(); i++) {
metric.labels[labels_name_[i]] = labels_value[i];
}
metric.quantiles.emplace(quantiles_[i].quantile, rates[i]);
}
summary.metrics.push_back(std::move(metric));
}
iguana::to_json(summary, str);
}
#endif
Quantiles quantiles_; // readonly
std::shared_ptr<block_t> block_;
std::shared_ptr<labels_block_t> labels_block_;
std::unique_ptr<coro_io::ExecutorWrapper<>> excutor_ = nullptr;
std::shared_ptr<asio::io_context::work> work_;
asio::io_context ctx_;
std::thread thd_;
std::chrono::milliseconds max_age_;
int age_buckets_;
};
} // namespace ylt
} // namespace ylt::metric

View File

@ -182,12 +182,23 @@ class coro_http_server {
}
}
void use_metrics(std::string url_path = "/metrics") {
void use_metrics(bool enable_json = false,
std::string url_path = "/metrics") {
init_metrics();
set_http_handler<http_method::GET>(
url_path, [](coro_http_request &req, coro_http_response &res) {
std::string str = async_simple::coro::syncAwait(
ylt::default_metric_manager::serialize_static());
url_path,
[enable_json](coro_http_request &req, coro_http_response &res) {
std::string str;
#ifdef CINATRA_ENABLE_METRIC_JSON
if (enable_json) {
str =
ylt::metric::default_metric_manager::serialize_to_json_static();
res.set_content_type<resp_content_type::json>();
}
else
#endif
str = ylt::metric::default_metric_manager::serialize_static();
res.set_status_and_content(status_type::ok, std::move(str));
});
}
@ -900,7 +911,7 @@ class coro_http_server {
private:
void init_metrics() {
using namespace ylt;
using namespace ylt::metric;
cinatra_metric_conf::enable_metric = true;
default_metric_manager::create_metric_static<counter_t>(

View File

@ -25,9 +25,8 @@ struct cinatra_metric_conf {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::counter_t>(
server_total_req);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_total_req);
if (m == nullptr) {
return;
}
@ -38,9 +37,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::counter_t>(
server_failed_req);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_failed_req);
if (m == nullptr) {
return;
}
@ -51,9 +49,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::gauge_t>(
server_total_fd);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::gauge_t>(server_total_fd);
if (m == nullptr) {
return;
}
@ -64,9 +61,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::gauge_t>(
server_total_fd);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::gauge_t>(server_total_fd);
if (m == nullptr) {
return;
}
@ -77,9 +73,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::counter_t>(
server_total_recv_bytes);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_total_recv_bytes);
if (m == nullptr) {
return;
}
@ -90,9 +85,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::counter_t>(
server_total_send_bytes);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_total_send_bytes);
if (m == nullptr) {
return;
}
@ -103,9 +97,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::histogram_t>(
server_req_latency);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::histogram_t>(server_req_latency);
if (m == nullptr) {
return;
}
@ -116,9 +109,8 @@ struct cinatra_metric_conf {
if (!enable_metric) {
return;
}
static auto m =
ylt::default_metric_manager::get_metric_static<ylt::histogram_t>(
server_read_latency);
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::histogram_t>(server_read_latency);
if (m == nullptr) {
return;
}

View File

@ -1,12 +1,11 @@
#include "ylt/metric/gauge.hpp"
#define DOCTEST_CONFIG_IMPLEMENT
#include <random>
#include "doctest.h"
#include "ylt/metric/counter.hpp"
#include "ylt/metric/histogram.hpp"
#include "ylt/metric/summary.hpp"
#include "ylt/metric.hpp"
using namespace ylt;
using namespace ylt::metric;
TEST_CASE("test no lable") {
{
@ -139,6 +138,13 @@ TEST_CASE("test gauge") {
CHECK(g.value() == 2);
g.inc(0);
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string str_json;
g.serialize_to_json(str_json);
std::cout << str_json << "\n";
CHECK(str_json.find("\"value\":2") != std::string::npos);
#endif
g.dec();
CHECK(g.value() == 1);
g.dec();
@ -156,6 +162,15 @@ TEST_CASE("test gauge") {
values = g.value_map();
CHECK(values[{"GET", "200", "/"}] == 3);
g.inc({"POST", "200", "/"}, 4);
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string str_json;
g.serialize_to_json(str_json);
std::cout << str_json << "\n";
CHECK(str_json.find("\"code\":\"200\"") != std::string::npos);
#endif
std::string str;
g.serialize(str);
std::cout << str;
@ -175,7 +190,7 @@ TEST_CASE("test gauge") {
}
TEST_CASE("test histogram") {
histogram_t h("test", "help", {5.0, 10.0, 20.0, 50.0, 100.0});
histogram_t h("test", "help", {5.23, 10.54, 20.0, 50.0, 100.0});
h.observe(23);
auto counts = h.get_bucket_counts();
CHECK(counts[3]->value() == 1);
@ -189,11 +204,18 @@ TEST_CASE("test histogram") {
CHECK(counts[0]->value() == 1);
std::string str;
h.serialize(str);
std::cout << str;
std::cout << str << "\n";
CHECK(str.find("test_count") != std::string::npos);
CHECK(str.find("test_sum") != std::string::npos);
CHECK(str.find("test_bucket{le=\"5") != std::string::npos);
CHECK(str.find("test_bucket{le=\"5.23") != std::string::npos);
CHECK(str.find("test_bucket{le=\"+Inf\"}") != std::string::npos);
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string str_json;
h.serialize_to_json(str_json);
std::cout << str_json << "\n";
CHECK(str_json.find("\"5.23\":1") != std::string::npos);
#endif
}
TEST_CASE("test summary") {
@ -217,6 +239,13 @@ TEST_CASE("test summary") {
CHECK(str.find("test_summary_count") != std::string::npos);
CHECK(str.find("test_summary_sum") != std::string::npos);
CHECK(str.find("test_summary{quantile=\"") != std::string::npos);
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string str_json;
async_simple::coro::syncAwait(summary.serialize_to_json_async(str_json));
std::cout << str_json << "\n";
CHECK(str_json.find("\"0.9\":") != std::string::npos);
#endif
}
TEST_CASE("test register metric") {
@ -245,8 +274,7 @@ TEST_CASE("test register metric") {
CHECK(map["get_count"]->as<counter_t>()->value() == 1);
CHECK(map["get_guage_count"]->as<gauge_t>()->value() == 1);
auto s =
async_simple::coro::syncAwait(default_metric_manager::serialize_static());
auto s = default_metric_manager::serialize_static();
std::cout << s << "\n";
CHECK(s.find("get_count 1") != std::string::npos);
CHECK(s.find("get_guage_count 1") != std::string::npos);
@ -275,6 +303,474 @@ TEST_CASE("test register metric") {
}
}
TEST_CASE("test remove metric and serialize metrics") {
using metric_mgr = metric_manager_t<1>;
metric_mgr::create_metric_dynamic<counter_t>("test_counter", "");
metric_mgr::create_metric_dynamic<counter_t>("test_counter2", "");
size_t count = metric_mgr::metric_count_dynamic();
CHECK(count == 2);
metric_mgr::remove_metric_dynamic("test_counter");
count = metric_mgr::metric_count_dynamic();
CHECK(count == 1);
metric_mgr::remove_metric_dynamic("test_counter2");
count = metric_mgr::metric_count_dynamic();
CHECK(count == 0);
CHECK_THROWS_AS(
metric_mgr::create_metric_static<counter_t>("test_static_counter", ""),
std::invalid_argument);
using metric_mgr2 = metric_manager_t<2>;
auto c =
metric_mgr2::create_metric_static<counter_t>("test_static_counter", "");
auto c2 =
metric_mgr2::create_metric_static<counter_t>("test_static_counter2", "");
c->inc();
c2->inc();
#ifdef CINATRA_ENABLE_METRIC_JSON
auto s = metric_mgr2::serialize_to_json_static();
std::cout << s << "\n";
auto s1 = metric_mgr2::serialize_to_json({c, c2});
CHECK(s == s1);
#endif
CHECK_THROWS_AS(metric_mgr2::metric_count_dynamic(), std::invalid_argument);
count = metric_mgr2::metric_count_static();
CHECK(count == 2);
CHECK_THROWS_AS(metric_mgr2::remove_metric_dynamic("test_static_counter"),
std::invalid_argument);
metric_mgr2::remove_metric_static("test_static_counter");
count = metric_mgr2::metric_count_static();
CHECK(count == 1);
}
TEST_CASE("test filter metrics static") {
using metric_mgr = metric_manager_t<3>;
auto c = metric_mgr::create_metric_static<counter_t>(
"test_static_counter", "",
std::map<std::string, std::string>{{"method", "GET"}});
auto c2 = metric_mgr::create_metric_static<counter_t>(
"test_static_counter2", "",
std::map<std::string, std::string>{{"url", "/"}});
c->inc({"GET"});
c2->inc({"/"});
metric_filter_options options;
options.name_regex = ".*counter.*";
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.size() == 2);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_static_counter") != std::string::npos);
std::cout << s << "\n";
}
options.label_regex = ".*ur.*";
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.size() == 1);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_static_counter2") != std::string::npos);
std::cout << s << "\n";
}
options.name_regex = "no_such_name";
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.empty());
auto s = metric_mgr::serialize(metrics);
CHECK(s.empty());
}
options = {};
options.label_regex = "no_such_label";
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.empty());
auto s = metric_mgr::serialize(metrics);
CHECK(s.empty());
}
// don't filter
options = {};
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.size() == 2);
}
// black
options.label_regex = ".*ur.*";
options.is_white = false;
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.size() == 1);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_static_counter") != std::string::npos);
CHECK(s.find("test_static_counter2") == std::string::npos);
}
options = {};
options.label_regex = ".*ur.*";
options.is_white = false;
{
auto metrics = metric_mgr::filter_metrics_static(options);
CHECK(metrics.size() == 1);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_static_counter") != std::string::npos);
CHECK(s.find("method") != std::string::npos);
CHECK(s.find("test_static_counter2") == std::string::npos);
CHECK(s.find("url") == std::string::npos);
}
}
TEST_CASE("test filter metrics dynamic") {
using metric_mgr = metric_manager_t<4>;
auto c = metric_mgr::create_metric_dynamic<counter_t>(
"test_dynamic_counter", "", std::vector<std::string>{{"method"}});
auto c2 = metric_mgr::create_metric_dynamic<counter_t>(
"test_dynamic_counter2", "", std::vector<std::string>{{"url"}});
c->inc({"GET"});
c->inc({"POST"});
c2->inc({"/"});
c2->inc({"/test"});
metric_filter_options options;
options.name_regex = ".*counter.*";
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.size() == 2);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_dynamic_counter") != std::string::npos);
std::cout << s << "\n";
}
options.label_regex = ".*ur.*";
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.size() == 1);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_dynamic_counter2") != std::string::npos);
std::cout << s << "\n";
}
options.name_regex = "no_such_name";
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.empty());
auto s = metric_mgr::serialize(metrics);
CHECK(s.empty());
}
options = {};
options.label_regex = "no_such_label";
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.empty());
auto s = metric_mgr::serialize(metrics);
CHECK(s.empty());
}
// don't filter
options = {};
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.size() == 2);
}
// black
options.label_regex = ".*ur.*";
options.is_white = false;
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.size() == 1);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_dynamic_counter") != std::string::npos);
CHECK(s.find("test_dynamic_counter2") == std::string::npos);
}
options = {};
options.label_regex = ".*ur.*";
options.is_white = false;
{
auto metrics = metric_mgr::filter_metrics_dynamic(options);
CHECK(metrics.size() == 1);
auto s = metric_mgr::serialize(metrics);
CHECK(s.find("test_dynamic_counter") != std::string::npos);
CHECK(s.find("method") != std::string::npos);
CHECK(s.find("test_dynamic_counter2") == std::string::npos);
CHECK(s.find("url") == std::string::npos);
}
}
TEST_CASE("test get metric by static labels and label") {
using metric_mgr = metric_manager_t<9>;
metric_mgr::create_metric_static<counter_t>(
"http_req_test", "",
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/"}});
metric_mgr::create_metric_static<gauge_t>(
"http_req_test1", "",
std::map<std::string, std::string>{{"method", "POST"}, {"url", "/"}});
metric_mgr::create_metric_static<counter_t>(
"http_req_test2", "",
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/test"}});
auto v = metric_mgr::get_metric_by_labels_static(
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/test"}});
CHECK(v[0]->name() == "http_req_test2");
v = metric_mgr::get_metric_by_labels_static(
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/"}});
CHECK(v[0]->name() == "http_req_test");
auto h1 = metric_mgr::create_metric_static<histogram_t>(
"http_req_static_hist", "help",
std::vector<double>{5.23, 10.54, 20.0, 50.0, 100.0},
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/"}});
h1->observe({"GET", "/"}, 23);
auto s1 = metric_mgr::create_metric_static<summary_t>(
"http_req_static_summary", "help",
summary_t::Quantiles{
{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}},
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/"}});
s1->observe({"GET", "/"}, 23);
auto vec = metric_mgr::get_metric_by_label_static({"method", "GET"});
CHECK(vec.size() == 4);
vec = metric_mgr::get_metric_by_label_static({"url", "/"});
CHECK(vec.size() == 4);
vec = metric_mgr::get_metric_by_label_static({"url", "/test"});
CHECK(vec.size() == 1);
vec = metric_mgr::get_metric_by_label_static({"method", "POST"});
CHECK(vec.size() == 1);
vec = metric_mgr::get_metric_by_labels_static(
std::map<std::string, std::string>{{"method", "HEAD"}, {"url", "/test"}});
CHECK(vec.empty());
vec = metric_mgr::get_metric_by_labels_static(
std::map<std::string, std::string>{{"method", "GET"}});
CHECK(vec.empty());
vec = metric_mgr::get_metric_by_label_static({"url", "/index"});
CHECK(vec.empty());
}
TEST_CASE("test get metric by dynamic labels") {
using metric_mgr = metric_manager_t<10>;
auto c = metric_mgr::create_metric_dynamic<counter_t>(
"http_req_static", "", std::vector<std::string>{"method", "code"});
auto c1 = metric_mgr::create_metric_dynamic<counter_t>(
"http_req_static1", "", std::vector<std::string>{"method", "code"});
auto c2 = metric_mgr::create_metric_dynamic<counter_t>(
"http_req_static2", "", std::vector<std::string>{"method", "code"});
auto c3 = metric_mgr::create_metric_dynamic<counter_t>(
"http_req_static3", "", std::vector<std::string>{"method", "code"});
c->inc({"POST", "200"});
c1->inc({"GET", "200"});
c2->inc({"POST", "301"});
c3->inc({"POST", "400"});
auto c4 = metric_mgr::create_metric_dynamic<counter_t>(
"http_req_static4", "", std::vector<std::string>{"host", "url"});
auto c5 = metric_mgr::create_metric_dynamic<counter_t>(
"http_req_static5", "", std::vector<std::string>{"host", "url"});
c4->inc({"shanghai", "/"});
c5->inc({"shanghai", "/test"});
auto vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "POST"}});
CHECK(vec.size() == 3);
vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "GET"}});
CHECK(vec.size() == 1);
vec = metric_mgr::get_metric_by_labels_dynamic({{"host", "shanghai"}});
CHECK(vec.size() == 2);
vec = metric_mgr::get_metric_by_labels_dynamic({{"url", "/"}});
CHECK(vec.size() == 1);
vec = metric_mgr::get_metric_by_labels_dynamic({{"url", "/test"}});
CHECK(vec.size() == 1);
vec = metric_mgr::get_metric_by_labels_dynamic({{"url", "/none"}});
CHECK(vec.size() == 0);
vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "HEAD"}});
CHECK(vec.size() == 0);
auto h1 = metric_mgr::create_metric_dynamic<histogram_t>(
"http_req_static_hist", "help",
std::vector<double>{5.23, 10.54, 20.0, 50.0, 100.0},
std::vector<std::string>{"method", "url"});
h1->observe({"GET", "/"}, 23);
auto s1 = metric_mgr::create_metric_dynamic<summary_t>(
"http_req_static_summary", "help",
summary_t::Quantiles{
{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}},
std::vector<std::string>{"method", "url"});
s1->observe({"GET", "/"}, 23);
vec = metric_mgr::get_metric_by_labels_dynamic({{"method", "GET"}});
CHECK(vec.size() >= 2);
auto str = metric_mgr::serialize(vec);
std::cout << str;
#ifdef CINATRA_ENABLE_METRIC_JSON
auto json_str = metric_mgr::serialize_to_json(vec);
std::cout << json_str << "\n";
#endif
}
TEST_CASE("test histogram serialize with dynamic labels") {
histogram_t h("test", "help", {5.23, 10.54, 20.0, 50.0, 100.0},
std::vector<std::string>{"method", "url"});
h.observe({"GET", "/"}, 23);
auto counts = h.get_bucket_counts();
CHECK(counts[3]->value({"GET", "/"}) == 1);
h.observe({"GET", "/"}, 42);
CHECK(counts[3]->value({"GET", "/"}) == 2);
h.observe({"GET", "/"}, 60);
CHECK(counts[4]->value({"GET", "/"}) == 1);
h.observe({"GET", "/"}, 120);
CHECK(counts[5]->value({"GET", "/"}) == 1);
h.observe({"GET", "/"}, 1);
CHECK(counts[0]->value({"GET", "/"}) == 1);
h.observe({"POST", "/"}, 23);
CHECK(counts[3]->value({"POST", "/"}) == 1);
h.observe({"POST", "/"}, 42);
CHECK(counts[3]->value({"POST", "/"}) == 2);
h.observe({"POST", "/"}, 60);
CHECK(counts[4]->value({"POST", "/"}) == 1);
h.observe({"POST", "/"}, 120);
CHECK(counts[5]->value({"POST", "/"}) == 1);
h.observe({"POST", "/"}, 1);
CHECK(counts[0]->value({"POST", "/"}) == 1);
std::string str;
h.serialize(str);
std::cout << str;
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string str_json;
h.serialize_to_json(str_json);
std::cout << str_json << "\n";
#endif
}
TEST_CASE("test histogram serialize with static labels") {
histogram_t h(
"test", "help", {5.23, 10.54, 20.0, 50.0, 100.0},
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/"}});
h.observe({"GET", "/"}, 23);
auto counts = h.get_bucket_counts();
CHECK(counts[3]->value({"GET", "/"}) == 1);
h.observe({"GET", "/"}, 42);
CHECK(counts[3]->value({"GET", "/"}) == 2);
h.observe({"GET", "/"}, 60);
CHECK(counts[4]->value({"GET", "/"}) == 1);
h.observe({"GET", "/"}, 120);
CHECK(counts[5]->value({"GET", "/"}) == 1);
h.observe({"GET", "/"}, 1);
CHECK(counts[0]->value({"GET", "/"}) == 1);
std::string str;
h.serialize(str);
std::cout << str;
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string str_json;
h.serialize_to_json(str_json);
std::cout << str_json << "\n";
#endif
}
TEST_CASE("test summary with dynamic labels") {
summary_t summary{"test_summary",
"summary help",
{{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}},
std::vector<std::string>{"method", "url"}};
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distr(1, 100);
for (int i = 0; i < 50; i++) {
summary.observe({"GET", "/"}, distr(gen));
summary.observe({"POST", "/test"}, distr(gen));
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
double sum;
uint64_t count;
auto rates = async_simple::coro::syncAwait(
summary.get_rates({"GET", "/"}, sum, count));
std::cout << rates.size() << "\n";
std::string str;
async_simple::coro::syncAwait(summary.serialize_async(str));
std::cout << str;
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string json_str;
async_simple::coro::syncAwait(summary.serialize_to_json_async(json_str));
std::cout << json_str << "\n";
#endif
}
TEST_CASE("test summary with static labels") {
summary_t summary{
"test_summary",
"summary help",
{{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}},
std::map<std::string, std::string>{{"method", "GET"}, {"url", "/"}}};
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distr(1, 100);
for (int i = 0; i < 50; i++) {
summary.observe({"GET", "/"}, distr(gen));
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
CHECK_THROWS_AS(summary.observe({"POST", "/"}, 1), std::invalid_argument);
double sum;
uint64_t count;
auto rates = async_simple::coro::syncAwait(
summary.get_rates({"GET", "/"}, sum, count));
std::cout << rates.size() << "\n";
std::string str;
async_simple::coro::syncAwait(summary.serialize_async(str));
std::cout << str;
#ifdef CINATRA_ENABLE_METRIC_JSON
std::string json_str;
async_simple::coro::syncAwait(summary.serialize_to_json_async(json_str));
std::cout << json_str << "\n";
#endif
}
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007)
int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }
DOCTEST_MSVC_SUPPRESS_WARNING_POP

View File

@ -148,18 +148,20 @@ some_counter.inc({"GET", "/"}, 1);
构造函数:
```cpp
// 无标签调用inc时不带标签如c.inc()
// 无标签调用inc时不带标签如c.inc()调用此函数则metric 为静态标签的metric
// name: 指标对象的名称,注册到指标管理器时会使用这个名称
// help: 指标对象的帮助信息
counter_t(std::string name, std::string help);
// labels: 静态标签,构造时需要将标签键值都填完整,如:{{"method", "GET"}, {"url", "/"}}
// 调用此函数则metric 为静态标签的metric
// 调用inc时必须带静态标签的值c.inc({"GET", "/"}, 1);
counter_t(std::string name, std::string help,
std::map<std::string, std::string> labels);
// labels_name: 动态标签的键名称,因为标签的值是动态的,而键的名称是固定的,所以这里只需要填键名称,如: {"method", "url"}
// 调用时inc时必须带动态标签的值c.inc({method, url}, 1);
// 调用此函数则metric 为动态标签的metric
counter_t(std::string name, std::string help,
std::vector<std::string> labels_name);
```
@ -230,12 +232,18 @@ class metric_t {
// 获取标签的键,如{"method", "url"}
const std::vector<std::string>& labels_name();
// 获取静态标签,如{{"method", "GET"}, {"code", "200"}}
const std::map<std::string, std::string>& get_static_labels();
// 序列化,调用派生类实现序列化
virtual void serialize(std::string& str);
// 给summary专用的api序列化调用派生类实现序列化
virtual async_simple::coro::Lazy<void> serialize_async(std::string& out);
// 序列化到json
void serialize_to_json(std::string& str);
// 将基类指针向下转换到派生类指针,如:
// std::shared_ptr<metric_t> c = std::make_shared<counter_t>("test", "test");
// counter_t* t = c->as<counter_t*>();
@ -302,6 +310,10 @@ struct metric_manager_t {
static bool register_metric_static(std::shared_ptr<metric_t> metric);
static bool register_metric_dynamic(std::shared_ptr<metric_t> metric);
// 根据metric名称删除metric
static bool remove_metric_static(const std::string& name);
static bool remove_metric_dynamic(const std::string& name);
// 获取注册的所有指标对象
static std::map<std::string, std::shared_ptr<metric_t>> metric_map_static();
static std::map<std::string, std::shared_ptr<metric_t>> metric_map_dynamic();
@ -324,9 +336,43 @@ struct metric_manager_t {
static std::shared_ptr<metric_t> get_metric_static(const std::string& name);
static std::shared_ptr<metric_t> get_metric_dynamic(const std::string& name);
// 根据静态标签获取所有的指标, 如{{"method", "GET"}, {"url", "/"}}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_labels_static(
const std::map<std::string, std::string>& labels);
// 根据标签值获取所有的静态标签的指标, 如{"method", "GET"}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_label_static(
const std::pair<std::string, std::string>& label);
// 根据标签值获取所有动态标签的指标, 如{"method", "GET"}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_labels_dynamic(
const std::map<std::string, std::string>& labels);
// 序列化
static async_simple::coro::Lazy<std::string> serialize_static();
static async_simple::coro::Lazy<std::string> serialize_dynamic();
// 序列化静态标签的指标到json
static std::string serialize_to_json_static();
// 序列化动态标签的指标到json
static std::string serialize_to_json_dynamic();
// 序列化metric集合到json
static std::string serialize_to_json(
const std::vector<std::shared_ptr<metric_t>>& metrics);
// 过滤配置选项如果name_regex和label_regex都设置了则会检查这两个条件如果只设置了一个则只检查设置过的条件
struct metric_filter_options {
std::optional<std::regex> name_regex{}; // metric 名称的过滤正则表达式
std::optional<std::regex> label_regex{};// metric label名称的过滤正则表达式
bool is_white = true; //true: 白名单包括语义false: 黑名单,排除语义
};
// 过滤静态标签的指标
static std::vector<std::shared_ptr<metric_t>> filter_metrics_static(
const metric_filter_options& options);
// 过滤动态标签的指标
static std::vector<std::shared_ptr<metric_t>> filter_metrics_dynamic(
const metric_filter_options& options);
};
using default_metric_manager = metric_manager_t<0>;
```
@ -347,11 +393,23 @@ using my_metric_manager = metric_manager_t<metric_id>;
// 内部还有一个+Inf 默认的桶,当输入的数据不在前面设置这些桶中,则会落到+Inf 默认桶中。
// 实际上桶的总数为 buckets.size() + 1
// 每个bucket 实际上对应了一个counter指标
// 调用此函数则metric为静态metric指标
histogram_t(std::string name, std::string help, std::vector<double> buckets);
// labels_value: 标签key后面可以使用动态标签值去observe调用此函数则metric为动态metric 指标
histogram_t(std::string name, std::string help, std::vector<double> buckets,
std::vector<std::string> labels_name);
// labels: 静态标签调用此函数则metric为静态metric指标
histogram_t(std::string name, std::string help, std::vector<double> buckets,
std::map<std::string, std::string> labels);
// 往histogram_t 中插入数据,内部会自动增加对应桶的计数
void observe(double value);
// 根据标签值插入数据可以是动态标签值也可以是静态标签值。如果是静态标签会做额外的检车检查传入的labels_value是否和注册时的静态标签值是否相同不相同会抛异常
void observe(const std::vector<std::string> &labels_value, double value);
// 获取所有桶对应的counter指标对象
std::vector<std::shared_ptr<counter_t>> get_bucket_counts();
@ -416,13 +474,28 @@ test_count 3.000000
```cpp
// Quantiles: 百分位和误差, 如:{{0.5, 0.05}, {0.9, 0.01}, {0.95, 0.005}, {0.99, 0.001}}
// 调用此函数则metric为静态metric 指标
summary_t(std::string name, std::string help, Quantiles quantiles);
// labels_name: 标签名调用此函数则metric为动态metric 指标
summary_t(std::string name, std::string help, Quantiles quantiles, std::vector<std::string> labels_name);
// static_labels静态标签调用此函数则metric为静态metric 指标
summary_t(std::string name, std::string help, Quantiles quantiles, std::map<std::string, std::string> static_labels);
// 往summary_t插入数据会自动计算百分位的数量
void observe(double value);
// 获取百分位结果
async_simple::coro::Lazy<std::vector<double>> get_rates();
// 根据标签值(动态或静态的标签值依据构造函数决定是动态还是静态metric)往summary_t插入数据会自动计算百分位的数量
void observe(std::vector<std::string> labels_value, double value);
// 获取分位数结果, sum 和count
async_simple::coro::Lazy<std::vector<double>> get_rates(double &sum,
uint64_t &count)
// 根据标签获取分位数结果, sum 和count
async_simple::coro::Lazy<std::vector<double>> get_rates(
const std::vector<std::string> &labels_value, double &sum,
uint64_t &count);
// 获取总和
async_simple::coro::Lazy<double> get_sum();