diff options
author | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
---|---|---|
committer | qrort <qrort@yandex-team.com> | 2022-11-30 23:47:12 +0300 |
commit | 22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch) | |
tree | bffa27765faf54126ad44bcafa89fadecb7a73d7 /library/cpp/unistat | |
parent | 332b99e2173f0425444abb759eebcb2fafaa9209 (diff) | |
download | ydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz |
validate canons without yatest_common
Diffstat (limited to 'library/cpp/unistat')
-rw-r--r-- | library/cpp/unistat/idl/stats.proto | 19 | ||||
-rw-r--r-- | library/cpp/unistat/raii.cpp | 1 | ||||
-rw-r--r-- | library/cpp/unistat/raii.h | 120 | ||||
-rw-r--r-- | library/cpp/unistat/types.h | 15 | ||||
-rw-r--r-- | library/cpp/unistat/unistat.cpp | 461 | ||||
-rw-r--r-- | library/cpp/unistat/unistat.h | 382 |
6 files changed, 998 insertions, 0 deletions
diff --git a/library/cpp/unistat/idl/stats.proto b/library/cpp/unistat/idl/stats.proto new file mode 100644 index 00000000000..10a63ec8a2a --- /dev/null +++ b/library/cpp/unistat/idl/stats.proto @@ -0,0 +1,19 @@ +message TInstanceStats { + message THistogram { + message TBucket { + required double Boundary = 1; + required uint64 Weight = 2; + } + repeated TBucket Bucket = 1; + } + + message TMetric { + required string Name = 1; + oneof value { + double Number = 2; + THistogram Hgram = 3; + } + } + + repeated TMetric Metric = 1; +} diff --git a/library/cpp/unistat/raii.cpp b/library/cpp/unistat/raii.cpp new file mode 100644 index 00000000000..c06a2e05c72 --- /dev/null +++ b/library/cpp/unistat/raii.cpp @@ -0,0 +1 @@ +#include "raii.h" diff --git a/library/cpp/unistat/raii.h b/library/cpp/unistat/raii.h new file mode 100644 index 00000000000..f8f46745fe2 --- /dev/null +++ b/library/cpp/unistat/raii.h @@ -0,0 +1,120 @@ +#pragma once + +#include "unistat.h" + +#include <util/datetime/base.h> +#include <util/generic/noncopyable.h> +#include <util/generic/va_args.h> +#include <util/generic/yexception.h> +#include <util/system/defaults.h> + +class TUnistatTimer: public TNonCopyable { +public: + template <typename T> + TUnistatTimer(TUnistat& unistat, T&& holename) + : Started_(Now()) + , HoleName_(ToString(holename)) + , Aggregator_(unistat) + { + } + + ~TUnistatTimer() { + if (!Dismiss_) { + Aggregator_.PushSignalUnsafe(HoleName_, (Now() - Started_).MillisecondsFloat()); + } + } + + void Dismiss() noexcept { + Dismiss_ = true; + } + + void Accept() noexcept { + Dismiss_ = false; + } + +private: + bool Dismiss_{false}; + const TInstant Started_; + const TString HoleName_; + TUnistat& Aggregator_; +}; + +class TUnistatExceptionCounter: public TNonCopyable { +public: + template <typename T, typename U> + TUnistatExceptionCounter(TUnistat& unistat, T&& hasExceptionHolename, U&& noExceptionHolename) + : HasExceptionHoleName_(ToString(hasExceptionHolename)) + , NoExceptionHoleName_(ToString(noExceptionHolename)) + , Aggregator_(unistat) + { + } + + template <typename T> + TUnistatExceptionCounter(TUnistat& unistat, T&& hasExceptionHolename) + : HasExceptionHoleName_(ToString(hasExceptionHolename)) + , Aggregator_(unistat) + { + } + + ~TUnistatExceptionCounter() { + if (!Dismiss_) { + if (UncaughtException()) { + Aggregator_.PushSignalUnsafe(HasExceptionHoleName_, 1.); + } else if (NoExceptionHoleName_) { + Aggregator_.PushSignalUnsafe(NoExceptionHoleName_, 1.); + } + } + } + + void Dismiss() noexcept { + Dismiss_ = true; + } + + void Accept() noexcept { + Dismiss_ = false; + } + +private: + bool Dismiss_{false}; + const TString HasExceptionHoleName_; + const TString NoExceptionHoleName_; + TUnistat& Aggregator_; +}; + +/** + * @def Y_UNISTAT_TIMER + * + * Macro is needed to time scope and push time into aggregator. + * + * @code + * void DoSomethingImportant() { + * Y_UNISTAT_TIMER(TUnistat::Instance(), "doing-important-stuff") + * // doing something here + * } + * @endcode + */ +#define Y_UNISTAT_TIMER(unistat, holeName) \ + ::TUnistatTimer Y_GENERATE_UNIQUE_ID(timer){unistat, holeName}; + +#define Y_UNISTAT_EXCEPTION_COUNTER_IMPL_2(unistat, hasExceptionHoleName) \ + ::TUnistatExceptionCounter Y_GENERATE_UNIQUE_ID(exceptionCounter){unistat, hasExceptionHoleName}; + +#define Y_UNISTAT_EXCEPTION_COUNTER_IMPL_3(unistat, hasExceptionHolename, noExceptionHolename) \ + ::TUnistatExceptionCounter Y_GENERATE_UNIQUE_ID(exceptionCounter){unistat, hasExceptionHolename, noExceptionHolename}; + +#define Y_UNISTAT_EXCEPTION_COUNTER_IMPL_DISPATCHER(_1, _2, _3, NAME, ...) NAME + +/** + * @def Y_UNISTAT_EXCEPTION_COUNTER + * + * Macro is needed to check if there was an exception on scope exit or not. + * + * @code + * void DoSomethingThatMayThrowException() { + * Y_UNISTAT_EXCEPTION_COUNTER(TUnistat::Instance(), "exception_occured", "no_exception") + * Y_UNISTAT_EXCEPTION_COUNTER(TUnistat::Instance(), "wow_exception_occured") + * // doing something here + * } + * @endcode + */ +#define Y_UNISTAT_EXCEPTION_COUNTER(...) Y_PASS_VA_ARGS(Y_UNISTAT_EXCEPTION_COUNTER_IMPL_DISPATCHER(__VA_ARGS__, Y_UNISTAT_EXCEPTION_COUNTER_IMPL_3, Y_UNISTAT_EXCEPTION_COUNTER_IMPL_2)(__VA_ARGS__)) diff --git a/library/cpp/unistat/types.h b/library/cpp/unistat/types.h new file mode 100644 index 00000000000..f4346c95f6d --- /dev/null +++ b/library/cpp/unistat/types.h @@ -0,0 +1,15 @@ +#pragma once + +#include <util/generic/string.h> + +enum class EAggregationType { + Average /* "aver" Брать среднее среди всех полученных хостовых значений */, + HostHistogram /* "hgram" Выполнять слияние всех полученных хостовых значений в гистограмму */, + Max /* "max" Брать максимальное среди всех полученных хостовых значений */, + Min /* "min" Брать минимальное среди всех полученных хостовых значений */, + Sum /* "summ" Брать сумму всех полученных хостовых значений */, + SumOne /* "sumone" summ c None для отсутсвия сигналов */, + LastValue /* "trnsp" Брать последнее среди всех полученных хостовых значений */ +}; + +const TString& ToString(EAggregationType x); diff --git a/library/cpp/unistat/unistat.cpp b/library/cpp/unistat/unistat.cpp new file mode 100644 index 00000000000..21bf49ff012 --- /dev/null +++ b/library/cpp/unistat/unistat.cpp @@ -0,0 +1,461 @@ +#include "unistat.h" +#include <util/generic/strbuf.h> + +using namespace NUnistat; + +TIntervalsBuilder& TIntervalsBuilder::Append(double value) { + Y_ENSURE(Intervals_.empty() || Intervals_.back() < value); + Intervals_.push_back(value); + return *this; +} + +TIntervalsBuilder& TIntervalsBuilder::AppendFlat(size_t count, double step) { + Y_ENSURE(Intervals_.size() > 0 && step > 0); + double x = Intervals_.back(); + for (size_t i = 0; i < count; ++i) { + x += step; + Intervals_.push_back(x); + } + return *this; +} + +TIntervalsBuilder& TIntervalsBuilder::AppendExp(size_t count, double base) { + Y_ENSURE(Intervals_.size() > 0 && base > 1); + double x = Intervals_.back(); + for (size_t i = 0; i < count; ++i) { + x *= base; + Intervals_.push_back(x); + } + return *this; +} + +TIntervalsBuilder& TIntervalsBuilder::FlatRange(size_t count, double start, double stop) { + Y_ENSURE(count > 0 && start < stop); + Append(start); + AppendFlat(count - 1, (stop - start) / count); + return *this; +} + +TIntervalsBuilder& TIntervalsBuilder::ExpRange(size_t count, double start, double stop) { + Y_ENSURE(count > 0 && start < stop); + Append(start); + AppendExp(count - 1, std::pow(stop / start, 1. / count)); + return *this; +} + +TIntervals TIntervalsBuilder::Build() { + Y_ENSURE(Intervals_.size() <= MAX_HISTOGRAM_SIZE); + Y_ENSURE(IsSorted(Intervals_.begin(), Intervals_.end())); + return std::move(Intervals_); +} + +IHolePtr TUnistat::DrillFloatHole(const TString& name, const TString& suffix, TPriority priority, TStartValue startValue, EAggregationType type, bool alwaysVisible) { + return DrillFloatHole(name, "", suffix, priority, startValue, type, alwaysVisible); +} + +IHolePtr TUnistat::DrillFloatHole(const TString& name, const TString& description, const TString& suffix, TPriority priority, TStartValue startValue, EAggregationType type, bool alwaysVisible) { + { + TReadGuard guard(Mutex); + IHolePtr* exhole = Holes.FindPtr(name); + if (exhole) { + return *exhole; + } + } + { + TWriteGuard guard(Mutex); + IHolePtr* exhole = Holes.FindPtr(name); + if (exhole) { + return *exhole; + } + IHolePtr hole = new TFloatHole(name, description, suffix, type, priority.Priority, startValue.StartValue, alwaysVisible); + for (const auto& tag : GlobalTags) { + hole->AddTag(tag.first, tag.second); + } + Holes[name] = hole; + HolesByPriorityAndTags.insert(hole.Get()); + return hole; + } +} + +IHolePtr TUnistat::DrillHistogramHole(const TString& name, const TString& suffix, TPriority priority, const NUnistat::TIntervals& intervals, EAggregationType type, bool alwaysVisible) { + return DrillHistogramHole(name, "", suffix, priority, intervals, type, alwaysVisible); +} + +IHolePtr TUnistat::DrillHistogramHole(const TString& name, const TString& description, const TString& suffix, TPriority priority, const NUnistat::TIntervals& intervals, EAggregationType type, bool alwaysVisible) { + { + TReadGuard guard(Mutex); + IHolePtr* exhole = Holes.FindPtr(name); + if (exhole) { + return *exhole; + } + } + { + TWriteGuard guard(Mutex); + IHolePtr* exhole = Holes.FindPtr(name); + if (exhole) { + return *exhole; + } + IHolePtr hole = new THistogramHole(name, description, suffix, type, priority.Priority, intervals, alwaysVisible); + for (const auto& tag : GlobalTags) { + hole->AddTag(tag.first, tag.second); + } + Holes[name] = hole; + HolesByPriorityAndTags.insert(hole.Get()); + return hole; + } +} + +void TUnistat::AddGlobalTag(const TString& tagName, const TString& tagValue) { + TWriteGuard guard(Mutex); + GlobalTags[tagName] = tagValue; + for (IHole* hole : HolesByPriorityAndTags) { + hole->AddTag(tagName, tagValue); + } +} + +bool TUnistat::PushSignalUnsafeImpl(const TStringBuf holename, double signal) { + IHolePtr* hole = Holes.FindPtr(holename); + if (!hole) { + return false; + } + + (*hole)->PushSignal(signal); + return true; +} + +TMaybe<TInstanceStats::TMetric> TUnistat::GetSignalValueUnsafeImpl(const TStringBuf holename) { + IHolePtr* hole = Holes.FindPtr(holename); + if (!hole) { + return Nothing(); + } + + TInstanceStats stat; + (*hole)->ExportToProto(stat); + if (!stat.GetMetric().size()) { + return Nothing(); + } + return std::move(*stat.MutableMetric(0)); +} + +bool TUnistat::ResetSignalUnsafeImpl(const TStringBuf holename) { + IHolePtr* hole = Holes.FindPtr(holename); + if (!hole) { + return false; + } + + (*hole)->ResetSignal(); + return true; +} + +TString TUnistat::CreateInfoDump(int level) const { + TReadGuard guard(Mutex); + NJsonWriter::TBuf json; + json.BeginObject(); + for (IHole* hole : HolesByPriorityAndTags) { + if (hole->GetPriority() < level) { + break; + } + hole->PrintInfo(json); + } + json.EndObject(); + return json.Str(); +} + +TString TUnistat::CreateJsonDump(int level, bool allHoles) const { + TReadGuard guard(Mutex); + NJsonWriter::TBuf json; + json.BeginList(); + bool filterZeroHoles = !allHoles && level >= 0; + for (IHole* hole : HolesByPriorityAndTags) { + if (hole->GetPriority() < level) { + break; + } + hole->PrintValue(json, filterZeroHoles); + } + json.EndList(); + return json.Str(); +} + +TString TUnistat::CreatePushDump(int level, const NUnistat::TTags& tags, ui32 ttl, bool allHoles) const { + TReadGuard guard(Mutex); + NJsonWriter::TBuf json; + json.BeginList(); + bool filterZeroHoles = !allHoles && level >= 0; + int prevPriority = Max<int>(); + TString prevTagsStr; + for (IHole* hole : HolesByPriorityAndTags) { + int priority = hole->GetPriority(); + TString tagStr = hole->GetTagsStr(); + if (priority < level) { + break; + } + if (prevPriority != priority || prevTagsStr != tagStr) { + if (prevPriority != Max<int>()) { + json.EndList(); + json.EndObject(); + } + json.BeginObject(); + if (ttl) { + json.WriteKey("ttl").WriteULongLong(ttl); + } + json.WriteKey("tags").BeginObject() + .WriteKey("_priority").WriteInt(hole->GetPriority()); + for (const auto& tag : tags) { + json.WriteKey(tag.first); + json.WriteString(tag.second); + } + for (const auto& tag : hole->GetTags()) { + json.WriteKey(tag.first); + json.WriteString(tag.second); + } + json.EndObject(); + json.WriteKey("values").BeginList(); + prevPriority = priority; + prevTagsStr = tagStr; + } + hole->PrintToPush(json, filterZeroHoles); + } + if (prevPriority != Max<int>()) { + json.EndList(); + json.EndObject(); + } + json.EndList(); + return json.Str(); +} + +void TUnistat::ExportToProto(TInstanceStats& stats, int level) const { + TReadGuard guard(Mutex); + + for (IHole* hole : HolesByPriorityAndTags) { + if (hole->GetPriority() < level) { + break; + } + hole->ExportToProto(stats); + } +} + +TString TUnistat::GetSignalDescriptions() const { + TReadGuard guard(Mutex); + NJsonWriter::TBuf json; + json.BeginObject(); + + for (IHole* hole : HolesByPriorityAndTags) { + json.WriteKey(hole->GetName()); + json.WriteString(hole->GetDescription()); + } + + json.EndObject(); + return json.Str(); +} + +TVector<TString> TUnistat::GetHolenames() const { + TReadGuard guard(Mutex); + + TVector<TString> holenames; + for (const auto& [holeName, holeData] : Holes) { + holenames.emplace_back(holeName); + } + + return holenames; +} + +bool TUnistat::EraseHole(const TString& name) { + TWriteGuard guard(Mutex); + auto holeIterator = Holes.find(name); + if (holeIterator == Holes.end()) { + return false; + } + + HolesByPriorityAndTags.erase(holeIterator->second.Get()); + Holes.erase(holeIterator); + return true; +} + +void TFloatHole::PrintInfo(NJsonWriter::TBuf& json) const { + TValue value; + value.Atomic = AtomicGet(Value.Atomic); + json.WriteKey(Name) + .BeginObject() + .WriteKey(TStringBuf("Priority")) + .WriteInt(Priority) + .WriteKey(TStringBuf("Value")) + .WriteDouble(value.Value) + .WriteKey(TStringBuf("Type")) + .WriteString(ToString(Type)) + .WriteKey(TStringBuf("Suffix")) + .WriteString(Suffix) + .WriteKey(TStringBuf("Tags")) + .WriteString(GetTagsStr()) + .EndObject(); +} + +void TFloatHole::PrintValue(NJsonWriter::TBuf& json, bool check) const { + if (check && !AtomicGet(Pushed)) { + return; + } + + TValue value; + value.Atomic = AtomicGet(Value.Atomic); + json.BeginList() + .WriteString(TString::Join(GetTagsStr(), Name, TStringBuf("_"), Suffix)) + .WriteDouble(value.Value) + .EndList(); +} + +void TFloatHole::PrintToPush(NJsonWriter::TBuf& json, bool check) const { + if (check && !AtomicGet(Pushed)) { + return; + } + + TValue value; + value.Atomic = AtomicGet(Value.Atomic); + json.BeginObject() + .WriteKey("name").WriteString(TString::Join(Name, TStringBuf("_"), Suffix)) + .WriteKey("val").WriteDouble(value.Value) + .EndObject(); +} + + +void TFloatHole::ExportToProto(TInstanceStats& stats) const { + if (!AtomicGet(Pushed)) { + return; + } + + TValue value; + value.Atomic = AtomicGet(Value.Atomic); + TInstanceStats::TMetric* metric = stats.AddMetric(); + metric->SetName(TString::Join(GetTagsStr(), Name, TStringBuf("_"), Suffix)); + metric->SetNumber(value.Value); +} + +void TFloatHole::PushSignal(double signal) { + AtomicSet(Pushed, 1); + TValue old, toset; + do { + old.Atomic = AtomicGet(Value.Atomic); + switch (Type) { + case EAggregationType::Max: + toset.Value = Max(signal, old.Value); + break; + case EAggregationType::Min: + toset.Value = Min(signal, old.Value); + break; + case EAggregationType::Sum: + toset.Value = signal + old.Value; + break; + case EAggregationType::LastValue: + toset.Value = signal; + break; + default: + assert(0); + } + } while (!AtomicCas(&Value.Atomic, toset.Atomic, old.Atomic)); +} + +void TFloatHole::ResetSignal() { + AtomicSet(Value.Atomic, 0); + AtomicSet(Pushed, 0); +} + +void THistogramHole::PrintInfo(NJsonWriter::TBuf& json) const { + json.WriteKey(Name) + .BeginObject() + .WriteKey(TStringBuf("Priority")) + .WriteInt(Priority) + .WriteKey(TStringBuf("Value")); + + PrintWeights(json); + + json.WriteKey(TStringBuf("Type")) + .WriteString(ToString(Type)); + json.WriteKey(TStringBuf("Suffix")) + .WriteString(Suffix); + json.WriteKey(TStringBuf("Tags")) + .WriteString(GetTagsStr()) + .EndObject(); +} + +void THistogramHole::PrintValue(NJsonWriter::TBuf& json, bool check) const { + if (check && !AtomicGet(Pushed)) { + return; + } + + json.BeginList() + .WriteString(TString::Join(GetTagsStr(), Name, TStringBuf("_"), Suffix)); + + PrintWeights(json); + + json.EndList(); +} + +void THistogramHole::PrintToPush(NJsonWriter::TBuf& json, bool check) const { + if (check && !AtomicGet(Pushed)) { + return; + } + + json.BeginObject() + .WriteKey("name").WriteString(TString::Join(Name, TStringBuf("_"), Suffix)) + .WriteKey("val"); + PrintWeights(json); + json.EndObject(); +} + +void THistogramHole::PrintWeights(NJsonWriter::TBuf& json) const { + json.BeginList(); + for (size_t i = 0, size = Weights.size(); i < size; ++i) { + json.BeginList() + .WriteDouble(Intervals[i]) + .WriteLongLong(AtomicGet(Weights[i])) + .EndList(); + } + json.EndList(); +} + +void THistogramHole::ExportToProto(TInstanceStats& stats) const { + if (!AtomicGet(Pushed)) { + return; + } + + TInstanceStats::TMetric* metric = stats.AddMetric(); + metric->SetName(TString::Join(GetTagsStr(), Name, TStringBuf("_"), Suffix)); + + TInstanceStats::THistogram* hgram = metric->MutableHgram(); + for (size_t i = 0, size = Weights.size(); i < size; ++i) { + TInstanceStats::THistogram::TBucket* bucket = hgram->AddBucket(); + bucket->SetBoundary(Intervals[i]); + bucket->SetWeight(AtomicGet(Weights[i])); + } +} + +void THistogramHole::PushSignal(double signal) { + AtomicSet(Pushed, 1); + + const size_t i = UpperBound(Intervals.cbegin(), Intervals.cend(), signal) - Intervals.cbegin(); // Intervals[i - 1] <= signal < Intervals[i] + if (i > 0) { + AtomicIncrement(Weights[i - 1]); + } +} + +void THistogramHole::ResetSignal() { + for (size_t i = 0; i < Intervals.size(); ++i) { + SetWeight(i, 0); + } + AtomicSet(Pushed, 0); +} + +void THistogramHole::SetWeight(ui32 index, TAtomicBase value) { + AtomicSet(Weights.at(index), value); +} + +void TUnistat::Reset() { + TWriteGuard guard(Mutex); + Holes.clear(); + HolesByPriorityAndTags.clear(); + GlobalTags.clear(); +} + +void TUnistat::ResetSignals() { + for (auto& hole : Holes) { + hole.second->ResetSignal(); + } +} diff --git a/library/cpp/unistat/unistat.h b/library/cpp/unistat/unistat.h new file mode 100644 index 00000000000..50acc0b30b5 --- /dev/null +++ b/library/cpp/unistat/unistat.h @@ -0,0 +1,382 @@ +#pragma once + +#include <util/generic/singleton.h> +#include <util/generic/string.h> +#include <library/cpp/deprecated/atomic/atomic.h> +#include <util/generic/ptr.h> +#include <util/generic/vector.h> +#include <util/generic/hash.h> +#include <util/generic/maybe.h> +#include <util/generic/set.h> +#include <util/generic/map.h> +#include <util/string/strip.h> +#include <util/system/rwlock.h> + +#include <library/cpp/json/writer/json.h> +#include <library/cpp/unistat/idl/stats.pb.h> + +#include <functional> + +#include "types.h" + +/* + Agregator of Search Statistics + see https://wiki.yandex-team.ru/jandekspoisk/sepe/monitoring/stat-handle + see https://st.yandex-team.ru/SEARCH-948 +*/ + +namespace NUnistat { + struct TPriority { + constexpr explicit TPriority(int priority) + : Priority(priority) + { + } + const int Priority; + }; + + struct TStartValue { + constexpr explicit TStartValue(double startValue) + : StartValue(startValue) + { + } + + const double StartValue; + }; + + class IHole { + public: + virtual int GetPriority() const = 0; + virtual TString GetName() const = 0; + virtual TString GetDescription() const = 0; + virtual TString GetTagsStr() const = 0; + virtual TMap<TString, TString> GetTags() const = 0; + virtual void PrintValue(NJsonWriter::TBuf& json, bool check = true) const = 0; + virtual void PrintInfo(NJsonWriter::TBuf& json) const = 0; + virtual void PrintToPush(NJsonWriter::TBuf& json, bool check = true) const = 0; + virtual void ExportToProto(TInstanceStats& stats) const = 0; + virtual void PushSignal(double signal) = 0; + virtual void ResetSignal() = 0; + virtual void AddTag(const TString& tagName, const TString& tagValue) = 0; + virtual ~IHole() = default; + }; + + class TBaseHole: public IHole { + public: + void AddTag(const TString& tagName, const TString& tagValue) override { + TWriteGuard guard(Mutex); + Tags[tagName] = tagValue; + RebuildTagString(); + } + + protected: + TBaseHole(const TString& name, const TString& description, const TString& suffix, int priority) + : Name(name) + , Description(description) + , Suffix(suffix) + , Priority(priority) + + { + ParseTagsFromName(name); + } + + int GetPriority() const override { + return Priority; + } + + TString GetName() const override { + return Name; + } + + TString GetDescription() const override { + return Description; + } + + TString GetTagsStr() const override { + TReadGuard guard(Mutex); + return TagsJoined; + } + + TMap<TString, TString> GetTags() const override { + TReadGuard guard(Mutex); + return Tags; + } + + TString Name; + const TString Description; + const TString Suffix; + int Priority = 1; + + TRWMutex Mutex; + TMap<TString, TString> Tags; + TString TagsJoined; + + private: + void RebuildTagString() { + TagsJoined.clear(); + TStringOutput ss{TagsJoined}; + for (const auto& tag : Tags) { + ss << tag.first << "=" << tag.second << ";"; + } + } + + void ParseTagsFromName(TStringBuf name) { + TStringBuf tmp = name; + while (TStringBuf tag = tmp.NextTok(';')) { + TStringBuf name, value; + if (tag.TrySplit('=', name, value)) { + TStringBuf nameStr = StripString(name); + TStringBuf valueStr = StripString(value); + if (nameStr) { + Tags[ToString(nameStr)] = ToString(valueStr); + } + } + else { + Name = ToString(StripString(tag)); + } + } + + RebuildTagString(); + } + }; + + class TTypedHole: public TBaseHole { + protected: + TTypedHole(const TString& name, + const TString& description, + const TString& suffix, + EAggregationType type, + int priority, + bool alwaysVisible = false) + : TBaseHole(name, description, suffix, priority) + , Type(type) + , Pushed(alwaysVisible) + { + } + + const EAggregationType Type; + TAtomic Pushed; + }; + + class TFloatHole: public TTypedHole { + public: + TFloatHole(const TString& name, + const TString& description, + const TString& suffix, + EAggregationType type, + int priority, + double startValue, + bool alwaysVisible) + : TTypedHole(name, description, suffix, type, priority, alwaysVisible) + { + Value.Value = startValue; + } + + void PrintValue(NJsonWriter::TBuf& json, bool check = true) const override; + void PrintToPush(NJsonWriter::TBuf& json, bool check) const override; + void PrintInfo(NJsonWriter::TBuf& json) const override; + void ExportToProto(TInstanceStats& stats) const override; + + void PushSignal(double signal) override; + virtual void ResetSignal() override; + + private: + union TValue { + double Value; + TAtomic Atomic; + }; + + TValue Value; + }; + + using TIntervals = TVector<double>; + + const size_t MAX_HISTOGRAM_SIZE = 50; + + // + // Examples: + // Append(7).AppendFlat(2, 3) -> {7, 10, 13} + // FlatRange(4, 1, 5).FlatRange(3, 5, 20) -> {1, 2, 3, 4, 5, 10, 15} + // ExpRange(4, 1, 16).ExpRange(3, 16, 1024) -> {1, 2, 4, 8, 16, 64, 256} + // + class TIntervalsBuilder { + public: + // Append single point + TIntervalsBuilder& Append(double value); + + // Appends @count points at equal intervals: + // [last + step, last + 2 * step, ..., last + count * step] + TIntervalsBuilder& AppendFlat(size_t count, double step); + + // Appends @count points at exponential intervals: + // [last * base, last * base^2, ..., last * base^count] + TIntervalsBuilder& AppendExp(size_t count, double base); + + // Adds @count points at equal intervals, @stop is not included: + // [start, start + d, start + 2d, ..., start + count * d = stop) + TIntervalsBuilder& FlatRange(size_t count, double start, double stop); + + // Adds @count points at exponential intervals, @stop is not included: + // [start, start * q, start * q^2, ..., start * q^count = stop) + TIntervalsBuilder& ExpRange(size_t count, double start, double stop); + + TIntervals Build(); + private: + TIntervals Intervals_; + }; + + class THistogramHole: public TTypedHole { + public: + THistogramHole(const TString& name, const TString& description, const TString& suffix, EAggregationType type, int priority, TIntervals intervals, bool alwaysVisible) + : TTypedHole(name, description, suffix, type, priority, alwaysVisible) + , Intervals(std::move(intervals)) + { + Weights.resize(Intervals.size()); + } + + void PrintValue(NJsonWriter::TBuf& json, bool check = true) const override; + void PrintToPush(NJsonWriter::TBuf& json, bool check) const override; + void PrintInfo(NJsonWriter::TBuf& json) const override; + void ExportToProto(TInstanceStats& stats) const override; + + void PushSignal(double signal) override; + virtual void ResetSignal() override; + + // for tests only + void SetWeight(ui32 index, TAtomicBase value); + + private: + void PrintWeights(NJsonWriter::TBuf& json) const; + + const TIntervals Intervals; + TVector<TAtomicBase> Weights; + }; + + struct THolePriorityComparator { + bool operator()(IHole* lhs, IHole* rhs) const { + // Priorities are sorted in descending order, tags and names in ascending order. + auto lp = lhs->GetPriority(); + auto rp = rhs->GetPriority(); + if (lp != rp) { + return lp > rp; + } + + const auto& lt = lhs->GetTagsStr(); + const auto& rt = rhs->GetTagsStr(); + int cmp = lt.compare(rt); + if (cmp) { + return cmp < 0; + } + + return lhs->GetName() < rhs->GetName(); + } + }; + + using IHolePtr = TAtomicSharedPtr<NUnistat::IHole>; + using TTags = TMap<TString, TString>; +} + +class TUnistat { +public: + static TUnistat& Instance() { + return *Singleton<TUnistat>(); + } + + NUnistat::IHolePtr DrillFloatHole(const TString& name, + const TString& description, + const TString& suffix, + NUnistat::TPriority priority, + NUnistat::TStartValue startValue = NUnistat::TStartValue(0), + EAggregationType type = EAggregationType::Sum, + bool alwaysVisible = false); + + NUnistat::IHolePtr DrillFloatHole(const TString& name, + const TString& suffix, + NUnistat::TPriority priority, + NUnistat::TStartValue startValue = NUnistat::TStartValue(0), + EAggregationType type = EAggregationType::Sum, + bool alwaysVisible = false); + + + NUnistat::IHolePtr DrillHistogramHole(const TString& name, + const TString& description, + const TString& suffix, + NUnistat::TPriority priority, + const NUnistat::TIntervals& intervals, + EAggregationType type = EAggregationType::HostHistogram, + bool alwaysVisible = false); + + NUnistat::IHolePtr DrillHistogramHole(const TString& name, + const TString& suffix, + NUnistat::TPriority priority, + const NUnistat::TIntervals& intervals, + EAggregationType type = EAggregationType::HostHistogram, + bool alwaysVisible = false); + + void AddGlobalTag(const TString& tagName, const TString& tagValue); + + /*It called Unsafe, because assumed, that all holes are initilized before + first usage. Underlying hash is not locked, when invoked */ + template <typename T> + bool PushSignalUnsafe(const T& holename, double signal) { + return PushSignalUnsafeImpl(GetHolename(holename), signal); + } + + template <typename T> + TMaybe<TInstanceStats::TMetric> GetSignalValueUnsafe(const T& holename) { + return GetSignalValueUnsafeImpl(GetHolename(holename)); + } + + template <typename T> + bool ResetSignalUnsafe(const T& holename) { + return ResetSignalUnsafeImpl(GetHolename(holename)); + } + + TString CreateJsonDump(int level, bool allHoles = true) const; + TString CreatePushDump(int level, const NUnistat::TTags& tags = NUnistat::TTags(), ui32 ttl = 0, bool allHoles = false) const; + TString CreateInfoDump(int level) const; + void ExportToProto(TInstanceStats& stats, int level) const; + TString GetSignalDescriptions() const; + TVector<TString> GetHolenames() const; + + /* Erase hole from TUnistat internal state + returns false if name wasn't found */ + bool EraseHole(const TString& name); + + void Reset(); + void ResetSignals(); + +private: + template <typename T> + std::enable_if_t<std::is_same<T, TStringBuf>::value || + std::is_same<T, TString>::value || + std::is_same<T, char*>::value || + std::is_same<T, const char*>::value, + const T&> + GetHolename(const T& holename) { + return holename; + } + + template <typename T> + std::enable_if_t<std::is_enum<T>::value, TString> + GetHolename(const T holename) { + return ToString(holename); + } + + template <typename T> + std::enable_if_t<(std::is_same<char, std::remove_all_extents_t<T>>::value && + std::is_array<T>::value && std::rank<T>::value == 1 && std::extent<T>::value > 1), + TStringBuf> + GetHolename(const T& holename) { + return {holename, std::extent<T>::value - 1}; + } + + bool PushSignalUnsafeImpl(const TStringBuf holename, double signal); + TMaybe<TInstanceStats::TMetric> GetSignalValueUnsafeImpl(const TStringBuf holename); + bool ResetSignalUnsafeImpl(const TStringBuf holename); + +private: + TRWMutex Mutex; + THashMap<TString, NUnistat::IHolePtr> Holes; + TSet<NUnistat::IHole*, NUnistat::THolePriorityComparator> HolesByPriorityAndTags; + TMap<TString, TString> GlobalTags; +}; |