584 lines
16 KiB
C++
584 lines
16 KiB
C++
#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"
|
|
#if __has_include("ylt/coro_io/coro_io.hpp")
|
|
#include "ylt/coro_io/coro_io.hpp"
|
|
#else
|
|
#include "cinatra/ylt/coro_io/coro_io.hpp"
|
|
#endif
|
|
|
|
#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,
|
|
Histogram,
|
|
Summary,
|
|
Nil,
|
|
};
|
|
|
|
struct metric_filter_options {
|
|
std::optional<std::regex> name_regex{};
|
|
std::optional<std::regex> label_regex{};
|
|
bool is_white = true;
|
|
};
|
|
|
|
struct vector_hash {
|
|
size_t operator()(const std::vector<std::string>& vec) const {
|
|
unsigned int seed = 131;
|
|
unsigned int hash = 0;
|
|
|
|
for (const auto& str : vec) {
|
|
for (auto ch : str) {
|
|
hash = hash * seed + ch;
|
|
}
|
|
}
|
|
|
|
return (hash & 0x7FFFFFFF);
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
using metric_hash_map =
|
|
std::unordered_map<std::vector<std::string>, T, vector_hash>;
|
|
|
|
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_created_time_(std::chrono::system_clock::now()) {}
|
|
metric_t(MetricType type, std::string name, std::string help,
|
|
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_; }
|
|
|
|
std::string_view help() { return help_; }
|
|
|
|
MetricType metric_type() { return type_; }
|
|
|
|
auto get_created_time() { return metric_created_time_; }
|
|
|
|
std::string_view metric_name() {
|
|
switch (type_) {
|
|
case MetricType::Counter:
|
|
return "counter";
|
|
case MetricType::Gauge:
|
|
return "gauge";
|
|
case MetricType::Histogram:
|
|
return "histogram";
|
|
case MetricType::Summary:
|
|
return "summary";
|
|
case MetricType::Nil:
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
const std::vector<std::string>& labels_name() { return labels_name_; }
|
|
|
|
const std::map<std::string, std::string>& get_static_labels() {
|
|
return static_labels_;
|
|
}
|
|
|
|
virtual metric_hash_map<double> 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>
|
|
T* as() {
|
|
return dynamic_cast<T*>(this);
|
|
}
|
|
|
|
protected:
|
|
void set_metric_type(MetricType type) { type_ = type; }
|
|
void serialize_head(std::string& str) {
|
|
str.append("# HELP ").append(name_).append(" ").append(help_).append("\n");
|
|
str.append("# TYPE ")
|
|
.append(name_)
|
|
.append(" ")
|
|
.append(metric_name())
|
|
.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;
|
|
do {
|
|
v = obj->load();
|
|
} while (!std::atomic_compare_exchange_weak(obj, &v, v + arg));
|
|
return v;
|
|
}
|
|
|
|
double mac_os_atomic_fetch_sub(std::atomic<double>* obj, double arg) {
|
|
double v;
|
|
do {
|
|
v = obj->load();
|
|
} while (!std::atomic_compare_exchange_weak(obj, &v, v - arg));
|
|
return v;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
std::chrono::system_clock::time_point metric_created_time_{};
|
|
};
|
|
|
|
template <size_t ID = 0>
|
|
struct metric_manager_t {
|
|
struct null_mutex_t {
|
|
void lock() {}
|
|
void unlock() {}
|
|
};
|
|
|
|
// create and register metric
|
|
template <typename T, typename... Args>
|
|
static std::shared_ptr<T> create_metric_static(const std::string& name,
|
|
const std::string& help,
|
|
Args&&... args) {
|
|
auto m = std::make_shared<T>(name, help, std::forward<Args>(args)...);
|
|
bool r = register_metric_static(m);
|
|
if (!r) {
|
|
return nullptr;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
template <typename T, typename... Args>
|
|
static std::shared_ptr<T> create_metric_dynamic(const std::string& name,
|
|
const std::string& help,
|
|
Args&&... args) {
|
|
auto m = std::make_shared<T>(name, help, std::forward<Args>(args)...);
|
|
bool r = register_metric_dynamic(m);
|
|
if (!r) {
|
|
return nullptr;
|
|
}
|
|
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 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>
|
|
static bool register_metric_dynamic(Metrics... metrics) {
|
|
bool r = true;
|
|
((void)(r && (r = register_metric_impl<true>(metrics), true)), ...);
|
|
return r;
|
|
}
|
|
|
|
template <typename... Metrics>
|
|
static bool register_metric_static(Metrics... metrics) {
|
|
bool r = true;
|
|
((void)(r && (r = register_metric_impl<false>(metrics), true)), ...);
|
|
return r;
|
|
}
|
|
|
|
static auto metric_map_static() { return metric_map_impl<false>(); }
|
|
static auto metric_map_dynamic() { return metric_map_impl<true>(); }
|
|
|
|
static size_t metric_count_static() { return metric_count_impl<false>(); }
|
|
|
|
static size_t metric_count_dynamic() { return metric_count_impl<true>(); }
|
|
|
|
static std::vector<std::string> metric_keys_static() {
|
|
return metric_keys_impl<false>();
|
|
}
|
|
|
|
static std::vector<std::string> metric_keys_dynamic() {
|
|
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 std::shared_ptr<T> get_metric_static(const std::string& name) {
|
|
auto m = get_metric_impl<false>(name);
|
|
if (m == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return std::dynamic_pointer_cast<T>(m);
|
|
}
|
|
|
|
template <typename T>
|
|
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 std::dynamic_pointer_cast<T>(m);
|
|
}
|
|
|
|
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 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:
|
|
template <bool need_lock>
|
|
static void check_lock() {
|
|
if (need_lock_ != need_lock) {
|
|
std::string str = "need lock ";
|
|
std::string s = need_lock_ ? "true" : "false";
|
|
std::string r = need_lock ? "true" : "false";
|
|
str.append(s).append(" but set as ").append(r);
|
|
throw std::invalid_argument(str);
|
|
}
|
|
}
|
|
|
|
template <bool need_lock = true>
|
|
static auto get_lock() {
|
|
check_lock<need_lock>();
|
|
if constexpr (need_lock) {
|
|
return std::scoped_lock(mtx_);
|
|
}
|
|
else {
|
|
return std::scoped_lock(null_mtx_);
|
|
}
|
|
}
|
|
|
|
template <bool need_lock>
|
|
static bool register_metric_impl(std::shared_ptr<metric_t> metric) {
|
|
// the first time regiter_metric will set metric_manager_t lock or not lock.
|
|
// visit metric_manager_t with different lock strategy will cause throw
|
|
// exception.
|
|
std::call_once(flag_, [] {
|
|
need_lock_ = need_lock;
|
|
});
|
|
|
|
std::string name(metric->name());
|
|
auto lock = get_lock<need_lock>();
|
|
bool r = metric_map_.emplace(name, std::move(metric)).second;
|
|
if (!r) {
|
|
CINATRA_LOG_ERROR << "duplicate registered metric name: " << name;
|
|
}
|
|
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>();
|
|
return metric_map_;
|
|
}
|
|
|
|
template <bool need_lock>
|
|
static size_t metric_count_impl() {
|
|
auto lock = get_lock<need_lock>();
|
|
return metric_map_.size();
|
|
}
|
|
|
|
template <bool need_lock>
|
|
static std::vector<std::string> metric_keys_impl() {
|
|
std::vector<std::string> keys;
|
|
{
|
|
auto lock = get_lock<need_lock>();
|
|
for (auto& pair : metric_map_) {
|
|
keys.push_back(pair.first);
|
|
}
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
template <bool need_lock>
|
|
static std::shared_ptr<metric_t> get_metric_impl(const std::string& name) {
|
|
auto lock = get_lock<need_lock>();
|
|
auto it = metric_map_.find(name);
|
|
if (it == metric_map_.end()) {
|
|
return nullptr;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
template <bool need_lock>
|
|
static auto collect() {
|
|
std::vector<std::shared_ptr<metric_t>> metrics;
|
|
{
|
|
auto lock = get_lock<need_lock>();
|
|
for (auto& pair : metric_map_) {
|
|
metrics.push_back(pair.second);
|
|
}
|
|
}
|
|
return metrics;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_;
|
|
static inline std::map<std::string, std::shared_ptr<metric_t>> metric_map_;
|
|
|
|
static inline null_mutex_t null_mtx_;
|
|
static inline std::atomic_bool need_lock_ = true;
|
|
static inline std::once_flag flag_;
|
|
};
|
|
|
|
using default_metric_manager = metric_manager_t<0>;
|
|
} // namespace ylt::metric
|