diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/monlib/dynamic_counters | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/monlib/dynamic_counters')
19 files changed, 2233 insertions, 0 deletions
diff --git a/library/cpp/monlib/dynamic_counters/contention_ut.cpp b/library/cpp/monlib/dynamic_counters/contention_ut.cpp new file mode 100644 index 0000000000..8798044ee3 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/contention_ut.cpp @@ -0,0 +1,61 @@ +#include "counters.h" +#include <library/cpp/testing/unittest/registar.h> +#include <util/system/event.h> +#include <util/system/thread.h> + +using namespace NMonitoring; + +Y_UNIT_TEST_SUITE(TDynamicCountersContentionTest) { + + Y_UNIT_TEST(EnsureNonlocking) { + TDynamicCounterPtr counters = MakeIntrusive<TDynamicCounters>(); + + class TConsumer : public ICountableConsumer { + TAutoEvent Ev; + TAutoEvent Response; + TDynamicCounterPtr Counters; + TThread Thread; + + public: + TConsumer(TDynamicCounterPtr counters) + : Counters(counters) + , Thread(std::bind(&TConsumer::ThreadFunc, this)) + { + Thread.Start(); + } + + ~TConsumer() override { + Thread.Join(); + } + + void OnCounter(const TString& /*labelName*/, const TString& /*labelValue*/, const TCounterForPtr* /*counter*/) override { + Ev.Signal(); + Response.Wait(); + } + + void OnHistogram(const TString& /*labelName*/, const TString& /*labelValue*/, IHistogramSnapshotPtr /*snapshot*/, bool /*derivative*/) override { + } + + void OnGroupBegin(const TString& /*labelName*/, const TString& /*labelValue*/, const TDynamicCounters* /*group*/) override { + } + + void OnGroupEnd(const TString& /*labelName*/, const TString& /*labelValue*/, const TDynamicCounters* /*group*/) override { + } + + private: + void ThreadFunc() { + // acts like a coroutine + Ev.Wait(); + auto ctr = Counters->GetSubgroup("label", "value")->GetCounter("name"); + Y_VERIFY(*ctr == 42); + Response.Signal(); + } + }; + + auto ctr = counters->GetSubgroup("label", "value")->GetCounter("name"); + *ctr = 42; + TConsumer consumer(counters); + counters->Accept({}, {}, consumer); + } + +} diff --git a/library/cpp/monlib/dynamic_counters/counters.cpp b/library/cpp/monlib/dynamic_counters/counters.cpp new file mode 100644 index 0000000000..3635d87d0d --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/counters.cpp @@ -0,0 +1,308 @@ +#include "counters.h" + +#include <library/cpp/monlib/service/pages/templates.h> + +#include <util/generic/cast.h> + +using namespace NMonitoring; + +namespace { + TDynamicCounters* AsDynamicCounters(const TIntrusivePtr<TCountableBase>& ptr) { + return dynamic_cast<TDynamicCounters*>(ptr.Get()); + } + + TCounterForPtr* AsCounter(const TIntrusivePtr<TCountableBase>& ptr) { + return dynamic_cast<TCounterForPtr*>(ptr.Get()); + } + + TExpiringCounter* AsExpiringCounter(const TIntrusivePtr<TCountableBase>& ptr) { + return dynamic_cast<TExpiringCounter*>(ptr.Get()); + } + + TExpiringHistogramCounter* AsExpiringHistogramCounter(const TIntrusivePtr<TCountableBase>& ptr) { + return dynamic_cast<TExpiringHistogramCounter*>(ptr.Get()); + } + + THistogramCounter* AsHistogram(const TIntrusivePtr<TCountableBase>& ptr) { + return dynamic_cast<THistogramCounter*>(ptr.Get()); + } + + TIntrusivePtr<TCounterForPtr> AsCounterRef(const TIntrusivePtr<TCountableBase>& ptr) { + return VerifyDynamicCast<TCounterForPtr*>(ptr.Get()); + } + + TIntrusivePtr<TDynamicCounters> AsGroupRef(const TIntrusivePtr<TCountableBase>& ptr) { + return VerifyDynamicCast<TDynamicCounters*>(ptr.Get()); + } + + THistogramPtr AsHistogramRef(const TIntrusivePtr<TCountableBase>& ptr) { + return VerifyDynamicCast<THistogramCounter*>(ptr.Get()); + } + + bool IsExpiringCounter(const TIntrusivePtr<TCountableBase>& ptr) { + return AsExpiringCounter(ptr) != nullptr || AsExpiringHistogramCounter(ptr) != nullptr; + } +} + +static constexpr TStringBuf INDENT = " "; + +TDynamicCounters::TDynamicCounters(EVisibility vis) +{ + Visibility_ = vis; +} + +TDynamicCounters::~TDynamicCounters() { +} + +TDynamicCounters::TCounterPtr TDynamicCounters::GetExpiringCounter(const TString& value, bool derivative, EVisibility vis) { + return GetExpiringNamedCounter("sensor", value, derivative, vis); +} + +TDynamicCounters::TCounterPtr TDynamicCounters::GetExpiringNamedCounter(const TString& name, const TString& value, bool derivative, EVisibility vis) { + return AsCounterRef(GetNamedCounterImpl<true, TExpiringCounter>(name, value, derivative, vis)); +} + +TDynamicCounters::TCounterPtr TDynamicCounters::GetCounter(const TString& value, bool derivative, EVisibility vis) { + return GetNamedCounter("sensor", value, derivative, vis); +} + +TDynamicCounters::TCounterPtr TDynamicCounters::GetNamedCounter(const TString& name, const TString& value, bool derivative, EVisibility vis) { + return AsCounterRef(GetNamedCounterImpl<false, TCounterForPtr>(name, value, derivative, vis)); +} + +THistogramPtr TDynamicCounters::GetHistogram(const TString& value, IHistogramCollectorPtr collector, bool derivative, EVisibility vis) { + return GetNamedHistogram("sensor", value, std::move(collector), derivative, vis); +} + +THistogramPtr TDynamicCounters::GetNamedHistogram(const TString& name, const TString& value, IHistogramCollectorPtr collector, bool derivative, EVisibility vis) { + return AsHistogramRef(GetNamedCounterImpl<false, THistogramCounter>(name, value, std::move(collector), derivative, vis)); +} + +THistogramPtr TDynamicCounters::GetExpiringHistogram(const TString& value, IHistogramCollectorPtr collector, bool derivative, EVisibility vis) { + return GetExpiringNamedHistogram("sensor", value, std::move(collector), derivative, vis); +} + +THistogramPtr TDynamicCounters::GetExpiringNamedHistogram(const TString& name, const TString& value, IHistogramCollectorPtr collector, bool derivative, EVisibility vis) { + return AsHistogramRef(GetNamedCounterImpl<true, TExpiringHistogramCounter>(name, value, std::move(collector), derivative, vis)); +} + +TDynamicCounters::TCounterPtr TDynamicCounters::FindCounter(const TString& value) const { + return FindNamedCounter("sensor", value); +} + +TDynamicCounters::TCounterPtr TDynamicCounters::FindNamedCounter(const TString& name, const TString& value) const { + return AsCounterRef(FindNamedCounterImpl<TCounterForPtr>(name, value)); +} + +THistogramPtr TDynamicCounters::FindHistogram(const TString& value) const { + return FindNamedHistogram("sensor", value); +} + +THistogramPtr TDynamicCounters::FindNamedHistogram(const TString& name,const TString& value) const { + return AsHistogramRef(FindNamedCounterImpl<THistogramCounter>(name, value)); +} + +void TDynamicCounters::RemoveCounter(const TString &value) { + RemoveNamedCounter("sensor", value); +} + +void TDynamicCounters::RemoveNamedCounter(const TString& name, const TString &value) { + auto g = LockForUpdate("RemoveNamedCounter", name, value); + if (const auto it = Counters.find({name, value}); it != Counters.end() && AsCounter(it->second)) { + Counters.erase(it); + } +} + +TIntrusivePtr<TDynamicCounters> TDynamicCounters::GetSubgroup(const TString& name, const TString& value) { + auto res = FindSubgroup(name, value); + if (!res) { + auto g = LockForUpdate("GetSubgroup", name, value); + const TChildId key(name, value); + if (const auto it = Counters.lower_bound(key); it != Counters.end() && it->first == key) { + res = AsGroupRef(it->second); + } else { + res = MakeIntrusive<TDynamicCounters>(this); + Counters.emplace_hint(it, key, res); + } + } + return res; +} + +TIntrusivePtr<TDynamicCounters> TDynamicCounters::FindSubgroup(const TString& name, const TString& value) const { + TReadGuard g(Lock); + const auto it = Counters.find({name, value}); + return it != Counters.end() ? AsDynamicCounters(it->second) : nullptr; +} + +void TDynamicCounters::RemoveSubgroup(const TString& name, const TString& value) { + auto g = LockForUpdate("RemoveSubgroup", name, value); + if (const auto it = Counters.find({name, value}); it != Counters.end() && AsDynamicCounters(it->second)) { + Counters.erase(it); + } +} + +void TDynamicCounters::ReplaceSubgroup(const TString& name, const TString& value, TIntrusivePtr<TDynamicCounters> subgroup) { + auto g = LockForUpdate("ReplaceSubgroup", name, value); + const auto it = Counters.find({name, value}); + Y_VERIFY(it != Counters.end() && AsDynamicCounters(it->second)); + it->second = std::move(subgroup); +} + +void TDynamicCounters::MergeWithSubgroup(const TString& name, const TString& value) { + auto g = LockForUpdate("MergeWithSubgroup", name, value); + auto it = Counters.find({name, value}); + Y_VERIFY(it != Counters.end()); + TIntrusivePtr<TDynamicCounters> subgroup = AsDynamicCounters(it->second); + Y_VERIFY(subgroup); + Counters.erase(it); + Counters.merge(subgroup->Resign()); + AtomicAdd(ExpiringCount, AtomicSwap(&subgroup->ExpiringCount, 0)); +} + +void TDynamicCounters::ResetCounters(bool derivOnly) { + TReadGuard g(Lock); + for (auto& [key, value] : Counters) { + if (auto counter = AsCounter(value)) { + if (!derivOnly || counter->ForDerivative()) { + *counter = 0; + } + } else if (auto subgroup = AsDynamicCounters(value)) { + subgroup->ResetCounters(derivOnly); + } + } +} + +void TDynamicCounters::RegisterCountable(const TString& name, const TString& value, TCountablePtr countable) { + Y_VERIFY(countable); + auto g = LockForUpdate("RegisterCountable", name, value); + const bool inserted = Counters.emplace(TChildId(name, value), std::move(countable)).second; + Y_VERIFY(inserted); +} + +void TDynamicCounters::RegisterSubgroup(const TString& name, const TString& value, TIntrusivePtr<TDynamicCounters> subgroup) { + RegisterCountable(name, value, subgroup); +} + +void TDynamicCounters::OutputHtml(IOutputStream& os) const { + HTML(os) { + PRE() { + OutputPlainText(os); + } + } +} + +void TDynamicCounters::EnumerateSubgroups(const std::function<void(const TString& name, const TString& value)>& output) const { + TReadGuard g(Lock); + for (const auto& [key, value] : Counters) { + if (AsDynamicCounters(value)) { + output(key.LabelName, key.LabelValue); + } + } +} + +void TDynamicCounters::OutputPlainText(IOutputStream& os, const TString& indent) const { + auto snap = ReadSnapshot(); + // mark private records in plain text output + auto outputVisibilityMarker = [] (EVisibility vis) { + return vis == EVisibility::Private ? "\t[PRIVATE]" : ""; + }; + + for (const auto& [key, value] : snap) { + if (const auto counter = AsCounter(value)) { + os << indent + << key.LabelName << '=' << key.LabelValue + << ": " << counter->Val() + << outputVisibilityMarker(counter->Visibility()) + << '\n'; + } else if (const auto histogram = AsHistogram(value)) { + os << indent + << key.LabelName << '=' << key.LabelValue + << ":" + << outputVisibilityMarker(histogram->Visibility()) + << "\n"; + + auto snapshot = histogram->Snapshot(); + for (ui32 i = 0, count = snapshot->Count(); i < count; i++) { + os << indent << INDENT << TStringBuf("bin="); + TBucketBound bound = snapshot->UpperBound(i); + if (bound == Max<TBucketBound>()) { + os << TStringBuf("inf"); + } else { + os << bound; + } + os << ": " << snapshot->Value(i) << '\n'; + } + } + } + + for (const auto& [key, value] : snap) { + if (const auto subgroup = AsDynamicCounters(value)) { + os << "\n"; + os << indent << key.LabelName << "=" << key.LabelValue << ":\n"; + subgroup->OutputPlainText(os, indent + INDENT); + } + } +} + +void TDynamicCounters::Accept(const TString& labelName, const TString& labelValue, ICountableConsumer& consumer) const { + if (!IsVisible(Visibility(), consumer.Visibility())) { + return; + } + + consumer.OnGroupBegin(labelName, labelValue, this); + for (auto& [key, value] : ReadSnapshot()) { + value->Accept(key.LabelName, key.LabelValue, consumer); + } + consumer.OnGroupEnd(labelName, labelValue, this); +} + +void TDynamicCounters::RemoveExpired() const { + if (AtomicGet(ExpiringCount) == 0) { + return; + } + + TWriteGuard g(Lock); + TAtomicBase count = 0; + + for (auto it = Counters.begin(); it != Counters.end();) { + if (IsExpiringCounter(it->second) && it->second->RefCount() == 1) { + it = Counters.erase(it); + ++count; + } else { + ++it; + } + } + + AtomicSub(ExpiringCount, count); +} + +template <bool expiring, class TCounterType, class... TArgs> +TDynamicCounters::TCountablePtr TDynamicCounters::GetNamedCounterImpl(const TString& name, const TString& value, TArgs&&... args) { + { + TReadGuard g(Lock); + auto it = Counters.find({name, value}); + if (it != Counters.end()) { + return it->second; + } + } + + auto g = LockForUpdate("GetNamedCounterImpl", name, value); + const TChildId key(name, value); + auto it = Counters.lower_bound(key); + if (it == Counters.end() || it->first != key) { + auto value = MakeIntrusive<TCounterType>(std::forward<TArgs>(args)...); + it = Counters.emplace_hint(it, key, value); + if constexpr (expiring) { + AtomicIncrement(ExpiringCount); + } + } + return it->second; +} + +template <class TCounterType> +TDynamicCounters::TCountablePtr TDynamicCounters::FindNamedCounterImpl(const TString& name, const TString& value) const { + TReadGuard g(Lock); + auto it = Counters.find({name, value}); + return it != Counters.end() ? it->second : nullptr; +} + diff --git a/library/cpp/monlib/dynamic_counters/counters.h b/library/cpp/monlib/dynamic_counters/counters.h new file mode 100644 index 0000000000..dc178cfbe0 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/counters.h @@ -0,0 +1,374 @@ +#pragma once + +#include <library/cpp/monlib/counters/counters.h> +#include <library/cpp/monlib/metrics/histogram_collector.h> + +#include <library/cpp/threading/light_rw_lock/lightrwlock.h> +#include <library/cpp/containers/stack_vector/stack_vec.h> + +#include <util/generic/cast.h> +#include <util/generic/map.h> +#include <util/generic/ptr.h> +#include <util/string/cast.h> +#include <util/system/rwlock.h> + +#include <functional> + +namespace NMonitoring { + struct TCounterForPtr; + struct TDynamicCounters; + struct ICountableConsumer; + + + struct TCountableBase: public TAtomicRefCount<TCountableBase> { + // Private means that the object must not be serialized unless the consumer + // has explicitly specified this by setting its Visibility to Private. + // + // Works only for the methods that accept ICountableConsumer + enum class EVisibility: ui8 { + Unspecified, + Public, + Private, + }; + + virtual ~TCountableBase() { + } + + virtual void Accept( + const TString& labelName, const TString& labelValue, + ICountableConsumer& consumer) const = 0; + + virtual EVisibility Visibility() const { + return Visibility_; + } + + protected: + EVisibility Visibility_{EVisibility::Unspecified}; + }; + + inline bool IsVisible(TCountableBase::EVisibility myLevel, TCountableBase::EVisibility consumerLevel) { + if (myLevel == TCountableBase::EVisibility::Private + && consumerLevel != TCountableBase::EVisibility::Private) { + + return false; + } + + return true; + } + + struct ICountableConsumer { + virtual ~ICountableConsumer() { + } + + virtual void OnCounter( + const TString& labelName, const TString& labelValue, + const TCounterForPtr* counter) = 0; + + virtual void OnHistogram( + const TString& labelName, const TString& labelValue, + IHistogramSnapshotPtr snapshot, bool derivative) = 0; + + virtual void OnGroupBegin( + const TString& labelName, const TString& labelValue, + const TDynamicCounters* group) = 0; + + virtual void OnGroupEnd( + const TString& labelName, const TString& labelValue, + const TDynamicCounters* group) = 0; + + virtual TCountableBase::EVisibility Visibility() const { + return TCountableBase::EVisibility::Unspecified; + } + }; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4522) // multiple assignment operators specified +#endif // _MSC_VER + + struct TCounterForPtr: public TDeprecatedCounter, public TCountableBase { + TCounterForPtr(bool derivative = false, EVisibility vis = EVisibility::Public) + : TDeprecatedCounter(0ULL, derivative) + { + Visibility_ = vis; + } + + TCounterForPtr(const TCounterForPtr&) = delete; + TCounterForPtr& operator=(const TCounterForPtr& other) = delete; + + void Accept( + const TString& labelName, const TString& labelValue, + ICountableConsumer& consumer) const override { + if (IsVisible(Visibility(), consumer.Visibility())) { + consumer.OnCounter(labelName, labelValue, this); + } + } + + TCountableBase::EVisibility Visibility() const override { + return Visibility_; + } + + using TDeprecatedCounter::operator++; + using TDeprecatedCounter::operator--; + using TDeprecatedCounter::operator+=; + using TDeprecatedCounter::operator-=; + using TDeprecatedCounter::operator=; + using TDeprecatedCounter::operator!; + }; + + struct TExpiringCounter: public TCounterForPtr { + explicit TExpiringCounter(bool derivative = false, EVisibility vis = EVisibility::Public) + : TCounterForPtr{derivative} + { + Visibility_ = vis; + } + + void Reset() { + TDeprecatedCounter::operator=(0); + } + }; + + struct THistogramCounter: public TCountableBase { + explicit THistogramCounter( + IHistogramCollectorPtr collector, bool derivative = true, EVisibility vis = EVisibility::Public) + : Collector_(std::move(collector)) + , Derivative_(derivative) + { + Visibility_ = vis; + } + + void Collect(i64 value) { + Collector_->Collect(value); + } + + void Collect(i64 value, ui32 count) { + Collector_->Collect(value, count); + } + + void Collect(double value, ui32 count) { + Collector_->Collect(value, count); + } + + void Collect(const IHistogramSnapshot& snapshot) { + Collector_->Collect(snapshot); + } + + void Accept( + const TString& labelName, const TString& labelValue, + ICountableConsumer& consumer) const override + { + if (IsVisible(Visibility(), consumer.Visibility())) { + consumer.OnHistogram(labelName, labelValue, Collector_->Snapshot(), Derivative_); + } + } + + void Reset() { + Collector_->Reset(); + } + + IHistogramSnapshotPtr Snapshot() const { + return Collector_->Snapshot(); + } + + private: + IHistogramCollectorPtr Collector_; + bool Derivative_; + }; + + struct TExpiringHistogramCounter: public THistogramCounter { + using THistogramCounter::THistogramCounter; + }; + + using THistogramPtr = TIntrusivePtr<THistogramCounter>; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + struct TDynamicCounters; + + typedef TIntrusivePtr<TDynamicCounters> TDynamicCounterPtr; + struct TDynamicCounters: public TCountableBase { + public: + using TCounterPtr = TIntrusivePtr<TCounterForPtr>; + using TOnLookupPtr = void (*)(const char *methodName, const TString &name, const TString &value); + + private: + TRWMutex Lock; + TCounterPtr LookupCounter; // Counts lookups by name + TOnLookupPtr OnLookup = nullptr; // Called on each lookup if not nullptr, intended for lightweight tracing. + + typedef TIntrusivePtr<TCountableBase> TCountablePtr; + + struct TChildId { + TString LabelName; + TString LabelValue; + TChildId() { + } + TChildId(const TString& labelName, const TString& labelValue) + : LabelName(labelName) + , LabelValue(labelValue) + { + } + auto AsTuple() const { + return std::make_tuple(std::cref(LabelName), std::cref(LabelValue)); + } + friend bool operator <(const TChildId& x, const TChildId& y) { + return x.AsTuple() < y.AsTuple(); + } + friend bool operator ==(const TChildId& x, const TChildId& y) { + return x.AsTuple() == y.AsTuple(); + } + friend bool operator !=(const TChildId& x, const TChildId& y) { + return x.AsTuple() != y.AsTuple(); + } + }; + + using TCounters = TMap<TChildId, TCountablePtr>; + using TLabels = TVector<TChildId>; + + /// XXX: hack for deferred removal of expired counters. Remove once Output* functions are not used for serialization + mutable TCounters Counters; + mutable TAtomic ExpiringCount = 0; + + public: + TDynamicCounters(TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + TDynamicCounters(const TDynamicCounters *origin) + : LookupCounter(origin->LookupCounter) + , OnLookup(origin->OnLookup) + {} + + ~TDynamicCounters() override; + + // This counter allows to track lookups by name within the whole subtree + void SetLookupCounter(TCounterPtr lookupCounter) { + TWriteGuard g(Lock); + LookupCounter = lookupCounter; + } + + void SetOnLookup(TOnLookupPtr onLookup) { + TWriteGuard g(Lock); + OnLookup = onLookup; + } + + TWriteGuard LockForUpdate(const char *method, const TString& name, const TString& value) { + auto res = TWriteGuard(Lock); + if (LookupCounter) { + ++*LookupCounter; + } + if (OnLookup) { + OnLookup(method, name, value); + } + return res; + } + + TStackVec<TCounters::value_type, 256> ReadSnapshot() const { + RemoveExpired(); + TReadGuard g(Lock); + TStackVec<TCounters::value_type, 256> items(Counters.begin(), Counters.end()); + return items; + } + + TCounterPtr GetCounter( + const TString& value, + bool derivative = false, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + TCounterPtr GetNamedCounter( + const TString& name, + const TString& value, + bool derivative = false, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + THistogramPtr GetHistogram( + const TString& value, + IHistogramCollectorPtr collector, + bool derivative = true, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + THistogramPtr GetNamedHistogram( + const TString& name, + const TString& value, + IHistogramCollectorPtr collector, + bool derivative = true, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + // These counters will be automatically removed from the registry + // when last reference to the counter expires. + TCounterPtr GetExpiringCounter( + const TString& value, + bool derivative = false, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + TCounterPtr GetExpiringNamedCounter( + const TString& name, + const TString& value, + bool derivative = false, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + THistogramPtr GetExpiringHistogram( + const TString& value, + IHistogramCollectorPtr collector, + bool derivative = true, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + THistogramPtr GetExpiringNamedHistogram( + const TString& name, + const TString& value, + IHistogramCollectorPtr collector, + bool derivative = true, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + TCounterPtr FindCounter(const TString& value) const; + TCounterPtr FindNamedCounter(const TString& name, const TString& value) const; + + THistogramPtr FindHistogram(const TString& value) const; + THistogramPtr FindNamedHistogram(const TString& name,const TString& value) const; + + void RemoveCounter(const TString &value); + void RemoveNamedCounter(const TString& name, const TString &value); + + TIntrusivePtr<TDynamicCounters> GetSubgroup(const TString& name, const TString& value); + TIntrusivePtr<TDynamicCounters> FindSubgroup(const TString& name, const TString& value) const; + void RemoveSubgroup(const TString& name, const TString& value); + void ReplaceSubgroup(const TString& name, const TString& value, TIntrusivePtr<TDynamicCounters> subgroup); + + // Move all counters from specified subgroup and remove the subgroup. + void MergeWithSubgroup(const TString& name, const TString& value); + // Recursively reset all/deriv counters to 0. + void ResetCounters(bool derivOnly = false); + + void RegisterSubgroup(const TString& name, + const TString& value, + TIntrusivePtr<TDynamicCounters> subgroup); + + void OutputHtml(IOutputStream& os) const; + void EnumerateSubgroups(const std::function<void(const TString& name, const TString& value)>& output) const; + + // mostly for debugging purposes -- use accept with encoder instead + void OutputPlainText(IOutputStream& os, const TString& indent = "") const; + + void Accept( + const TString& labelName, const TString& labelValue, + ICountableConsumer& consumer) const override; + + private: + TCounters Resign() { + TCounters counters; + TWriteGuard g(Lock); + Counters.swap(counters); + return counters; + } + + void RegisterCountable(const TString& name, const TString& value, TCountablePtr countable); + void RemoveExpired() const; + + template <bool expiring, class TCounterType, class... TArgs> + TCountablePtr GetNamedCounterImpl(const TString& name, const TString& value, TArgs&&... args); + + template <class TCounterType> + TCountablePtr FindNamedCounterImpl(const TString& name, const TString& value) const; + }; + +} diff --git a/library/cpp/monlib/dynamic_counters/counters_ut.cpp b/library/cpp/monlib/dynamic_counters/counters_ut.cpp new file mode 100644 index 0000000000..3591037e0a --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/counters_ut.cpp @@ -0,0 +1,342 @@ +#include "counters.h" + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NMonitoring; + +class TCountersPrinter: public ICountableConsumer { +public: + TCountersPrinter(IOutputStream* out) + : Out_(out) + , Level_(0) + { + } + +private: + void OnCounter( + const TString& labelName, const TString& labelValue, + const TCounterForPtr* counter) override { + Indent(Out_, Level_) + << labelName << ':' << labelValue + << " = " << counter->Val() << '\n'; + } + + void OnHistogram( + const TString& labelName, const TString& labelValue, + IHistogramSnapshotPtr snapshot, bool /*derivative*/) override { + Indent(Out_, Level_) + << labelName << ':' << labelValue + << " = " << *snapshot << '\n'; + } + + void OnGroupBegin( + const TString& labelName, const TString& labelValue, + const TDynamicCounters*) override { + Indent(Out_, Level_++) << labelName << ':' << labelValue << " {\n"; + } + + void OnGroupEnd( + const TString&, const TString&, + const TDynamicCounters*) override { + Indent(Out_, --Level_) << "}\n"; + } + + static IOutputStream& Indent(IOutputStream* out, int level) { + for (int i = 0; i < level; i++) { + out->Write(" "); + } + return *out; + } + +private: + IOutputStream* Out_; + int Level_ = 0; +}; + +Y_UNIT_TEST_SUITE(TDynamicCountersTest) { + Y_UNIT_TEST(CountersConsumer) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + auto usersCounter = rootGroup->GetNamedCounter("users", "count"); + *usersCounter = 7; + + auto hostGroup = rootGroup->GetSubgroup("counters", "resources"); + auto cpuCounter = hostGroup->GetNamedCounter("resource", "cpu"); + *cpuCounter = 30; + + auto memGroup = hostGroup->GetSubgroup("resource", "mem"); + auto usedCounter = memGroup->GetCounter("used"); + auto freeCounter = memGroup->GetCounter("free"); + *usedCounter = 100; + *freeCounter = 28; + + auto netGroup = hostGroup->GetSubgroup("resource", "net"); + auto rxCounter = netGroup->GetCounter("rx", true); + auto txCounter = netGroup->GetCounter("tx", true); + *rxCounter = 8; + *txCounter = 9; + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " counters:resources {\n" + " resource:cpu = 30\n" + " resource:mem {\n" + " sensor:free = 28\n" + " sensor:used = 100\n" + " }\n" + " resource:net {\n" + " sensor:rx = 8\n" + " sensor:tx = 9\n" + " }\n" + " }\n" + " users:count = 7\n" + "}\n"); + } + + Y_UNIT_TEST(MergeSubgroup) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + auto sensor1 = rootGroup->GetNamedCounter("sensor", "1"); + *sensor1 = 1; + + auto group1 = rootGroup->GetSubgroup("group", "1"); + auto sensor2 = group1->GetNamedCounter("sensor", "2"); + *sensor2 = 2; + + auto group2 = group1->GetSubgroup("group", "2"); + auto sensor3 = group2->GetNamedCounter("sensor", "3"); + *sensor3 = 3; + + rootGroup->MergeWithSubgroup("group", "1"); + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " group:2 {\n" + " sensor:3 = 3\n" + " }\n" + " sensor:1 = 1\n" + " sensor:2 = 2\n" + "}\n"); + } + + Y_UNIT_TEST(ResetCounters) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + auto sensor1 = rootGroup->GetNamedCounter("sensor", "1"); + *sensor1 = 1; + + auto group1 = rootGroup->GetSubgroup("group", "1"); + auto sensor2 = group1->GetNamedCounter("sensor", "2"); + *sensor2 = 2; + + auto group2 = group1->GetSubgroup("group", "2"); + auto sensor3 = group2->GetNamedCounter("sensor", "3", true); + *sensor3 = 3; + + rootGroup->ResetCounters(true); + + TStringStream ss1; + TCountersPrinter printer1(&ss1); + rootGroup->Accept("root", "counters", printer1); + + UNIT_ASSERT_STRINGS_EQUAL(ss1.Str(), + "root:counters {\n" + " group:1 {\n" + " group:2 {\n" + " sensor:3 = 0\n" + " }\n" + " sensor:2 = 2\n" + " }\n" + " sensor:1 = 1\n" + "}\n"); + + rootGroup->ResetCounters(); + + TStringStream ss2; + TCountersPrinter printer2(&ss2); + rootGroup->Accept("root", "counters", printer2); + + UNIT_ASSERT_STRINGS_EQUAL(ss2.Str(), + "root:counters {\n" + " group:1 {\n" + " group:2 {\n" + " sensor:3 = 0\n" + " }\n" + " sensor:2 = 0\n" + " }\n" + " sensor:1 = 0\n" + "}\n"); + } + + Y_UNIT_TEST(RemoveCounter) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + rootGroup->GetNamedCounter("label", "1"); + rootGroup->GetCounter("2"); + rootGroup->GetCounter("3"); + rootGroup->GetSubgroup("group", "1"); + + rootGroup->RemoveNamedCounter("label", "1"); + rootGroup->RemoveNamedCounter("label", "5"); + rootGroup->RemoveNamedCounter("group", "1"); + rootGroup->RemoveCounter("2"); + rootGroup->RemoveCounter("5"); + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " group:1 {\n" + " }\n" + " sensor:3 = 0\n" + "}\n"); + } + + Y_UNIT_TEST(RemoveSubgroup) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + rootGroup->GetSubgroup("group", "1"); + rootGroup->GetSubgroup("group", "2"); + rootGroup->GetCounter("2"); + + rootGroup->RemoveSubgroup("group", "1"); + rootGroup->RemoveSubgroup("group", "3"); + rootGroup->RemoveSubgroup("sensor", "2"); + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " group:2 {\n" + " }\n" + " sensor:2 = 0\n" + "}\n"); + } + + Y_UNIT_TEST(ExpiringCounters) { + TDynamicCounterPtr rootGroup{new TDynamicCounters()}; + + { + auto c = rootGroup->GetExpiringCounter("foo"); + auto h = rootGroup->GetExpiringHistogram("bar", ExplicitHistogram({1, 42})); + h->Collect(15); + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " sensor:bar = {1: 0, 42: 1, inf: 0}\n" + " sensor:foo = 0\n" + "}\n"); + } + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + "}\n"); + } + + Y_UNIT_TEST(ExpiringCountersDiesAfterRegistry) { + TDynamicCounters::TCounterPtr ptr; + + { + TDynamicCounterPtr rootGroup{new TDynamicCounters()}; + ptr = rootGroup->GetExpiringCounter("foo"); + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " sensor:foo = 0\n" + "}\n"); + } + } + + Y_UNIT_TEST(HistogramCounter) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + auto h = rootGroup->GetHistogram("timeMillis", ExponentialHistogram(4, 2)); + for (i64 i = 1; i < 100; i++) { + h->Collect(i); + } + + TStringStream ss; + TCountersPrinter printer(&ss); + rootGroup->Accept("root", "counters", printer); + UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), + "root:counters {\n" + " sensor:timeMillis = {1: 1, 2: 1, 4: 2, inf: 95}\n" + "}\n"); + } + + Y_UNIT_TEST(CounterLookupCounter) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + TDynamicCounters::TCounterPtr lookups = rootGroup->GetCounter("Lookups", true); + rootGroup->SetLookupCounter(lookups); + + // Create subtree and check that counter is inherited + TDynamicCounterPtr serviceGroup = rootGroup->GetSubgroup("service", "MyService"); + UNIT_ASSERT_VALUES_EQUAL(lookups->Val(), 1); + + TDynamicCounterPtr subGroup = serviceGroup->GetSubgroup("component", "MyComponent"); + UNIT_ASSERT_VALUES_EQUAL(lookups->Val(), 2); + + auto counter = subGroup->GetNamedCounter("range", "20 msec", true); + UNIT_ASSERT_VALUES_EQUAL(lookups->Val(), 3); + + auto hist = subGroup->GetHistogram("timeMsec", ExponentialHistogram(4, 2)); + UNIT_ASSERT_VALUES_EQUAL(lookups->Val(), 4); + + // Replace the counter for subGroup + auto subGroupLookups = rootGroup->GetCounter("LookupsInMyComponent", true); + UNIT_ASSERT_VALUES_EQUAL(lookups->Val(), 5); + subGroup->SetLookupCounter(subGroupLookups); + auto counter2 = subGroup->GetNamedCounter("range", "30 msec", true); + UNIT_ASSERT_VALUES_EQUAL(subGroupLookups->Val(), 1); + UNIT_ASSERT_VALUES_EQUAL(lookups->Val(), 5); + } + + Y_UNIT_TEST(FindCounters) { + TDynamicCounterPtr rootGroup(new TDynamicCounters()); + + auto counter = rootGroup->FindCounter("counter1"); + UNIT_ASSERT(!counter); + rootGroup->GetCounter("counter1"); + counter = rootGroup->FindCounter("counter1"); + UNIT_ASSERT(counter); + + counter = rootGroup->FindNamedCounter("name", "counter2"); + UNIT_ASSERT(!counter); + rootGroup->GetNamedCounter("name", "counter2"); + counter = rootGroup->FindNamedCounter("name", "counter2"); + UNIT_ASSERT(counter); + + auto histogram = rootGroup->FindHistogram("histogram1"); + UNIT_ASSERT(!histogram); + rootGroup->GetHistogram("histogram1", ExponentialHistogram(4, 2)); + histogram = rootGroup->FindHistogram("histogram1"); + UNIT_ASSERT(histogram); + + histogram = rootGroup->FindNamedHistogram("name", "histogram2"); + UNIT_ASSERT(!histogram); + rootGroup->GetNamedHistogram("name", "histogram2", ExponentialHistogram(4, 2)); + histogram = rootGroup->FindNamedHistogram("name", "histogram2"); + UNIT_ASSERT(histogram); + } +} diff --git a/library/cpp/monlib/dynamic_counters/encode.cpp b/library/cpp/monlib/dynamic_counters/encode.cpp new file mode 100644 index 0000000000..ffa48d276e --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/encode.cpp @@ -0,0 +1,131 @@ +#include "encode.h" + +#include <library/cpp/monlib/encode/encoder.h> +#include <library/cpp/monlib/encode/json/json.h> +#include <library/cpp/monlib/encode/spack/spack_v1.h> +#include <library/cpp/monlib/encode/prometheus/prometheus.h> + +#include <util/stream/str.h> + +namespace NMonitoring { + namespace { + constexpr TInstant ZERO_TIME = TInstant::Zero(); + + class TConsumer final: public ICountableConsumer { + using TLabel = std::pair<TString, TString>; // name, value + + public: + explicit TConsumer(NMonitoring::IMetricEncoderPtr encoderImpl, TCountableBase::EVisibility vis) + : EncoderImpl_(std::move(encoderImpl)) + , Visibility_{vis} + { + } + + void OnCounter( + const TString& labelName, const TString& labelValue, + const TCounterForPtr* counter) override { + NMonitoring::EMetricType metricType = counter->ForDerivative() + ? NMonitoring::EMetricType::RATE + : NMonitoring::EMetricType::GAUGE; + EncoderImpl_->OnMetricBegin(metricType); + EncodeLabels(labelName, labelValue); + + if (metricType == NMonitoring::EMetricType::GAUGE) { + EncoderImpl_->OnDouble(ZERO_TIME, static_cast<double>(counter->Val())); + } else { + EncoderImpl_->OnUint64(ZERO_TIME, counter->Val()); + } + + EncoderImpl_->OnMetricEnd(); + } + + void OnHistogram( + const TString& labelName, const TString& labelValue, + IHistogramSnapshotPtr snapshot, bool derivative) override { + NMonitoring::EMetricType metricType = derivative ? EMetricType::HIST_RATE : EMetricType::HIST; + + EncoderImpl_->OnMetricBegin(metricType); + EncodeLabels(labelName, labelValue); + EncoderImpl_->OnHistogram(ZERO_TIME, snapshot); + EncoderImpl_->OnMetricEnd(); + } + + void OnGroupBegin( + const TString& labelName, const TString& labelValue, + const TDynamicCounters*) override { + if (labelName.empty() && labelValue.empty()) { + // root group has empty label name and value + EncoderImpl_->OnStreamBegin(); + } else { + ParentLabels_.emplace_back(labelName, labelValue); + } + } + + void OnGroupEnd( + const TString& labelName, const TString& labelValue, + const TDynamicCounters*) override { + if (labelName.empty() && labelValue.empty()) { + // root group has empty label name and value + EncoderImpl_->OnStreamEnd(); + EncoderImpl_->Close(); + } else { + ParentLabels_.pop_back(); + } + } + + TCountableBase::EVisibility Visibility() const override { + return Visibility_; + } + + private: + void EncodeLabels(const TString& labelName, const TString& labelValue) { + EncoderImpl_->OnLabelsBegin(); + for (const auto& label : ParentLabels_) { + EncoderImpl_->OnLabel(label.first, label.second); + } + EncoderImpl_->OnLabel(labelName, labelValue); + EncoderImpl_->OnLabelsEnd(); + } + + private: + NMonitoring::IMetricEncoderPtr EncoderImpl_; + TVector<TLabel> ParentLabels_; + TCountableBase::EVisibility Visibility_; + }; + + } + + THolder<ICountableConsumer> CreateEncoder(IOutputStream* out, EFormat format, TCountableBase::EVisibility vis) { + switch (format) { + case EFormat::JSON: + return MakeHolder<TConsumer>(NMonitoring::EncoderJson(out), vis); + case EFormat::SPACK: + return MakeHolder<TConsumer>(NMonitoring::EncoderSpackV1( + out, + NMonitoring::ETimePrecision::SECONDS, + NMonitoring::ECompression::ZSTD), vis); + case EFormat::PROMETHEUS: + return MakeHolder<TConsumer>(NMonitoring::EncoderPrometheus( + out), vis); + default: + ythrow yexception() << "unsupported metric encoding format: " << format; + break; + } + } + + THolder<ICountableConsumer> AsCountableConsumer(IMetricEncoderPtr encoder, TCountableBase::EVisibility visibility) { + return MakeHolder<TConsumer>(std::move(encoder), visibility); + } + + void ToJson(const TDynamicCounters& counters, IOutputStream* out) { + TConsumer consumer{EncoderJson(out), TCountableBase::EVisibility::Public}; + counters.Accept(TString{}, TString{}, consumer); + } + + TString ToJson(const TDynamicCounters& counters) { + TStringStream ss; + ToJson(counters, &ss); + return ss.Str(); + } + +} diff --git a/library/cpp/monlib/dynamic_counters/encode.h b/library/cpp/monlib/dynamic_counters/encode.h new file mode 100644 index 0000000000..c79964d7cb --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/encode.h @@ -0,0 +1,23 @@ +#pragma once + +#include "counters.h" + +#include <library/cpp/monlib/encode/encoder.h> +#include <library/cpp/monlib/encode/format.h> + +namespace NMonitoring { + + THolder<ICountableConsumer> CreateEncoder( + IOutputStream* out, + EFormat format, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public + ); + + THolder<ICountableConsumer> AsCountableConsumer( + NMonitoring::IMetricEncoderPtr encoder, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public); + + void ToJson(const TDynamicCounters& counters, IOutputStream* out); + + TString ToJson(const TDynamicCounters& counters); +} diff --git a/library/cpp/monlib/dynamic_counters/encode_ut.cpp b/library/cpp/monlib/dynamic_counters/encode_ut.cpp new file mode 100644 index 0000000000..52d77b6b41 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/encode_ut.cpp @@ -0,0 +1,226 @@ +#include "encode.h" + +#include <library/cpp/monlib/encode/json/json.h> +#include <library/cpp/monlib/encode/spack/spack_v1.h> +#include <library/cpp/monlib/encode/protobuf/protobuf.h> + +#include <library/cpp/monlib/encode/protobuf/protos/samples.pb.h> +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/buffer.h> +#include <util/stream/buffer.h> + +namespace NMonitoring { + struct TTestData: public TDynamicCounters { + TTestData() { + auto hostGroup = GetSubgroup("counters", "resources"); + { + auto cpuCounter = hostGroup->GetNamedCounter("resource", "cpu"); + *cpuCounter = 30; + + auto memGroup = hostGroup->GetSubgroup("resource", "mem"); + auto usedCounter = memGroup->GetCounter("used"); + auto freeCounter = memGroup->GetCounter("free"); + *usedCounter = 100; + *freeCounter = 28; + + auto netGroup = hostGroup->GetSubgroup("resource", "net"); + auto rxCounter = netGroup->GetCounter("rx", true); + auto txCounter = netGroup->GetCounter("tx", true); + *rxCounter = 8; + *txCounter = 9; + } + + auto usersCounter = GetNamedCounter("users", "count"); + *usersCounter = 7; + + auto responseTimeMillis = GetHistogram("responseTimeMillis", ExplicitHistogram({1, 5, 10, 15, 20, 100, 200})); + for (i64 i = 0; i < 400; i++) { + responseTimeMillis->Collect(i); + } + } + }; + + void AssertLabelsEqual(const NProto::TLabel& l, TStringBuf name, TStringBuf value) { + UNIT_ASSERT_STRINGS_EQUAL(l.GetName(), name); + UNIT_ASSERT_STRINGS_EQUAL(l.GetValue(), value); + } + + void AssertResult(const NProto::TSingleSamplesList& samples) { + UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 7); + + { + auto s = samples.GetSamples(0); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2); + AssertLabelsEqual(s.GetLabels(0), "counters", "resources"); + AssertLabelsEqual(s.GetLabels(1), "resource", "cpu"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE); + UNIT_ASSERT_DOUBLES_EQUAL(s.GetFloat64(), 30.0, Min<double>()); + } + { + auto s = samples.GetSamples(1); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 3); + AssertLabelsEqual(s.GetLabels(0), "counters", "resources"); + AssertLabelsEqual(s.GetLabels(1), "resource", "mem"); + AssertLabelsEqual(s.GetLabels(2), "sensor", "free"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE); + UNIT_ASSERT_DOUBLES_EQUAL(s.GetFloat64(), 28.0, Min<double>()); + } + { + auto s = samples.GetSamples(2); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 3); + AssertLabelsEqual(s.GetLabels(0), "counters", "resources"); + AssertLabelsEqual(s.GetLabels(1), "resource", "mem"); + AssertLabelsEqual(s.GetLabels(2), "sensor", "used"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE); + UNIT_ASSERT_DOUBLES_EQUAL(s.GetFloat64(), 100.0, Min<double>()); + } + { + auto s = samples.GetSamples(3); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 3); + AssertLabelsEqual(s.GetLabels(0), "counters", "resources"); + AssertLabelsEqual(s.GetLabels(1), "resource", "net"); + AssertLabelsEqual(s.GetLabels(2), "sensor", "rx"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::RATE); + UNIT_ASSERT_VALUES_EQUAL(s.GetUint64(), 8); + } + { + auto s = samples.GetSamples(4); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 3); + AssertLabelsEqual(s.GetLabels(0), "counters", "resources"); + AssertLabelsEqual(s.GetLabels(1), "resource", "net"); + AssertLabelsEqual(s.GetLabels(2), "sensor", "tx"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::RATE); + UNIT_ASSERT_VALUES_EQUAL(s.GetUint64(), 9); + } + { + auto s = samples.GetSamples(5); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1); + AssertLabelsEqual(s.GetLabels(0), "sensor", "responseTimeMillis"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::HIST_RATE); + + const NProto::THistogram& h = s.GetHistogram(); + + UNIT_ASSERT_EQUAL(h.BoundsSize(), 8); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(0), 1, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(1), 5, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(2), 10, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(3), 15, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(4), 20, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(5), 100, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(6), 200, Min<double>()); + UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(7), Max<double>(), Min<double>()); + + UNIT_ASSERT_EQUAL(h.ValuesSize(), 8); + UNIT_ASSERT_EQUAL(h.GetValues(0), 2); + UNIT_ASSERT_EQUAL(h.GetValues(1), 4); + UNIT_ASSERT_EQUAL(h.GetValues(2), 5); + UNIT_ASSERT_EQUAL(h.GetValues(3), 5); + UNIT_ASSERT_EQUAL(h.GetValues(4), 5); + UNIT_ASSERT_EQUAL(h.GetValues(5), 80); + UNIT_ASSERT_EQUAL(h.GetValues(6), 100); + UNIT_ASSERT_EQUAL(h.GetValues(7), 199); + } + { + auto s = samples.GetSamples(6); + UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1); + AssertLabelsEqual(s.GetLabels(0), "users", "count"); + UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE); + UNIT_ASSERT_DOUBLES_EQUAL(s.GetFloat64(), 7, Min<double>()); + } + } + + Y_UNIT_TEST_SUITE(TDynamicCountersEncodeTest) { + TTestData Data; + + Y_UNIT_TEST(Json) { + TString result; + { + TStringOutput out(result); + auto encoder = CreateEncoder(&out, EFormat::JSON); + Data.Accept(TString(), TString(), *encoder); + } + + NProto::TSingleSamplesList samples; + { + auto e = EncoderProtobuf(&samples); + DecodeJson(result, e.Get()); + } + + AssertResult(samples); + } + + Y_UNIT_TEST(Spack) { + TBuffer result; + { + TBufferOutput out(result); + auto encoder = CreateEncoder(&out, EFormat::SPACK); + Data.Accept(TString(), TString(), *encoder); + } + + NProto::TSingleSamplesList samples; + { + auto e = EncoderProtobuf(&samples); + TBufferInput in(result); + DecodeSpackV1(&in, e.Get()); + } + + AssertResult(samples); + } + + Y_UNIT_TEST(PrivateSubgroupIsNotSerialized) { + TBuffer result; + auto subGroup = MakeIntrusive<TDynamicCounters>(TCountableBase::EVisibility::Private); + subGroup->GetCounter("hello"); + Data.RegisterSubgroup("foo", "bar", subGroup); + + { + TBufferOutput out(result); + auto encoder = CreateEncoder(&out, EFormat::SPACK); + Data.Accept(TString(), TString(), *encoder); + } + + NProto::TSingleSamplesList samples; + { + auto e = EncoderProtobuf(&samples); + TBufferInput in(result); + DecodeSpackV1(&in, e.Get()); + } + + AssertResult(samples); + } + + Y_UNIT_TEST(PrivateCounterIsNotSerialized) { + TBuffer result; + Data.GetCounter("foo", false, TCountableBase::EVisibility::Private); + + { + TBufferOutput out(result); + auto encoder = CreateEncoder(&out, EFormat::SPACK); + Data.Accept(TString(), TString(), *encoder); + } + + NProto::TSingleSamplesList samples; + { + auto e = EncoderProtobuf(&samples); + TBufferInput in(result); + DecodeSpackV1(&in, e.Get()); + } + + AssertResult(samples); + } + + Y_UNIT_TEST(ToJson) { + TString result = ToJson(Data); + + NProto::TSingleSamplesList samples; + { + auto e = EncoderProtobuf(&samples); + DecodeJson(result, e.Get()); + } + + AssertResult(samples); + } + } + +} diff --git a/library/cpp/monlib/dynamic_counters/golovan_page.cpp b/library/cpp/monlib/dynamic_counters/golovan_page.cpp new file mode 100644 index 0000000000..49cf2d39bb --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/golovan_page.cpp @@ -0,0 +1,79 @@ +#include "golovan_page.h" + +#include <library/cpp/monlib/service/pages/templates.h> + +#include <util/string/split.h> +#include <util/system/tls.h> + +using namespace NMonitoring; + +class TGolovanCountableConsumer: public ICountableConsumer { +public: + using TOutputCallback = std::function<void()>; + + TGolovanCountableConsumer(IOutputStream& out, TOutputCallback& OutputCallback) + : out(out) + { + if (OutputCallback) { + OutputCallback(); + } + + out << HTTPOKJSON << "["; + FirstCounter = true; + } + + void OnCounter(const TString&, const TString& value, const TCounterForPtr* counter) override { + if (FirstCounter) { + FirstCounter = false; + } else { + out << ","; + } + + out << "[\"" << prefix + value; + if (counter->ForDerivative()) { + out << "_dmmm"; + } else { + out << "_ahhh"; + } + + out << "\"," << counter->Val() << "]"; + } + + void OnHistogram(const TString&, const TString&, IHistogramSnapshotPtr, bool) override { + } + + void OnGroupBegin(const TString&, const TString& value, const TDynamicCounters*) override { + prefix += value; + if (!value.empty()) { + prefix += "_"; + } + } + + void OnGroupEnd(const TString&, const TString&, const TDynamicCounters*) override { + prefix = ""; + } + + void Flush() { + out << "]"; + out.Flush(); + } + +private: + IOutputStream& out; + bool FirstCounter; + TString prefix; +}; + +TGolovanCountersPage::TGolovanCountersPage(const TString& path, TIntrusivePtr<NMonitoring::TDynamicCounters> counters, + TOutputCallback outputCallback) + : IMonPage(path) + , Counters(counters) + , OutputCallback(outputCallback) +{ +} + +void TGolovanCountersPage::Output(IMonHttpRequest& request) { + TGolovanCountableConsumer consumer(request.Output(), OutputCallback); + Counters->Accept("", "", consumer); + consumer.Flush(); +} diff --git a/library/cpp/monlib/dynamic_counters/golovan_page.h b/library/cpp/monlib/dynamic_counters/golovan_page.h new file mode 100644 index 0000000000..e1772c7734 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/golovan_page.h @@ -0,0 +1,25 @@ +#pragma once + +#include "counters.h" + +#include <library/cpp/monlib/service/pages/mon_page.h> + +#include <util/generic/ptr.h> + +#include <functional> + +// helper class to output json for Golovan. +class TGolovanCountersPage: public NMonitoring::IMonPage { +public: + using TOutputCallback = std::function<void()>; + + const TIntrusivePtr<NMonitoring::TDynamicCounters> Counters; + + TGolovanCountersPage(const TString& path, TIntrusivePtr<NMonitoring::TDynamicCounters> counters, + TOutputCallback outputCallback = nullptr); + + void Output(NMonitoring::IMonHttpRequest& request) override; + +private: + TOutputCallback OutputCallback; +}; diff --git a/library/cpp/monlib/dynamic_counters/page.cpp b/library/cpp/monlib/dynamic_counters/page.cpp new file mode 100644 index 0000000000..5124a47bb3 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/page.cpp @@ -0,0 +1,141 @@ +#include "page.h" +#include "encode.h" + +#include <library/cpp/monlib/service/pages/templates.h> +#include <library/cpp/string_utils/quote/quote.h> + +#include <util/string/split.h> +#include <util/system/tls.h> + +using namespace NMonitoring; + +namespace { + Y_POD_STATIC_THREAD(TDynamicCounters*) + currentCounters(nullptr); +} + +TMaybe<EFormat> ParseFormat(TStringBuf str) { + if (str == TStringBuf("json")) { + return EFormat::JSON; + } else if (str == TStringBuf("spack")) { + return EFormat::SPACK; + } else if (str == TStringBuf("prometheus")) { + return EFormat::PROMETHEUS; + } else { + return Nothing(); + } +} + +void TDynamicCountersPage::Output(NMonitoring::IMonHttpRequest& request) { + if (OutputCallback) { + OutputCallback(); + } + + TCountableBase::EVisibility visibility{ + TCountableBase::EVisibility::Public + }; + + TVector<TStringBuf> parts; + StringSplitter(request.GetPathInfo()) + .Split('/') + .SkipEmpty() + .Collect(&parts); + + TMaybe<EFormat> format = !parts.empty() ? ParseFormat(parts.back()) : Nothing(); + if (format) { + parts.pop_back(); + } + + if (!parts.empty() && parts.back() == TStringBuf("private")) { + visibility = TCountableBase::EVisibility::Private; + parts.pop_back(); + } + + auto counters = Counters; + + for (const auto& escaped : parts) { + const auto part = CGIUnescapeRet(escaped); + + TVector<TString> labels; + StringSplitter(part).Split('=').SkipEmpty().Collect(&labels); + + if (labels.size() != 2U) + return NotFound(request); + + if (const auto child = counters->FindSubgroup( + labels.front(), + labels.back())) { + + counters = child; + } else { + return HandleAbsentSubgroup(request); + } + } + + if (!format) { + currentCounters = counters.Get(); + THtmlMonPage::Output(request); + currentCounters = nullptr; + return; + } + + IOutputStream& out = request.Output(); + if (*format == EFormat::JSON) { + out << HTTPOKJSON; + } else if (*format == EFormat::SPACK) { + out << HTTPOKSPACK; + } else if (*format == EFormat::PROMETHEUS) { + out << HTTPOKPROMETHEUS; + } else { + ythrow yexception() << "unsupported metric encoding format: " << *format; + } + + auto encoder = CreateEncoder(&out, *format, visibility); + counters->Accept(TString(), TString(), *encoder); + out.Flush(); +} + +void TDynamicCountersPage::HandleAbsentSubgroup(IMonHttpRequest& request) { + if (UnknownGroupPolicy == EUnknownGroupPolicy::Error) { + NotFound(request); + } else if (UnknownGroupPolicy == EUnknownGroupPolicy::Ignore) { + NoContent(request); + } else { + Y_FAIL("Unsupported policy set"); + } +} + +void TDynamicCountersPage::BeforePre(IMonHttpRequest& request) { + IOutputStream& out = request.Output(); + HTML(out) { + DIV() { + out << "<a href='" << request.GetPath() << "/json'>Counters as JSON</a>"; + out << " for <a href='https://wiki.yandex-team.ru/solomon/'>Solomon</a>"; + } + + H5() { + out << "Counters subgroups"; + } + UL() { + currentCounters->EnumerateSubgroups([&](const TString& name, const TString& value) { + LI() { + TString pathPart = name + "=" + value; + Quote(pathPart, ""); + out << "\n<a href='" << request.GetPath() << "/" << pathPart << "'>" << name << " " << value << "</a>"; + } + }); + } + + H4() { + out << "Counters as text"; + } + } +} + +void TDynamicCountersPage::OutputText(IOutputStream& out, IMonHttpRequest&) { + currentCounters->OutputPlainText(out); +} + +void TDynamicCountersPage::SetUnknownGroupPolicy(EUnknownGroupPolicy value) { + UnknownGroupPolicy = value; +} diff --git a/library/cpp/monlib/dynamic_counters/page.h b/library/cpp/monlib/dynamic_counters/page.h new file mode 100644 index 0000000000..1f0ef6a5ea --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/page.h @@ -0,0 +1,50 @@ +#pragma once + +#include "counters.h" + +#include <library/cpp/monlib/service/pages/pre_mon_page.h> + +#include <util/generic/ptr.h> + +#include <functional> + +namespace NMonitoring { + enum class EUnknownGroupPolicy { + Error, // send 404 + Ignore, // send 204 + }; + + struct TDynamicCountersPage: public TPreMonPage { + public: + using TOutputCallback = std::function<void()>; + + private: + const TIntrusivePtr<TDynamicCounters> Counters; + TOutputCallback OutputCallback; + EUnknownGroupPolicy UnknownGroupPolicy {EUnknownGroupPolicy::Error}; + + private: + void HandleAbsentSubgroup(IMonHttpRequest& request); + + public: + TDynamicCountersPage(const TString& path, + const TString& title, + TIntrusivePtr<TDynamicCounters> counters, + TOutputCallback outputCallback = nullptr) + : TPreMonPage(path, title) + , Counters(counters) + , OutputCallback(outputCallback) + { + } + + void Output(NMonitoring::IMonHttpRequest& request) override; + + void BeforePre(NMonitoring::IMonHttpRequest& request) override; + + void OutputText(IOutputStream& out, NMonitoring::IMonHttpRequest&) override; + + /// If set to Error, responds with 404 if the requested subgroup is not found. This is the default. + /// If set to Ignore, responds with 204 if the requested subgroup is not found + void SetUnknownGroupPolicy(EUnknownGroupPolicy value); + }; +} diff --git a/library/cpp/monlib/dynamic_counters/percentile/percentile.h b/library/cpp/monlib/dynamic_counters/percentile/percentile.h new file mode 100644 index 0000000000..73c482bce9 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/percentile/percentile.h @@ -0,0 +1,59 @@ +#pragma once + +#include "percentile_base.h" + +namespace NMonitoring { + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Percentile tracker for monitoring +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template <size_t BUCKET_SIZE, size_t BUCKET_COUNT, size_t FRAME_COUNT> +struct TPercentileTracker : public TPercentileBase { + TAtomic Items[BUCKET_COUNT]; + TAtomicBase Frame[FRAME_COUNT][BUCKET_COUNT]; + size_t CurrentFrame; + + TPercentileTracker() + : CurrentFrame(0) + { + for (size_t i = 0; i < BUCKET_COUNT; ++i) { + AtomicSet(Items[i], 0); + } + for (size_t frame = 0; frame < FRAME_COUNT; ++frame) { + for (size_t bucket = 0; bucket < BUCKET_COUNT; ++bucket) { + Frame[frame][bucket] = 0; + } + } + } + + void Increment(size_t value) { + AtomicIncrement(Items[Min((value + BUCKET_SIZE - 1) / BUCKET_SIZE, BUCKET_COUNT - 1)]); + } + + // shift frame (call periodically) + void Update() { + TVector<TAtomicBase> totals(BUCKET_COUNT); + totals.resize(BUCKET_COUNT); + TAtomicBase total = 0; + for (size_t i = 0; i < BUCKET_COUNT; ++i) { + TAtomicBase item = AtomicGet(Items[i]); + TAtomicBase prevItem = Frame[CurrentFrame][i]; + Frame[CurrentFrame][i] = item; + total += item - prevItem; + totals[i] = total; + } + + for (size_t i = 0; i < Percentiles.size(); ++i) { + TPercentile &percentile = Percentiles[i]; + auto threshold = (TAtomicBase)(percentile.first * (float)total); + threshold = Min(threshold, total); + auto it = LowerBound(totals.begin(), totals.end(), threshold); + size_t index = it - totals.begin(); + (*percentile.second) = index * BUCKET_SIZE; + } + CurrentFrame = (CurrentFrame + 1) % FRAME_COUNT; + } +}; + +} // NMonitoring diff --git a/library/cpp/monlib/dynamic_counters/percentile/percentile_base.h b/library/cpp/monlib/dynamic_counters/percentile/percentile_base.h new file mode 100644 index 0000000000..d3c825c43d --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/percentile/percentile_base.h @@ -0,0 +1,36 @@ +#pragma once + +#include <library/cpp/monlib/dynamic_counters/counters.h> + +#include <util/string/printf.h> + +namespace NMonitoring { + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Percentile tracker for monitoring +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct TPercentileBase : public TThrRefBase { + using TPercentile = std::pair<float, NMonitoring::TDynamicCounters::TCounterPtr>; + using TPercentiles = TVector<TPercentile>; + + TPercentiles Percentiles; + + void Initialize(const TIntrusivePtr<NMonitoring::TDynamicCounters> &counters, const TVector<float> &thresholds, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public) { + Percentiles.reserve(thresholds.size()); + for (size_t i = 0; i < thresholds.size(); ++i) { + Percentiles.emplace_back(thresholds[i], + counters->GetNamedCounter("percentile", Sprintf("%.1f", thresholds[i] * 100.f), false, visibility)); + } + } + + void Initialize(const TIntrusivePtr<NMonitoring::TDynamicCounters> &counters, TString group, TString subgroup, + TString name, const TVector<float> &thresholds, + TCountableBase::EVisibility visibility = TCountableBase::EVisibility::Public) { + auto subCounters = counters->GetSubgroup(group, subgroup)->GetSubgroup("sensor", name); + Initialize(subCounters, thresholds, visibility); + } +}; + +} // NMonitoring diff --git a/library/cpp/monlib/dynamic_counters/percentile/percentile_lg.h b/library/cpp/monlib/dynamic_counters/percentile/percentile_lg.h new file mode 100644 index 0000000000..0042cd9a6a --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/percentile/percentile_lg.h @@ -0,0 +1,182 @@ +#pragma once + +#include <library/cpp/containers/stack_vector/stack_vec.h> + +#include <util/generic/bitops.h> + +#include <cmath> + +#include "percentile_base.h" + +namespace NMonitoring { + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Percentile tracker for monitoring +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template <size_t BASE_BITS, size_t EXP_BITS, size_t FRAME_COUNT> +struct TPercentileTrackerLg : public TPercentileBase { + static constexpr size_t BUCKET_COUNT = size_t(1) << EXP_BITS; + static constexpr size_t BUCKET_SIZE = size_t(1) << BASE_BITS; + static constexpr size_t ITEMS_COUNT = BUCKET_COUNT * BUCKET_SIZE; + static constexpr size_t TRACKER_LIMIT = BUCKET_SIZE * ((size_t(1) << BUCKET_COUNT) - 1) + - (size_t(1) << (BUCKET_COUNT - 1)); + static constexpr size_t MAX_GRANULARITY = size_t(1) << (BUCKET_COUNT - 1); + + size_t Borders[BUCKET_COUNT]; + TAtomic Items[ITEMS_COUNT]; + TAtomicBase Frame[FRAME_COUNT][ITEMS_COUNT]; + size_t CurrentFrame; + + TPercentileTrackerLg() + : CurrentFrame(0) + { + Borders[0] = 0; + for (size_t i = 1; i < BUCKET_COUNT; ++i) { + Borders[i] = Borders[i-1] + (BUCKET_SIZE << (i - 1)); + } + for (size_t i = 0; i < ITEMS_COUNT; ++i) { + AtomicSet(Items[i], 0); + } + for (size_t frame = 0; frame < FRAME_COUNT; ++frame) { + for (size_t bucket = 0; bucket < ITEMS_COUNT; ++bucket) { + Frame[frame][bucket] = 0; + } + } + } + + size_t inline BucketIdxIf(size_t value) { + static_assert(BASE_BITS == 5, "if-based bucket calculation cannot be used if BASE_BITS != 5"); + size_t bucket_idx; + if (value < 8160) { + if (value < 480) { + if (value < 96) { + if (value < 32) { + bucket_idx = 0; + } else { + bucket_idx = 1; + } + } else { + if (value < 224) { + bucket_idx = 2; + } else { + bucket_idx = 3; + } + } + } else { + if (value < 2016) { + if (value < 992) { + bucket_idx = 4; + } else { + bucket_idx = 5; + } + } else { + if (value < 4064) { + bucket_idx = 6; + } else { + bucket_idx = 7; + } + } + } + } else { + if (value < 131040) { + if (value < 32736) { + if (value < 16352) { + bucket_idx = 8; + } else { + bucket_idx = 9; + } + } else { + if (value < 65504) { + bucket_idx = 10; + } else { + bucket_idx = 11; + } + } + } else { + if (value < 524256) { + if (value < 262112) { + bucket_idx = 12; + } else { + bucket_idx = 13; + } + } else { + if (value < 1048544) { + bucket_idx = 14; + } else { + bucket_idx = 15; + } + } + } + } + return Min(bucket_idx, BUCKET_COUNT - 1); + } + + size_t inline BucketIdxBinarySearch(size_t value) { + size_t l = 0; + size_t r = BUCKET_COUNT; + while (l < r - 1) { + size_t mid = (l + r) / 2; + if (value < Borders[mid]) { + r = mid; + } else { + l = mid; + } + } + return l; + } + + size_t inline BucketIdxMostSignificantBit(size_t value) { + size_t bucket_idx = MostSignificantBit(value + BUCKET_SIZE) - BASE_BITS; + return Min(bucket_idx, BUCKET_COUNT - 1); + } + + void Increment(size_t value) { + size_t bucket_idx = BucketIdxMostSignificantBit(value); + size_t inside_bucket_idx = (value - Borders[bucket_idx] + (1 << bucket_idx) - 1) >> bucket_idx; + size_t idx = bucket_idx * BUCKET_SIZE + inside_bucket_idx; + AtomicIncrement(Items[Min(idx, ITEMS_COUNT - 1)]); + } + + // Needed only for tests + size_t GetPercentile(float threshold) { + TStackVec<TAtomicBase, ITEMS_COUNT> totals(ITEMS_COUNT); + TAtomicBase total = 0; + for (size_t i = 0; i < ITEMS_COUNT; ++i) { + total += AtomicGet(Items[i]); + totals[i] = total; + } + TAtomicBase item_threshold = std::llround(threshold * (float)total); + item_threshold = Min(item_threshold, total); + auto it = LowerBound(totals.begin(), totals.end(), item_threshold); + size_t index = it - totals.begin(); + size_t bucket_idx = index / BUCKET_SIZE; + return Borders[bucket_idx] + ((index % BUCKET_SIZE) << bucket_idx); + } + + // shift frame (call periodically) + void Update() { + TStackVec<TAtomicBase, ITEMS_COUNT> totals(ITEMS_COUNT); + TAtomicBase total = 0; + for (size_t i = 0; i < ITEMS_COUNT; ++i) { + TAtomicBase item = AtomicGet(Items[i]); + TAtomicBase prevItem = Frame[CurrentFrame][i]; + Frame[CurrentFrame][i] = item; + total += item - prevItem; + totals[i] = total; + } + + for (size_t i = 0; i < Percentiles.size(); ++i) { + TPercentile &percentile = Percentiles[i]; + TAtomicBase threshold = std::llround(percentile.first * (float)total); + threshold = Min(threshold, total); + auto it = LowerBound(totals.begin(), totals.end(), threshold); + size_t index = it - totals.begin(); + size_t bucket_idx = index / BUCKET_SIZE; + (*percentile.second) = Borders[bucket_idx] + ((index % BUCKET_SIZE) << bucket_idx); + } + CurrentFrame = (CurrentFrame + 1) % FRAME_COUNT; + } +}; + +} // NMonitoring diff --git a/library/cpp/monlib/dynamic_counters/percentile/percentile_ut.cpp b/library/cpp/monlib/dynamic_counters/percentile/percentile_ut.cpp new file mode 100644 index 0000000000..6c8bb54ec9 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/percentile/percentile_ut.cpp @@ -0,0 +1,129 @@ +#include "percentile_lg.h" +#include <library/cpp/testing/unittest/registar.h> + +using namespace NMonitoring; + +Y_UNIT_TEST_SUITE(PercentileTest) { + +template<size_t A, size_t B, size_t B_BEGIN> +void printSizeAndLimit() { + using TPerc = TPercentileTrackerLg<A, B, 15>; + Cout << "TPercentileTrackerLg<" << A << ", " << B << ", 15>" + << "; sizeof# " << LeftPad(HumanReadableSize(sizeof(TPerc), SF_BYTES), 7) + << "; max_granularity# " << LeftPad(HumanReadableSize(TPerc::MAX_GRANULARITY, SF_QUANTITY), 5) + << "; limit# " << LeftPad(HumanReadableSize(TPerc::TRACKER_LIMIT , SF_QUANTITY), 5) << Endl; + if constexpr (B > 1) { + printSizeAndLimit<A, B - 1, B_BEGIN>(); + } else if constexpr (A > 1) { + Cout << Endl; + printSizeAndLimit<A - 1, B_BEGIN, B_BEGIN>(); + } +} + + Y_UNIT_TEST(PrintTrackerLgSizeAndLimits) { + printSizeAndLimit<10, 5, 5>(); + } + + Y_UNIT_TEST(TrackerLimitTest) { + { + using TPerc = TPercentileTrackerLg<1, 0, 1>; + TPerc tracker; + tracker.Increment(Max<size_t>()); + UNIT_ASSERT_EQUAL(TPerc::TRACKER_LIMIT, tracker.GetPercentile(1.0)); + } + { + using TPerc = TPercentileTrackerLg<1, 1, 1>; + TPerc tracker; + tracker.Increment(Max<size_t>()); + UNIT_ASSERT_EQUAL(TPerc::TRACKER_LIMIT, tracker.GetPercentile(1.0)); + } + { + using TPerc = TPercentileTrackerLg<1, 5, 1>; + TPerc tracker; + tracker.Increment(Max<size_t>()); + UNIT_ASSERT_EQUAL(TPerc::TRACKER_LIMIT, tracker.GetPercentile(1.0)); + } + { + using TPerc = TPercentileTrackerLg<2, 1, 1>; + TPerc tracker; + tracker.Increment(Max<size_t>()); + UNIT_ASSERT_EQUAL(TPerc::TRACKER_LIMIT, tracker.GetPercentile(1.0)); + } + { + using TPerc = TPercentileTrackerLg<5, 4, 1>; + TPerc tracker; + tracker.Increment(Max<size_t>()); + UNIT_ASSERT_EQUAL(TPerc::TRACKER_LIMIT, tracker.GetPercentile(1.0)); + } + } + + Y_UNIT_TEST(BucketIdxIfvsBucketIdxBinarySearch) { + for (size_t var = 0; var < 5; var++) { + if (var == 0) { + TPercentileTrackerLg<3, 2, 15> tracker; + for (size_t i = 0; i < 3000000; i += 1) { + size_t num1 = tracker.BucketIdxMostSignificantBit(i); + size_t num2 = tracker.BucketIdxBinarySearch(i); + UNIT_ASSERT_EQUAL(num1, num2); + } + } else if (var == 1) { + TPercentileTrackerLg<4, 4, 15> tracker; + for (size_t i = 0; i < 3000000; i += 1) { + size_t num1 = tracker.BucketIdxMostSignificantBit(i); + size_t num2 = tracker.BucketIdxBinarySearch(i); + UNIT_ASSERT_EQUAL(num1, num2); + } + } else if (var == 2) { + TPercentileTrackerLg<5, 3, 15> tracker; + for (size_t i = 0; i < 3000000; i += 1) { + size_t num1 = tracker.BucketIdxMostSignificantBit(i); + size_t num2 = tracker.BucketIdxBinarySearch(i); + size_t num3 = tracker.BucketIdxIf(i); + UNIT_ASSERT_EQUAL(num1, num2); + UNIT_ASSERT_EQUAL(num2, num3); + } + } else if (var == 3) { + TPercentileTrackerLg<5, 4, 15> tracker; + for (size_t i = 0; i < 3000000; i += 1) { + size_t num1 = tracker.BucketIdxMostSignificantBit(i); + size_t num2 = tracker.BucketIdxBinarySearch(i); + size_t num3 = tracker.BucketIdxIf(i); + UNIT_ASSERT_EQUAL(num1, num2); + UNIT_ASSERT_EQUAL(num2, num3); + } + } else if (var == 4) { + TPercentileTrackerLg<6, 5, 15> tracker; + for (size_t i = 0; i < 3000000; i += 1) { + size_t num1 = tracker.BucketIdxMostSignificantBit(i); + size_t num2 = tracker.BucketIdxBinarySearch(i); + UNIT_ASSERT_EQUAL(num1, num2); + } + for (size_t i = 0; i < 400000000000ul; i += 1303) { + size_t num1 = tracker.BucketIdxMostSignificantBit(i); + size_t num2 = tracker.BucketIdxBinarySearch(i); + UNIT_ASSERT_EQUAL(num1, num2); + } + } + } + } + + Y_UNIT_TEST(DifferentPercentiles) { + TPercentileTrackerLg<5, 4, 15> tracker; + TVector<size_t> values({0, 115, 1216, 15, 3234567, 1234567, 216546, 263421, 751654, 96, 224, 223, 225}); + TVector<size_t> percentiles50({0, 0, 116, 15, 116, 116, 1216, 1216, 217056, 1216, 1216, 224, 232}); + TVector<size_t> percentiles75({0, 116, 116, 116, 1216, 1245152, 217056, 270304, 753632, 753632, + 270304, 270304, 270304}); + TVector<size_t> percentiles90({ 0, 116, 1216, 1216, 2064352, 1245152, 1245152, 1245152, 1245152, + 1245152, 1245152, 1245152, 1245152}); + TVector<size_t> percentiles100({ 0, 116, 1216, 1216, 2064352, 2064352, 2064352, 2064352, 2064352, + 2064352, 2064352, 2064352, 2064352 }); + + for (size_t i = 0; i < values.size(); ++i) { + tracker.Increment(values[i]); + UNIT_ASSERT_EQUAL(tracker.GetPercentile(0.5), percentiles50[i]); + UNIT_ASSERT_EQUAL(tracker.GetPercentile(0.75), percentiles75[i]); + UNIT_ASSERT_EQUAL(tracker.GetPercentile(0.90), percentiles90[i]); + UNIT_ASSERT_EQUAL(tracker.GetPercentile(1.0), percentiles100[i]); + } + } +} diff --git a/library/cpp/monlib/dynamic_counters/percentile/ut/ya.make b/library/cpp/monlib/dynamic_counters/percentile/ut/ya.make new file mode 100644 index 0000000000..f9f3564101 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/percentile/ut/ya.make @@ -0,0 +1,9 @@ +UNITTEST_FOR(library/cpp/monlib/dynamic_counters/percentile) + + OWNER(alexvru g:kikimr g:solomon) + + SRCS( + percentile_ut.cpp + ) + +END() diff --git a/library/cpp/monlib/dynamic_counters/percentile/ya.make b/library/cpp/monlib/dynamic_counters/percentile/ya.make new file mode 100644 index 0000000000..cb52cdd9ad --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/percentile/ya.make @@ -0,0 +1,15 @@ +LIBRARY() + + OWNER(alexvru g:kikimr g:solomon) + + SRCS( + percentile.h + percentile_lg.h + ) + + PEERDIR( + library/cpp/containers/stack_vector + library/cpp/monlib/dynamic_counters + ) + +END() diff --git a/library/cpp/monlib/dynamic_counters/ut/ya.make b/library/cpp/monlib/dynamic_counters/ut/ya.make new file mode 100644 index 0000000000..8242f2fe30 --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/ut/ya.make @@ -0,0 +1,16 @@ +UNITTEST_FOR(library/cpp/monlib/dynamic_counters) + +OWNER(jamel) + +SRCS( + contention_ut.cpp + counters_ut.cpp + encode_ut.cpp +) + +PEERDIR( + library/cpp/monlib/encode/protobuf + library/cpp/monlib/encode/json +) + +END() diff --git a/library/cpp/monlib/dynamic_counters/ya.make b/library/cpp/monlib/dynamic_counters/ya.make new file mode 100644 index 0000000000..aafe1c34be --- /dev/null +++ b/library/cpp/monlib/dynamic_counters/ya.make @@ -0,0 +1,27 @@ +LIBRARY() + +OWNER( + g:solomon + jamel +) + +NO_WSHADOW() + +SRCS( + counters.cpp + encode.cpp + golovan_page.cpp + page.cpp +) + +PEERDIR( + library/cpp/containers/stack_vector + library/cpp/monlib/encode/json + library/cpp/monlib/encode/spack + library/cpp/monlib/encode/prometheus + library/cpp/monlib/service/pages + library/cpp/string_utils/quote + library/cpp/threading/light_rw_lock +) + +END() |