diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db67740..5674718 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,10 +17,10 @@ jobs: CXX: "g++-11" LANG: "en_US.UTF-8" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Conan+Cmake Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.conan @@ -57,6 +57,8 @@ jobs: cd $BUILD_DIR cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. cmake --build . + echo "==== ldd ====" + ldd bin/spectatord_main || true - name: Test spectator-cpp run: | diff --git a/.gitignore b/.gitignore index 0b6bfbc..96e66f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store .idea/ -cmake-build-debug/ +cmake-build-debug cmake-build/ venv/ diff --git a/README.md b/README.md index 5bdfc57..fde5020 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,8 @@ # Spectator-cpp -Simple library for instructing code to record dimensional time series. It sends all activity to -a [spectatord](https://github.com/Netflix-Skunkworks/spectatord) sidecar. - -## Description - -This implements a basic [Spectator](https://github.com/Netflix/spectator) library for instrumenting -C++ applications, sending all metric activity to a sidecar. +This implements a basic [Spectator](https://github.com/Netflix/spectator) library for instrumenting Go applications. It +consists of a thin client designed to send metrics through [spectatord](https://github.com/Netflix-Skunkworks/spectatord). ## Instrumenting Code @@ -80,19 +75,29 @@ int main() { } } ``` + ## High-Volume Publishing -By default, the library sends every meter change to the spectatord sidecar immediately. This involves a blocking `send` call and underlying system calls, and may not be the most efficient way to publish metrics in high-volume use cases. -For this purpose a simple buffering functionality in `Publisher` is implemented, and it can be turned on by passing a buffer size to the `spectator::Config` constructor. It is important to note that, until this buffer fills up, the `Publisher` will not send nay meters to the sidecar. Therefore, if your application doesn't emit meters at a high rate, you should either keep the buffer very small, or do not configure a buffer size at all, which will fall back to the "publish immediately" mode of operation. +By default, the library sends every meter change to the spectatord sidecar immediately. This involves a blocking +`send` call and underlying system calls, and may not be the most efficient way to publish metrics in high-volume +use cases. For this purpose a simple buffering functionality in `Publisher` is implemented, and it can be turned +on by passing a buffer size to the `spectator::Config` constructor. It is important to note that, until this buffer +fills up, the `Publisher` will not send nay meters to the sidecar. Therefore, if your application doesn't emit +meters at a high rate, you should either keep the buffer very small, or do not configure a buffer size at all, +which will fall back to the "publish immediately" mode of operation. ## Local Development ```shell +# setup python venv and activate, to gain access to conan cli ./setup-venv.sh source venv/bin/activate -./build.sh # [clean|skiptest] + +# link clion default build directory to our build directory +ln -s cmake-build cmake-build-debug + +./build.sh # [clean|clean --force|skiptest] ``` * CLion > Preferences > Plugins > Marketplace > Conan > Install * CLion > Preferences > Build, Execution, Deploy > Conan > Conan Executable: $PROJECT_HOME/venv/bin/conan -* CLion > Bottom Bar: Conan > Left Button: Match Profile > CMake Profile: Debug, Conan Profile: default diff --git a/requirements.txt b/requirements.txt index e81fb8b..2ce40bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -conan==1.62.0 +conan==1.64.1 diff --git a/setup-venv.sh b/setup-venv.sh index 7f50e7f..7bfcded 100755 --- a/setup-venv.sh +++ b/setup-venv.sh @@ -1,17 +1,21 @@ #!/usr/bin/env bash -PYTHON3=$(which python3) +MAYBE_PYTHON=$(find /apps -maxdepth 1 -type l -name "python*") -if [[ -z $PYTHON3 ]]; then - echo "python3 is not available - please install" - exit 1 +if [[ -n "$MAYBE_PYTHON" ]]; then + PYTHON3="$MAYBE_PYTHON/bin/python3" + echo "using $PYTHON3 ($($PYTHON3 -V))" +else + PYTHON3="python3" + echo "using $(which $PYTHON3) ($($PYTHON3 -V))" fi -python3 -m venv venv - +# create and activate virtualenv +$PYTHON3 -m venv venv source venv/bin/activate if [[ -f requirements.txt ]]; then - pip3 install --upgrade pip wheel - pip3 install --requirement requirements.txt + # use the virtualenv python + python -m pip install --upgrade pip wheel + python -m pip install --requirement requirements.txt fi diff --git a/spectator/meter_type.h b/spectator/meter_type.h index 5a61fe2..3a8d565 100644 --- a/spectator/meter_type.h +++ b/spectator/meter_type.h @@ -4,17 +4,17 @@ namespace spectator { enum class MeterType { + AgeGauge, Counter, DistSummary, Gauge, MaxGauge, - AgeGauge, MonotonicCounter, - Timer, + MonotonicCounterUint, + PercentileDistSummary, PercentileTimer, - PercentileDistSummary + Timer }; - } // Provide a formatter for MeterType @@ -25,6 +25,9 @@ struct fmt::formatter : fmt::formatter { using namespace spectator; std::string_view s = "unknown"; switch (meter_type) { + case MeterType::AgeGauge: + s = "age-gauge"; + break; case MeterType::Counter: s = "counter"; break; @@ -37,20 +40,20 @@ struct fmt::formatter : fmt::formatter { case MeterType::MaxGauge: s = "max-gauge"; break; - case MeterType::AgeGauge: - s = "age-gauge"; - break; case MeterType::MonotonicCounter: s = "monotonic-counter"; break; - case MeterType::Timer: - s = "timer"; + case MeterType::MonotonicCounterUint: + s = "monotonic-counter-uint"; + break; + case MeterType::PercentileDistSummary: + s = "percentile-distribution-summary"; break; case MeterType::PercentileTimer: s = "percentile-timer"; break; - case MeterType::PercentileDistSummary: - s = "percentile-distribution-summary"; + case MeterType::Timer: + s = "timer"; break; } return fmt::formatter::format(s, context); diff --git a/spectator/monotonic_counter_test.cc b/spectator/monotonic_counter_test.cc index 18dac4b..ec91a9f 100644 --- a/spectator/monotonic_counter_test.cc +++ b/spectator/monotonic_counter_test.cc @@ -16,11 +16,10 @@ TEST(MonotonicCounter, Set) { MonotonicCounter c{id, &publisher}; MonotonicCounter c2{id2, &publisher}; - c.Set(42); + c.Set(42.1); c2.Set(2); c.Set(43); - std::vector expected = {"C:ctr:42", "C:ctr2,key=val:2", - "C:ctr:43"}; + std::vector expected = {"C:ctr:42.1", "C:ctr2,key=val:2", "C:ctr:43"}; EXPECT_EQ(publisher.SentMessages(), expected); } } // namespace diff --git a/spectator/monotonic_counter_uint_test.cc b/spectator/monotonic_counter_uint_test.cc new file mode 100644 index 0000000..351cf2b --- /dev/null +++ b/spectator/monotonic_counter_uint_test.cc @@ -0,0 +1,25 @@ +#include "stateless_meters.h" +#include "test_publisher.h" +#include + +namespace { + +using spectator::Id; +using spectator::MonotonicCounterUint; +using spectator::Tags; +using spectator::TestPublisher; + +TEST(MonotonicCounterUint, Set) { + TestPublisher publisher; + auto id = std::make_shared("ctr", Tags{}); + auto id2 = std::make_shared("ctr2", Tags{{"key", "val"}}); + MonotonicCounterUint c{id, &publisher}; + MonotonicCounterUint c2{id2, &publisher}; + + c.Set(42); + c2.Set(2); + c.Set(43); + std::vector expected = {"U:ctr:42", "U:ctr2,key=val:2", "U:ctr:43"}; + EXPECT_EQ(publisher.SentMessages(), expected); +} +} // namespace \ No newline at end of file diff --git a/spectator/registry.h b/spectator/registry.h index 4e54780..3edc25f 100644 --- a/spectator/registry.h +++ b/spectator/registry.h @@ -53,16 +53,20 @@ struct single_table_state { return new_meter; } + auto get_age_gauge(IdPtr id) { + return get_or_create(std::move(id)); + } + auto get_counter(IdPtr id) { return get_or_create(std::move(id)); } - auto get_gauge(IdPtr id) { - return get_or_create(std::move(id)); + auto get_ds(IdPtr id) { + return get_or_create(std::move(id)); } - auto get_age_gauge(IdPtr id) { - return get_or_create(std::move(id)); + auto get_gauge(IdPtr id) { + return get_or_create(std::move(id)); } auto get_max_gauge(IdPtr id) { @@ -73,12 +77,8 @@ struct single_table_state { return get_or_create(std::move(id)); } - auto get_timer(IdPtr id) { - return get_or_create(std::move(id)); - } - - auto get_ds(IdPtr id) { - return get_or_create(std::move(id)); + auto get_monotonic_counter_uint(IdPtr id) { + return get_or_create(std::move(id)); } auto get_perc_ds(IdPtr id, int64_t min, int64_t max) { @@ -90,6 +90,10 @@ struct single_table_state { return get_or_create(std::move(id), min, max); } + auto get_timer(IdPtr id) { + return get_or_create(std::move(id)); + } + auto measurements() { std::vector result; @@ -113,6 +117,8 @@ template class base_registry { public: using logger_ptr = std::shared_ptr; + using age_gauge_t = typename Types::age_gauge_t; + using age_gauge_ptr = std::shared_ptr; using counter_t = typename Types::counter_t; using counter_ptr = std::shared_ptr; using dist_summary_t = typename Types::ds_t; @@ -121,10 +127,10 @@ class base_registry { using gauge_ptr = std::shared_ptr; using max_gauge_t = typename Types::max_gauge_t; using max_gauge_ptr = std::shared_ptr; - using age_gauge_t = typename Types::age_gauge_t; - using age_gauge_ptr = std::shared_ptr; using monotonic_counter_t = typename Types::monotonic_counter_t; using monotonic_counter_ptr = std::shared_ptr; + using monotonic_counter_uint_t = typename Types::monotonic_counter_uint_t; + using monotonic_counter_uint_ptr = std::shared_ptr; using perc_dist_summary_t = typename Types::perc_ds_t; using perc_dist_summary_ptr = std::shared_ptr; using perc_timer_t = typename Types::perc_timer_t; @@ -135,7 +141,16 @@ class base_registry { explicit base_registry(logger_ptr logger = DefaultLogger()) : logger_(std::move(logger)) {} - auto GetCounter(const IdPtr& id) { return state_.get_counter(final_id(id)); } + auto GetAgeGauge(const IdPtr& id) { + return state_.get_age_gauge(final_id(id)); + } + auto GetAgeGauge(absl::string_view name, Tags tags = {}) { + return GetAgeGauge(Id::of(name, std::move(tags))); + } + + auto GetCounter(const IdPtr& id) { + return state_.get_counter(final_id(id)); + } auto GetCounter(absl::string_view name, Tags tags = {}) { return GetCounter(Id::of(name, std::move(tags))); } @@ -147,7 +162,9 @@ class base_registry { return GetDistributionSummary(Id::of(name, std::move(tags))); } - auto GetGauge(const IdPtr& id) { return state_.get_gauge(final_id(id)); } + auto GetGauge(const IdPtr& id) { + return state_.get_gauge(final_id(id)); + } auto GetGauge(absl::string_view name, Tags tags = {}) { return GetGauge(Id::of(name, std::move(tags))); } @@ -159,13 +176,6 @@ class base_registry { return GetMaxGauge(Id::of(name, std::move(tags))); } - auto GetAgeGauge(const IdPtr& id) { - return state_.get_age_gauge(final_id(id)); - } - auto GetAgeGauge(absl::string_view name, Tags tags = {}) { - return GetAgeGauge(Id::of(name, std::move(tags))); - } - auto GetMonotonicCounter(const IdPtr& id) { return state_.get_monotonic_counter(final_id(id)); } @@ -173,59 +183,53 @@ class base_registry { return GetMonotonicCounter(Id::of(name, std::move(tags))); } - auto GetTimer(const IdPtr& id) { return state_.get_timer(final_id(id)); } - auto GetTimer(absl::string_view name, Tags tags = {}) { - return GetTimer(Id::of(name, std::move(tags))); + auto GetMonotonicCounterUint(const IdPtr& id) { + return state_.get_monotonic_counter_uint(final_id(id)); + } + auto GetMonotonicCounterUint(absl::string_view name, Tags tags = {}) { + return GetMonotonicCounterUint(Id::of(name, std::move(tags))); } - auto GetPercentileDistributionSummary(const IdPtr& id, int64_t min, - int64_t max) { + auto GetPercentileDistributionSummary(const IdPtr& id, int64_t min, int64_t max) { return state_.get_perc_ds(final_id(id), min, max); } - - auto GetPercentileDistributionSummary(absl::string_view name, int64_t min, - int64_t max) { + auto GetPercentileDistributionSummary(absl::string_view name, int64_t min, int64_t max) { return GetPercentileDistributionSummary(Id::of(name), min, max); } - auto GetPercentileDistributionSummary(absl::string_view name, Tags tags, int64_t min, int64_t max) { - return GetPercentileDistributionSummary(Id::of(name, std::move(tags)), min, - max); + return GetPercentileDistributionSummary(Id::of(name, std::move(tags)), min, max); } - auto GetPercentileTimer(const IdPtr& id, absl::Duration min, - absl::Duration max) { + auto GetPercentileTimer(const IdPtr& id, absl::Duration min, absl::Duration max) { return state_.get_perc_timer(final_id(id), min, max); } - auto GetPercentileTimer(const IdPtr& id, std::chrono::nanoseconds min, std::chrono::nanoseconds max) { - return state_.get_perc_timer(final_id(id), absl::FromChrono(min), - absl::FromChrono(max)); + return state_.get_perc_timer(final_id(id), absl::FromChrono(min), absl::FromChrono(max)); } - - auto GetPercentileTimer(absl::string_view name, absl::Duration min, - absl::Duration max) { + auto GetPercentileTimer(absl::string_view name, absl::Duration min, absl::Duration max) { return GetPercentileTimer(Id::of(name), min, max); } - - auto GetPercentileTimer(absl::string_view name, Tags tags, absl::Duration min, - absl::Duration max) { + auto GetPercentileTimer(absl::string_view name, Tags tags, + absl::Duration min, absl::Duration max) { return GetPercentileTimer(Id::of(name, std::move(tags)), min, max); } - - auto GetPercentileTimer(absl::string_view name, std::chrono::nanoseconds min, - std::chrono::nanoseconds max) { - return GetPercentileTimer(Id::of(name), absl::FromChrono(min), + auto GetPercentileTimer(absl::string_view name, + std::chrono::nanoseconds min, std::chrono::nanoseconds max) { + return GetPercentileTimer(Id::of(name), absl::FromChrono(min), absl::FromChrono(max)); + } + auto GetPercentileTimer(absl::string_view name, Tags tags, + std::chrono::nanoseconds min, std::chrono::nanoseconds max) { + return GetPercentileTimer(Id::of(name, std::move(tags)), absl::FromChrono(min), absl::FromChrono(max)); } - auto GetPercentileTimer(absl::string_view name, Tags tags, - std::chrono::nanoseconds min, - std::chrono::nanoseconds max) { - return GetPercentileTimer(Id::of(name, std::move(tags)), - absl::FromChrono(min), absl::FromChrono(max)); + auto GetTimer(const IdPtr& id) { + return state_.get_timer(final_id(id)); + } + auto GetTimer(absl::string_view name, Tags tags = {}) { + return GetTimer(Id::of(name, std::move(tags))); } auto Measurements() { return state_.measurements(); } @@ -252,6 +256,7 @@ struct stateless_types { using max_gauge_t = MaxGauge; using age_gauge_t = AgeGauge; using monotonic_counter_t = MonotonicCounter; + using monotonic_counter_uint_t = MonotonicCounterUint; using perc_timer_t = PercentileTimer; using perc_ds_t = PercentileDistributionSummary; using timer_t = Timer; @@ -263,49 +268,45 @@ struct stateless { using types = Types; std::unique_ptr publisher; - auto get_counter(IdPtr id) { - return std::make_shared(std::move(id), - publisher.get()); + auto get_age_gauge(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } - auto get_gauge(IdPtr id) { - return std::make_shared(std::move(id), - publisher.get()); + auto get_counter(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } - auto get_max_gauge(IdPtr id) { - return std::make_shared(std::move(id), - publisher.get()); + auto get_ds(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } - auto get_age_gauge(IdPtr id) { - return std::make_shared(std::move(id), - publisher.get()); + auto get_gauge(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } - auto get_monotonic_counter(IdPtr id) { - return std::make_shared( - std::move(id), publisher.get()); + auto get_max_gauge(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } - auto get_timer(IdPtr id) { - return std::make_shared(std::move(id), - publisher.get()); + auto get_monotonic_counter(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } - auto get_ds(IdPtr id) { - return std::make_shared(std::move(id), - publisher.get()); + auto get_monotonic_counter_uint(IdPtr id) { + return std::make_shared(std::move(id), + publisher.get()); } auto get_perc_ds(IdPtr id, int64_t min, int64_t max) { - return std::make_shared( - std::move(id), publisher.get(), min, max); + return std::make_shared(std::move(id), publisher.get(), min, max); } auto get_perc_timer(IdPtr id, absl::Duration min, absl::Duration max) { - return std::make_shared( - std::move(id), publisher.get(), min, max); + return std::make_shared(std::move(id), publisher.get(), min, max); + } + + auto get_timer(IdPtr id) { + return std::make_shared(std::move(id), publisher.get()); } auto measurements() { return std::vector{}; } diff --git a/spectator/stateful_meters.h b/spectator/stateful_meters.h index ad792db..deacb1a 100644 --- a/spectator/stateful_meters.h +++ b/spectator/stateful_meters.h @@ -40,37 +40,15 @@ class StatefulMeter { IdPtr id_; }; -class StatefulCounter : public StatefulMeter { - public: - explicit StatefulCounter(IdPtr id) : StatefulMeter(std::move(id)) {} - [[nodiscard]] double Count() const { return count_; }; - - MeterType GetType() const override { return MeterType::Counter; } - - void Add(double delta) { - if (delta > 0) { - detail::add_double(&count_, delta); - } - } - void Increment() { Add(1); } - - void Measure(std::vector* measurements) override { - auto count = count_.exchange(0.0); - if (count > 0) { - measurements->emplace_back(Id::WithDefaultStat(id_, "count"), count); - } - } - - private: - std::atomic count_ = 0.0; -}; - template class TestDistribution : public StatefulMeter { public: explicit TestDistribution(IdPtr id) : StatefulMeter(std::move(id)) {} + int64_t Count() const { return count_; } + double TotalAmount() const { return total_; } + MeterType GetType() const override { return DistType::meter_type; } void Measure(std::vector* measurements) override { @@ -114,31 +92,73 @@ struct summary_distribution { static constexpr auto total_name = "totalAmount"; }; -class StatefulTimer : public TestDistribution { +class StatefulAgeGauge : public StatefulMeter { public: - explicit StatefulTimer(IdPtr id) - : TestDistribution(std::move(id)) {} + explicit StatefulAgeGauge(IdPtr id) : StatefulMeter(std::move(id)) {} - void Record(absl::Duration amount) { record(absl::ToDoubleSeconds(amount)); } + [[nodiscard]] double Get() const { return value_; } - void Record(std::chrono::nanoseconds amount) { - Record(absl::FromChrono(amount)); + MeterType GetType() const override { return MeterType::AgeGauge; } + + void Set(double amount) { value_ = amount; } + + void Measure(std::vector* measurements) override { + auto v = value_.exchange(kNaN); + if (std::isnan(v)) { + return; + } + measurements->emplace_back(Id::WithDefaultStat(id_, "gauge"), v); } + + private: + static constexpr auto kNaN = std::numeric_limits::quiet_NaN(); + std::atomic value_ = kNaN; +}; + +class StatefulCounter : public StatefulMeter { + public: + explicit StatefulCounter(IdPtr id) : StatefulMeter(std::move(id)) {} + + [[nodiscard]] double Count() const { return count_; }; + + MeterType GetType() const override { return MeterType::Counter; } + + void Add(double delta) { + if (delta > 0) { + detail::add_double(&count_, delta); + } + } + + void Increment() { Add(1); } + + void Measure(std::vector* measurements) override { + auto count = count_.exchange(0.0); + if (count > 0) { + measurements->emplace_back(Id::WithDefaultStat(id_, "count"), count); + } + } + + private: + std::atomic count_ = 0.0; }; class StatefulDistSum : public TestDistribution { public: - explicit StatefulDistSum(IdPtr id) - : TestDistribution(std::move(id)) {} + explicit StatefulDistSum(IdPtr id): TestDistribution(std::move(id)) {} + void Record(double amount) { record(amount); } }; class StatefulGauge : public StatefulMeter { public: explicit StatefulGauge(IdPtr id) : StatefulMeter(std::move(id)) {} + [[nodiscard]] double Get() const { return value_; } + MeterType GetType() const override { return MeterType::Gauge; } + void Set(double amount) { value_ = amount; } + void Measure(std::vector* measurements) override { auto v = value_.exchange(kNaN); if (std::isnan(v)) { @@ -155,10 +175,15 @@ class StatefulGauge : public StatefulMeter { class StatefulMaxGauge : public StatefulMeter { public: explicit StatefulMaxGauge(IdPtr id) : StatefulMeter(std::move(id)) {} + [[nodiscard]] double Get() const { return value_; } + MeterType GetType() const override { return MeterType::MaxGauge; } + void Set(double amount) { detail::update_max(&value_, amount); } + void Update(double amount) { Set(amount); } + void Measure(std::vector* measurements) override { auto v = value_.exchange(kMinValue); if (v == kMinValue) { @@ -172,31 +197,46 @@ class StatefulMaxGauge : public StatefulMeter { std::atomic value_ = kMinValue; }; -class StatefulAgeGauge : public StatefulMeter { +class StatefulMonoCounter : public StatefulMeter { public: - explicit StatefulAgeGauge(IdPtr id) : StatefulMeter(std::move(id)) {} - [[nodiscard]] double Get() const { return value_; } - MeterType GetType() const override { return MeterType::AgeGauge; } + explicit StatefulMonoCounter(IdPtr id) : StatefulMeter(std::move(id)) {} + + MeterType GetType() const override { return MeterType::MonotonicCounter; } + + [[nodiscard]] double Delta() const { return value_ - prev_value_; } + void Set(double amount) { value_ = amount; } + void Measure(std::vector* measurements) override { - auto v = value_.exchange(kNaN); - if (std::isnan(v)) { - return; + auto delta = Delta(); + prev_value_ = value_.load(); + if (delta > 0) { + measurements->emplace_back(id_->WithStat("count"), delta); } - measurements->emplace_back(Id::WithDefaultStat(id_, "gauge"), v); } private: static constexpr auto kNaN = std::numeric_limits::quiet_NaN(); std::atomic value_ = kNaN; + std::atomic prev_value_ = kNaN; }; -class StatefulMonoCounter : public StatefulMeter { +class StatefulMonoCounterUint : public StatefulMeter { public: - explicit StatefulMonoCounter(IdPtr id) : StatefulMeter(std::move(id)) {} - MeterType GetType() const override { return MeterType::MonotonicCounter; } - [[nodiscard]] double Delta() const { return value_ - prev_value_; } - void Set(double amount) { value_ = amount; } + explicit StatefulMonoCounterUint(IdPtr id) : StatefulMeter(std::move(id)) {} + + MeterType GetType() const override { return MeterType::MonotonicCounterUint; } + + [[nodiscard]] double Delta() const { + if (value_ < prev_value_) { + return kMax - prev_value_ + value_ + 1; + } else { + return value_ - prev_value_; + } + } + + void Set(uint64_t amount) { value_ = amount; } + void Measure(std::vector* measurements) override { auto delta = Delta(); prev_value_ = value_.load(); @@ -206,17 +246,18 @@ class StatefulMonoCounter : public StatefulMeter { } private: - static constexpr auto kNaN = std::numeric_limits::quiet_NaN(); - std::atomic value_ = kNaN; - std::atomic prev_value_ = kNaN; + static constexpr auto kMax = std::numeric_limits::max(); + std::atomic value_ = 0; + std::atomic prev_value_ = 0; }; class StatefulPercTimer : public StatefulMeter { public: - StatefulPercTimer(IdPtr id, std::chrono::nanoseconds, - std::chrono::nanoseconds) + StatefulPercTimer(IdPtr id, std::chrono::nanoseconds, std::chrono::nanoseconds) : StatefulMeter(std::move(id)) {} + [[nodiscard]] MeterType GetType() const override { return MeterType::PercentileTimer; } + void Measure(std::vector*) override {} private: @@ -224,14 +265,24 @@ class StatefulPercTimer : public StatefulMeter { class StatefulPercDistSum : public StatefulMeter { public: - StatefulPercDistSum(IdPtr id, int64_t, int64_t) - : StatefulMeter(std::move(id)) {} + StatefulPercDistSum(IdPtr id, int64_t, int64_t): StatefulMeter(std::move(id)) {} + [[nodiscard]] MeterType GetType() const override { return MeterType::PercentileDistSummary; } + void Measure(std::vector*) override {} }; +class StatefulTimer : public TestDistribution { + public: + explicit StatefulTimer(IdPtr id): TestDistribution(std::move(id)) {} + + void Record(absl::Duration amount) { record(absl::ToDoubleSeconds(amount)); } + + void Record(std::chrono::nanoseconds amount) { Record(absl::FromChrono(amount)); } +}; + struct stateful_meters { using counter_t = StatefulCounter; using ds_t = StatefulDistSum; @@ -239,6 +290,7 @@ struct stateful_meters { using max_gauge_t = StatefulMaxGauge; using age_gauge_t = StatefulAgeGauge; using monotonic_counter_t = StatefulMonoCounter; + using monotonic_counter_uint_t = StatefulMonoCounterUint; using perc_timer_t = StatefulPercTimer; using perc_ds_t = StatefulPercDistSum; using timer_t = StatefulTimer; diff --git a/spectator/stateless_meters.h b/spectator/stateless_meters.h index 7c4b815..6b537d6 100644 --- a/spectator/stateless_meters.h +++ b/spectator/stateless_meters.h @@ -59,6 +59,18 @@ class StatelessMeter { std::string value_prefix_; }; +template +class AgeGauge : public StatelessMeter { + public: + AgeGauge(IdPtr id, Pub* publisher) + : StatelessMeter(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 class Counter : public StatelessMeter { public: @@ -107,25 +119,25 @@ class MaxGauge : public StatelessMeter { }; template -class AgeGauge : public StatelessMeter { +class MonotonicCounter : public StatelessMeter { public: - AgeGauge(IdPtr id, Pub* publisher) + MonotonicCounter(IdPtr id, Pub* publisher) : StatelessMeter(std::move(id), publisher) {} - void Set(double value) noexcept { this->send(value); } + void Set(double amount) noexcept { this->send(amount); } protected: - std::string_view Type() override { return "A"; } + std::string_view Type() override { return "C"; } }; template -class MonotonicCounter : public StatelessMeter { +class MonotonicCounterUint : public StatelessMeter { public: - MonotonicCounter(IdPtr id, Pub* publisher) + MonotonicCounterUint(IdPtr id, Pub* publisher) : StatelessMeter(std::move(id), publisher) {} - void Set(double amount) noexcept { this->send(amount); } + void Set(uint64_t amount) noexcept { this->send(amount); } protected: - std::string_view Type() override { return "C"; } + std::string_view Type() override { return "U"; } }; template diff --git a/spectator/statelessregistry_test.cc b/spectator/statelessregistry_test.cc index 6c437fe..62283f8 100644 --- a/spectator/statelessregistry_test.cc +++ b/spectator/statelessregistry_test.cc @@ -23,67 +23,94 @@ class TestStatelessRegistry } }; +TEST(StatelessRegistry, AgeGauge) { + TestStatelessRegistry r; + auto ag = r.GetAgeGauge("foo"); + auto ag2 = r.GetAgeGauge("bar", {{"id", "2"}}); + ag->Now(); + ag2->Set(100); + std::vector expected = {"A:foo:0", "A:bar,id=2:100"}; + EXPECT_EQ(r.SentMessages(), expected); +} + TEST(StatelessRegistry, Counter) { TestStatelessRegistry r; auto c = r.GetCounter("foo"); c->Increment(); EXPECT_EQ(r.SentMessages().front(), "c:foo:1"); + r.Reset(); - auto c2 = r.GetCounter("foo", {{"k1", "v1"}}); - c2->Add(2); + c = r.GetCounter("foo", {{"k1", "v1"}}); + c->Add(2); EXPECT_EQ(r.SentMessages().front(), "c:foo,k1=v1:2"); } TEST(StatelessRegistry, DistSummary) { TestStatelessRegistry r; - auto ds = r.GetDistributionSummary("ds"); + auto ds = r.GetDistributionSummary("foo"); ds->Record(100); - EXPECT_EQ(r.SentMessages().front(), "d:ds:100"); + EXPECT_EQ(r.SentMessages().front(), "d:foo:100"); + r.Reset(); - auto c2 = r.GetDistributionSummary("ds", {{"k1", "v1"}}); - c2->Record(2); - EXPECT_EQ(r.SentMessages().front(), "d:ds,k1=v1:2"); + ds = r.GetDistributionSummary("bar", {{"k1", "v1"}}); + ds->Record(2); + EXPECT_EQ(r.SentMessages().front(), "d:bar,k1=v1:2"); } TEST(StatelessRegistry, Gauge) { TestStatelessRegistry r; - auto g = r.GetGauge("g"); - auto g2 = r.GetGauge("g", {{"id", "2"}}); + auto g = r.GetGauge("foo"); + auto g2 = r.GetGauge("bar", {{"id", "2"}}); g->Set(100); g2->Set(101); - std::vector expected = {"g:g:100", "g:g,id=2:101"}; + std::vector expected = {"g:foo:100", "g:bar,id=2:101"}; EXPECT_EQ(r.SentMessages(), expected); } TEST(StatelessRegistry, MaxGauge) { TestStatelessRegistry r; - auto g = r.GetMaxGauge("g"); - auto g2 = r.GetMaxGauge("g", {{"id", "2"}}); - g->Update(100); - g2->Set(101); + auto m = r.GetMaxGauge("foo"); + auto m2 = r.GetMaxGauge("bar", {{"id", "2"}}); + m->Update(100); + m2->Set(101); + std::vector expected = {"m:foo:100", "m:bar,id=2:101"}; + EXPECT_EQ(r.SentMessages(), expected); } TEST(StatelessRegistry, MonotonicCounter) { TestStatelessRegistry r; - auto c = r.GetMonotonicCounter("m"); - auto c2 = r.GetMonotonicCounter("m", {{"id", "2"}}); - c->Set(100); + auto m = r.GetMonotonicCounter("foo"); + auto m2 = r.GetMonotonicCounter("bar", {{"id", "2"}}); + m->Set(101.1); + m2->Set(102.2); + std::vector expected = {"C:foo:101.1", "C:bar,id=2:102.2"}; + EXPECT_EQ(r.SentMessages(), expected); +} + +TEST(StatelessRegistry, MonotonicCounterUint) { + TestStatelessRegistry r; + auto m = r.GetMonotonicCounterUint("foo"); + auto m2 = r.GetMonotonicCounterUint("bar", {{"id", "2"}}); + m->Set(100); + m2->Set(101); + std::vector expected = {"U:foo:100", "U:bar,id=2:101"}; + EXPECT_EQ(r.SentMessages(), expected); } TEST(StatelessRegistry, Timer) { TestStatelessRegistry r; - auto t = r.GetTimer("t"); - auto t2 = r.GetTimer("t", {{"id", "2"}}); + auto t = r.GetTimer("foo"); + auto t2 = r.GetTimer("bar", {{"id", "2"}}); t->Record(std::chrono::microseconds(100)); t2->Record(absl::Seconds(0.1)); + std::vector expected = {"t:foo:0.0001", "t:bar,id=2:0.1"}; + EXPECT_EQ(r.SentMessages(), expected); } TEST(StatelessRegistry, PercentileTimer) { TestStatelessRegistry r; - auto t = - r.GetPercentileTimer("name", absl::ZeroDuration(), absl::Seconds(10)); - auto t2 = - r.GetPercentileTimer("name2", absl::Milliseconds(1), absl::Seconds(1)); + auto t = r.GetPercentileTimer("foo", absl::ZeroDuration(), absl::Seconds(10)); + auto t2 = r.GetPercentileTimer("bar", absl::Milliseconds(1), absl::Seconds(1)); t->Record(std::chrono::microseconds(100)); t2->Record(std::chrono::microseconds(100)); @@ -94,16 +121,16 @@ TEST(StatelessRegistry, PercentileTimer) { t->Record(std::chrono::milliseconds(100)); t2->Record(std::chrono::milliseconds(100)); - std::vector expected = {"T:name:0.0001", "T:name2:0.001", - "T:name:5", "T:name2:1", - "T:name:0.1", "T:name2:0.1"}; + std::vector expected = {"T:foo:0.0001", "T:bar:0.001", + "T:foo:5", "T:bar:1", + "T:foo:0.1", "T:bar:0.1"}; EXPECT_EQ(r.SentMessages(), expected); } TEST(StatelessRegistry, PercentileDistributionSummary) { TestStatelessRegistry r; - auto t = r.GetPercentileDistributionSummary("name", 0, 1000); - auto t2 = r.GetPercentileDistributionSummary("name2", 10, 100); + auto t = r.GetPercentileDistributionSummary("foo", 0, 1000); + auto t2 = r.GetPercentileDistributionSummary("bar", 10, 100); t->Record(5); t2->Record(5); @@ -114,9 +141,9 @@ TEST(StatelessRegistry, PercentileDistributionSummary) { t->Record(50); t2->Record(50); - std::vector expected = {"D:name:5", "D:name2:10", - "D:name:500", "D:name2:100", - "D:name:50", "D:name2:50"}; + std::vector expected = {"D:foo:5", "D:bar:10", + "D:foo:500", "D:bar:100", + "D:foo:50", "D:bar:50"}; EXPECT_EQ(r.SentMessages(), expected); } @@ -125,6 +152,7 @@ void test_meter(T&& m1, T&& m2) { auto id1 = m1->MeterId(); auto id2 = m2->MeterId(); EXPECT_EQ(*id1, *id2); + spectator::Tags expected{{"x.spectator", "v1"}}; EXPECT_EQ(id1->GetTags(), expected); } @@ -140,7 +168,7 @@ TEST(StatelessRegistry, ExtraTags) { auto c_id = r.GetCounter(Id::of("name")); test_meter(c_name, c_id); - // DistSum + // DistSummaries auto d_name = r.GetDistributionSummary("ds"); auto d_id = r.GetDistributionSummary(Id::of("ds")); test_meter(d_name, d_id); @@ -150,11 +178,6 @@ TEST(StatelessRegistry, ExtraTags) { auto g_id = r.GetGauge(Id::of("g")); test_meter(g_name, g_id); - // Timers - auto t_name = r.GetTimer("t1"); - auto t_id = r.GetTimer(Id::of("t1")); - test_meter(t_name, t_id); - // MaxGauge auto mx_name = r.GetMaxGauge("m1"); auto mx_id = r.GetMaxGauge(Id::of("m1")); @@ -165,16 +188,27 @@ TEST(StatelessRegistry, ExtraTags) { auto mo_id = r.GetMonotonicCounter(Id::of("mo1")); test_meter(mo_name, mo_id); - // PercDs + // MonoCounter Uint + auto mo_u_name = r.GetMonotonicCounterUint("mo1"); + auto mo_u_id = r.GetMonotonicCounterUint(Id::of("mo1")); + test_meter(mo_name, mo_id); + + // Pct DistSummaries auto pds_name = r.GetPercentileDistributionSummary("pds", 0, 100); auto pds_id = r.GetPercentileDistributionSummary(Id::of("pds"), 0, 100); test_meter(pds_name, pds_id); + // Pct Timers auto pt_name = r.GetPercentileTimer("t", absl::ZeroDuration(), absl::Seconds(1)); auto pt_id = r.GetPercentileTimer(Id::of("t"), absl::ZeroDuration(), absl::Seconds(1)); test_meter(pt_name, pt_id); + + // Timers + auto t_name = r.GetTimer("t1"); + auto t_id = r.GetTimer(Id::of("t1")); + test_meter(t_name, t_id); } } // namespace