aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/unistat
diff options
context:
space:
mode:
authorqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
committerqrort <qrort@yandex-team.com>2022-11-30 23:47:12 +0300
commit22f8ae0e3f5d68b92aecccdf96c1d841a0334311 (patch)
treebffa27765faf54126ad44bcafa89fadecb7a73d7 /library/cpp/unistat
parent332b99e2173f0425444abb759eebcb2fafaa9209 (diff)
downloadydb-22f8ae0e3f5d68b92aecccdf96c1d841a0334311.tar.gz
validate canons without yatest_common
Diffstat (limited to 'library/cpp/unistat')
-rw-r--r--library/cpp/unistat/idl/stats.proto19
-rw-r--r--library/cpp/unistat/raii.cpp1
-rw-r--r--library/cpp/unistat/raii.h120
-rw-r--r--library/cpp/unistat/types.h15
-rw-r--r--library/cpp/unistat/unistat.cpp461
-rw-r--r--library/cpp/unistat/unistat.h382
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;
+};