spectator/stateless_meters.h (212 lines of code) (raw):
#pragma once
#include "id.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
namespace spectator {
namespace detail {
#include "valid_chars.inc"
inline std::string as_string(std::string_view v) {
return {v.data(), v.size()};
}
inline bool contains_non_atlas_char(const std::string& input) {
return std::any_of(input.begin(), input.end(), [](char c) { return !kAtlasChars[c]; });
}
inline std::string replace_invalid_characters(const std::string& input) {
if (contains_non_atlas_char(input)) {
std::string result{input};
for (char &c : result) {
if (!kAtlasChars[c]) {
c = '_';
}
}
return result;
} else {
return input;
}
}
inline std::string create_prefix(const Id& id, std::string_view type_name) {
std::string res = as_string(type_name) + ":" + replace_invalid_characters(id.Name());
for (const auto& tags : id.GetTags()) {
auto first = replace_invalid_characters(tags.first);
auto second = replace_invalid_characters(tags.second);
absl::StrAppend(&res, ",", first, "=", second);
}
absl::StrAppend(&res, ":");
return res;
}
template <typename T>
T restrict(T amount, T min, T max) {
auto r = amount;
if (r > max) {
r = max;
} else if (r < min) {
r = min;
}
return r;
}
} // namespace detail
template <typename Pub>
class StatelessMeter {
public:
StatelessMeter(IdPtr id, Pub* publisher)
: id_(std::move(id)), publisher_(publisher) {
assert(publisher_ != nullptr);
}
virtual ~StatelessMeter() = default;
std::string GetPrefix() {
if (value_prefix_.empty()) {
value_prefix_ = detail::create_prefix(*id_, Type());
}
return value_prefix_;
}
[[nodiscard]] IdPtr MeterId() const noexcept { return id_; }
[[nodiscard]] virtual std::string_view Type() = 0;
protected:
void send(double value) {
if (value_prefix_.empty()) {
value_prefix_ = detail::create_prefix(*id_, Type());
}
auto msg = absl::StrFormat("%s%f", value_prefix_, value);
// remove trailing zeros and decimal points
msg.erase(msg.find_last_not_of('0') + 1, std::string::npos);
msg.erase(msg.find_last_not_of('.') + 1, std::string::npos);
publisher_->send(msg);
}
void send_uint(uint64_t value) {
if (value_prefix_.empty()) {
value_prefix_ = detail::create_prefix(*id_, Type());
}
auto msg = absl::StrFormat("%s%u", value_prefix_, value);
publisher_->send(msg);
}
private:
IdPtr id_;
Pub* publisher_;
std::string value_prefix_;
};
template <typename Pub>
class AgeGauge : public StatelessMeter<Pub> {
public:
AgeGauge(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Now() noexcept { this->send(0); }
void Set(double value) noexcept { this->send(value); }
protected:
std::string_view Type() override { return "A"; }
};
template <typename Pub>
class Counter : public StatelessMeter<Pub> {
public:
Counter(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Increment() noexcept { this->send(1); };
void Add(double delta) noexcept { this->send(delta); }
protected:
std::string_view Type() override { return "c"; }
};
template <typename Pub>
class DistributionSummary : public StatelessMeter<Pub> {
public:
DistributionSummary(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Record(double amount) noexcept { this->send(amount); }
protected:
std::string_view Type() override { return "d"; }
};
template <typename Pub>
class Gauge : public StatelessMeter<Pub> {
public:
Gauge(IdPtr id, Pub* publisher, unsigned int ttl_seconds = 0)
: StatelessMeter<Pub>(std::move(id), publisher) {
if (ttl_seconds > 0) {
type_str_ = "g," + std::to_string(ttl_seconds);
}
}
void Set(double value) noexcept { this->send(value); }
protected:
std::string_view Type() override { return type_str_; }
private:
std::string type_str_{"g"};
};
template <typename Pub>
class MaxGauge : public StatelessMeter<Pub> {
public:
MaxGauge(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Update(double value) noexcept { this->send(value); }
// synonym for Update for consistency with the Gauge interface
void Set(double value) noexcept { this->send(value); }
protected:
std::string_view Type() override { return "m"; }
};
template <typename Pub>
class MonotonicCounter : public StatelessMeter<Pub> {
public:
MonotonicCounter(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Set(double amount) noexcept { this->send(amount); }
protected:
std::string_view Type() override { return "C"; }
};
template <typename Pub>
class MonotonicCounterUint : public StatelessMeter<Pub> {
public:
MonotonicCounterUint(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Set(uint64_t amount) noexcept { this->send_uint(amount); }
protected:
std::string_view Type() override { return "U"; }
};
template <typename Pub>
class PercentileDistributionSummary : public StatelessMeter<Pub> {
public:
PercentileDistributionSummary(IdPtr id, Pub* publisher, int64_t min,
int64_t max)
: StatelessMeter<Pub>(std::move(id), publisher), min_{min}, max_{max} {}
void Record(int64_t amount) noexcept {
this->send(detail::restrict(amount, min_, max_));
}
protected:
std::string_view Type() override { return "D"; }
private:
int64_t min_;
int64_t max_;
};
template <typename Pub>
class PercentileTimer : public StatelessMeter<Pub> {
public:
PercentileTimer(IdPtr id, Pub* publisher, absl::Duration min,
absl::Duration max)
: StatelessMeter<Pub>(std::move(id), publisher), min_(min), max_(max) {}
PercentileTimer(IdPtr id, Pub* publisher, std::chrono::nanoseconds min,
std::chrono::nanoseconds max)
: PercentileTimer(std::move(id), publisher, absl::FromChrono(min),
absl::FromChrono(max)) {}
void Record(std::chrono::nanoseconds amount) noexcept {
Record(absl::FromChrono(amount));
}
void Record(absl::Duration amount) noexcept {
auto duration = detail::restrict(amount, min_, max_);
this->send(absl::ToDoubleSeconds(duration));
}
protected:
std::string_view Type() override { return "T"; }
private:
absl::Duration min_;
absl::Duration max_;
};
template <typename Pub>
class Timer : public StatelessMeter<Pub> {
public:
Timer(IdPtr id, Pub* publisher)
: StatelessMeter<Pub>(std::move(id), publisher) {}
void Record(std::chrono::nanoseconds amount) noexcept {
Record(absl::FromChrono(amount));
}
void Record(absl::Duration amount) noexcept {
auto secs = absl::ToDoubleSeconds(amount);
this->send(secs);
}
protected:
std::string_view Type() override { return "t"; }
};
} // namespace spectator