diff options
author | serxa <serxa@yandex-team.ru> | 2022-02-10 16:49:08 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:49:08 +0300 |
commit | e5d4696304c6689379ac7ce334512404d4b7836c (patch) | |
tree | 5d5cb817648f650d76cf1076100726fd9b8448e8 /library/cpp/lwtrace/mon/mon_lwtrace.cpp | |
parent | d6d7db348c2cc64e71243cab9940ee6778f4317d (diff) | |
download | ydb-e5d4696304c6689379ac7ce334512404d4b7836c.tar.gz |
Restoring authorship annotation for <serxa@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'library/cpp/lwtrace/mon/mon_lwtrace.cpp')
-rw-r--r-- | library/cpp/lwtrace/mon/mon_lwtrace.cpp | 7878 |
1 files changed, 3939 insertions, 3939 deletions
diff --git a/library/cpp/lwtrace/mon/mon_lwtrace.cpp b/library/cpp/lwtrace/mon/mon_lwtrace.cpp index b400103f58..a61ee9ce22 100644 --- a/library/cpp/lwtrace/mon/mon_lwtrace.cpp +++ b/library/cpp/lwtrace/mon/mon_lwtrace.cpp @@ -1,8 +1,8 @@ #include "mon_lwtrace.h" -#include <algorithm> -#include <iterator> - +#include <algorithm> +#include <iterator> + #include <google/protobuf/text_format.h> #include <library/cpp/lwtrace/mon/analytics/all.h> #include <library/cpp/lwtrace/all.h> @@ -12,14 +12,14 @@ #include <library/cpp/resource/resource.h> #include <library/cpp/string_utils/base64/base64.h> #include <library/cpp/html/pcdata/pcdata.h> -#include <util/string/escape.h> -#include <util/system/condvar.h> -#include <util/system/execpath.h> +#include <util/string/escape.h> +#include <util/system/condvar.h> +#include <util/system/execpath.h> #include <util/system/hostname.h> using namespace NMonitoring; -#define WWW_CHECK(cond, ...) \ +#define WWW_CHECK(cond, ...) \ do { \ if (!(cond)) { \ ythrow yexception() << Sprintf(__VA_ARGS__); \ @@ -27,1383 +27,1383 @@ using namespace NMonitoring; } while (false) \ /**/ -#define WWW_HTML_INNER(out) HTML(out) WITH_SCOPED(tmp, TScopedHtmlInner(out)) -#define WWW_HTML(out) out << NMonitoring::HTTPOKHTML; WWW_HTML_INNER(out) - -namespace NLwTraceMonPage { - -struct TTrackLogRefs { - struct TItem { - const TThread::TId ThreadId; - const NLWTrace::TLogItem* Ptr; - - TItem() - : ThreadId(0) - , Ptr(nullptr) - {} - - TItem(TThread::TId tid, const NLWTrace::TLogItem& ref) - : ThreadId(tid) - , Ptr(&ref) - {} - - TItem(const TItem& o) - : ThreadId(o.ThreadId) - , Ptr(o.Ptr) - {} - - operator const NLWTrace::TLogItem&() const { return *Ptr; } - }; - +#define WWW_HTML_INNER(out) HTML(out) WITH_SCOPED(tmp, TScopedHtmlInner(out)) +#define WWW_HTML(out) out << NMonitoring::HTTPOKHTML; WWW_HTML_INNER(out) + +namespace NLwTraceMonPage { + +struct TTrackLogRefs { + struct TItem { + const TThread::TId ThreadId; + const NLWTrace::TLogItem* Ptr; + + TItem() + : ThreadId(0) + , Ptr(nullptr) + {} + + TItem(TThread::TId tid, const NLWTrace::TLogItem& ref) + : ThreadId(tid) + , Ptr(&ref) + {} + + TItem(const TItem& o) + : ThreadId(o.ThreadId) + , Ptr(o.Ptr) + {} + + operator const NLWTrace::TLogItem&() const { return *Ptr; } + }; + using TItems = TVector<TItem>; - TItems Items; - - TTrackLogRefs() {} - - TTrackLogRefs(const TTrackLogRefs& other) - : Items(other.Items) - {} - - void Clear() - { - Items.clear(); - } - - ui64 GetTimestampCycles() const - { - return Items.empty()? 0: Items.front().Ptr->GetTimestampCycles(); - } -}; - -// -// Templates to treat both NLWTrace::TLogItem and NLWTrace::TTrackLog in the same way (e.g. in TLogQuery) -// - -template <class TLog> -struct TLogTraits {}; - -template <> -struct TLogTraits<NLWTrace::TLogItem> { - using TLog = NLWTrace::TLogItem; - using const_iterator = const NLWTrace::TLogItem*; - using const_reverse_iterator = const NLWTrace::TLogItem*; - - static const_iterator begin(const TLog& log) { return &log; } - static const_iterator end(const TLog& log) { return &log + 1; } - static const_reverse_iterator rbegin(const TLog& log) { return &log; } - static const_reverse_iterator rend(const TLog& log) { return &log + 1; } - static bool empty(const TLog&) { return false; } - static const NLWTrace::TLogItem& front(const TLog& log) { return log; } - static const NLWTrace::TLogItem& back(const TLog& log) { return log; } -}; - -template <> -struct TLogTraits<NLWTrace::TTrackLog> { - using TLog = NLWTrace::TTrackLog; - using const_iterator = NLWTrace::TTrackLog::TItems::const_iterator; - using const_reverse_iterator = NLWTrace::TTrackLog::TItems::const_reverse_iterator; - - static const_iterator begin(const TLog& log) { return log.Items.begin(); } - static const_iterator end(const TLog& log) { return log.Items.end(); } - static const_reverse_iterator rbegin(const TLog& log) { return log.Items.rbegin(); } - static const_reverse_iterator rend(const TLog& log) { return log.Items.rend(); } - static bool empty(const TLog& log) { return log.Items.empty(); } - static const NLWTrace::TLogItem& front(const TLog& log) { return log.Items.front(); } - static const NLWTrace::TLogItem& back(const TLog& log) { return log.Items.back(); } -}; - -template <> -struct TLogTraits<TTrackLogRefs> { - using TLog = TTrackLogRefs; - using const_iterator = TTrackLogRefs::TItems::const_iterator; - using const_reverse_iterator = TTrackLogRefs::TItems::const_reverse_iterator; - - static const_iterator begin(const TLog& log) { return log.Items.begin(); } - static const_iterator end(const TLog& log) { return log.Items.end(); } - static const_reverse_iterator rbegin(const TLog& log) { return log.Items.rbegin(); } - static const_reverse_iterator rend(const TLog& log) { return log.Items.rend(); } - static bool empty(const TLog& log) { return log.Items.empty(); } - static const NLWTrace::TLogItem& front(const TLog& log) { return log.Items.front(); } - static const NLWTrace::TLogItem& back(const TLog& log) { return log.Items.back(); } -}; - -/* - * Log Query Language: - * Data expressions: - * 1) myparam[0], myparam[-1] // the first and the last myparam in any probe/provider - * 2) myparam // the first (the same as [0]) - * 3) PROVIDER..myparam // any probe with myparam in PROVIDER - * 4) MyProbe._elapsedMs // Ms since track begin for the first MyProbe event - * 5) PROVIDER.MyProbe._sliceUs // Us since previous event in track for the first PROVIDER.MyProbe event - */ - -struct TLogQuery { -private: - enum ESpecialParam { - NotSpecial = 0, - // The last '*' can be one of: Ms, Us, Ns, Min, Hours, (blank means seconds) - // '*Time' can be one of: RTime (since cut ts for given dataset), NTime (negative time since now), Time (since machine start) - TrackDuration = 1, // _track* - TrackBeginTime = 2, // _begin*Time* - TrackEndTime = 3, // _end*Time* - ElapsedDuration = 4, // _elapsed* - SliceDuration = 5, // _slice* - ThreadTime = 6, // _thr*Time* - }; - - template <class TLog, class TTr = TLogTraits<TLog>> - struct TExecuteQuery; - - template <class TLog, class TTr> - friend struct TExecuteQuery; - - template <class TLog, class TTr> - struct TExecuteQuery { - const TLogQuery& Query; - const TLog* Log = nullptr; - bool Reversed; - - i64 Skip; - const NLWTrace::TLogItem* Item = nullptr; - typename TTr::const_iterator FwdIter; - typename TTr::const_reverse_iterator RevIter; - - NLWTrace::TTypedParam Result; - - explicit TExecuteQuery(const TLogQuery& query, const TLog& log) - : Query(query) - , Log(&log) - , Reversed(Query.Index < 0) - , Skip(Reversed? -Query.Index - 1: Query.Index) - , FwdIter() - , RevIter() - {} - - void ExecuteQuery() - { - if (!Reversed) { - for (auto i = TTr::begin(*Log), e = TTr::end(*Log); i != e; ++i) { - if (FwdIteration(i)) { - return; - } - } - } else { - for (auto i = TTr::rbegin(*Log), e = TTr::rend(*Log); i != e; ++i) { - if (RevIteration(i)) { - return; - } - } - } - } - - bool FwdIteration(typename TTr::const_iterator it) - { - FwdIter = it; - Item = &*it; - return ProcessItem(); - } - - bool RevIteration(typename TTr::const_reverse_iterator it) - { - RevIter = it; - Item = &*it; - return ProcessItem(); - } - - bool ProcessItem() - { - if (Query.Provider && Query.Provider != Item->Probe->Event.GetProvider()) { - return false; - } - if (Query.Probe && Query.Probe != Item->Probe->Event.Name) { - return false; - } - switch (Query.SpecialParam) { - case NotSpecial: - if (Item->Probe->Event.Signature.FindParamIndex(Query.ParamName) != size_t(-1)) { - break; // param found - } else { - return false; // param not found - } - case TrackDuration: Y_FAIL(); - case TrackBeginTime: Y_FAIL(); - case TrackEndTime: Y_FAIL(); - case ElapsedDuration: break; - case SliceDuration: break; - case ThreadTime: break; - } - if (Skip > 0) { - Skip--; - return false; - } - switch (Query.SpecialParam) { - case NotSpecial: - Result = NLWTrace::TTypedParam(Item->GetParam(Query.ParamName)); - return true; - case TrackDuration: Y_FAIL(); - case TrackBeginTime: Y_FAIL(); - case TrackEndTime: Y_FAIL(); - case ElapsedDuration: - Result = NLWTrace::TTypedParam(Query.Duration( - Log->GetTimestampCycles(), - Item->GetTimestampCycles())); - return true; - case SliceDuration: - Result = NLWTrace::TTypedParam(Query.Duration( - PrevOrSame().GetTimestampCycles(), - Item->GetTimestampCycles())); - return true; - case ThreadTime: - Result = NLWTrace::TTypedParam(Query.Instant(Item->GetTimestampCycles())); - return true; - } - return true; - } - - const NLWTrace::TLogItem& PrevOrSame() const - { - if (!Reversed) { - auto i = FwdIter; - if (i != TTr::begin(*Log)) { - i--; - } - return *i; - } else { - auto j = RevIter + 1; - if (j == TTr::rend(*Log)) { - return *RevIter; - } - return *j; - } - } - }; - + TItems Items; + + TTrackLogRefs() {} + + TTrackLogRefs(const TTrackLogRefs& other) + : Items(other.Items) + {} + + void Clear() + { + Items.clear(); + } + + ui64 GetTimestampCycles() const + { + return Items.empty()? 0: Items.front().Ptr->GetTimestampCycles(); + } +}; + +// +// Templates to treat both NLWTrace::TLogItem and NLWTrace::TTrackLog in the same way (e.g. in TLogQuery) +// + +template <class TLog> +struct TLogTraits {}; + +template <> +struct TLogTraits<NLWTrace::TLogItem> { + using TLog = NLWTrace::TLogItem; + using const_iterator = const NLWTrace::TLogItem*; + using const_reverse_iterator = const NLWTrace::TLogItem*; + + static const_iterator begin(const TLog& log) { return &log; } + static const_iterator end(const TLog& log) { return &log + 1; } + static const_reverse_iterator rbegin(const TLog& log) { return &log; } + static const_reverse_iterator rend(const TLog& log) { return &log + 1; } + static bool empty(const TLog&) { return false; } + static const NLWTrace::TLogItem& front(const TLog& log) { return log; } + static const NLWTrace::TLogItem& back(const TLog& log) { return log; } +}; + +template <> +struct TLogTraits<NLWTrace::TTrackLog> { + using TLog = NLWTrace::TTrackLog; + using const_iterator = NLWTrace::TTrackLog::TItems::const_iterator; + using const_reverse_iterator = NLWTrace::TTrackLog::TItems::const_reverse_iterator; + + static const_iterator begin(const TLog& log) { return log.Items.begin(); } + static const_iterator end(const TLog& log) { return log.Items.end(); } + static const_reverse_iterator rbegin(const TLog& log) { return log.Items.rbegin(); } + static const_reverse_iterator rend(const TLog& log) { return log.Items.rend(); } + static bool empty(const TLog& log) { return log.Items.empty(); } + static const NLWTrace::TLogItem& front(const TLog& log) { return log.Items.front(); } + static const NLWTrace::TLogItem& back(const TLog& log) { return log.Items.back(); } +}; + +template <> +struct TLogTraits<TTrackLogRefs> { + using TLog = TTrackLogRefs; + using const_iterator = TTrackLogRefs::TItems::const_iterator; + using const_reverse_iterator = TTrackLogRefs::TItems::const_reverse_iterator; + + static const_iterator begin(const TLog& log) { return log.Items.begin(); } + static const_iterator end(const TLog& log) { return log.Items.end(); } + static const_reverse_iterator rbegin(const TLog& log) { return log.Items.rbegin(); } + static const_reverse_iterator rend(const TLog& log) { return log.Items.rend(); } + static bool empty(const TLog& log) { return log.Items.empty(); } + static const NLWTrace::TLogItem& front(const TLog& log) { return log.Items.front(); } + static const NLWTrace::TLogItem& back(const TLog& log) { return log.Items.back(); } +}; + +/* + * Log Query Language: + * Data expressions: + * 1) myparam[0], myparam[-1] // the first and the last myparam in any probe/provider + * 2) myparam // the first (the same as [0]) + * 3) PROVIDER..myparam // any probe with myparam in PROVIDER + * 4) MyProbe._elapsedMs // Ms since track begin for the first MyProbe event + * 5) PROVIDER.MyProbe._sliceUs // Us since previous event in track for the first PROVIDER.MyProbe event + */ + +struct TLogQuery { +private: + enum ESpecialParam { + NotSpecial = 0, + // The last '*' can be one of: Ms, Us, Ns, Min, Hours, (blank means seconds) + // '*Time' can be one of: RTime (since cut ts for given dataset), NTime (negative time since now), Time (since machine start) + TrackDuration = 1, // _track* + TrackBeginTime = 2, // _begin*Time* + TrackEndTime = 3, // _end*Time* + ElapsedDuration = 4, // _elapsed* + SliceDuration = 5, // _slice* + ThreadTime = 6, // _thr*Time* + }; + + template <class TLog, class TTr = TLogTraits<TLog>> + struct TExecuteQuery; + + template <class TLog, class TTr> + friend struct TExecuteQuery; + + template <class TLog, class TTr> + struct TExecuteQuery { + const TLogQuery& Query; + const TLog* Log = nullptr; + bool Reversed; + + i64 Skip; + const NLWTrace::TLogItem* Item = nullptr; + typename TTr::const_iterator FwdIter; + typename TTr::const_reverse_iterator RevIter; + + NLWTrace::TTypedParam Result; + + explicit TExecuteQuery(const TLogQuery& query, const TLog& log) + : Query(query) + , Log(&log) + , Reversed(Query.Index < 0) + , Skip(Reversed? -Query.Index - 1: Query.Index) + , FwdIter() + , RevIter() + {} + + void ExecuteQuery() + { + if (!Reversed) { + for (auto i = TTr::begin(*Log), e = TTr::end(*Log); i != e; ++i) { + if (FwdIteration(i)) { + return; + } + } + } else { + for (auto i = TTr::rbegin(*Log), e = TTr::rend(*Log); i != e; ++i) { + if (RevIteration(i)) { + return; + } + } + } + } + + bool FwdIteration(typename TTr::const_iterator it) + { + FwdIter = it; + Item = &*it; + return ProcessItem(); + } + + bool RevIteration(typename TTr::const_reverse_iterator it) + { + RevIter = it; + Item = &*it; + return ProcessItem(); + } + + bool ProcessItem() + { + if (Query.Provider && Query.Provider != Item->Probe->Event.GetProvider()) { + return false; + } + if (Query.Probe && Query.Probe != Item->Probe->Event.Name) { + return false; + } + switch (Query.SpecialParam) { + case NotSpecial: + if (Item->Probe->Event.Signature.FindParamIndex(Query.ParamName) != size_t(-1)) { + break; // param found + } else { + return false; // param not found + } + case TrackDuration: Y_FAIL(); + case TrackBeginTime: Y_FAIL(); + case TrackEndTime: Y_FAIL(); + case ElapsedDuration: break; + case SliceDuration: break; + case ThreadTime: break; + } + if (Skip > 0) { + Skip--; + return false; + } + switch (Query.SpecialParam) { + case NotSpecial: + Result = NLWTrace::TTypedParam(Item->GetParam(Query.ParamName)); + return true; + case TrackDuration: Y_FAIL(); + case TrackBeginTime: Y_FAIL(); + case TrackEndTime: Y_FAIL(); + case ElapsedDuration: + Result = NLWTrace::TTypedParam(Query.Duration( + Log->GetTimestampCycles(), + Item->GetTimestampCycles())); + return true; + case SliceDuration: + Result = NLWTrace::TTypedParam(Query.Duration( + PrevOrSame().GetTimestampCycles(), + Item->GetTimestampCycles())); + return true; + case ThreadTime: + Result = NLWTrace::TTypedParam(Query.Instant(Item->GetTimestampCycles())); + return true; + } + return true; + } + + const NLWTrace::TLogItem& PrevOrSame() const + { + if (!Reversed) { + auto i = FwdIter; + if (i != TTr::begin(*Log)) { + i--; + } + return *i; + } else { + auto j = RevIter + 1; + if (j == TTr::rend(*Log)) { + return *RevIter; + } + return *j; + } + } + }; + TString Text; TString Provider; TString Probe; TString ParamName; - ESpecialParam SpecialParam = NotSpecial; - i64 Index = 0; - double TimeUnitSec = 1.0; - i64 ZeroTs = 0; - i64 RTimeZeroTs = 0; - i64 NTimeZeroTs = 0; - -public: - TLogQuery() {} - + ESpecialParam SpecialParam = NotSpecial; + i64 Index = 0; + double TimeUnitSec = 1.0; + i64 ZeroTs = 0; + i64 RTimeZeroTs = 0; + i64 NTimeZeroTs = 0; + +public: + TLogQuery() {} + explicit TLogQuery(const TString& text) - : Text(text) - { - try { - if (!Text.empty()) { - ParseQuery(Text); - } - } catch (...) { - ythrow yexception() - << CurrentExceptionMessage() - << " while parsing track log query: " - << Text; - } - } - - operator bool() const - { - return !Text.empty(); - } - - template <class TLog> - NLWTrace::TTypedParam ExecuteQuery(const TLog& log) const - { - using TTr = TLogTraits<TLog>; - - WWW_CHECK(Text, "execute of empty log query"); - if (TTr::empty(log)) { - return NLWTrace::TTypedParam(); - } - - if (SpecialParam == TrackDuration) { - return Duration( - log.GetTimestampCycles(), - TTr::back(log).GetTimestampCycles()); - } else if (SpecialParam == TrackBeginTime) { - return Instant(log.GetTimestampCycles()); - } else if (SpecialParam == TrackEndTime) { - return Instant(TTr::back(log).GetTimestampCycles()); - } - - TExecuteQuery<TLog, TTr> exec(*this, log); - exec.ExecuteQuery(); - return exec.Result; - } - -private: - NLWTrace::TTypedParam Duration(ui64 ts1, ui64 ts2) const - { - double sec = NHPTimer::GetSeconds(ts1 < ts2? ts2 - ts1: 0); - return NLWTrace::TTypedParam(sec / TimeUnitSec); - } - - NLWTrace::TTypedParam Instant(ui64 ts) const - { - double sec = NHPTimer::GetSeconds(i64(ts) - ZeroTs); - return NLWTrace::TTypedParam(sec / TimeUnitSec); - } - + : Text(text) + { + try { + if (!Text.empty()) { + ParseQuery(Text); + } + } catch (...) { + ythrow yexception() + << CurrentExceptionMessage() + << " while parsing track log query: " + << Text; + } + } + + operator bool() const + { + return !Text.empty(); + } + + template <class TLog> + NLWTrace::TTypedParam ExecuteQuery(const TLog& log) const + { + using TTr = TLogTraits<TLog>; + + WWW_CHECK(Text, "execute of empty log query"); + if (TTr::empty(log)) { + return NLWTrace::TTypedParam(); + } + + if (SpecialParam == TrackDuration) { + return Duration( + log.GetTimestampCycles(), + TTr::back(log).GetTimestampCycles()); + } else if (SpecialParam == TrackBeginTime) { + return Instant(log.GetTimestampCycles()); + } else if (SpecialParam == TrackEndTime) { + return Instant(TTr::back(log).GetTimestampCycles()); + } + + TExecuteQuery<TLog, TTr> exec(*this, log); + exec.ExecuteQuery(); + return exec.Result; + } + +private: + NLWTrace::TTypedParam Duration(ui64 ts1, ui64 ts2) const + { + double sec = NHPTimer::GetSeconds(ts1 < ts2? ts2 - ts1: 0); + return NLWTrace::TTypedParam(sec / TimeUnitSec); + } + + NLWTrace::TTypedParam Instant(ui64 ts) const + { + double sec = NHPTimer::GetSeconds(i64(ts) - ZeroTs); + return NLWTrace::TTypedParam(sec / TimeUnitSec); + } + void ParseQuery(const TString& s) - { + { auto parts = SplitString(s, "."); - WWW_CHECK(parts.size() <= 3, "too many name specifiers"); + WWW_CHECK(parts.size() <= 3, "too many name specifiers"); ParseParamSelector(parts.back()); - if (parts.size() >= 2) { - ParseProbeSelector(parts[parts.size() - 2]); - } - if (parts.size() >= 3) { - ParseProviderSelector(parts[parts.size() - 3]); - } - } - + if (parts.size() >= 2) { + ParseProbeSelector(parts[parts.size() - 2]); + } + if (parts.size() >= 3) { + ParseProviderSelector(parts[parts.size() - 3]); + } + } + void ParseParamSelector(const TString& s) - { - size_t bracket = s.find('['); + { + size_t bracket = s.find('['); if (bracket == TString::npos) { - ParseParamName(s); - Index = 0; - } else { - ParseParamName(s.substr(0, bracket)); - size_t bracket2 = s.find(']', bracket); + ParseParamName(s); + Index = 0; + } else { + ParseParamName(s.substr(0, bracket)); + size_t bracket2 = s.find(']', bracket); WWW_CHECK(bracket2 != TString::npos, "closing braket ']' is missing"); - Index = FromString<i64>(s.substr(bracket + 1, bracket2 - bracket - 1)); - } - } - + Index = FromString<i64>(s.substr(bracket + 1, bracket2 - bracket - 1)); + } + } + void ParseParamName(const TString& s) - { - ParamName = s; + { + ParamName = s; TString paramName = s; - + const static TVector<std::pair<TString, ESpecialParam>> specials = { - { "_track", TrackDuration }, - { "_begin", TrackBeginTime }, - { "_end", TrackEndTime }, - { "_elapsed", ElapsedDuration }, - { "_slice", SliceDuration }, - { "_thr", ThreadTime } - }; - - // Check for special params - SpecialParam = NotSpecial; - for (const auto& p : specials) { - if (paramName.StartsWith(p.first)) { - SpecialParam = p.second; + { "_track", TrackDuration }, + { "_begin", TrackBeginTime }, + { "_end", TrackEndTime }, + { "_elapsed", ElapsedDuration }, + { "_slice", SliceDuration }, + { "_thr", ThreadTime } + }; + + // Check for special params + SpecialParam = NotSpecial; + for (const auto& p : specials) { + if (paramName.StartsWith(p.first)) { + SpecialParam = p.second; paramName.erase(0, p.first.size()); - break; - } - } - - if (SpecialParam == NotSpecial) { - return; - } - + break; + } + } + + if (SpecialParam == NotSpecial) { + return; + } + const static TVector<std::pair<TString, double>> timeUnits = { - { "Ms", 1e-3 }, - { "Us", 1e-6 }, - { "Ns", 1e-9 }, - { "Min", 60.0 }, - { "Hours", 3600.0 } - }; - - // Parse units for special params - TimeUnitSec = 1.0; - for (const auto& p : timeUnits) { - if (paramName.EndsWith(p.first)) { - TimeUnitSec = p.second; + { "Ms", 1e-3 }, + { "Us", 1e-6 }, + { "Ns", 1e-9 }, + { "Min", 60.0 }, + { "Hours", 3600.0 } + }; + + // Parse units for special params + TimeUnitSec = 1.0; + for (const auto& p : timeUnits) { + if (paramName.EndsWith(p.first)) { + TimeUnitSec = p.second; paramName.erase(paramName.size() - p.first.size()); - break; - } - } - - if (SpecialParam == ThreadTime || - SpecialParam == TrackBeginTime || - SpecialParam == TrackEndTime) - { - // Parse time zero for special instant params + break; + } + } + + if (SpecialParam == ThreadTime || + SpecialParam == TrackBeginTime || + SpecialParam == TrackEndTime) + { + // Parse time zero for special instant params const TVector<std::pair<TString, i64>> timeZeros = { - { "RTime", RTimeZeroTs }, - { "NTime", NTimeZeroTs }, - { "Time", 0ll } - }; - ZeroTs = -1; - for (const auto& p : timeZeros) { - if (paramName.EndsWith(p.first)) { - ZeroTs = p.second; + { "RTime", RTimeZeroTs }, + { "NTime", NTimeZeroTs }, + { "Time", 0ll } + }; + ZeroTs = -1; + for (const auto& p : timeZeros) { + if (paramName.EndsWith(p.first)) { + ZeroTs = p.second; paramName.erase(paramName.size() - p.first.size()); - break; - } - } + break; + } + } WWW_CHECK(ZeroTs != -1, "wrong special param name (postfix '*Time' required): %s", s.data()); - } - + } + WWW_CHECK(paramName.empty(), "wrong special param name: %s", s.data()); - } - + } + void ParseProbeSelector(const TString& s) - { - Probe = s; - } - + { + Probe = s; + } + void ParseProviderSelector(const TString& s) - { - Provider = s; - } -}; - + { + Provider = s; + } +}; + using TVariants = TVector<std::pair<TString, TString>>; using TTags = TSet<TString>; - + TString GetProbeName(const NLWTrace::TProbe* probe, const char* sep = ".") -{ +{ return TString(probe->Event.GetProvider()) + sep + probe->Event.Name; -} - -struct TAdHocTraceConfig { - NLWTrace::TQuery Cfg; - - TAdHocTraceConfig() {} // Invalid config - - TAdHocTraceConfig(ui16 logSize, ui64 logDurationUs, bool logShuttle) - { - auto block = Cfg.AddBlocks(); // Create one block to distinguish valid config - if (logSize) { - Cfg.SetPerThreadLogSize(logSize); - } - if (logDurationUs) { - Cfg.SetLogDurationUs(logDurationUs); - } - if (logShuttle) { - block->AddAction()->MutableRunLogShuttleAction(); - } - } - +} + +struct TAdHocTraceConfig { + NLWTrace::TQuery Cfg; + + TAdHocTraceConfig() {} // Invalid config + + TAdHocTraceConfig(ui16 logSize, ui64 logDurationUs, bool logShuttle) + { + auto block = Cfg.AddBlocks(); // Create one block to distinguish valid config + if (logSize) { + Cfg.SetPerThreadLogSize(logSize); + } + if (logDurationUs) { + Cfg.SetLogDurationUs(logDurationUs); + } + if (logShuttle) { + block->AddAction()->MutableRunLogShuttleAction(); + } + } + TAdHocTraceConfig(const TString& provider, const TString& probe, ui16 logSize = 0, ui64 logDurationUs = 0, bool logShuttle = false) - : TAdHocTraceConfig(logSize, logDurationUs, logShuttle) - { - auto block = Cfg.MutableBlocks(0); - auto pdesc = block->MutableProbeDesc(); - pdesc->SetProvider(provider); - pdesc->SetName(probe); - } - + : TAdHocTraceConfig(logSize, logDurationUs, logShuttle) + { + auto block = Cfg.MutableBlocks(0); + auto pdesc = block->MutableProbeDesc(); + pdesc->SetProvider(provider); + pdesc->SetName(probe); + } + explicit TAdHocTraceConfig(const TString& group, ui16 logSize = 0, ui64 logDurationUs = 0, bool logShuttle = false) - : TAdHocTraceConfig(logSize, logDurationUs, logShuttle) - { - auto block = Cfg.MutableBlocks(0); - auto pdesc = block->MutableProbeDesc(); - pdesc->SetGroup(group); - } - + : TAdHocTraceConfig(logSize, logDurationUs, logShuttle) + { + auto block = Cfg.MutableBlocks(0); + auto pdesc = block->MutableProbeDesc(); + pdesc->SetGroup(group); + } + TString Id() const - { - TStringStream ss; - for (size_t blockIdx = 0; blockIdx < Cfg.BlocksSize(); blockIdx++) { - if (!ss.Str().empty()) { - ss << "/"; - } - auto block = Cfg.GetBlocks(blockIdx); - auto pdesc = block.GetProbeDesc(); + { + TStringStream ss; + for (size_t blockIdx = 0; blockIdx < Cfg.BlocksSize(); blockIdx++) { + if (!ss.Str().empty()) { + ss << "/"; + } + auto block = Cfg.GetBlocks(blockIdx); + auto pdesc = block.GetProbeDesc(); if (pdesc.GetProvider()) { - ss << "." << pdesc.GetProvider() << "." << pdesc.GetName(); + ss << "." << pdesc.GetProvider() << "." << pdesc.GetName(); } else if (pdesc.GetGroup()) { - ss << ".Group." << pdesc.GetGroup(); - } - // TODO[serxa]: handle predicate - for (size_t actionIdx = 0; actionIdx < block.ActionSize(); actionIdx++) { - const NLWTrace::TAction& action = block.GetAction(actionIdx); - if (action.HasRunLogShuttleAction()) { - auto ls = action.GetRunLogShuttleAction(); - ss << ".alsr"; - if (ls.GetIgnore()) { - ss << "-i"; - } + ss << ".Group." << pdesc.GetGroup(); + } + // TODO[serxa]: handle predicate + for (size_t actionIdx = 0; actionIdx < block.ActionSize(); actionIdx++) { + const NLWTrace::TAction& action = block.GetAction(actionIdx); + if (action.HasRunLogShuttleAction()) { + auto ls = action.GetRunLogShuttleAction(); + ss << ".alsr"; + if (ls.GetIgnore()) { + ss << "-i"; + } if (ls.GetShuttlesCount()) { - ss << "-s" << ls.GetShuttlesCount(); - } + ss << "-s" << ls.GetShuttlesCount(); + } if (ls.GetMaxTrackLength()) { - ss << "-t" << ls.GetMaxTrackLength(); - } - } else if (action.HasEditLogShuttleAction()) { - auto ls = action.GetEditLogShuttleAction(); - ss << ".alse"; - if (ls.GetIgnore()) { - ss << "-i"; - } - } else if (action.HasDropLogShuttleAction()) { - ss << ".alsd"; - } - } - } + ss << "-t" << ls.GetMaxTrackLength(); + } + } else if (action.HasEditLogShuttleAction()) { + auto ls = action.GetEditLogShuttleAction(); + ss << ".alse"; + if (ls.GetIgnore()) { + ss << "-i"; + } + } else if (action.HasDropLogShuttleAction()) { + ss << ".alsd"; + } + } + } if (Cfg.GetPerThreadLogSize()) { - ss << ".l" << Cfg.GetPerThreadLogSize(); - } + ss << ".l" << Cfg.GetPerThreadLogSize(); + } if (Cfg.GetLogDurationUs()) { - ui64 logDurationUs = Cfg.GetLogDurationUs(); - if (logDurationUs % (60 * 1000 * 1000) == 0) - ss << ".d" << logDurationUs / (60 * 1000 * 1000) << "m"; - if (logDurationUs % (1000 * 1000) == 0) - ss << ".d" << logDurationUs / (1000 * 1000) << "s"; - else if (logDurationUs % 1000 == 0) - ss << ".d" << logDurationUs / 1000 << "ms"; - else - ss << ".d" << logDurationUs << "us"; - } - return ss.Str(); - } - - bool IsValid() const - { - return Cfg.BlocksSize() > 0; - } - - NLWTrace::TQuery Query() const - { - if (!IsValid()) { - ythrow yexception() << "invalid adhoc trace config"; - } - return Cfg; - } - + ui64 logDurationUs = Cfg.GetLogDurationUs(); + if (logDurationUs % (60 * 1000 * 1000) == 0) + ss << ".d" << logDurationUs / (60 * 1000 * 1000) << "m"; + if (logDurationUs % (1000 * 1000) == 0) + ss << ".d" << logDurationUs / (1000 * 1000) << "s"; + else if (logDurationUs % 1000 == 0) + ss << ".d" << logDurationUs / 1000 << "ms"; + else + ss << ".d" << logDurationUs << "us"; + } + return ss.Str(); + } + + bool IsValid() const + { + return Cfg.BlocksSize() > 0; + } + + NLWTrace::TQuery Query() const + { + if (!IsValid()) { + ythrow yexception() << "invalid adhoc trace config"; + } + return Cfg; + } + bool ParseId(const TString& id) - { - if (IsAdHocId(id)) { + { + if (IsAdHocId(id)) { for (const TString& block : SplitString(id, "/")) { - if (block.empty()) { - continue; - } - size_t cutPos = (block[0] == '.'? 1: 0); + if (block.empty()) { + continue; + } + size_t cutPos = (block[0] == '.'? 1: 0); TVector<TString> parts = SplitString(block.substr(cutPos), "."); WWW_CHECK(parts.size() >= 2, "too few parts in adhoc trace id '%s' block '%s'", id.data(), block.data()); - auto blockPb = Cfg.AddBlocks(); - auto pdescPb = blockPb->MutableProbeDesc(); - if (parts[0] == "Group") { - pdescPb->SetGroup(parts[1]); - } else { - pdescPb->SetProvider(parts[0]); - pdescPb->SetName(parts[1]); - } - bool defaultAction = true; - for (auto i = parts.begin() + 2, e = parts.end(); i != e; ++i) { + auto blockPb = Cfg.AddBlocks(); + auto pdescPb = blockPb->MutableProbeDesc(); + if (parts[0] == "Group") { + pdescPb->SetGroup(parts[1]); + } else { + pdescPb->SetProvider(parts[0]); + pdescPb->SetName(parts[1]); + } + bool defaultAction = true; + for (auto i = parts.begin() + 2, e = parts.end(); i != e; ++i) { const TString& part = *i; - if (part.empty()) { - continue; - } - switch (part[0]) { - case 'l': Cfg.SetPerThreadLogSize(FromString<ui16>(part.substr(1))); break; - case 'd': Cfg.SetLogDurationUs(ParseDuration(part.substr(1))); break; - case 's': blockPb->MutablePredicate()->SetSampleRate(1.0 / Max<ui64>(1, FromString<ui64>(part.substr(1)))); break; - case 'p': ParsePredicate(blockPb->MutablePredicate()->AddOperators(), part.substr(1)); break; - case 'a': ParseAction(blockPb->AddAction(), part.substr(1)); defaultAction = false; break; + if (part.empty()) { + continue; + } + switch (part[0]) { + case 'l': Cfg.SetPerThreadLogSize(FromString<ui16>(part.substr(1))); break; + case 'd': Cfg.SetLogDurationUs(ParseDuration(part.substr(1))); break; + case 's': blockPb->MutablePredicate()->SetSampleRate(1.0 / Max<ui64>(1, FromString<ui64>(part.substr(1)))); break; + case 'p': ParsePredicate(blockPb->MutablePredicate()->AddOperators(), part.substr(1)); break; + case 'a': ParseAction(blockPb->AddAction(), part.substr(1)); defaultAction = false; break; default: WWW_CHECK(false, "unknown adhoc trace part type '%s' in '%s'", part.data(), id.data()); - } - } - if (defaultAction) { - blockPb->AddAction()->MutableLogAction(); - } - } - return true; - } - return false; - } -private: + } + } + if (defaultAction) { + blockPb->AddAction()->MutableLogAction(); + } + } + return true; + } + return false; + } +private: static bool IsAdHocId(const TString& id) - { - return !id.empty() && id[0] == '.'; - } - + { + return !id.empty() && id[0] == '.'; + } + void ParsePredicate(NLWTrace::TOperator* op, const TString& p) - { - size_t sign = p.find_first_of("=!><"); + { + size_t sign = p.find_first_of("=!><"); WWW_CHECK(sign != TString::npos, "wrong predicate format in adhoc trace: %s", p.data()); - op->AddArgument()->SetParam(p.substr(0, sign)); - size_t value = sign + 1; - switch (p[sign]) { - case '=': - op->SetType(NLWTrace::OT_EQ); - break; - case '!': { + op->AddArgument()->SetParam(p.substr(0, sign)); + size_t value = sign + 1; + switch (p[sign]) { + case '=': + op->SetType(NLWTrace::OT_EQ); + break; + case '!': { WWW_CHECK(p.size() > sign + 1, "wrong predicate operator format in adhoc trace: %s", p.data()); WWW_CHECK(p[sign + 1] == '=', "wrong predicate operator format in adhoc trace: %s", p.data()); - value++; - op->SetType(NLWTrace::OT_NE); - break; - } - case '<': { + value++; + op->SetType(NLWTrace::OT_NE); + break; + } + case '<': { WWW_CHECK(p.size() > sign + 1, "wrong predicate operator format in adhoc trace: %s", p.data()); - if (p[sign + 1] == '=') { - value++; - op->SetType(NLWTrace::OT_LE); - } else { - op->SetType(NLWTrace::OT_LT); - } - break; - } - case '>': { + if (p[sign + 1] == '=') { + value++; + op->SetType(NLWTrace::OT_LE); + } else { + op->SetType(NLWTrace::OT_LT); + } + break; + } + case '>': { WWW_CHECK(p.size() > sign + 1, "wrong predicate operator format in adhoc trace: %s", p.data()); - if (p[sign + 1] == '=') { - value++; - op->SetType(NLWTrace::OT_GE); - } else { - op->SetType(NLWTrace::OT_GT); - } - break; - } + if (p[sign + 1] == '=') { + value++; + op->SetType(NLWTrace::OT_GE); + } else { + op->SetType(NLWTrace::OT_GT); + } + break; + } default: WWW_CHECK(false, "wrong predicate operator format in adhoc trace: %s", p.data()); - } - op->AddArgument()->SetValue(p.substr(value)); - } - + } + op->AddArgument()->SetValue(p.substr(value)); + } + void ParseAction(NLWTrace::TAction* action, const TString& a) - { - // NOTE: checks for longer action names should go first, your captain. - if (a.substr(0, 3) == "lsr") { - auto pb = action->MutableRunLogShuttleAction(); + { + // NOTE: checks for longer action names should go first, your captain. + if (a.substr(0, 3) == "lsr") { + auto pb = action->MutableRunLogShuttleAction(); for (const TString& opt : SplitString(a.substr(3), "-")) { - if (!opt.empty()) { - switch (opt[0]) { - case 'i': pb->SetIgnore(true); break; - case 's': pb->SetShuttlesCount(FromString<ui64>(opt.substr(1))); break; - case 't': pb->SetMaxTrackLength(FromString<ui64>(opt.substr(1))); break; + if (!opt.empty()) { + switch (opt[0]) { + case 'i': pb->SetIgnore(true); break; + case 's': pb->SetShuttlesCount(FromString<ui64>(opt.substr(1))); break; + case 't': pb->SetMaxTrackLength(FromString<ui64>(opt.substr(1))); break; default: WWW_CHECK(false, "unknown adhoc trace log shuttle opt '%s' in '%s'", opt.data(), a.data()); - } - } - } - } else if (a.substr(0, 3) == "lse") { - auto pb = action->MutableEditLogShuttleAction(); + } + } + } + } else if (a.substr(0, 3) == "lse") { + auto pb = action->MutableEditLogShuttleAction(); for (const TString& opt : SplitString(a.substr(3), "-")) { - if (!opt.empty()) { - switch (opt[0]) { - case 'i': pb->SetIgnore(true); break; + if (!opt.empty()) { + switch (opt[0]) { + case 'i': pb->SetIgnore(true); break; default: WWW_CHECK(false, "unknown adhoc trace log shuttle opt '%s' in '%s'", opt.data(), a.data()); - } - } - } - } else if (a.substr(0, 3) == "lsd") { - action->MutableDropLogShuttleAction(); - } else if (a.substr(0, 1) == "l") { - auto pb = action->MutableLogAction(); + } + } + } + } else if (a.substr(0, 3) == "lsd") { + action->MutableDropLogShuttleAction(); + } else if (a.substr(0, 1) == "l") { + auto pb = action->MutableLogAction(); for (const TString& opt : SplitString(a.substr(1), "-")) { - if (!opt.empty()) { - switch (opt[0]) { - case 't': pb->SetLogTimestamp(true); break; - case 'r': pb->SetMaxRecords(FromString<ui32>(opt.substr(1))); break; + if (!opt.empty()) { + switch (opt[0]) { + case 't': pb->SetLogTimestamp(true); break; + case 'r': pb->SetMaxRecords(FromString<ui32>(opt.substr(1))); break; default: WWW_CHECK(false, "unknown adhoc trace log opt '%s' in '%s'", opt.data(), a.data()); - } - } - } - } else { + } + } + } + } else { WWW_CHECK(false, "wrong action format in adhoc trace: %s", a.data()); - } - } - + } + } + static ui64 ParseDuration(const TString& s) - { - if (s.substr(s.length() - 2) == "us") - return FromString<ui64>(s.substr(0, s.length() - 2)); - if (s.substr(s.length() - 2) == "ms") - return FromString<ui64>(s.substr(0, s.length() - 2)) * 1000; - if (s.substr(s.length() - 1) == "s") - return FromString<ui64>(s.substr(0, s.length() - 1)) * 1000 * 1000; - if (s.substr(s.length() - 1) == "m") - return FromString<ui64>(s.substr(0, s.length() - 1)) * 60 * 1000 * 1000; - else - return FromString<ui64>(s); - } -}; - -// Class that maintains one thread iff there are adhoc traces and cleans'em by deadlines -class TTraceCleaner { -private: - NLWTrace::TManager* TraceMngr; - TAtomic Quit = 0; - - TMutex Mtx; - TCondVar WakeCondVar; + { + if (s.substr(s.length() - 2) == "us") + return FromString<ui64>(s.substr(0, s.length() - 2)); + if (s.substr(s.length() - 2) == "ms") + return FromString<ui64>(s.substr(0, s.length() - 2)) * 1000; + if (s.substr(s.length() - 1) == "s") + return FromString<ui64>(s.substr(0, s.length() - 1)) * 1000 * 1000; + if (s.substr(s.length() - 1) == "m") + return FromString<ui64>(s.substr(0, s.length() - 1)) * 60 * 1000 * 1000; + else + return FromString<ui64>(s); + } +}; + +// Class that maintains one thread iff there are adhoc traces and cleans'em by deadlines +class TTraceCleaner { +private: + NLWTrace::TManager* TraceMngr; + TAtomic Quit = 0; + + TMutex Mtx; + TCondVar WakeCondVar; THashMap<TString, TInstant> Deadlines; - volatile bool ThreadIsRunning = false; - THolder<TThread> Thread; -public: - TTraceCleaner(NLWTrace::TManager* traceMngr) - : TraceMngr(traceMngr) - {} - - ~TTraceCleaner() - { - AtomicSet(Quit, 1); - WakeCondVar.Signal(); - // TThread dtor joins thread - } - - // Returns deadline for specified trace id or zero + volatile bool ThreadIsRunning = false; + THolder<TThread> Thread; +public: + TTraceCleaner(NLWTrace::TManager* traceMngr) + : TraceMngr(traceMngr) + {} + + ~TTraceCleaner() + { + AtomicSet(Quit, 1); + WakeCondVar.Signal(); + // TThread dtor joins thread + } + + // Returns deadline for specified trace id or zero TInstant GetDeadline(const TString& id) const - { - TGuard<TMutex> g(Mtx); - auto iter = Deadlines.find(id); - return iter != Deadlines.end()? iter->second: TInstant::Zero(); - } - - // Postpone deletion of specified trace for specified timeout + { + TGuard<TMutex> g(Mtx); + auto iter = Deadlines.find(id); + return iter != Deadlines.end()? iter->second: TInstant::Zero(); + } + + // Postpone deletion of specified trace for specified timeout void Postpone(const TString& id, TDuration timeout, bool allowLowering) - { - TGuard<TMutex> g(Mtx); - TInstant newDeadline = TInstant::Now() + timeout; - if (Deadlines[id] < newDeadline) { - Deadlines[id] = newDeadline; - } else if (allowLowering) { // Deadline lowering requires wake - Deadlines[id] = newDeadline; - WakeCondVar.Signal(); - } - if (newDeadline != TInstant::Max() && !ThreadIsRunning) { - // Note that dtor joins previous thread if any - Thread.Reset(new TThread(ThreadProc, this)); - Thread->Start(); - ThreadIsRunning = true; - } - } - - // Forget about specified trace deletion + { + TGuard<TMutex> g(Mtx); + TInstant newDeadline = TInstant::Now() + timeout; + if (Deadlines[id] < newDeadline) { + Deadlines[id] = newDeadline; + } else if (allowLowering) { // Deadline lowering requires wake + Deadlines[id] = newDeadline; + WakeCondVar.Signal(); + } + if (newDeadline != TInstant::Max() && !ThreadIsRunning) { + // Note that dtor joins previous thread if any + Thread.Reset(new TThread(ThreadProc, this)); + Thread->Start(); + ThreadIsRunning = true; + } + } + + // Forget about specified trace deletion void Forget(const TString& id) - { - TGuard<TMutex> g(Mtx); - Deadlines.erase(id); - WakeCondVar.Signal(); // in case thread is not required any more - } -private: - void Exec() - { - while (!AtomicGet(Quit)) { - TGuard<TMutex> g(Mtx); - - // Delete all timed out traces - TInstant now = TInstant::Now(); - TInstant nextDeadline = TInstant::Max(); - for (auto i = Deadlines.begin(), e = Deadlines.end(); i != e;) { + { + TGuard<TMutex> g(Mtx); + Deadlines.erase(id); + WakeCondVar.Signal(); // in case thread is not required any more + } +private: + void Exec() + { + while (!AtomicGet(Quit)) { + TGuard<TMutex> g(Mtx); + + // Delete all timed out traces + TInstant now = TInstant::Now(); + TInstant nextDeadline = TInstant::Max(); + for (auto i = Deadlines.begin(), e = Deadlines.end(); i != e;) { const TString& id = i->first; - TInstant deadline = i->second; - if (deadline < now) { - try { - TraceMngr->Delete(id); - } catch (...) { - // already deleted - } - Deadlines.erase(i++); - } else { - nextDeadline = Min(nextDeadline, deadline); - ++i; - } - } - - // Stop thread if there is no more work - if (Deadlines.empty() || nextDeadline == TInstant::Max()) { - ThreadIsRunning = false; - break; - } - - // Wait until next deadline or quit - WakeCondVar.WaitD(Mtx, nextDeadline); - } - } - - static void* ThreadProc(void* _this) - { + TInstant deadline = i->second; + if (deadline < now) { + try { + TraceMngr->Delete(id); + } catch (...) { + // already deleted + } + Deadlines.erase(i++); + } else { + nextDeadline = Min(nextDeadline, deadline); + ++i; + } + } + + // Stop thread if there is no more work + if (Deadlines.empty() || nextDeadline == TInstant::Max()) { + ThreadIsRunning = false; + break; + } + + // Wait until next deadline or quit + WakeCondVar.WaitD(Mtx, nextDeadline); + } + } + + static void* ThreadProc(void* _this) + { TString name = "LWTraceCleaner"; - // Copy-pasted from kikimr/core/util/thread.h -#if defined(_linux_) - TStringStream linuxName; - linuxName << TStringBuf(GetExecPath()).RNextTok('/') << "." << name; + // Copy-pasted from kikimr/core/util/thread.h +#if defined(_linux_) + TStringStream linuxName; + linuxName << TStringBuf(GetExecPath()).RNextTok('/') << "." << name; TThread::SetCurrentThreadName(linuxName.Str().data()); -#else +#else TThread::SetCurrentThreadName(name.data()); -#endif - static_cast<TTraceCleaner*>(_this)->Exec(); - return nullptr; - } -}; - -class TChromeTrace { -private: +#endif + static_cast<TTraceCleaner*>(_this)->Exec(); + return nullptr; + } +}; + +class TChromeTrace { +private: TMultiMap<double, TString> TraceEvents; THashMap<TThread::TId, TString> Tids; -public: +public: void Add(TThread::TId tid, ui64 tsCycles, const TString& ph, const TString& cat, - const NLWTrace::TLogItem* argsItem = nullptr, + const NLWTrace::TLogItem* argsItem = nullptr, const TString& name = TString(), const TString& id = TString()) - { - auto tidIter = Tids.find(tid); - if (tidIter == Tids.end()) { - tidIter = Tids.emplace(tid, ToString(Tids.size() + 1)).first; - } + { + auto tidIter = Tids.find(tid); + if (tidIter == Tids.end()) { + tidIter = Tids.emplace(tid, ToString(Tids.size() + 1)).first; + } const TString& shortId = tidIter->second; - double ts = Timestamp(tsCycles); - TraceEvents.emplace(ts, Event(shortId, ts, ph, cat, argsItem, name, id)); - } - + double ts = Timestamp(tsCycles); + TraceEvents.emplace(ts, Event(shortId, ts, ph, cat, argsItem, name, id)); + } + void Output(IOutputStream& os) - { - os << "{\"traceEvents\":["; - bool first = true; - for (auto kv : TraceEvents) { - if (!first) { - os << ",\n"; - } - os << kv.second; - first = false; - } - os << "]}"; - } - -private: + { + os << "{\"traceEvents\":["; + bool first = true; + for (auto kv : TraceEvents) { + if (!first) { + os << ",\n"; + } + os << kv.second; + first = false; + } + os << "]}"; + } + +private: static TString Event(const TString& tid, double ts, const TString& ph, const TString& cat, - const NLWTrace::TLogItem* argsItem, + const NLWTrace::TLogItem* argsItem, const TString& name, const TString& id) - { - TStringStream ss; - pid_t pid = 1; - ss << "{\"pid\":" << pid - << ",\"tid\":" << tid - << ",\"ts\":" << Sprintf("%lf", ts) - << ",\"ph\":\"" << ph << "\"" - << ",\"cat\":\"" << cat << "\""; - if (name) { - ss << ",\"name\":\"" << name << "\""; - } - if (id) { - ss << ",\"id\":\"" << id << "\""; - } - if (argsItem && argsItem->SavedParamsCount > 0) { - ss << ",\"args\":{"; + { + TStringStream ss; + pid_t pid = 1; + ss << "{\"pid\":" << pid + << ",\"tid\":" << tid + << ",\"ts\":" << Sprintf("%lf", ts) + << ",\"ph\":\"" << ph << "\"" + << ",\"cat\":\"" << cat << "\""; + if (name) { + ss << ",\"name\":\"" << name << "\""; + } + if (id) { + ss << ",\"id\":\"" << id << "\""; + } + if (argsItem && argsItem->SavedParamsCount > 0) { + ss << ",\"args\":{"; TString paramValues[LWTRACE_MAX_PARAMS]; - argsItem->Probe->Event.Signature.SerializeParams(argsItem->Params, paramValues); - bool first = true; - for (size_t pi = 0; pi < argsItem->SavedParamsCount; pi++, first = false) { - if (!first) { - ss << ","; - } + argsItem->Probe->Event.Signature.SerializeParams(argsItem->Params, paramValues); + bool first = true; + for (size_t pi = 0; pi < argsItem->SavedParamsCount; pi++, first = false) { + if (!first) { + ss << ","; + } ss << "\"" << TString(argsItem->Probe->Event.Signature.ParamNames[pi]) << "\":" - "\"" << paramValues[pi] << "\""; - } - ss << "}"; - } - ss << "}"; - return ss.Str(); - } - - static double Timestamp(ui64 cycles) - { - return double(cycles) * 1000000.0 / NHPTimer::GetClockRate(); - } -}; - -TString MakeUrl(const TCgiParameters& e, const THashMap<TString, TString>& values) -{ - TStringStream ss; - bool first = true; - for (const auto& [k, v] : e) { - if (values.find(k) == values.end()) { - ss << (first? "?": "&") << k << "=" << v; - first = false; - } - } - for (const auto& [k, v] : values) { - ss << (first? "?": "&") << k << "=" << v; - first = false; - } - return ss.Str(); -} - + "\"" << paramValues[pi] << "\""; + } + ss << "}"; + } + ss << "}"; + return ss.Str(); + } + + static double Timestamp(ui64 cycles) + { + return double(cycles) * 1000000.0 / NHPTimer::GetClockRate(); + } +}; + +TString MakeUrl(const TCgiParameters& e, const THashMap<TString, TString>& values) +{ + TStringStream ss; + bool first = true; + for (const auto& [k, v] : e) { + if (values.find(k) == values.end()) { + ss << (first? "?": "&") << k << "=" << v; + first = false; + } + } + for (const auto& [k, v] : values) { + ss << (first? "?": "&") << k << "=" << v; + first = false; + } + return ss.Str(); +} + TString MakeUrl(const TCgiParameters& e, const TString& key, const TString& value, bool keep = false) -{ - TStringStream ss; - bool first = true; - for (const auto& kv : e) { - if (keep || kv.first != key) { - ss << (first? "?": "&") << kv.first << "=" << kv.second; - first = false; - } - } - ss << (first? "?": "&") << key << "=" << value; - return ss.Str(); -} - +{ + TStringStream ss; + bool first = true; + for (const auto& kv : e) { + if (keep || kv.first != key) { + ss << (first? "?": "&") << kv.first << "=" << kv.second; + first = false; + } + } + ss << (first? "?": "&") << key << "=" << value; + return ss.Str(); +} + TString MakeUrlAdd(const TCgiParameters& e, const TString& key, const TString& value) -{ - TStringStream ss; - bool first = true; - for (const auto& kv : e) { - ss << (first? "?": "&") << kv.first << "=" << kv.second; - first = false; - } - ss << (first? "?": "&") << key << "=" << value; - return ss.Str(); -} - +{ + TStringStream ss; + bool first = true; + for (const auto& kv : e) { + ss << (first? "?": "&") << kv.first << "=" << kv.second; + first = false; + } + ss << (first? "?": "&") << key << "=" << value; + return ss.Str(); +} + TString MakeUrlReplace(const TCgiParameters& e, const TString& key, const TString& oldValue, const TString& newValue) -{ - TStringStream ss; - bool first = true; - bool inserted = false; - for (const auto& kv : e) { - if (kv.first == key && (kv.second == oldValue || kv.second == newValue)) { - if (!inserted) { - inserted = true; - ss << (first? "?": "&") << key << "=" << newValue; - first = false; - } - } else { - ss << (first? "?": "&") << kv.first << "=" << kv.second; - first = false; - } - } - if (!inserted) { - ss << (first? "?": "&") << key << "=" << newValue; - } - return ss.Str(); -} - +{ + TStringStream ss; + bool first = true; + bool inserted = false; + for (const auto& kv : e) { + if (kv.first == key && (kv.second == oldValue || kv.second == newValue)) { + if (!inserted) { + inserted = true; + ss << (first? "?": "&") << key << "=" << newValue; + first = false; + } + } else { + ss << (first? "?": "&") << kv.first << "=" << kv.second; + first = false; + } + } + if (!inserted) { + ss << (first? "?": "&") << key << "=" << newValue; + } + return ss.Str(); +} + TString MakeUrlErase(const TCgiParameters& e, const TString& key, const TString& value) -{ - TStringStream ss; - bool first = true; - for (const auto& kv : e) { - if (kv.first != key || kv.second != value) { - ss << (first? "?": "&") << kv.first << "=" << kv.second; - first = false; - } - } - return ss.Str(); -} - +{ + TStringStream ss; + bool first = true; + for (const auto& kv : e) { + if (kv.first != key || kv.second != value) { + ss << (first? "?": "&") << kv.first << "=" << kv.second; + first = false; + } + } + return ss.Str(); +} + TString EscapeSubvalue(const TString& s) -{ +{ TString ret; ret.reserve(s.size()); for (size_t i = 0; i < s.size(); i++) { - char c = s[i]; - if (c == ':') { - ret.append("^c"); - } else if (c == '^') { - ret.append("^^"); - } else { - ret.append(c); - } - } - return ret; -} - + char c = s[i]; + if (c == ':') { + ret.append("^c"); + } else if (c == '^') { + ret.append("^^"); + } else { + ret.append(c); + } + } + return ret; +} + TString UnescapeSubvalue(const TString& s) -{ +{ TString ret; ret.reserve(s.size()); for (size_t i = 0; i < s.size(); i++) { - char c = s[i]; + char c = s[i]; if (c == '^' && i + 1 < s.size()) { - char c2 = s[++i]; - if (c2 == 'c') { - ret.append(':'); - } else if (c2 == '^') { - ret.append('^'); - } else { - ret.append(c); - ret.append(c2); - } - } else { - ret.append(c); - } - } - return ret; -} - + char c2 = s[++i]; + if (c2 == 'c') { + ret.append(':'); + } else if (c2 == '^') { + ret.append('^'); + } else { + ret.append(c); + ret.append(c2); + } + } else { + ret.append(c); + } + } + return ret; +} + TVector<TString> Subvalues(const TCgiParameters& e, const TString& key) -{ - if (!e.Has(key)) { +{ + if (!e.Has(key)) { return TVector<TString>(); - } else { + } else { TVector<TString> ret; for (const TString& s : SplitString(e.Get(key), ":", 0, KEEP_EMPTY_TOKENS)) { - ret.push_back(UnescapeSubvalue(s)); - } - if (ret.empty()) { - ret.push_back(""); - } - return ret; - } -} - + ret.push_back(UnescapeSubvalue(s)); + } + if (ret.empty()) { + ret.push_back(""); + } + return ret; + } +} + TString ParseTagsOut(const TString& taggedStr, TTags& tags) -{ +{ auto vec = SplitString(taggedStr, "-"); - if (vec.empty()) { - return ""; - } - auto iter = vec.begin(); + if (vec.empty()) { + return ""; + } + auto iter = vec.begin(); TString value = *iter++; - for (;iter != vec.end(); ++iter) { - tags.insert(*iter); - } - return value; -} - + for (;iter != vec.end(); ++iter) { + tags.insert(*iter); + } + return value; +} + TString JoinTags(TTags tags) { return JoinStrings(TVector<TString>(tags.begin(), tags.end()), "-"); -} - +} + TString MakeValue(const TVector<TString>& subvalues) -{ +{ TVector<TString> subvaluesEsc; for (const TString& s : subvalues) { - subvaluesEsc.push_back(EscapeSubvalue(s)); - } + subvaluesEsc.push_back(EscapeSubvalue(s)); + } return JoinStrings(subvaluesEsc, ":"); -} - +} + TString MakeUrlAddSub(const TCgiParameters& e, const TString& key, const TString& subvalue) -{ +{ const TString& value = e.Get(key); - auto subvalues = Subvalues(e, key); - subvalues.push_back(subvalue); - return MakeUrlReplace(e, key, value, MakeValue(subvalues)); -} - + auto subvalues = Subvalues(e, key); + subvalues.push_back(subvalue); + return MakeUrlReplace(e, key, value, MakeValue(subvalues)); +} + TString MakeUrlReplaceSub(const TCgiParameters& e, const TString& key, const TString& oldSubvalue, const TString& newSubvalue) -{ +{ const TString& value = e.Get(key); - auto subvalues = Subvalues(e, key); - auto iter = std::find(subvalues.begin(), subvalues.end(), oldSubvalue); - if (iter != subvalues.end()) { - *iter = newSubvalue; - } else { - subvalues.push_back(newSubvalue); - } - return MakeUrlReplace(e, key, value, MakeValue(subvalues)); -} - + auto subvalues = Subvalues(e, key); + auto iter = std::find(subvalues.begin(), subvalues.end(), oldSubvalue); + if (iter != subvalues.end()) { + *iter = newSubvalue; + } else { + subvalues.push_back(newSubvalue); + } + return MakeUrlReplace(e, key, value, MakeValue(subvalues)); +} + TString MakeUrlEraseSub(const TCgiParameters& e, const TString& key, const TString& subvalue) -{ +{ const TString& value = e.Get(key); - auto subvalues = Subvalues(e, key); - auto iter = std::find(subvalues.begin(), subvalues.end(), subvalue); - if (iter != subvalues.end()) { - subvalues.erase(iter); - } - if (subvalues.empty()) { - return MakeUrlErase(e, key, value); - } else { - return MakeUrlReplace(e, key, value, MakeValue(subvalues)); - } -} - + auto subvalues = Subvalues(e, key); + auto iter = std::find(subvalues.begin(), subvalues.end(), subvalue); + if (iter != subvalues.end()) { + subvalues.erase(iter); + } + if (subvalues.empty()) { + return MakeUrlErase(e, key, value); + } else { + return MakeUrlReplace(e, key, value, MakeValue(subvalues)); + } +} + template <bool sub> TString UrlAdd(const TCgiParameters& e, const TString& key, const TString& value); template <> TString UrlAdd<false>(const TCgiParameters& e, const TString& key, const TString& value) { - return MakeUrlAdd(e, key, value); -} + return MakeUrlAdd(e, key, value); +} template <> TString UrlAdd<true>(const TCgiParameters& e, const TString& key, const TString& value) { - return MakeUrlAddSub(e, key, value); -} - + return MakeUrlAddSub(e, key, value); +} + template <bool sub> TString UrlReplace(const TCgiParameters& e, const TString& key, const TString& oldValue, const TString& newValue); template <> TString UrlReplace<false>(const TCgiParameters& e, const TString& key, const TString& oldValue, const TString& newValue) { - return MakeUrlReplace(e, key, oldValue, newValue); -} + return MakeUrlReplace(e, key, oldValue, newValue); +} template <> TString UrlReplace<true>(const TCgiParameters& e, const TString& key, const TString& oldValue, const TString& newValue) { - return MakeUrlReplaceSub(e, key, oldValue, newValue); -} - + return MakeUrlReplaceSub(e, key, oldValue, newValue); +} + template <bool sub> TString UrlErase(const TCgiParameters& e, const TString& key, const TString& value); template <> TString UrlErase<false>(const TCgiParameters& e, const TString& key, const TString& value) { - return MakeUrlErase(e, key, value); -} + return MakeUrlErase(e, key, value); +} template <> TString UrlErase<true>(const TCgiParameters& e, const TString& key, const TString& value) { - return MakeUrlEraseSub(e, key, value); -} - + return MakeUrlEraseSub(e, key, value); +} + void OutputCommonHeader(IOutputStream& out) -{ - out << NResource::Find("lwtrace/mon/static/header.html") << Endl; -} - +{ + out << NResource::Find("lwtrace/mon/static/header.html") << Endl; +} + void OutputCommonFooter(IOutputStream& out) -{ - out << NResource::Find("lwtrace/mon/static/footer.html") << Endl; -} - -struct TScopedHtmlInner { +{ + out << NResource::Find("lwtrace/mon/static/footer.html") << Endl; +} + +struct TScopedHtmlInner { explicit TScopedHtmlInner(IOutputStream& str) - : Str(str) - { - Str << "<!DOCTYPE html>\n" - "<html>"; - HTML(str) { - HEAD() { OutputCommonHeader(Str); } - } - Str << "<body>"; - } - - ~TScopedHtmlInner() - { - OutputCommonFooter(Str); - Str << "</body></html>"; - } - - inline operator bool () const noexcept { return true; } - + : Str(str) + { + Str << "<!DOCTYPE html>\n" + "<html>"; + HTML(str) { + HEAD() { OutputCommonHeader(Str); } + } + Str << "<body>"; + } + + ~TScopedHtmlInner() + { + OutputCommonFooter(Str); + Str << "</body></html>"; + } + + inline operator bool () const noexcept { return true; } + IOutputStream &Str; -}; - +}; + TString NavbarHeader() -{ - return "<div class=\"navbar-header\">" - "<a class=\"navbar-brand\" href=\"?mode=\">LWTrace</a>" - "</div>"; -} - -struct TSelectorsContainer { +{ + return "<div class=\"navbar-header\">" + "<a class=\"navbar-brand\" href=\"?mode=\">LWTrace</a>" + "</div>"; +} + +struct TSelectorsContainer { TSelectorsContainer(IOutputStream& str) - : Str(str) - { - Str << "<nav id=\"selectors-container\" class=\"navbar navbar-default\">" - "<div class=\"container-fluid\">" - << NavbarHeader() << - "<div class=\"navbar-text\" style=\"margin-top:12px;margin-bottom:10px\">"; - } - - ~TSelectorsContainer() { - try { - Str << - "</div>" - "<div class=\"container-fluid\">" - "<div class=\"pull-right\">" - "<button id=\"download-btn\"" - " type=\"button\" style=\"display: inline-block;margin:7px\"" - " title=\"Chromium trace (load it in chrome://tracing/)\"" - " class=\"btn btn-default hidden\">" - "<span class=\"glyphicon glyphicon-download-alt\"></span>" - "</button>" - "</div>" - "</div>" - "</div></nav>"; - } catch(...) {} - } - + : Str(str) + { + Str << "<nav id=\"selectors-container\" class=\"navbar navbar-default\">" + "<div class=\"container-fluid\">" + << NavbarHeader() << + "<div class=\"navbar-text\" style=\"margin-top:12px;margin-bottom:10px\">"; + } + + ~TSelectorsContainer() { + try { + Str << + "</div>" + "<div class=\"container-fluid\">" + "<div class=\"pull-right\">" + "<button id=\"download-btn\"" + " type=\"button\" style=\"display: inline-block;margin:7px\"" + " title=\"Chromium trace (load it in chrome://tracing/)\"" + " class=\"btn btn-default hidden\">" + "<span class=\"glyphicon glyphicon-download-alt\"></span>" + "</button>" + "</div>" + "</div>" + "</div></nav>"; + } catch(...) {} + } + IOutputStream& Str; -}; - -struct TNullContainer { +}; + +struct TNullContainer { TNullContainer(IOutputStream&) {} -}; - -class TPageGenBase: public std::exception {}; -template <class TContainer = TNullContainer> -class TPageGen: public TPageGenBase { -private: +}; + +class TPageGenBase: public std::exception {}; +template <class TContainer = TNullContainer> +class TPageGen: public TPageGenBase { +private: TString Content; TString HttpResponse; -public: - void BuildResponse() - { - TStringStream ss; - WWW_HTML(ss) { - TContainer container(ss); - ss << Content; - } - HttpResponse = ss.Str(); - } - +public: + void BuildResponse() + { + TStringStream ss; + WWW_HTML(ss) { + TContainer container(ss); + ss << Content; + } + HttpResponse = ss.Str(); + } + explicit TPageGen(const TString& content = TString()) - : Content(content) - { - BuildResponse(); - } - + : Content(content) + { + BuildResponse(); + } + void Append(const TString& moreContent) - { - Content.append(moreContent); - BuildResponse(); - } - + { + Content.append(moreContent); + BuildResponse(); + } + void Prepend(const TString& moreContent) - { - Content.prepend(moreContent); - BuildResponse(); - } - + { + Content.prepend(moreContent); + BuildResponse(); + } + virtual const char* what() const noexcept { return HttpResponse.data(); } operator bool() const { return !Content.empty(); } -}; - -enum EStyleFlags { - // bit 1 - Link = 0x0, - Button = 0x1, - - // bit 2 - NonErasable = 0x0, - Erasable = 0x2, - - // bit 3-4 - Medium = 0x0, - Large = 0x4, - Small = 0x8, - ExtraSmall = 0xC, - SizeMask = 0xC, - - // bit 5 - NoCaret = 0x0, - Caret = 0x10, - - // bit 6 - SimpleValue = 0x0, - CompositeValue = 0x20 -}; - -template <ui64 flags> +}; + +enum EStyleFlags { + // bit 1 + Link = 0x0, + Button = 0x1, + + // bit 2 + NonErasable = 0x0, + Erasable = 0x2, + + // bit 3-4 + Medium = 0x0, + Large = 0x4, + Small = 0x8, + ExtraSmall = 0xC, + SizeMask = 0xC, + + // bit 5 + NoCaret = 0x0, + Caret = 0x10, + + // bit 6 + SimpleValue = 0x0, + CompositeValue = 0x20 +}; + +template <ui64 flags> TString BtnClass() { - if ((flags & SizeMask) == Large) { - return "btn btn-lg"; - } else if ((flags & SizeMask) == Small) { - return "btn btn-sm"; - } else if ((flags & SizeMask) == ExtraSmall) { - return "btn btn-xs"; - } - return "btn"; -} - + if ((flags & SizeMask) == Large) { + return "btn btn-lg"; + } else if ((flags & SizeMask) == Small) { + return "btn btn-sm"; + } else if ((flags & SizeMask) == ExtraSmall) { + return "btn btn-xs"; + } + return "btn"; +} + void SelectorTitle(IOutputStream& os, const TString& text) -{ - if (!text.empty()) { - os << text; - } -} - -template <ui64 flags> +{ + if (!text.empty()) { + os << text; + } +} + +template <ui64 flags> void BtnHref(IOutputStream& os, const TString& text, const TString& href, bool push = false) -{ - if (flags & Button) { - os << "<button type=\"button\" style=\"display: inline-block;margin:3px\" class=\"" - << BtnClass<flags>() << " " - << (push? "btn-primary": "btn-default") - << "\" onClick=\"window.location.href='" << href << "';\">" - << text - << "</button>"; - } else { - os << "<a href=\"" << href << "\">" - << text - << "</a>"; - } -} - +{ + if (flags & Button) { + os << "<button type=\"button\" style=\"display: inline-block;margin:3px\" class=\"" + << BtnClass<flags>() << " " + << (push? "btn-primary": "btn-default") + << "\" onClick=\"window.location.href='" << href << "';\">" + << text + << "</button>"; + } else { + os << "<a href=\"" << href << "\">" + << text + << "</a>"; + } +} + void DropdownBeginSublist(IOutputStream& os, const TString& text) -{ - os << "<li>" << text << "<ul class=\"dropdown-menu\">"; -} - +{ + os << "<li>" << text << "<ul class=\"dropdown-menu\">"; +} + void DropdownEndSublist(IOutputStream& os) -{ - os << "</ul></li>"; -} - +{ + os << "</ul></li>"; +} + void DropdownItem(IOutputStream& os, const TString& text, const TString& href, bool separated = false) -{ - if (separated) { - os << "<li role=\"separator\" class=\"divider\"></li>"; - } - os << "<li><a href=\"" << href << "\">" << text << "</a></li>"; -} - +{ + if (separated) { + os << "<li role=\"separator\" class=\"divider\"></li>"; + } + os << "<li><a href=\"" << href << "\">" << text << "</a></li>"; +} + TString SuggestSelection() -{ - return "--- "; -} - +{ + return "--- "; +} + TString RemoveSelection() -{ - return "Remove"; -} - +{ + return "Remove"; +} + TString GetDescription(const TString& value, const TVariants& variants) -{ - for (const auto& var : variants) { - if (value == var.first) { - return var.second; - } - } - if (!value) { - return SuggestSelection(); - } - return value; -} - -template <ui64 flags, bool sub = false> +{ + for (const auto& var : variants) { + if (value == var.first) { + return var.second; + } + } + if (!value) { + return SuggestSelection(); + } + return value; +} + +template <ui64 flags, bool sub = false> void DropdownSelector(IOutputStream& os, const TCgiParameters& e, const TString& param, const TString& value, const TString& text, const TVariants& variants, const TString& realValue = TString()) -{ - HTML(os) { - SelectorTitle(os, text); - os << "<div class=\"dropdown\" style=\"display:inline-block;margin:3px\">"; - if (flags & Button) { - os << "<button class=\"" << BtnClass<flags>() << " btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\">"; - } else { - os << "<a href=\"#\" data-toggle=\"dropdown\">"; - } - os << GetDescription(flags & CompositeValue? realValue: value, variants); - if (flags & Caret) { - os << "<span class=\"caret\"></span>"; - } - if (flags & Button) { - os <<"</button>"; - } else { - os <<"</a>"; - } - UL_CLASS ("dropdown-menu") { - for (const auto& var : variants) { - DropdownItem(os, var.second, UrlReplace<sub>(e, param, value, var.first)); - } - if (flags & Erasable) { - DropdownItem(os, RemoveSelection(), UrlErase<sub>(e, param, value), true); - } - } - os << "</div>"; - } -} - +{ + HTML(os) { + SelectorTitle(os, text); + os << "<div class=\"dropdown\" style=\"display:inline-block;margin:3px\">"; + if (flags & Button) { + os << "<button class=\"" << BtnClass<flags>() << " btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\">"; + } else { + os << "<a href=\"#\" data-toggle=\"dropdown\">"; + } + os << GetDescription(flags & CompositeValue? realValue: value, variants); + if (flags & Caret) { + os << "<span class=\"caret\"></span>"; + } + if (flags & Button) { + os <<"</button>"; + } else { + os <<"</a>"; + } + UL_CLASS ("dropdown-menu") { + for (const auto& var : variants) { + DropdownItem(os, var.second, UrlReplace<sub>(e, param, value, var.first)); + } + if (flags & Erasable) { + DropdownItem(os, RemoveSelection(), UrlErase<sub>(e, param, value), true); + } + } + os << "</div>"; + } +} + void RequireSelection(TStringStream& ss, const TCgiParameters& e, const TString& param, const TString& text, const TVariants& variants) -{ +{ const TString& value = e.Get(param); - DropdownSelector<Link>(ss, e, param, value, text, variants); - if (!value) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } -} - + DropdownSelector<Link>(ss, e, param, value, text, variants); + if (!value) { + throw TPageGen<TSelectorsContainer>(ss.Str()); + } +} + void RequireMultipleSelection(TStringStream& ss, const TCgiParameters& e, const TString& param, const TString& text, const TVariants& variants) -{ - SelectorTitle(ss, text); +{ + SelectorTitle(ss, text); TSet<TString> selectedValues; for (const TString& subvalue : Subvalues(e, param)) { - selectedValues.insert(subvalue); - } + selectedValues.insert(subvalue); + } for (const TString& subvalue : Subvalues(e, param)) { - DropdownSelector<Erasable, true>(ss, e, param, subvalue, "", variants); - } + DropdownSelector<Erasable, true>(ss, e, param, subvalue, "", variants); + } if (selectedValues.contains("")) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } else { - BtnHref<Button|ExtraSmall>(ss, "+", MakeUrlAddSub(e, param, "")); - if (selectedValues.empty()) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } - } -} - + throw TPageGen<TSelectorsContainer>(ss.Str()); + } else { + BtnHref<Button|ExtraSmall>(ss, "+", MakeUrlAddSub(e, param, "")); + if (selectedValues.empty()) { + throw TPageGen<TSelectorsContainer>(ss.Str()); + } + } +} + void OptionalSelection(TStringStream& ss, const TCgiParameters& e, const TString& param, const TString& text, const TVariants& variants) { @@ -1424,251 +1424,251 @@ void OptionalSelection(TStringStream& ss, const TCgiParameters& e, const TString void OptionalMultipleSelection(TStringStream& ss, const TCgiParameters& e, const TString& param, const TString& text, const TVariants& variants) -{ +{ TSet<TString> selectedValues; for (const TString& subvalue : Subvalues(e, param)) { - selectedValues.insert(subvalue); - } - if (!selectedValues.empty()) { - SelectorTitle(ss, text); - } + selectedValues.insert(subvalue); + } + if (!selectedValues.empty()) { + SelectorTitle(ss, text); + } for (const TString& subvalue : Subvalues(e, param)) { - DropdownSelector<Erasable, true>(ss, e, param, subvalue, "", variants); - } + DropdownSelector<Erasable, true>(ss, e, param, subvalue, "", variants); + } if (selectedValues.contains("")) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } else { - BtnHref<Button|ExtraSmall>(ss, selectedValues.empty()? text: "+", MakeUrlAddSub(e, param, "")); - } -} - -TVariants ListColumns(const NAnalytics::TTable& table) -{ + throw TPageGen<TSelectorsContainer>(ss.Str()); + } else { + BtnHref<Button|ExtraSmall>(ss, selectedValues.empty()? text: "+", MakeUrlAddSub(e, param, "")); + } +} + +TVariants ListColumns(const NAnalytics::TTable& table) +{ TSet<TString> cols; -// bool addSpecialCols = false; -// if (addSpecialCols) { -// cols.insert("_count"); -// } - for (auto& row : table) { - for (auto& kv : row) { - cols.insert(kv.first); - } - } - TVariants result; - for (const auto& s : cols) { - result.emplace_back(s, s); - } - return result; -} - +// bool addSpecialCols = false; +// if (addSpecialCols) { +// cols.insert("_count"); +// } + for (auto& row : table) { + for (auto& kv : row) { + cols.insert(kv.first); + } + } + TVariants result; + for (const auto& s : cols) { + result.emplace_back(s, s); + } + return result; +} + TString TaggedValue(const TString& value, const TString& tag) -{ - if (!tag) { - return value; - } - return value + "-" + tag; -} - +{ + if (!tag) { + return value; + } + return value + "-" + tag; +} + TVariants ValueVars(const TVariants& values, const TString& tag) -{ - TVariants ret; - for (auto& p : values) { - ret.emplace_back(TaggedValue(p.first, tag), p.second); - } - return ret; -} - +{ + TVariants ret; + for (auto& p : values) { + ret.emplace_back(TaggedValue(p.first, tag), p.second); + } + return ret; +} + TVariants TagVars(const TString& value, const TVariants& tags) -{ - TVariants ret; - for (auto& p : tags) { - ret.emplace_back(TaggedValue(value, p.first), p.second); - } - return ret; -} - -TVariants SeriesTags() -{ - TVariants ret; // MSVS2013 doesn't understand complex initializer lists - ret.emplace_back("", "as is"); - ret.emplace_back("stack", "cumulative"); - return ret; -} - -void SeriesSelectors(TStringStream& ss, const TCgiParameters& e, +{ + TVariants ret; + for (auto& p : tags) { + ret.emplace_back(TaggedValue(value, p.first), p.second); + } + return ret; +} + +TVariants SeriesTags() +{ + TVariants ret; // MSVS2013 doesn't understand complex initializer lists + ret.emplace_back("", "as is"); + ret.emplace_back("stack", "cumulative"); + return ret; +} + +void SeriesSelectors(TStringStream& ss, const TCgiParameters& e, const TString& xparam, const TString& yparam, const NAnalytics::TTable& data) -{ - TTags xtags; +{ + TTags xtags; TString xn = ParseTagsOut(e.Get(xparam), xtags); - DropdownSelector<Erasable, true>(ss, e, xparam, e.Get(xparam), "with Ox:", - ValueVars(ListColumns(data), JoinTags(xtags))); - if (xn) { - DropdownSelector<Link, true>(ss, e, xparam, e.Get(xparam), "", - TagVars(xn, SeriesTags())); - } - + DropdownSelector<Erasable, true>(ss, e, xparam, e.Get(xparam), "with Ox:", + ValueVars(ListColumns(data), JoinTags(xtags))); + if (xn) { + DropdownSelector<Link, true>(ss, e, xparam, e.Get(xparam), "", + TagVars(xn, SeriesTags())); + } + TString yns = e.Get(yparam); - SelectorTitle(ss, "and Oy:"); - bool first = true; - bool hasEmpty = false; - for (auto& subvalue : Subvalues(e, yparam)) { - TTags ytags; + SelectorTitle(ss, "and Oy:"); + bool first = true; + bool hasEmpty = false; + for (auto& subvalue : Subvalues(e, yparam)) { + TTags ytags; TString yn = ParseTagsOut(subvalue, ytags); - DropdownSelector<Erasable, true>(ss, e, yparam, subvalue, first? "": ", ", - ValueVars(ListColumns(data), JoinTags(ytags))); - if (yn) { - DropdownSelector<Link, true>(ss, e, yparam, subvalue, "", - TagVars(yn, SeriesTags())); - } - first = false; - if (yn.empty()) { - hasEmpty = true; - } - } - - if (hasEmpty) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } else { - BtnHref<Button|ExtraSmall>(ss, "+", MakeUrlAddSub(e, yparam, "")); - } - - if (!xn || !yns) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } -} - + DropdownSelector<Erasable, true>(ss, e, yparam, subvalue, first? "": ", ", + ValueVars(ListColumns(data), JoinTags(ytags))); + if (yn) { + DropdownSelector<Link, true>(ss, e, yparam, subvalue, "", + TagVars(yn, SeriesTags())); + } + first = false; + if (yn.empty()) { + hasEmpty = true; + } + } + + if (hasEmpty) { + throw TPageGen<TSelectorsContainer>(ss.Str()); + } else { + BtnHref<Button|ExtraSmall>(ss, "+", MakeUrlAddSub(e, yparam, "")); + } + + if (!xn || !yns) { + throw TPageGen<TSelectorsContainer>(ss.Str()); + } +} + class TProbesHtmlPrinter { private: TVector<TVector<TString>> TableData; - static constexpr int TimeoutSec = 15 * 60; // default timeout + static constexpr int TimeoutSec = 15 * 60; // default timeout public: void Push(const NLWTrace::TProbe* probe) { TableData.emplace_back(); - auto& row = TableData.back(); - + auto& row = TableData.back(); + row.emplace_back(); TString& groups = row.back(); bool first = true; for (const char* const* i = probe->Event.Groups; *i != nullptr; ++i, first = false) { groups.append(TString(first? "": ", ") + GroupHtml(*i)); } - - row.push_back(ProbeHtml(probe->Event.GetProvider(), probe->Event.Name)); - + + row.push_back(ProbeHtml(probe->Event.GetProvider(), probe->Event.Name)); + row.emplace_back(); TString& params = row.back(); first = true; for (size_t i = 0; i < probe->Event.Signature.ParamCount; i++, first = false) { params.append(TString(first? "": ", ") + probe->Event.Signature.ParamTypes[i] - + " " + probe->Event.Signature.ParamNames[i]); + + " " + probe->Event.Signature.ParamNames[i]); } - - row.emplace_back(ToString(probe->GetExecutorsCount())); + + row.emplace_back(ToString(probe->GetExecutorsCount())); } - + void Output(IOutputStream& os) - { - HTML(os) { - TABLE() { - TABLEHEAD() { - TABLEH() { os << "Groups"; } - TABLEH() { os << "Name"; } - TABLEH() { os << "Params"; } - TABLEH() { os << "ExecCount"; } - } - TABLEBODY() { - for (auto& row : TableData) { - TABLER() { + { + HTML(os) { + TABLE() { + TABLEHEAD() { + TABLEH() { os << "Groups"; } + TABLEH() { os << "Name"; } + TABLEH() { os << "Params"; } + TABLEH() { os << "ExecCount"; } + } + TABLEBODY() { + for (auto& row : TableData) { + TABLER() { for (TString& cell : row) { - TABLED() { os << cell; } - } - } - } - } - } - } - } -private: + TABLED() { os << cell; } + } + } + } + } + } + } + } +private: TString GroupHtml(const TString& group) - { - TStringStream ss; - ss << "<div class=\"dropdown\" style=\"display:inline-block\">" - "<a href=\"#\" data-toggle=\"dropdown\">" << group << "</a>" - "<ul class=\"dropdown-menu\">" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(group).Id() << "'});\">" - "Trace 1000 items</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(group, 10000).Id() << "'});\">" - "Trace 10000 items</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(group, 0, 1000000).Id() << "'});\">" - "Trace 1 second</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(group, 0, 10000000).Id() << "'});\">" - "Trace 10 seconds</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(group, 0, 0, true).Id() << "'});\">" - "Trace 1000 tracks</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(group, 10000, 0, true).Id() << "'});\">" - "Trace 10000 tracks</a></li>" - "</ul>" - "</div>"; - return ss.Str(); - } - + { + TStringStream ss; + ss << "<div class=\"dropdown\" style=\"display:inline-block\">" + "<a href=\"#\" data-toggle=\"dropdown\">" << group << "</a>" + "<ul class=\"dropdown-menu\">" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(group).Id() << "'});\">" + "Trace 1000 items</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(group, 10000).Id() << "'});\">" + "Trace 10000 items</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(group, 0, 1000000).Id() << "'});\">" + "Trace 1 second</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(group, 0, 10000000).Id() << "'});\">" + "Trace 10 seconds</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(group, 0, 0, true).Id() << "'});\">" + "Trace 1000 tracks</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(group, 10000, 0, true).Id() << "'});\">" + "Trace 10000 tracks</a></li>" + "</ul>" + "</div>"; + return ss.Str(); + } + TString ProbeHtml(const TString& provider, const TString& probe) - { - TStringStream ss; - ss << "<div class=\"dropdown\">" - "<a href=\"#\" data-toggle=\"dropdown\">" << probe << "</a>" - "<ul class=\"dropdown-menu\">" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(provider, probe).Id() << "'});\">" - "Trace 1000 items</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(provider, probe, 10000).Id() << "'});\">" - "Trace 10000 items</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(provider, probe, 0, 1000000).Id() << "'});\">" - "Trace 1 second</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(provider, probe, 0, 10000000).Id() << "'});\">" - "Trace 10 seconds</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(provider, probe, 0, 0, true).Id() << "'});\">" - "Trace 1000 tracks</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," - "{id:'" << TAdHocTraceConfig(provider, probe, 10000, 0, true).Id() << "'});\">" - "Trace 10000 tracks</a></li>" - "</ul>" - "</div>"; - return ss.Str(); - } + { + TStringStream ss; + ss << "<div class=\"dropdown\">" + "<a href=\"#\" data-toggle=\"dropdown\">" << probe << "</a>" + "<ul class=\"dropdown-menu\">" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(provider, probe).Id() << "'});\">" + "Trace 1000 items</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(provider, probe, 10000).Id() << "'});\">" + "Trace 10000 items</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(provider, probe, 0, 1000000).Id() << "'});\">" + "Trace 1 second</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(provider, probe, 0, 10000000).Id() << "'});\">" + "Trace 10 seconds</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(provider, probe, 0, 0, true).Id() << "'});\">" + "Trace 1000 tracks</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=new&ui=y&timeout=" << TimeoutSec << "'," + "{id:'" << TAdHocTraceConfig(provider, probe, 10000, 0, true).Id() << "'});\">" + "Trace 10000 tracks</a></li>" + "</ul>" + "</div>"; + return ss.Str(); + } }; -void TDashboardRegistry::Register(const NLWTrace::TDashboard& dashboard) { +void TDashboardRegistry::Register(const NLWTrace::TDashboard& dashboard) { TGuard<TMutex> g(Mutex); Dashboards[dashboard.GetName()] = dashboard; } -void TDashboardRegistry::Register(const TVector<NLWTrace::TDashboard>& dashboards) { - for (const auto& dashboard : dashboards) { - Register(dashboard); - } -} - +void TDashboardRegistry::Register(const TVector<NLWTrace::TDashboard>& dashboards) { + for (const auto& dashboard : dashboards) { + Register(dashboard); + } +} + void TDashboardRegistry::Register(const TString& dashText) { - NLWTrace::TDashboard dash; + NLWTrace::TDashboard dash; if (!google::protobuf::TextFormat::ParseFromString(dashText, &dash)) { ythrow yexception() << "Couldn't parse into dashboard"; } Register(dash); } -bool TDashboardRegistry::Get(const TString& name, NLWTrace::TDashboard& dash) { +bool TDashboardRegistry::Get(const TString& name, NLWTrace::TDashboard& dash) { TGuard<TMutex> g(Mutex); if (!Dashboards.contains(name)) { return false; @@ -1698,488 +1698,488 @@ void TDashboardRegistry::Output(TStringStream& ss) { } } -class ILogSource { -public: - virtual ~ILogSource() {} +class ILogSource { +public: + virtual ~ILogSource() {} virtual TString GetId() = 0; - virtual TInstant GetStartTime() = 0; - virtual TDuration GetTimeout(TInstant now) = 0; - virtual ui64 GetEventsCount() = 0; - virtual ui64 GetThreadsCount() = 0; -}; - -class TTraceLogSource : public ILogSource { -private: + virtual TInstant GetStartTime() = 0; + virtual TDuration GetTimeout(TInstant now) = 0; + virtual ui64 GetEventsCount() = 0; + virtual ui64 GetThreadsCount() = 0; +}; + +class TTraceLogSource : public ILogSource { +private: TString Id; - const TTraceCleaner& Cleaner; - const NLWTrace::TSession* Trace; -public: - TTraceLogSource(const TString& id, const NLWTrace::TSession* trace, const TTraceCleaner& cleaner) - : Id(id) - , Cleaner(cleaner) - , Trace(trace) - {} - + const TTraceCleaner& Cleaner; + const NLWTrace::TSession* Trace; +public: + TTraceLogSource(const TString& id, const NLWTrace::TSession* trace, const TTraceCleaner& cleaner) + : Id(id) + , Cleaner(cleaner) + , Trace(trace) + {} + TString GetId() override - { - return Id; - } - - TInstant GetStartTime() override - { - return Trace->GetStartTime(); - } - - TDuration GetTimeout(TInstant now) override - { - TInstant deadline = Cleaner.GetDeadline(Id); - if (deadline < now) { - return TDuration::Zero(); - } else if (deadline == TInstant::Max()) { - return TDuration::Max(); - } else { - return deadline - now; - } - } - - ui64 GetEventsCount() override - { - return Trace->GetEventsCount(); - } - - ui64 GetThreadsCount() override - { - return Trace->GetThreadsCount(); - } -}; - -class TSnapshotLogSource : public ILogSource { -private: + { + return Id; + } + + TInstant GetStartTime() override + { + return Trace->GetStartTime(); + } + + TDuration GetTimeout(TInstant now) override + { + TInstant deadline = Cleaner.GetDeadline(Id); + if (deadline < now) { + return TDuration::Zero(); + } else if (deadline == TInstant::Max()) { + return TDuration::Max(); + } else { + return deadline - now; + } + } + + ui64 GetEventsCount() override + { + return Trace->GetEventsCount(); + } + + ui64 GetThreadsCount() override + { + return Trace->GetThreadsCount(); + } +}; + +class TSnapshotLogSource : public ILogSource { +private: TString Sid; - // Log should be used for read-only purpose, because it can be accessed from multiple threads - // Atomic pointer is used to avoid thread-safety issues with snapshot deletion - // (I hope protobuf const-implementation doesn't use any mutable non-thread-safe stuff inside) - TAtomicSharedPtr<NLWTrace::TLogPb> Log; -public: - // Constructor should be called under SnapshotsMtx lock + // Log should be used for read-only purpose, because it can be accessed from multiple threads + // Atomic pointer is used to avoid thread-safety issues with snapshot deletion + // (I hope protobuf const-implementation doesn't use any mutable non-thread-safe stuff inside) + TAtomicSharedPtr<NLWTrace::TLogPb> Log; +public: + // Constructor should be called under SnapshotsMtx lock TSnapshotLogSource(const TString& sid, const TAtomicSharedPtr<NLWTrace::TLogPb>& log) - : Sid(sid) - , Log(log) - {} - + : Sid(sid) + , Log(log) + {} + TString GetId() override - { - return Sid + "~"; - } - - TInstant GetStartTime() override - { - return TInstant::MicroSeconds(Log->GetCrtTime()); - } - - TDuration GetTimeout(TInstant now) override - { - Y_UNUSED(now); - return TDuration::Max(); - } - - ui64 GetEventsCount() override - { - return Log->GetEventsCount(); - } - - ui64 GetThreadsCount() override - { - return Log->ThreadLogsSize(); - } -}; - -class TLogSources { -private: - TTraceCleaner& Cleaner; - TInstant Now; + { + return Sid + "~"; + } + + TInstant GetStartTime() override + { + return TInstant::MicroSeconds(Log->GetCrtTime()); + } + + TDuration GetTimeout(TInstant now) override + { + Y_UNUSED(now); + return TDuration::Max(); + } + + ui64 GetEventsCount() override + { + return Log->GetEventsCount(); + } + + ui64 GetThreadsCount() override + { + return Log->ThreadLogsSize(); + } +}; + +class TLogSources { +private: + TTraceCleaner& Cleaner; + TInstant Now; using TLogSourcePtr = std::unique_ptr<ILogSource>; TMap<TString, TLogSourcePtr> LogSources; -public: - explicit TLogSources(TTraceCleaner& cleaner, TInstant now = TInstant::Now()) - : Cleaner(cleaner) - , Now(now) - {} - +public: + explicit TLogSources(TTraceCleaner& cleaner, TInstant now = TInstant::Now()) + : Cleaner(cleaner) + , Now(now) + {} + void Push(const TString& sid, const TAtomicSharedPtr<NLWTrace::TLogPb>& log) - { - TLogSourcePtr ls(new TSnapshotLogSource(sid, log)); + { + TLogSourcePtr ls(new TSnapshotLogSource(sid, log)); LogSources.emplace(ls->GetId(), std::move(ls)); - } - - void Push(const TString& id, const NLWTrace::TSession* trace) - { - TLogSourcePtr ls(new TTraceLogSource(id, trace, Cleaner)); + } + + void Push(const TString& id, const NLWTrace::TSession* trace) + { + TLogSourcePtr ls(new TTraceLogSource(id, trace, Cleaner)); LogSources.emplace(ls->GetId(), std::move(ls)); - } - - template <class TFunc> - void ForEach(TFunc& func) - { - for (auto& kv : LogSources) { + } + + template <class TFunc> + void ForEach(TFunc& func) + { + for (auto& kv : LogSources) { func.Push(kv.second.get()); - } - } - - template <class TFunc> - void ForEach(TFunc& func) const - { - for (const auto& kv : LogSources) { + } + } + + template <class TFunc> + void ForEach(TFunc& func) const + { + for (const auto& kv : LogSources) { func.Push(kv.second.get()); - } - } -}; - + } + } +}; + class TTracesHtmlPrinter { private: IOutputStream& Os; - TInstant Now; + TInstant Now; public: explicit TTracesHtmlPrinter(IOutputStream& os) : Os(os) - , Now(TInstant::Now()) + , Now(TInstant::Now()) {} - void Push(ILogSource* src) + void Push(ILogSource* src) { TString id = src->GetId(); Os << "<tr>"; Os << "<td>"; try { - Os << src->GetStartTime().ToStringUpToSeconds(); + Os << src->GetStartTime().ToStringUpToSeconds(); } catch (...) { Os << "error: " << CurrentExceptionMessage(); } Os << "</td>" - << "<td><div class=\"dropdown\">" - "<a href=\"#\" data-toggle=\"dropdown\">" << TimeoutToString(src->GetTimeout(Now)) << "</a>" - "<ul class=\"dropdown-menu\">" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=60', {id:'" << id << "'});\">1 min</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=600', {id:'" << id << "'});\">10 min</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=3600', {id:'" << id << "'});\">1 hour</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=86400', {id:'" << id << "'});\">1 day</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=604800', {id:'" << id << "'});\">1 week</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y', {id:'" << id << "'});\">no timeout</a></li>" - "</ul>" - "</div></td>" + << "<td><div class=\"dropdown\">" + "<a href=\"#\" data-toggle=\"dropdown\">" << TimeoutToString(src->GetTimeout(Now)) << "</a>" + "<ul class=\"dropdown-menu\">" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=60', {id:'" << id << "'});\">1 min</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=600', {id:'" << id << "'});\">10 min</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=3600', {id:'" << id << "'});\">1 hour</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=86400', {id:'" << id << "'});\">1 day</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y&timeout=604800', {id:'" << id << "'});\">1 week</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=settimeout&ui=y', {id:'" << id << "'});\">no timeout</a></li>" + "</ul>" + "</div></td>" << "<td>" << EncodeHtmlPcdata(id) << "</td>" - << "<td>" << src->GetEventsCount() << "</td>" - << "<td>" << src->GetThreadsCount() << "</td>" + << "<td>" << src->GetEventsCount() << "</td>" + << "<td>" << src->GetThreadsCount() << "</td>" << "<td><a href=\"?mode=log&id=" << id << "\">Text</a></td>" << "<td><a href=\"?mode=log&format=json&id=" << id << "\">Json</a></td>" << "<td><a href=\"?mode=query&id=" << id << "\">Query</a></td>" - << "<td><a href=\"?mode=analytics&id=" << id << "\">Analytics</a></td>" - << "<td><div class=\"dropdown navbar-right\">" // navbar-right is hack to drop left - "<a href=\"#\" data-toggle=\"dropdown\">Modify</a>" - "<ul class=\"dropdown-menu\">" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=make_snapshot&ui=y', {id:'" << id << "'});\">Snapshot</a></li>" - "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=delete&ui=y', {id:'" << id << "'});\">Delete</a></li>" - "</ul>" - "</div></td>" + << "<td><a href=\"?mode=analytics&id=" << id << "\">Analytics</a></td>" + << "<td><div class=\"dropdown navbar-right\">" // navbar-right is hack to drop left + "<a href=\"#\" data-toggle=\"dropdown\">Modify</a>" + "<ul class=\"dropdown-menu\">" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=make_snapshot&ui=y', {id:'" << id << "'});\">Snapshot</a></li>" + "<li><a href=\"#\" onClick=\"$.redirectPost('?mode=delete&ui=y', {id:'" << id << "'});\">Delete</a></li>" + "</ul>" + "</div></td>" << "</tr>\n"; } -private: +private: static TString TimeoutToString(TDuration d) - { - TStringStream ss; - if (d == TDuration::Zero()) { - ss << "0"; - } else if (d == TDuration::Max()) { - ss << "-"; - } else { - ui64 us = d.GetValue(); - ui64 ms = us / 1000; - ui64 sec = ms / 1000; - ui64 min = sec / 60; - ui64 hours = min / 60; - ui64 days = hours / 24; - ui64 weeks = days / 7; - us -= ms * 1000; - ms -= sec * 1000; - sec -= min * 60; - min -= hours * 60; - hours -= days * 24; - days -= weeks * 7; - int terms = 0; - if ((terms > 0 && terms < 2) || ( terms == 0 && weeks)) { ss << (ss.Str()? " ": "") << weeks << "w"; terms++; } - if ((terms > 0 && terms < 2) || ( terms == 0 && days)) { ss << (ss.Str()? " ": "") << days << "d"; terms++; } - if ((terms > 0 && terms < 2) || ( terms == 0 && hours)) { ss << (ss.Str()? " ": "") << hours << "h"; terms++; } - if ((terms > 0 && terms < 2) || ( terms == 0 && min)) { ss << (ss.Str()? " ": "") << min << "m"; terms++; } - if ((terms > 0 && terms < 2) || ( terms == 0 && sec)) { ss << (ss.Str()? " ": "") << sec << "s"; terms++; } - if ((terms > 0 && terms < 2) || ( terms == 0 && ms)) { ss << (ss.Str()? " ": "") << ms << "ms"; terms++; } - if ((terms > 0 && terms < 2) || ( terms == 0 && us)) { ss << (ss.Str()? " ": "") << us << "us"; terms++; } - } - return ss.Str(); - } + { + TStringStream ss; + if (d == TDuration::Zero()) { + ss << "0"; + } else if (d == TDuration::Max()) { + ss << "-"; + } else { + ui64 us = d.GetValue(); + ui64 ms = us / 1000; + ui64 sec = ms / 1000; + ui64 min = sec / 60; + ui64 hours = min / 60; + ui64 days = hours / 24; + ui64 weeks = days / 7; + us -= ms * 1000; + ms -= sec * 1000; + sec -= min * 60; + min -= hours * 60; + hours -= days * 24; + days -= weeks * 7; + int terms = 0; + if ((terms > 0 && terms < 2) || ( terms == 0 && weeks)) { ss << (ss.Str()? " ": "") << weeks << "w"; terms++; } + if ((terms > 0 && terms < 2) || ( terms == 0 && days)) { ss << (ss.Str()? " ": "") << days << "d"; terms++; } + if ((terms > 0 && terms < 2) || ( terms == 0 && hours)) { ss << (ss.Str()? " ": "") << hours << "h"; terms++; } + if ((terms > 0 && terms < 2) || ( terms == 0 && min)) { ss << (ss.Str()? " ": "") << min << "m"; terms++; } + if ((terms > 0 && terms < 2) || ( terms == 0 && sec)) { ss << (ss.Str()? " ": "") << sec << "s"; terms++; } + if ((terms > 0 && terms < 2) || ( terms == 0 && ms)) { ss << (ss.Str()? " ": "") << ms << "ms"; terms++; } + if ((terms > 0 && terms < 2) || ( terms == 0 && us)) { ss << (ss.Str()? " ": "") << us << "us"; terms++; } + } + return ss.Str(); + } }; -class TTracesLister { -private: - TVariants& Variants; -public: - TTracesLister(TVariants& variants) - : Variants(variants) - {} - void Push(ILogSource* src) - { - Variants.emplace_back(src->GetId(), src->GetId()); - } -}; - -TVariants ListTraces(const TLogSources& srcs) -{ - TVariants variants; - TTracesLister lister(variants); - srcs.ForEach(lister); - return variants; -} - -class TTimestampCutter { -private: +class TTracesLister { +private: + TVariants& Variants; +public: + TTracesLister(TVariants& variants) + : Variants(variants) + {} + void Push(ILogSource* src) + { + Variants.emplace_back(src->GetId(), src->GetId()); + } +}; + +TVariants ListTraces(const TLogSources& srcs) +{ + TVariants variants; + TTracesLister lister(variants); + srcs.ForEach(lister); + return variants; +} + +class TTimestampCutter { +private: THashMap<TThread::TId, std::pair<ui64, TInstant>> CutTsForThread; // tid -> time of first item - mutable ui64 CutTsMax = 0; - mutable TInstant CutInstantMax; - bool Enabled; - ui64 NowTs; -public: - explicit TTimestampCutter(bool enabled) - : Enabled(enabled) - , NowTs(GetCycleCount()) - {} - - void Push(TThread::TId tid, const NLWTrace::TLogItem& item) - { - auto it = CutTsForThread.find(tid); - if (it != CutTsForThread.end()) { - ui64& ts = it->second.first; - TInstant& inst = it->second.second; - ts = Min(ts, item.TimestampCycles); - inst = Min(inst, item.Timestamp); - } else { - CutTsForThread[tid] = std::make_pair(item.TimestampCycles, item.Timestamp); - } - } - - // Timestamp from which we are ensured that cyclic log for every thread is not truncated - // NOTE: should NOT be called from Push(tid, item) functions - ui64 StartTimestamp() const - { - if (CutTsMax == 0) { - FindStartTime(); - } - return CutTsMax; - } - - ui64 NowTimestamp() const - { - return NowTs; - } - - TInstant StartInstant() const - { - if (CutInstantMax == TInstant::Zero()) { - FindStartTime(); - } - return CutInstantMax; - } - - // Returns true iff item should be skipped to avoid surprizes - bool Skip(const NLWTrace::TLogItem& item) const - { - return Enabled && item.TimestampCycles < StartTimestamp(); - } - -private: - void FindStartTime() const - { - for (auto& kv : CutTsForThread) { - CutTsMax = Max(CutTsMax, kv.second.first); - CutInstantMax = Max(CutInstantMax, kv.second.second); - } - } -}; - + mutable ui64 CutTsMax = 0; + mutable TInstant CutInstantMax; + bool Enabled; + ui64 NowTs; +public: + explicit TTimestampCutter(bool enabled) + : Enabled(enabled) + , NowTs(GetCycleCount()) + {} + + void Push(TThread::TId tid, const NLWTrace::TLogItem& item) + { + auto it = CutTsForThread.find(tid); + if (it != CutTsForThread.end()) { + ui64& ts = it->second.first; + TInstant& inst = it->second.second; + ts = Min(ts, item.TimestampCycles); + inst = Min(inst, item.Timestamp); + } else { + CutTsForThread[tid] = std::make_pair(item.TimestampCycles, item.Timestamp); + } + } + + // Timestamp from which we are ensured that cyclic log for every thread is not truncated + // NOTE: should NOT be called from Push(tid, item) functions + ui64 StartTimestamp() const + { + if (CutTsMax == 0) { + FindStartTime(); + } + return CutTsMax; + } + + ui64 NowTimestamp() const + { + return NowTs; + } + + TInstant StartInstant() const + { + if (CutInstantMax == TInstant::Zero()) { + FindStartTime(); + } + return CutInstantMax; + } + + // Returns true iff item should be skipped to avoid surprizes + bool Skip(const NLWTrace::TLogItem& item) const + { + return Enabled && item.TimestampCycles < StartTimestamp(); + } + +private: + void FindStartTime() const + { + for (auto& kv : CutTsForThread) { + CutTsMax = Max(CutTsMax, kv.second.first); + CutInstantMax = Max(CutInstantMax, kv.second.second); + } + } +}; + class TLogFilter { private: - struct TFilter { + struct TFilter { TString ParamName; TString ParamValue; - bool Parsed; - - TLogQuery Query; - NLWTrace::TLiteral Value; - + bool Parsed; + + TLogQuery Query; + NLWTrace::TLiteral Value; + explicit TFilter(const TString& text) - { - if (!text) { // Neither ParamName nor ParamValue is selected - ParamName.clear(); - ParamValue.clear(); - Parsed = false; - return; - } - size_t pos = text.find('='); + { + if (!text) { // Neither ParamName nor ParamValue is selected + ParamName.clear(); + ParamValue.clear(); + Parsed = false; + return; + } + size_t pos = text.find('='); if (pos == TString::npos) { // Only ParamName has been selected - ParamName = text; - ParamValue.clear(); - Parsed = false; - return; - } - // Both ParamName and ParamValue have been selected - ParamValue = text.substr(pos + 1); - ParamName = text.substr(0, pos); - Parsed = true; - - Query = TLogQuery(ParamName); - Value = NLWTrace::TLiteral(ParamValue); - } - }; + ParamName = text; + ParamValue.clear(); + Parsed = false; + return; + } + // Both ParamName and ParamValue have been selected + ParamValue = text.substr(pos + 1); + ParamName = text.substr(0, pos); + Parsed = true; + + Query = TLogQuery(ParamName); + Value = NLWTrace::TLiteral(ParamValue); + } + }; TVector<TFilter> Filters; THashSet<const NLWTrace::TSignature*> Signatures; // Just to list param names - TVariants ParamNames; + TVariants ParamNames; THashMap<TString, THashSet<TString>> FilteredParamValues; // paramName -> { paramValue } public: explicit TLogFilter(const TVector<TString>& filters) { for (const TString& subvalue : filters) { - TFilter filter(subvalue); - FilteredParamValues[filter.ParamName]; // just create empty set to gather values later - if (filter.Parsed) { - Filters.push_back(filter); - } + TFilter filter(subvalue); + FilteredParamValues[filter.ParamName]; // just create empty set to gather values later + if (filter.Parsed) { + Filters.push_back(filter); + } } } - virtual ~TLogFilter() {} - - template <class TLog> - bool Filter(const TLog& log) + virtual ~TLogFilter() {} + + template <class TLog> + bool Filter(const TLog& log) { - Gather(log); - for (const TFilter& filter : Filters) { - if (filter.Query.ExecuteQuery(log) != filter.Value) { - return false; + Gather(log); + for (const TFilter& filter : Filters) { + if (filter.Query.ExecuteQuery(log) != filter.Value) { + return false; } } - return true; + return true; } - + void FilterSelectors(TStringStream& ss, const TCgiParameters& e, const TString& fparam) - { - bool first = true; - bool allParsed = true; + { + bool first = true; + bool allParsed = true; for (const TString& subvalue : Subvalues(e, fparam)) { - TFilter filter(subvalue); - allParsed = allParsed && filter.Parsed; - if (first) { - SelectorTitle(ss, "where"); - } - DropdownSelector<Erasable | CompositeValue, true>( - ss, e, fparam, subvalue, first? "": ", ", ListParamNames(), - filter.ParamName - ); - if (filter.ParamName) { - DropdownSelector<Link | CompositeValue, true>( - ss, e, fparam, subvalue, "=", ListParamValues(filter.ParamName), - filter.ParamValue? (filter.ParamName + "=" + filter.ParamValue): "" - ); - } - first = false; - } - - if (!allParsed) { - throw TPageGen<TSelectorsContainer>(ss.Str()); - } else { - BtnHref<Button|ExtraSmall>(ss, first? "where": "+", MakeUrlAddSub(e, fparam, "")); - } - } - - const TVariants& ListParamNames() - { - if (ParamNames.empty()) { + TFilter filter(subvalue); + allParsed = allParsed && filter.Parsed; + if (first) { + SelectorTitle(ss, "where"); + } + DropdownSelector<Erasable | CompositeValue, true>( + ss, e, fparam, subvalue, first? "": ", ", ListParamNames(), + filter.ParamName + ); + if (filter.ParamName) { + DropdownSelector<Link | CompositeValue, true>( + ss, e, fparam, subvalue, "=", ListParamValues(filter.ParamName), + filter.ParamValue? (filter.ParamName + "=" + filter.ParamValue): "" + ); + } + first = false; + } + + if (!allParsed) { + throw TPageGen<TSelectorsContainer>(ss.Str()); + } else { + BtnHref<Button|ExtraSmall>(ss, first? "where": "+", MakeUrlAddSub(e, fparam, "")); + } + } + + const TVariants& ListParamNames() + { + if (ParamNames.empty()) { THashSet<TString> paramNames; - for (const NLWTrace::TSignature* sgn: Signatures) { - for (size_t pi = 0; pi < sgn->ParamCount; pi++) { - paramNames.insert(sgn->ParamNames[pi]); - } - } - for (auto& pn : paramNames) { - ParamNames.emplace_back(pn, pn); - } - } - return ParamNames; - } - + for (const NLWTrace::TSignature* sgn: Signatures) { + for (size_t pi = 0; pi < sgn->ParamCount; pi++) { + paramNames.insert(sgn->ParamNames[pi]); + } + } + for (auto& pn : paramNames) { + ParamNames.emplace_back(pn, pn); + } + } + return ParamNames; + } + bool IsFiltered(const TString& paramName) const - { + { return FilteredParamValues.contains(paramName); - } - -private: - // Gather param names and values for selectors - void Gather(const NLWTrace::TLogItem& item) - { - Signatures.insert(&item.Probe->Event.Signature); - if (!FilteredParamValues.empty() && item.SavedParamsCount > 0) { + } + +private: + // Gather param names and values for selectors + void Gather(const NLWTrace::TLogItem& item) + { + Signatures.insert(&item.Probe->Event.Signature); + if (!FilteredParamValues.empty() && item.SavedParamsCount > 0) { TString paramValues[LWTRACE_MAX_PARAMS]; - item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); - for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { - auto iter = FilteredParamValues.find(item.Probe->Event.Signature.ParamNames[pi]); - if (iter != FilteredParamValues.end()) { - iter->second.insert(paramValues[pi]); - } - } - } - } - - void Gather(const NLWTrace::TTrackLog& tl) - { - for (const NLWTrace::TLogItem& item : tl.Items) { - Gather(item); - } - } - + item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); + for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { + auto iter = FilteredParamValues.find(item.Probe->Event.Signature.ParamNames[pi]); + if (iter != FilteredParamValues.end()) { + iter->second.insert(paramValues[pi]); + } + } + } + } + + void Gather(const NLWTrace::TTrackLog& tl) + { + for (const NLWTrace::TLogItem& item : tl.Items) { + Gather(item); + } + } + TVariants ListParamValues(const TString& paramName) const - { - TVariants result; - auto iter = FilteredParamValues.find(paramName); - if (iter != FilteredParamValues.end()) { + { + TVariants result; + auto iter = FilteredParamValues.find(paramName); + if (iter != FilteredParamValues.end()) { for (const TString& paramValue : iter->second) { - result.emplace_back(paramName + "=" + paramValue, paramValue); - } - } - Sort(result.begin(), result.end()); - return result; - } + result.emplace_back(paramName + "=" + paramValue, paramValue); + } + } + Sort(result.begin(), result.end()); + return result; + } }; -static void EscapeJSONString(IOutputStream& os, const TString& s) -{ - for (TString::const_iterator i = s.begin(), e = s.end(); i != e; ++i) { - char c = *i; - if (c < ' ') { - os << Sprintf("\\u%04x", int(c)); - } else if (c == '"') { - os << "\\\""; - } else if (c == '\\') { - os << "\\\\"; - } else { - os << c; - } - } -} - -static TString EscapeJSONString(const TString& s) -{ - TStringStream ss; - EscapeJSONString(ss, s); - return ss.Str(); -} - +static void EscapeJSONString(IOutputStream& os, const TString& s) +{ + for (TString::const_iterator i = s.begin(), e = s.end(); i != e; ++i) { + char c = *i; + if (c < ' ') { + os << Sprintf("\\u%04x", int(c)); + } else if (c == '"') { + os << "\\\""; + } else if (c == '\\') { + os << "\\\\"; + } else { + os << c; + } + } +} + +static TString EscapeJSONString(const TString& s) +{ + TStringStream ss; + EscapeJSONString(ss, s); + return ss.Str(); +} + class TLogJsonPrinter { private: IOutputStream& Os; @@ -2199,15 +2199,15 @@ public: ; } - void OutputFooter(const NLWTrace::TSession* trace) + void OutputFooter(const NLWTrace::TSession* trace) { Os << "\n\t\t]" "\n\t, \"threads\": [" ; - trace->ReadThreads(*this); + trace->ReadThreads(*this); Os << "]" - "\n\t, \"events_count\": " << trace->GetEventsCount() << - "\n\t, \"threads_count\": " << trace->GetThreadsCount() << + "\n\t, \"events_count\": " << trace->GetEventsCount() << + "\n\t, \"threads_count\": " << trace->GetThreadsCount() << "\n\t, \"timestamp\": " << Now().GetValue() << "\n}" ; @@ -2219,7 +2219,7 @@ public: FirstThread = false; } - void Push(TThread::TId tid, const NLWTrace::TLogItem& item) + void Push(TThread::TId tid, const NLWTrace::TLogItem& item) { Os << "\n\t\t" << (FirstItem? "": ", "); FirstItem = false; @@ -2246,31 +2246,31 @@ public: class TLogTextPrinter : public TLogFilter { private: - TMultiMap<NLWTrace::TTypedParam, std::pair<TThread::TId, NLWTrace::TLogItem> > Items; - TMultiMap<NLWTrace::TTypedParam, NLWTrace::TTrackLog> Depot; - THashMap<NLWTrace::TProbe*, size_t> ProbeId; - TVector<NLWTrace::TProbe*> Probes; - TTimestampCutter CutTs; - TLogQuery Order; - bool ReverseOrder = false; - ui64 Head = 0; - ui64 Tail = 0; - bool ShowTs = false; + TMultiMap<NLWTrace::TTypedParam, std::pair<TThread::TId, NLWTrace::TLogItem> > Items; + TMultiMap<NLWTrace::TTypedParam, NLWTrace::TTrackLog> Depot; + THashMap<NLWTrace::TProbe*, size_t> ProbeId; + TVector<NLWTrace::TProbe*> Probes; + TTimestampCutter CutTs; + TLogQuery Order; + bool ReverseOrder = false; + ui64 Head = 0; + ui64 Tail = 0; + bool ShowTs = false; public: - TLogTextPrinter(const TVector<TString>& filters, ui64 head, ui64 tail, const TString& order, bool reverseOrder, bool cutTs, bool showTs) - : TLogFilter(filters) - , CutTs(cutTs) - , Order(order) - , ReverseOrder(reverseOrder) - , Head(head) - , Tail(tail) - , ShowTs(showTs) - {} - + TLogTextPrinter(const TVector<TString>& filters, ui64 head, ui64 tail, const TString& order, bool reverseOrder, bool cutTs, bool showTs) + : TLogFilter(filters) + , CutTs(cutTs) + , Order(order) + , ReverseOrder(reverseOrder) + , Head(head) + , Tail(tail) + , ShowTs(showTs) + {} + TLogTextPrinter(const TCgiParameters& e) : TLogTextPrinter( - Subvalues(e, "f"), - e.Has("head")? FromString<ui64>(e.Get("head")): 0, + Subvalues(e, "f"), + e.Has("head")? FromString<ui64>(e.Get("head")): 0, e.Has("tail")? FromString<ui64>(e.Get("tail")): 0, e.Get("s"), e.Get("reverse") == "y", @@ -2278,1630 +2278,1630 @@ public: e.Get("showts") == "y") {} - enum EFormat { - Text, - Json - }; - + enum EFormat { + Text, + Json + }; + void Output(IOutputStream& os) const { - OutputItems<Text>(os); - OutputDepot<Text>(os); - } - - void OutputJson(IOutputStream& os) const - { - os << "{\"depot\":[\n"; - OutputItems<Json>(os); - OutputDepot<Json>(os); - os << "],\"probes\":["; - bool first = true; - for (const NLWTrace::TProbe* probe : Probes) { - os << (first? "": ",") << "{\"provider\":\"" << probe->Event.GetProvider() - << "\",\"name\":\"" << probe->Event.Name << "\"}"; - first = false; - } - os << "]}"; - } - - NLWTrace::TTypedParam GetKey(const NLWTrace::TLogItem& item) - { - return Order? Order.ExecuteQuery(item): NLWTrace::TTypedParam(item.GetTimestampCycles()); - } - - NLWTrace::TTypedParam GetKey(const NLWTrace::TTrackLog& tl) - { - return Order? Order.ExecuteQuery(tl): NLWTrace::TTypedParam(tl.GetTimestampCycles()); - } - - void Push(TThread::TId tid, const NLWTrace::TLogItem& item) - { - CutTs.Push(tid, item); + OutputItems<Text>(os); + OutputDepot<Text>(os); + } + + void OutputJson(IOutputStream& os) const + { + os << "{\"depot\":[\n"; + OutputItems<Json>(os); + OutputDepot<Json>(os); + os << "],\"probes\":["; + bool first = true; + for (const NLWTrace::TProbe* probe : Probes) { + os << (first? "": ",") << "{\"provider\":\"" << probe->Event.GetProvider() + << "\",\"name\":\"" << probe->Event.Name << "\"}"; + first = false; + } + os << "]}"; + } + + NLWTrace::TTypedParam GetKey(const NLWTrace::TLogItem& item) + { + return Order? Order.ExecuteQuery(item): NLWTrace::TTypedParam(item.GetTimestampCycles()); + } + + NLWTrace::TTypedParam GetKey(const NLWTrace::TTrackLog& tl) + { + return Order? Order.ExecuteQuery(tl): NLWTrace::TTypedParam(tl.GetTimestampCycles()); + } + + void Push(TThread::TId tid, const NLWTrace::TLogItem& item) + { + CutTs.Push(tid, item); if (Filter(item)) { - AddId(item); - Items.emplace(GetKey(item), std::make_pair(tid, item)); - } - } - - void Push(TThread::TId tid, const NLWTrace::TTrackLog& tl) - { - Y_UNUSED(tid); - if (Filter(tl)) { - AddId(tl); - Depot.emplace(GetKey(tl), tl); - } - } - + AddId(item); + Items.emplace(GetKey(item), std::make_pair(tid, item)); + } + } + + void Push(TThread::TId tid, const NLWTrace::TTrackLog& tl) + { + Y_UNUSED(tid); + if (Filter(tl)) { + AddId(tl); + Depot.emplace(GetKey(tl), tl); + } + } + private: - void AddId(const NLWTrace::TLogItem& item) - { - if (ProbeId.find(item.Probe) == ProbeId.end()) { - size_t id = Probes.size(); - ProbeId[item.Probe] = id; - Probes.emplace_back(item.Probe); - } - } - - void AddId(const NLWTrace::TTrackLog& tl) - { - for (const auto& item : tl.Items) { - AddId(item); - } - } - - bool HeadTailFilter(ui64 idx, ui64 size) const - { - bool headOk = idx < Head; - bool tailOk = size < Tail + idx + 1ull; - if (Head && Tail) { - return headOk || tailOk; - } else if (Head) { - return headOk; - } else if (Tail) { - return tailOk; - } else { - return true; - } - } - - template <EFormat Format> + void AddId(const NLWTrace::TLogItem& item) + { + if (ProbeId.find(item.Probe) == ProbeId.end()) { + size_t id = Probes.size(); + ProbeId[item.Probe] = id; + Probes.emplace_back(item.Probe); + } + } + + void AddId(const NLWTrace::TTrackLog& tl) + { + for (const auto& item : tl.Items) { + AddId(item); + } + } + + bool HeadTailFilter(ui64 idx, ui64 size) const + { + bool headOk = idx < Head; + bool tailOk = size < Tail + idx + 1ull; + if (Head && Tail) { + return headOk || tailOk; + } else if (Head) { + return headOk; + } else if (Tail) { + return tailOk; + } else { + return true; + } + } + + template <EFormat Format> void OutputItems(IOutputStream& os) const - { - ui64 idx = 0; - ui64 size = Items.size(); - ui64 startTs = ShowTs? CutTs.StartTimestamp(): 0; - ui64 prevTs = 0; - bool first = true; - if (!ReverseOrder) { - for (auto i = Items.begin(), e = Items.end(); i != e; ++i, idx++) { - if (HeadTailFilter(idx, size)) { - OutputItem<Format, true>(os, i->second.first, i->second.second, startTs, prevTs, first); - prevTs = startTs? i->second.second.GetTimestampCycles(): 0; - } - } - } else { - for (auto i = Items.rbegin(), e = Items.rend(); i != e; ++i, idx++) { - if (HeadTailFilter(idx, size)) { - OutputItem<Format, true>(os, i->second.first, i->second.second, startTs, prevTs, first); - prevTs = startTs? i->second.second.GetTimestampCycles(): 0; - } - } - } - } - - template <EFormat Format> + { + ui64 idx = 0; + ui64 size = Items.size(); + ui64 startTs = ShowTs? CutTs.StartTimestamp(): 0; + ui64 prevTs = 0; + bool first = true; + if (!ReverseOrder) { + for (auto i = Items.begin(), e = Items.end(); i != e; ++i, idx++) { + if (HeadTailFilter(idx, size)) { + OutputItem<Format, true>(os, i->second.first, i->second.second, startTs, prevTs, first); + prevTs = startTs? i->second.second.GetTimestampCycles(): 0; + } + } + } else { + for (auto i = Items.rbegin(), e = Items.rend(); i != e; ++i, idx++) { + if (HeadTailFilter(idx, size)) { + OutputItem<Format, true>(os, i->second.first, i->second.second, startTs, prevTs, first); + prevTs = startTs? i->second.second.GetTimestampCycles(): 0; + } + } + } + } + + template <EFormat Format> void OutputDepot(IOutputStream& os) const - { - ui64 idx = 0; - ui64 size = Depot.size(); - bool first = true; - if (!ReverseOrder) { - for (auto i = Depot.begin(), e = Depot.end(); i != e; ++i, idx++) { - if (HeadTailFilter(idx, size)) { - OutputTrackLog<Format>(os, i->second, first); - } - } - } else { - for (auto i = Depot.rbegin(), e = Depot.rend(); i != e; ++i, idx++) { - if (HeadTailFilter(idx, size)) { - OutputTrackLog<Format>(os, i->second, first); - } - } - } - } - - template <EFormat Format, bool AsTrack = false> - void OutputItem(IOutputStream& os, TThread::TId tid, const NLWTrace::TLogItem& item, ui64 startTs, ui64 prevTs, bool& first) const - { - if (CutTs.Skip(item)) { - return; - } - if constexpr (Format == Text) { - if (startTs) { - if (!prevTs) { - prevTs = item.GetTimestampCycles(); - } - os << Sprintf("%10.3lf %+10.3lf ms ", - NHPTimer::GetSeconds(item.GetTimestampCycles() - startTs) * 1000.0, - NHPTimer::GetSeconds(item.GetTimestampCycles() - prevTs) * 1000.0); - } - if (tid) { - os << "<" << tid << "> "; - } - if (item.Timestamp != TInstant::Zero()) { - os << "[" << item.Timestamp << "] "; - } else { - os << "[" << item.TimestampCycles << "] "; - } - os << GetProbeName(item.Probe) << "("; - if (item.SavedParamsCount > 0) { - TString ParamValues[LWTRACE_MAX_PARAMS]; - item.Probe->Event.Signature.SerializeParams(item.Params, ParamValues); - bool first = true; - for (size_t i = 0; i < item.SavedParamsCount; i++, first = false) { - os << (first? "": ", ") << item.Probe->Event.Signature.ParamNames[i] << "='" << EscapeC(ParamValues[i]) << "'"; - } - } - os << ")\n"; - } else if constexpr (Format == Json) { - if (auto probeId = ProbeId.find(item.Probe); probeId != ProbeId.end()) { - os << (first? "": ",") << (AsTrack? "[":"") << "[\"" << tid << "\",\""; - if (item.Timestamp != TInstant::Zero()) { - os << item.Timestamp.MicroSeconds(); - } else { - os << Sprintf("%.3lf", NHPTimer::GetSeconds(item.TimestampCycles) * 1e9); - } - os << "\"," << probeId->second << ",{"; - if (item.SavedParamsCount > 0) { - TString ParamValues[LWTRACE_MAX_PARAMS]; - item.Probe->Event.Signature.SerializeParams(item.Params, ParamValues); - bool first = true; - for (size_t i = 0; i < item.SavedParamsCount; i++, first = false) { - os << (first? "": ",") << "\"" << item.Probe->Event.Signature.ParamNames[i] << "\":\""; - EscapeJSONString(os, ParamValues[i]); - os << "\""; - } - } - os << "}]" << (AsTrack? "]":""); - } - } - first = false; - } - - template <EFormat Format> - void OutputTrackLog(IOutputStream& os, const NLWTrace::TTrackLog& tl, bool& first) const - { - if constexpr (Format == Json) { - os << (first? "": ",") << "["; - } - first = false; - ui64 prevTs = tl.GetTimestampCycles(); - bool firstItem = true; - for (const NLWTrace::TTrackLog::TItem& item: tl.Items) { - OutputItem<Format>(os, item.ThreadId, item, tl.GetTimestampCycles(), prevTs, firstItem); - prevTs = item.GetTimestampCycles(); - } - if constexpr (Format == Json) { - os << "]"; - } - os << "\n"; - } + { + ui64 idx = 0; + ui64 size = Depot.size(); + bool first = true; + if (!ReverseOrder) { + for (auto i = Depot.begin(), e = Depot.end(); i != e; ++i, idx++) { + if (HeadTailFilter(idx, size)) { + OutputTrackLog<Format>(os, i->second, first); + } + } + } else { + for (auto i = Depot.rbegin(), e = Depot.rend(); i != e; ++i, idx++) { + if (HeadTailFilter(idx, size)) { + OutputTrackLog<Format>(os, i->second, first); + } + } + } + } + + template <EFormat Format, bool AsTrack = false> + void OutputItem(IOutputStream& os, TThread::TId tid, const NLWTrace::TLogItem& item, ui64 startTs, ui64 prevTs, bool& first) const + { + if (CutTs.Skip(item)) { + return; + } + if constexpr (Format == Text) { + if (startTs) { + if (!prevTs) { + prevTs = item.GetTimestampCycles(); + } + os << Sprintf("%10.3lf %+10.3lf ms ", + NHPTimer::GetSeconds(item.GetTimestampCycles() - startTs) * 1000.0, + NHPTimer::GetSeconds(item.GetTimestampCycles() - prevTs) * 1000.0); + } + if (tid) { + os << "<" << tid << "> "; + } + if (item.Timestamp != TInstant::Zero()) { + os << "[" << item.Timestamp << "] "; + } else { + os << "[" << item.TimestampCycles << "] "; + } + os << GetProbeName(item.Probe) << "("; + if (item.SavedParamsCount > 0) { + TString ParamValues[LWTRACE_MAX_PARAMS]; + item.Probe->Event.Signature.SerializeParams(item.Params, ParamValues); + bool first = true; + for (size_t i = 0; i < item.SavedParamsCount; i++, first = false) { + os << (first? "": ", ") << item.Probe->Event.Signature.ParamNames[i] << "='" << EscapeC(ParamValues[i]) << "'"; + } + } + os << ")\n"; + } else if constexpr (Format == Json) { + if (auto probeId = ProbeId.find(item.Probe); probeId != ProbeId.end()) { + os << (first? "": ",") << (AsTrack? "[":"") << "[\"" << tid << "\",\""; + if (item.Timestamp != TInstant::Zero()) { + os << item.Timestamp.MicroSeconds(); + } else { + os << Sprintf("%.3lf", NHPTimer::GetSeconds(item.TimestampCycles) * 1e9); + } + os << "\"," << probeId->second << ",{"; + if (item.SavedParamsCount > 0) { + TString ParamValues[LWTRACE_MAX_PARAMS]; + item.Probe->Event.Signature.SerializeParams(item.Params, ParamValues); + bool first = true; + for (size_t i = 0; i < item.SavedParamsCount; i++, first = false) { + os << (first? "": ",") << "\"" << item.Probe->Event.Signature.ParamNames[i] << "\":\""; + EscapeJSONString(os, ParamValues[i]); + os << "\""; + } + } + os << "}]" << (AsTrack? "]":""); + } + } + first = false; + } + + template <EFormat Format> + void OutputTrackLog(IOutputStream& os, const NLWTrace::TTrackLog& tl, bool& first) const + { + if constexpr (Format == Json) { + os << (first? "": ",") << "["; + } + first = false; + ui64 prevTs = tl.GetTimestampCycles(); + bool firstItem = true; + for (const NLWTrace::TTrackLog::TItem& item: tl.Items) { + OutputItem<Format>(os, item.ThreadId, item, tl.GetTimestampCycles(), prevTs, firstItem); + prevTs = item.GetTimestampCycles(); + } + if constexpr (Format == Json) { + os << "]"; + } + os << "\n"; + } }; -class TLogAnalyzer: public TLogFilter { -private: +class TLogAnalyzer: public TLogFilter { +private: TMultiMap<ui64, std::pair<TThread::TId, NLWTrace::TLogItem>> Items; TVector<NLWTrace::TTrackLog> Depot; THashMap<TString, TTrackLogRefs> Groups; - NAnalytics::TTable Table; - bool TableCreated = false; + NAnalytics::TTable Table; + bool TableCreated = false; TVector<TString> GroupBy; - TTimestampCutter CutTs; -public: + TTimestampCutter CutTs; +public: TLogAnalyzer(const TVector<TString>& filters, const TVector<TString>& groupBy, bool cutTs) - : TLogFilter(filters) - , CutTs(cutTs) - { + : TLogFilter(filters) + , CutTs(cutTs) + { for (const TString& groupParam : groupBy) { - GroupBy.push_back(groupParam); - } - } - - const NAnalytics::TTable& GetTable() - { - if (!TableCreated) { - TableCreated = true; - if (GroupBy.empty()) { - for (auto i = Items.begin(), e = Items.end(); i != e; ++i) { - ParseItems(i->second.first, i->second.second); - } - ParseDepot(); - } else { - for (auto i = Items.begin(), e = Items.end(); i != e; ++i) { - Map(i->second.first, i->second.second); - } - Reduce(); - } - } - return Table; - } - - void Push(TThread::TId tid, const NLWTrace::TLogItem& item) - { - CutTs.Push(tid, item); - if (Filter(item)) { - Items.emplace(item.TimestampCycles, std::make_pair(tid, item)); - } - } - - void Push(TThread::TId, const NLWTrace::TTrackLog& tl) - { - if (Filter(tl)) { - Depot.emplace_back(tl); - } - } -private: - void FillRow(NAnalytics::TRow& row, const NLWTrace::TLogItem& item) - { - if (item.SavedParamsCount > 0) { + GroupBy.push_back(groupParam); + } + } + + const NAnalytics::TTable& GetTable() + { + if (!TableCreated) { + TableCreated = true; + if (GroupBy.empty()) { + for (auto i = Items.begin(), e = Items.end(); i != e; ++i) { + ParseItems(i->second.first, i->second.second); + } + ParseDepot(); + } else { + for (auto i = Items.begin(), e = Items.end(); i != e; ++i) { + Map(i->second.first, i->second.second); + } + Reduce(); + } + } + return Table; + } + + void Push(TThread::TId tid, const NLWTrace::TLogItem& item) + { + CutTs.Push(tid, item); + if (Filter(item)) { + Items.emplace(item.TimestampCycles, std::make_pair(tid, item)); + } + } + + void Push(TThread::TId, const NLWTrace::TTrackLog& tl) + { + if (Filter(tl)) { + Depot.emplace_back(tl); + } + } +private: + void FillRow(NAnalytics::TRow& row, const NLWTrace::TLogItem& item) + { + if (item.SavedParamsCount > 0) { TString paramValues[LWTRACE_MAX_PARAMS]; - item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); - for (size_t i = 0; i < item.SavedParamsCount; i++) { + item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); + for (size_t i = 0; i < item.SavedParamsCount; i++) { double value = FromString<double>(paramValues[i].data(), paramValues[i].size(), NAN); // If value cannot be cast to double or is inf/nan -- assume it's a string - if (isfinite(value)) { - row[item.Probe->Event.Signature.ParamNames[i]] = value; + if (isfinite(value)) { + row[item.Probe->Event.Signature.ParamNames[i]] = value; } else { row[item.Probe->Event.Signature.ParamNames[i]] = paramValues[i]; - } - } - } - } - + } + } + } + } + TString GetParam(const NLWTrace::TLogItem& item, TString* paramValues, const TString& paramName) - { - for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { - if (paramName == item.Probe->Event.Signature.ParamNames[pi]) { - return paramValues[pi]; - } - } + { + for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { + if (paramName == item.Probe->Event.Signature.ParamNames[pi]) { + return paramValues[pi]; + } + } return TString(); - } - + } + TString GetGroup(const NLWTrace::TLogItem& item, TString* paramValues) - { - TStringStream ss; - bool first = true; + { + TStringStream ss; + bool first = true; for (const TString& groupParam : GroupBy) { - ss << (first? "": "|") << GetParam(item, paramValues, groupParam); - first = false; - } - return ss.Str(); - } - - void ParseItems(TThread::TId tid, const NLWTrace::TLogItem& item) - { - if (CutTs.Skip(item)) { - return; - } + ss << (first? "": "|") << GetParam(item, paramValues, groupParam); + first = false; + } + return ss.Str(); + } + + void ParseItems(TThread::TId tid, const NLWTrace::TLogItem& item) + { + if (CutTs.Skip(item)) { + return; + } Table.emplace_back(); - NAnalytics::TRow& row = Table.back(); - row["_thread"] = tid; - if (item.Timestamp != TInstant::Zero()) { - row["_wallTime"] = item.Timestamp.SecondsFloat(); - row["_wallRTime"] = item.Timestamp.SecondsFloat() - CutTs.StartInstant().SecondsFloat(); - } - row["_cycles"] = item.TimestampCycles; - row["_thrTime"] = CyclesToDuration((ui64)item.TimestampCycles).SecondsFloat(); - row["_thrRTime"] = double(i64(item.TimestampCycles) - i64(CutTs.StartTimestamp())) / NHPTimer::GetCyclesPerSecond(); - row["_thrNTime"] = double(i64(item.TimestampCycles) - i64(CutTs.NowTimestamp())) / NHPTimer::GetCyclesPerSecond(); - row.Name = GetProbeName(item.Probe); - FillRow(row, item); - } - - void Map(TThread::TId tid, const NLWTrace::TLogItem& item) - { - if (item.SavedParamsCount > 0 && !CutTs.Skip(item)) { + NAnalytics::TRow& row = Table.back(); + row["_thread"] = tid; + if (item.Timestamp != TInstant::Zero()) { + row["_wallTime"] = item.Timestamp.SecondsFloat(); + row["_wallRTime"] = item.Timestamp.SecondsFloat() - CutTs.StartInstant().SecondsFloat(); + } + row["_cycles"] = item.TimestampCycles; + row["_thrTime"] = CyclesToDuration((ui64)item.TimestampCycles).SecondsFloat(); + row["_thrRTime"] = double(i64(item.TimestampCycles) - i64(CutTs.StartTimestamp())) / NHPTimer::GetCyclesPerSecond(); + row["_thrNTime"] = double(i64(item.TimestampCycles) - i64(CutTs.NowTimestamp())) / NHPTimer::GetCyclesPerSecond(); + row.Name = GetProbeName(item.Probe); + FillRow(row, item); + } + + void Map(TThread::TId tid, const NLWTrace::TLogItem& item) + { + if (item.SavedParamsCount > 0 && !CutTs.Skip(item)) { TString paramValues[LWTRACE_MAX_PARAMS]; - item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); - TTrackLogRefs& tl = Groups[GetGroup(item, paramValues)]; - tl.Items.emplace_back(tid, item); - } - } - - void Reduce() - { - for (auto& v : Groups) { + item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); + TTrackLogRefs& tl = Groups[GetGroup(item, paramValues)]; + tl.Items.emplace_back(tid, item); + } + } + + void Reduce() + { + for (auto& v : Groups) { const TString& group = v.first; - const TTrackLogRefs& tl = v.second; + const TTrackLogRefs& tl = v.second; Table.emplace_back(); - NAnalytics::TRow& row = Table.back(); - row.Name = group; - for (const NLWTrace::TLogItem& item : tl.Items) { - FillRow(row, item); - } - } - } - - void ParseDepot() - { - for (NLWTrace::TTrackLog& tl : Depot) { - Table.emplace_back(); - NAnalytics::TRow& row = Table.back(); - for (const NLWTrace::TLogItem& item : tl.Items) { - FillRow(row, item); - } - } - } -}; - -struct TSampleOpts { - bool ShowProvider = false; - size_t SizeLimit = 50; -}; - -enum ENodeType { - NT_ROOT, - NT_PROBE, - NT_PARAM -}; - -class TPatternTree; -struct TPatternNode; - -struct TTrack : public TTrackLogRefs { + NAnalytics::TRow& row = Table.back(); + row.Name = group; + for (const NLWTrace::TLogItem& item : tl.Items) { + FillRow(row, item); + } + } + } + + void ParseDepot() + { + for (NLWTrace::TTrackLog& tl : Depot) { + Table.emplace_back(); + NAnalytics::TRow& row = Table.back(); + for (const NLWTrace::TLogItem& item : tl.Items) { + FillRow(row, item); + } + } + } +}; + +struct TSampleOpts { + bool ShowProvider = false; + size_t SizeLimit = 50; +}; + +enum ENodeType { + NT_ROOT, + NT_PROBE, + NT_PARAM +}; + +class TPatternTree; +struct TPatternNode; + +struct TTrack : public TTrackLogRefs { TString TrackId; - TPatternNode* LastNode = nullptr; -}; - -using TTrackTr = TLogTraits<TTrackLogRefs>; -using TTrackIter = TTrackTr::const_iterator; - -// Visitor for tree traversing -class IVisitor { -public: - virtual ~IVisitor() {} - virtual void Visit(TPatternNode* node) = 0; -}; - -// Per-node classifier -class TClassifier { -public: - explicit TClassifier(TPatternNode* node, ENodeType childType, bool keepHead = false) - : Node(node) - , KeepHead(keepHead) - , ChildType(childType) - {} - virtual ~TClassifier() {} - virtual TPatternNode* Classify(TTrackIter cur, const TTrack& track) = 0; - virtual void Accept(IVisitor* visitor) = 0; - virtual bool IsLeaf() = 0; - ENodeType GetChildType() const { return ChildType; } -public: - TPatternNode* Node; - const bool KeepHead; - ENodeType ChildType; -}; - -// Track classification tree node -struct TPatternNode { + TPatternNode* LastNode = nullptr; +}; + +using TTrackTr = TLogTraits<TTrackLogRefs>; +using TTrackIter = TTrackTr::const_iterator; + +// Visitor for tree traversing +class IVisitor { +public: + virtual ~IVisitor() {} + virtual void Visit(TPatternNode* node) = 0; +}; + +// Per-node classifier +class TClassifier { +public: + explicit TClassifier(TPatternNode* node, ENodeType childType, bool keepHead = false) + : Node(node) + , KeepHead(keepHead) + , ChildType(childType) + {} + virtual ~TClassifier() {} + virtual TPatternNode* Classify(TTrackIter cur, const TTrack& track) = 0; + virtual void Accept(IVisitor* visitor) = 0; + virtual bool IsLeaf() = 0; + ENodeType GetChildType() const { return ChildType; } +public: + TPatternNode* Node; + const bool KeepHead; + ENodeType ChildType; +}; + +// Track classification tree node +struct TPatternNode { TString Name; - TPatternNode* Parent = nullptr; - THolder<TClassifier> Classifier; - struct TDesc { - ENodeType Type = NT_ROOT; - // NT_PROBE - const NLWTrace::TProbe* Probe = nullptr; - // NT_PARAM - size_t Rollbacks = 0; + TPatternNode* Parent = nullptr; + THolder<TClassifier> Classifier; + struct TDesc { + ENodeType Type = NT_ROOT; + // NT_PROBE + const NLWTrace::TProbe* Probe = nullptr; + // NT_PARAM + size_t Rollbacks = 0; TString ParamName; TString ParamValue; - } Desc; - - ui64 TrackCount = 0; - struct TTrackEntry { - TTrack* Track; - ui64 ResTotal; - ui64 ResLast; - - TTrackEntry(TTrack* track, ui64 resTotal, ui64 resLast) - : Track(track) - , ResTotal(resTotal) - , ResLast(resLast) - {} - }; - + } Desc; + + ui64 TrackCount = 0; + struct TTrackEntry { + TTrack* Track; + ui64 ResTotal; + ui64 ResLast; + + TTrackEntry(TTrack* track, ui64 resTotal, ui64 resLast) + : Track(track) + , ResTotal(resTotal) + , ResLast(resLast) + {} + }; + TVector<TTrackEntry> Tracks; - - ui64 ResTotalSum = 0; - ui64 ResTotalMax = 0; + + ui64 ResTotalSum = 0; + ui64 ResTotalMax = 0; TVector<ui64> ResTotalAll; - - ui64 ResLastSum = 0; - ui64 ResLastMax = 0; + + ui64 ResLastSum = 0; + ui64 ResLastMax = 0; TVector<ui64> ResLastAll; - + TVector<ui64> TimelineSum; - NAnalytics::TTable Slices; - + NAnalytics::TTable Slices; + TString GetPath() const - { - if (Parent) { - return Parent->GetPath() + Name; - } - return "/"; - } - - NAnalytics::TTable GetTable() const - { - using namespace NAnalytics; - NAnalytics::TTable ret; - for (ui64 x : ResTotalAll) { + { + if (Parent) { + return Parent->GetPath() + Name; + } + return "/"; + } + + NAnalytics::TTable GetTable() const + { + using namespace NAnalytics; + NAnalytics::TTable ret; + for (ui64 x : ResTotalAll) { ret.emplace_back(); - TRow& row = ret.back(); - row["resTotal"] = double(x) * 1000.0 / NHPTimer::GetClockRate(); - } - for (ui64 x : ResLastAll) { + TRow& row = ret.back(); + row["resTotal"] = double(x) * 1000.0 / NHPTimer::GetClockRate(); + } + for (ui64 x : ResLastAll) { ret.emplace_back(); - TRow& row = ret.back(); - row["resLast"] = double(x) * 1000.0 / NHPTimer::GetClockRate(); - } - return ret; - } - + TRow& row = ret.back(); + row["resLast"] = double(x) * 1000.0 / NHPTimer::GetClockRate(); + } + return ret; + } + template <typename TReader> void OutputSample(const TString& bn, double b1, double b2, const TSampleOpts& opts, TReader& reader) const - { - bool filterTotal = false; - if (bn == "resTotal") { - filterTotal = true; - } else { + { + bool filterTotal = false; + if (bn == "resTotal") { + filterTotal = true; + } else { WWW_CHECK(bn == "resLast", "wrong sample filter param: %s", bn.data()); - } - - size_t spaceLeft = opts.SizeLimit; - for (const TTrackEntry& entry : Tracks) { - const TTrack* track = entry.Track; - // Filter out tracks that are not in sample - if (filterTotal) { - double resTotalMs = double(entry.ResTotal) * 1000.0 / NHPTimer::GetClockRate(); - if (resTotalMs < b1 || resTotalMs > b2) { - continue; - } - } else { - double resLastMs = double(entry.ResLast) * 1000.0 / NHPTimer::GetClockRate(); - if (resLastMs < b1 || resLastMs > b2) { - continue; - } - } - + } + + size_t spaceLeft = opts.SizeLimit; + for (const TTrackEntry& entry : Tracks) { + const TTrack* track = entry.Track; + // Filter out tracks that are not in sample + if (filterTotal) { + double resTotalMs = double(entry.ResTotal) * 1000.0 / NHPTimer::GetClockRate(); + if (resTotalMs < b1 || resTotalMs > b2) { + continue; + } + } else { + double resLastMs = double(entry.ResLast) * 1000.0 / NHPTimer::GetClockRate(); + if (resLastMs < b1 || resLastMs > b2) { + continue; + } + } + NLWTrace::TTrackLog tl; for (TTrackIter i = TTrackTr::begin(*track), e = TTrackTr::end(*track); i != e; ++i) { - const NLWTrace::TLogItem& item = *i; + const NLWTrace::TLogItem& item = *i; const auto threadId = i->ThreadId; tl.Items.push_back(NLWTrace::TTrackLog::TItem(threadId, item)); - } + } reader.Push(0, tl); - if (spaceLeft) { - spaceLeft--; - if (!spaceLeft) { - break; - } - } - } - } -}; - -// Track classification tree -class TPatternTree { -public: - // Per-node classifier by probe name - class TClassifyByProbe : public TClassifier { - private: + if (spaceLeft) { + spaceLeft--; + if (!spaceLeft) { + break; + } + } + } + } +}; + +// Track classification tree +class TPatternTree { +public: + // Per-node classifier by probe name + class TClassifyByProbe : public TClassifier { + private: using TChildren = THashMap<NLWTrace::TProbe*, TPatternNode>; - TChildren Children; + TChildren Children; TVector<TChildren::value_type*> SortedChildren; - public: - explicit TClassifyByProbe(TPatternNode* node) - : TClassifier(node, NT_PROBE) - {} - - TPatternNode* Classify(TTrackIter cur, const TTrack& track) override - { - Y_UNUSED(track); - const NLWTrace::TLogItem& item = *cur; - TPatternNode* node = &Children[item.Probe]; - node->Name = "/" + GetProbeName(item.Probe); - node->Desc.Type = NT_PROBE; - node->Desc.Probe = item.Probe; - return node; - } - - void Accept(IVisitor* visitor) override - { - if (SortedChildren.size() != Children.size()) { - SortedChildren.clear(); - SortedChildren.reserve(Children.size()); - for (auto i = Children.begin(), e = Children.end(); i != e; ++i) { - SortedChildren.push_back(&*i); - } - Sort(SortedChildren, [] (TChildren::value_type* lhs, TChildren::value_type* rhs) { - NLWTrace::TProbe* lp = lhs->first; - NLWTrace::TProbe* rp = rhs->first; - if (int cmp = strcmp(lp->Event.GetProvider(), rp->Event.GetProvider())) { - return cmp < 0; - } - return strcmp(lp->Event.Name, rp->Event.Name) < 0; - }); - } - for (auto* kv : SortedChildren) { - visitor->Visit(&kv->second); - } - } - - bool IsLeaf() override { return Children.empty(); } - }; - - // Per-node classifier by probe param value - class TClassifyByParam : public TClassifier { - private: - size_t Rollbacks; // How many items should we look back in track to locate probe + public: + explicit TClassifyByProbe(TPatternNode* node) + : TClassifier(node, NT_PROBE) + {} + + TPatternNode* Classify(TTrackIter cur, const TTrack& track) override + { + Y_UNUSED(track); + const NLWTrace::TLogItem& item = *cur; + TPatternNode* node = &Children[item.Probe]; + node->Name = "/" + GetProbeName(item.Probe); + node->Desc.Type = NT_PROBE; + node->Desc.Probe = item.Probe; + return node; + } + + void Accept(IVisitor* visitor) override + { + if (SortedChildren.size() != Children.size()) { + SortedChildren.clear(); + SortedChildren.reserve(Children.size()); + for (auto i = Children.begin(), e = Children.end(); i != e; ++i) { + SortedChildren.push_back(&*i); + } + Sort(SortedChildren, [] (TChildren::value_type* lhs, TChildren::value_type* rhs) { + NLWTrace::TProbe* lp = lhs->first; + NLWTrace::TProbe* rp = rhs->first; + if (int cmp = strcmp(lp->Event.GetProvider(), rp->Event.GetProvider())) { + return cmp < 0; + } + return strcmp(lp->Event.Name, rp->Event.Name) < 0; + }); + } + for (auto* kv : SortedChildren) { + visitor->Visit(&kv->second); + } + } + + bool IsLeaf() override { return Children.empty(); } + }; + + // Per-node classifier by probe param value + class TClassifyByParam : public TClassifier { + private: + size_t Rollbacks; // How many items should we look back in track to locate probe TString ParamName; using TChildren = THashMap<TString, TPatternNode>; - TChildren Children; + TChildren Children; TVector<TChildren::value_type*> SortedChildren; - public: + public: TClassifyByParam(TPatternNode* node, size_t rollbacks, const TString& paramName) - : TClassifier(node, NT_PARAM, true) - , Rollbacks(rollbacks) - , ParamName(paramName) - {} - - TPatternNode* Classify(TTrackIter cur, const TTrack& track) override - { - WWW_CHECK((i64)Rollbacks >= 0 && std::distance(TTrackTr::begin(track), cur) >= (i64)Rollbacks, "wrong rollbacks in node '%s'", + : TClassifier(node, NT_PARAM, true) + , Rollbacks(rollbacks) + , ParamName(paramName) + {} + + TPatternNode* Classify(TTrackIter cur, const TTrack& track) override + { + WWW_CHECK((i64)Rollbacks >= 0 && std::distance(TTrackTr::begin(track), cur) >= (i64)Rollbacks, "wrong rollbacks in node '%s'", Node->GetPath().data()); - const NLWTrace::TLogItem& item = *(cur - Rollbacks); - WWW_CHECK(item.SavedParamsCount > 0, "classify by params on probe w/o param loggging in node '%s'", + const NLWTrace::TLogItem& item = *(cur - Rollbacks); + WWW_CHECK(item.SavedParamsCount > 0, "classify by params on probe w/o param loggging in node '%s'", Node->GetPath().data()); TString paramValues[LWTRACE_MAX_PARAMS]; TString* paramValue = nullptr; - item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); - for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { - if (item.Probe->Event.Signature.ParamNames[pi] == ParamName) { - paramValue = ¶mValues[pi]; - } - } - WWW_CHECK(paramValue, "param '%s' not found in probe '%s' at path '%s'", + item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); + for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { + if (item.Probe->Event.Signature.ParamNames[pi] == ParamName) { + paramValue = ¶mValues[pi]; + } + } + WWW_CHECK(paramValue, "param '%s' not found in probe '%s' at path '%s'", ParamName.data(), GetProbeName(item.Probe).data(), Node->GetPath().data()); - - TPatternNode* node = &Children[*paramValue]; - // Path example: "//Provider1.Probe1/Provider2.Probe2@1.xxx=123@2.type=harakiri" - node->Name = "@" + ToString(Rollbacks) + "." + ParamName + "=" + *paramValue; - node->Desc.Type = NT_PARAM; - node->Desc.Rollbacks = Rollbacks; - node->Desc.ParamName = ParamName; - node->Desc.ParamValue = *paramValue; - return node; - } - - void Accept(IVisitor* visitor) override - { - if (SortedChildren.size() != Children.size()) { - SortedChildren.clear(); - SortedChildren.reserve(Children.size()); - for (auto i = Children.begin(), e = Children.end(); i != e; ++i) { - SortedChildren.push_back(&*i); - } - Sort(SortedChildren, [] (TChildren::value_type* lhs, TChildren::value_type* rhs) { - return lhs->first < rhs->first; - }); - } - for (auto* kv : SortedChildren) { - visitor->Visit(&kv->second); - } - } - - bool IsLeaf() override { return Children.empty(); } - }; -private: - TPatternNode Root; + + TPatternNode* node = &Children[*paramValue]; + // Path example: "//Provider1.Probe1/Provider2.Probe2@1.xxx=123@2.type=harakiri" + node->Name = "@" + ToString(Rollbacks) + "." + ParamName + "=" + *paramValue; + node->Desc.Type = NT_PARAM; + node->Desc.Rollbacks = Rollbacks; + node->Desc.ParamName = ParamName; + node->Desc.ParamValue = *paramValue; + return node; + } + + void Accept(IVisitor* visitor) override + { + if (SortedChildren.size() != Children.size()) { + SortedChildren.clear(); + SortedChildren.reserve(Children.size()); + for (auto i = Children.begin(), e = Children.end(); i != e; ++i) { + SortedChildren.push_back(&*i); + } + Sort(SortedChildren, [] (TChildren::value_type* lhs, TChildren::value_type* rhs) { + return lhs->first < rhs->first; + }); + } + for (auto* kv : SortedChildren) { + visitor->Visit(&kv->second); + } + } + + bool IsLeaf() override { return Children.empty(); } + }; +private: + TPatternNode Root; THashMap<TString, std::pair<size_t, TString>> ParamClassifiers; // path -> (rollbacks, param) TString SelectedPattern; - TPatternNode* SelectedNode = nullptr; + TPatternNode* SelectedNode = nullptr; TVector<ui64> Timeline; // Just to avoid reallocations -public: - TPatternTree(const TCgiParameters& e) - { +public: + TPatternTree(const TCgiParameters& e) + { for (const TString& cl : Subvalues(e, "classify")) { - size_t at = cl.find_last_of('@'); + size_t at = cl.find_last_of('@'); if (at != TString::npos) { - size_t dot = cl.find('.', at + 1); + size_t dot = cl.find('.', at + 1); if (dot != TString::npos) { - size_t rollbacks = FromString<size_t>(cl.substr(at + 1, dot - at - 1)); - ParamClassifiers[cl.substr(0, at)] = std::make_pair(rollbacks, cl.substr(dot + 1)); - } - } - } - SelectedPattern = e.Get("pattern"); - InitNode(&Root, nullptr); - } - - TPatternNode* GetSelectedNode() - { - return SelectedNode; - } - - NAnalytics::TTable GetSelectedTable() - { - if (SelectedNode) { - return SelectedNode->GetTable(); - } else { - return NAnalytics::TTable(); - } - } - + size_t rollbacks = FromString<size_t>(cl.substr(at + 1, dot - at - 1)); + ParamClassifiers[cl.substr(0, at)] = std::make_pair(rollbacks, cl.substr(dot + 1)); + } + } + } + SelectedPattern = e.Get("pattern"); + InitNode(&Root, nullptr); + } + + TPatternNode* GetSelectedNode() + { + return SelectedNode; + } + + NAnalytics::TTable GetSelectedTable() + { + if (SelectedNode) { + return SelectedNode->GetTable(); + } else { + return NAnalytics::TTable(); + } + } + template <typename TReader> void OutputSelectedSample(const TString& bn, double b1, double b2, const TSampleOpts& opts, TReader& reader) - { - if (SelectedNode) { + { + if (SelectedNode) { SelectedNode->OutputSample(bn, b1, b2, opts, reader); - } - } - - // Register track in given node + } + } + + // Register track in given node void AddTrackToNode(TPatternNode* node, TTrack& track, ui64 resTotal, TVector<ui64>& timeline) - { - if (!SelectedNode) { - if (node->GetPath() == SelectedPattern) { - SelectedNode = node; - } - } - - // Counting - node->TrackCount++; - - // Resource total - node->ResTotalSum += resTotal; - node->ResTotalMax = Max(node->ResTotalMax, resTotal); - node->ResTotalAll.push_back(resTotal); - - // Resource last - ui64 resLast = 0; - resLast = resTotal - (timeline.size() < 2? 0: timeline[timeline.size() - 2]); - node->ResLastSum += resLast; - node->ResLastMax = Max(node->ResLastMax, resLast); - node->ResLastAll.push_back(resLast); - - // Timeline - if (node->TimelineSum.size() < timeline.size()) { - node->TimelineSum.resize(timeline.size()); - } - for (size_t i = 0; i < timeline.size(); i++) { - node->TimelineSum[i] += timeline[i]; - } - - if (node == SelectedNode && !timeline.empty()) { - node->Slices.emplace_back(); - NAnalytics::TRow& row = node->Slices.back(); - ui64 prev = 0; - for (size_t i = 0; i < timeline.size(); i++) { - // Note that col names should go in lexicographical order - // in the same way as slices go in pattern timeline - double sliceMs = double(timeline[i] - prev) * 1000.0 / NHPTimer::GetClockRate(); - row[Sprintf("%09lu", i)] = sliceMs; - prev = timeline[i]; - } - } - - // Interlink node and track - node->Tracks.emplace_back(&track, resTotal, resLast); - track.LastNode = node; - } - - bool CheckPattern(const char*& pi, const char* pe, TStringBuf str) - { - auto si = str.begin(), se = str.end(); - for (;pi != pe && si != se; ++pi, ++si) { - if (*pi != *si) { - return false; - } - } - return si == se; - } - -#define WWW_CHECK_PATTERN(str) if (!CheckPattern(pi, pe, (str))) { return false; } - + { + if (!SelectedNode) { + if (node->GetPath() == SelectedPattern) { + SelectedNode = node; + } + } + + // Counting + node->TrackCount++; + + // Resource total + node->ResTotalSum += resTotal; + node->ResTotalMax = Max(node->ResTotalMax, resTotal); + node->ResTotalAll.push_back(resTotal); + + // Resource last + ui64 resLast = 0; + resLast = resTotal - (timeline.size() < 2? 0: timeline[timeline.size() - 2]); + node->ResLastSum += resLast; + node->ResLastMax = Max(node->ResLastMax, resLast); + node->ResLastAll.push_back(resLast); + + // Timeline + if (node->TimelineSum.size() < timeline.size()) { + node->TimelineSum.resize(timeline.size()); + } + for (size_t i = 0; i < timeline.size(); i++) { + node->TimelineSum[i] += timeline[i]; + } + + if (node == SelectedNode && !timeline.empty()) { + node->Slices.emplace_back(); + NAnalytics::TRow& row = node->Slices.back(); + ui64 prev = 0; + for (size_t i = 0; i < timeline.size(); i++) { + // Note that col names should go in lexicographical order + // in the same way as slices go in pattern timeline + double sliceMs = double(timeline[i] - prev) * 1000.0 / NHPTimer::GetClockRate(); + row[Sprintf("%09lu", i)] = sliceMs; + prev = timeline[i]; + } + } + + // Interlink node and track + node->Tracks.emplace_back(&track, resTotal, resLast); + track.LastNode = node; + } + + bool CheckPattern(const char*& pi, const char* pe, TStringBuf str) + { + auto si = str.begin(), se = str.end(); + for (;pi != pe && si != se; ++pi, ++si) { + if (*pi != *si) { + return false; + } + } + return si == se; + } + +#define WWW_CHECK_PATTERN(str) if (!CheckPattern(pi, pe, (str))) { return false; } + bool MatchTrack(const TTrack& track, const TString& patternStr) - { + { const char* pi = patternStr.data(); const char* pe = pi + patternStr.size(); - WWW_CHECK_PATTERN("/"); - for (TTrackIter i = TTrackTr::begin(track), e = TTrackTr::end(track); i != e; ++i) { - if (pi == pe) { - return true; - } - const NLWTrace::TLogItem& item = *i; - WWW_CHECK_PATTERN("/"); - WWW_CHECK_PATTERN(item.Probe->Event.GetProvider()); - WWW_CHECK_PATTERN("."); - WWW_CHECK_PATTERN(item.Probe->Event.Name); - while (true) { - if (pi == pe) { - return true; - } - char c = *pi; - if (c == '/') { - break; - } else if (c == '@') { - pi++; - // Parse rollbacks - TStringBuf p(pi, pe); - size_t dot = p.find('.'); - if (dot == TStringBuf::npos) { - return false; - } - size_t rollbacks = 0; - try { - rollbacks = FromString<size_t>(p.substr(0, dot)); - } catch (...) { - return false; - } - - // Parse param name - size_t equals = p.find('=', dot + 1); - if (equals == TStringBuf::npos) { - return false; - } - TStringBuf paramName = p.substr(dot + 1, equals - dot - 1); - - pi += equals + 1; // Advance to value - - // Check param value - if ((i64)rollbacks < 0 || std::distance(TTrackTr::begin(track), i) < (i64)rollbacks) { - return false; - } - const NLWTrace::TLogItem& mitem = *(i - rollbacks); - if (mitem.SavedParamsCount == 0) { - return false; - } + WWW_CHECK_PATTERN("/"); + for (TTrackIter i = TTrackTr::begin(track), e = TTrackTr::end(track); i != e; ++i) { + if (pi == pe) { + return true; + } + const NLWTrace::TLogItem& item = *i; + WWW_CHECK_PATTERN("/"); + WWW_CHECK_PATTERN(item.Probe->Event.GetProvider()); + WWW_CHECK_PATTERN("."); + WWW_CHECK_PATTERN(item.Probe->Event.Name); + while (true) { + if (pi == pe) { + return true; + } + char c = *pi; + if (c == '/') { + break; + } else if (c == '@') { + pi++; + // Parse rollbacks + TStringBuf p(pi, pe); + size_t dot = p.find('.'); + if (dot == TStringBuf::npos) { + return false; + } + size_t rollbacks = 0; + try { + rollbacks = FromString<size_t>(p.substr(0, dot)); + } catch (...) { + return false; + } + + // Parse param name + size_t equals = p.find('=', dot + 1); + if (equals == TStringBuf::npos) { + return false; + } + TStringBuf paramName = p.substr(dot + 1, equals - dot - 1); + + pi += equals + 1; // Advance to value + + // Check param value + if ((i64)rollbacks < 0 || std::distance(TTrackTr::begin(track), i) < (i64)rollbacks) { + return false; + } + const NLWTrace::TLogItem& mitem = *(i - rollbacks); + if (mitem.SavedParamsCount == 0) { + return false; + } TString paramValues[LWTRACE_MAX_PARAMS]; TString* paramValue = nullptr; - mitem.Probe->Event.Signature.SerializeParams(mitem.Params, paramValues); - for (size_t pi = 0; pi < mitem.SavedParamsCount; pi++) { - if (mitem.Probe->Event.Signature.ParamNames[pi] == paramName) { - paramValue = ¶mValues[pi]; - } - } - if (!paramValue) { - return false; - } - WWW_CHECK_PATTERN(*paramValue); - } else { - return false; - } - } - } - return true; - } - -#undef WWW_CHECK_PATTERN - - // Push new track through pattern tree - void AddTrack(TTrack& track) - { - // Truncate long tracks - if (track.Items.size() > 50) { - track.Items.resize(50); - } - - if (SelectedPattern) { - if (!MatchTrack(track, SelectedPattern)) { - return; - } - } - - Timeline.clear(); - TPatternNode* node = &Root; - AddTrackToNode(node, track, 0, Timeline); - ui64 trackStart = TTrackTr::front(track).TimestampCycles; - for (TTrackIter i = TTrackTr::begin(track), e = TTrackTr::end(track); i != e;) { - // Get or create child by classification - TPatternNode* parent = node; - node = node->Classifier->Classify(i, track); - if (!node->Classifier) { - InitNode(node, parent); - } - - const NLWTrace::TLogItem& item = *i; - ui64 resTotal = item.TimestampCycles - trackStart; - if (i != TTrackTr::begin(track)) { - Timeline.push_back(resTotal); - } - AddTrackToNode(node, track, resTotal, Timeline); - - // Move through track - if (!node->Classifier->KeepHead) { - ++i; - } - } - } - - // Traverse pattern tree (the only way to extract data from it) - template <class TOnNode, class TOnDescend, class TOnAscend> - void Traverse(TOnNode&& onNode, TOnDescend&& onDescend, TOnAscend&& onAscend) - { - struct TVisitor : public IVisitor { - TOnNode OnNode; - TOnDescend OnDescend; - TOnAscend OnAscend; - TVisitor(TOnNode&& onNode, TOnDescend&& onDescend, TOnAscend&& onAscend) - : OnNode(onNode) - , OnDescend(onDescend) - , OnAscend(onAscend) - {} - virtual void Visit(TPatternNode* node) override - { - OnNode(node); - if (!node->Classifier->IsLeaf()) { - OnDescend(); - node->Classifier->Accept(this); - OnAscend(); - } - } - }; - TVisitor visitor(std::move(onNode), std::move(onDescend), std::move(onAscend)); - visitor.Visit(&Root); - } - - TPatternNode* GetRoot() - { - return &Root; - } - -private: - void InitNode(TPatternNode* node, TPatternNode* parent) - { - node->Parent = parent; - auto iter = ParamClassifiers.find(node->GetPath()); - if (iter != ParamClassifiers.end()) { - node->Classifier.Reset(new TClassifyByParam(node, iter->second.first, iter->second.second)); - } else { - node->Classifier.Reset(new TClassifyByProbe(node)); - } - } -}; - -class TLogTrackExtractor: public TLogFilter { -private: - // Data storage + mitem.Probe->Event.Signature.SerializeParams(mitem.Params, paramValues); + for (size_t pi = 0; pi < mitem.SavedParamsCount; pi++) { + if (mitem.Probe->Event.Signature.ParamNames[pi] == paramName) { + paramValue = ¶mValues[pi]; + } + } + if (!paramValue) { + return false; + } + WWW_CHECK_PATTERN(*paramValue); + } else { + return false; + } + } + } + return true; + } + +#undef WWW_CHECK_PATTERN + + // Push new track through pattern tree + void AddTrack(TTrack& track) + { + // Truncate long tracks + if (track.Items.size() > 50) { + track.Items.resize(50); + } + + if (SelectedPattern) { + if (!MatchTrack(track, SelectedPattern)) { + return; + } + } + + Timeline.clear(); + TPatternNode* node = &Root; + AddTrackToNode(node, track, 0, Timeline); + ui64 trackStart = TTrackTr::front(track).TimestampCycles; + for (TTrackIter i = TTrackTr::begin(track), e = TTrackTr::end(track); i != e;) { + // Get or create child by classification + TPatternNode* parent = node; + node = node->Classifier->Classify(i, track); + if (!node->Classifier) { + InitNode(node, parent); + } + + const NLWTrace::TLogItem& item = *i; + ui64 resTotal = item.TimestampCycles - trackStart; + if (i != TTrackTr::begin(track)) { + Timeline.push_back(resTotal); + } + AddTrackToNode(node, track, resTotal, Timeline); + + // Move through track + if (!node->Classifier->KeepHead) { + ++i; + } + } + } + + // Traverse pattern tree (the only way to extract data from it) + template <class TOnNode, class TOnDescend, class TOnAscend> + void Traverse(TOnNode&& onNode, TOnDescend&& onDescend, TOnAscend&& onAscend) + { + struct TVisitor : public IVisitor { + TOnNode OnNode; + TOnDescend OnDescend; + TOnAscend OnAscend; + TVisitor(TOnNode&& onNode, TOnDescend&& onDescend, TOnAscend&& onAscend) + : OnNode(onNode) + , OnDescend(onDescend) + , OnAscend(onAscend) + {} + virtual void Visit(TPatternNode* node) override + { + OnNode(node); + if (!node->Classifier->IsLeaf()) { + OnDescend(); + node->Classifier->Accept(this); + OnAscend(); + } + } + }; + TVisitor visitor(std::move(onNode), std::move(onDescend), std::move(onAscend)); + visitor.Visit(&Root); + } + + TPatternNode* GetRoot() + { + return &Root; + } + +private: + void InitNode(TPatternNode* node, TPatternNode* parent) + { + node->Parent = parent; + auto iter = ParamClassifiers.find(node->GetPath()); + if (iter != ParamClassifiers.end()) { + node->Classifier.Reset(new TClassifyByParam(node, iter->second.first, iter->second.second)); + } else { + node->Classifier.Reset(new TClassifyByProbe(node)); + } + } +}; + +class TLogTrackExtractor: public TLogFilter { +private: + // Data storage TMultiMap<ui64, std::pair<TThread::TId, NLWTrace::TLogItem>> Items; TVector<NLWTrace::TTrackLog> Depot; - - // Data refs organized in tracks + + // Data refs organized in tracks THashMap<TString, TTrack> Tracks; TVector<TTrack> TracksFromDepot; - - // Analysis + + // Analysis TVector<TString> GroupBy; THashSet<TString> TrackIds; // The same content as in GroupBy - TTimestampCutter CutTs; - TPatternTree Tree; -public: + TTimestampCutter CutTs; + TPatternTree Tree; +public: TLogTrackExtractor(const TCgiParameters& e, const TVector<TString>& filters, const TVector<TString>& groupBy) - : TLogFilter(filters) - , CutTs(true) // Always cut input data for tracks - , Tree(e) - { + : TLogFilter(filters) + , CutTs(true) // Always cut input data for tracks + , Tree(e) + { for (const TString& groupParam : groupBy) { - GroupBy.push_back(groupParam); - TrackIds.insert(groupParam); - } - } - - // For reading lwtrace log (input point for all data) - void Push(TThread::TId tid, const NLWTrace::TLogItem& item) - { - CutTs.Push(tid, item); - if (Filter(item)) { - Items.emplace(item.TimestampCycles, std::make_pair(tid, item)); - } - } - - // For reading lwtrace depot (input point for all data) - void Push(TThread::TId, const NLWTrace::TTrackLog& tl) - { - if (Filter(tl)) { - Depot.emplace_back(tl); - } - } - - // Analyze logs that have been read - void Run() - { - RunImplLog(); - RunImplDepot(); - } - - void RunImplLog() - { - // Create tracks by filling them with lwtrace items in order of occurance time - for (auto& kv : Items) { - AddItemToTrack(kv.second.first, kv.second.second); - } - // Push tracks throught pattern tree - for (auto& kv : Tracks) { - TTrack& track = kv.second; - track.TrackId = kv.first; - Tree.AddTrack(track); - } - } - - void RunImplDepot() - { - // Create tracks from depot - // OPTIMIZE[serxa]: this convertion is not necessary, done just to keep things simple - for (NLWTrace::TTrackLog& tl : Depot) { - TTrack& track = TracksFromDepot.emplace_back(); - track.TrackId = ToString(tl.Id); - for (const NLWTrace::TTrackLog::TItem& i : tl.Items) { - track.Items.emplace_back(i.ThreadId, i); - } - } - for (TTrack& t : TracksFromDepot) { - Tree.AddTrack(t); - } - } - - // Selected node distribution - NAnalytics::TTable Distribution(const TString& bn, const TString& b1Str, const TString& b2Str, const TString& widthStr) - { - using namespace NAnalytics; - - const NAnalytics::TTable& inputTable = Tree.GetSelectedTable(); - double b1 = b1Str? FromString<double>(b1Str): MinValue(bn, inputTable); - double b2 = b2Str? FromString<double>(b2Str): MaxValue(bn, inputTable); - if (isfinite(b1) && isfinite(b2)) { - WWW_CHECK(b1 <= b2, "invalid xrange [%le; %le]", b1, b2); - double width = widthStr? FromString<double>(widthStr): 99; - double dx = (b2 - b1) / width; - if (!(dx > 0)) { - dx = 1.0; - } - return HistogramAll(inputTable, bn, b1, b2, dx); - } else { - // Empty table -- it's ok -- leave data table empty - return NAnalytics::TTable(); - } - } - - // Selected sample + GroupBy.push_back(groupParam); + TrackIds.insert(groupParam); + } + } + + // For reading lwtrace log (input point for all data) + void Push(TThread::TId tid, const NLWTrace::TLogItem& item) + { + CutTs.Push(tid, item); + if (Filter(item)) { + Items.emplace(item.TimestampCycles, std::make_pair(tid, item)); + } + } + + // For reading lwtrace depot (input point for all data) + void Push(TThread::TId, const NLWTrace::TTrackLog& tl) + { + if (Filter(tl)) { + Depot.emplace_back(tl); + } + } + + // Analyze logs that have been read + void Run() + { + RunImplLog(); + RunImplDepot(); + } + + void RunImplLog() + { + // Create tracks by filling them with lwtrace items in order of occurance time + for (auto& kv : Items) { + AddItemToTrack(kv.second.first, kv.second.second); + } + // Push tracks throught pattern tree + for (auto& kv : Tracks) { + TTrack& track = kv.second; + track.TrackId = kv.first; + Tree.AddTrack(track); + } + } + + void RunImplDepot() + { + // Create tracks from depot + // OPTIMIZE[serxa]: this convertion is not necessary, done just to keep things simple + for (NLWTrace::TTrackLog& tl : Depot) { + TTrack& track = TracksFromDepot.emplace_back(); + track.TrackId = ToString(tl.Id); + for (const NLWTrace::TTrackLog::TItem& i : tl.Items) { + track.Items.emplace_back(i.ThreadId, i); + } + } + for (TTrack& t : TracksFromDepot) { + Tree.AddTrack(t); + } + } + + // Selected node distribution + NAnalytics::TTable Distribution(const TString& bn, const TString& b1Str, const TString& b2Str, const TString& widthStr) + { + using namespace NAnalytics; + + const NAnalytics::TTable& inputTable = Tree.GetSelectedTable(); + double b1 = b1Str? FromString<double>(b1Str): MinValue(bn, inputTable); + double b2 = b2Str? FromString<double>(b2Str): MaxValue(bn, inputTable); + if (isfinite(b1) && isfinite(b2)) { + WWW_CHECK(b1 <= b2, "invalid xrange [%le; %le]", b1, b2); + double width = widthStr? FromString<double>(widthStr): 99; + double dx = (b2 - b1) / width; + if (!(dx > 0)) { + dx = 1.0; + } + return HistogramAll(inputTable, bn, b1, b2, dx); + } else { + // Empty table -- it's ok -- leave data table empty + return NAnalytics::TTable(); + } + } + + // Selected sample template <typename TReader> void OutputSample(const TString& bn, double b1, double b2, const TSampleOpts& opts, TReader& reader) - { + { Tree.OutputSelectedSample(bn, b1, b2, opts, reader); - } - - // Tabular representation of tracks data + } + + // Tabular representation of tracks data void OutputTable(IOutputStream& os, const TCgiParameters& e) - { - ui64 tracksTotal = Tree.GetRoot()->TrackCount; - - double maxAvgResTotal = 0; - double maxMaxResTotal = 0; - Tree.Traverse([&] (TPatternNode* node) { - if (node->TrackCount > 0) { - maxAvgResTotal = Max(maxAvgResTotal, double(node->ResTotalSum) / node->TrackCount); - maxMaxResTotal = Max(maxMaxResTotal, double(node->ResTotalMax)); - Sort(node->ResTotalAll); - Sort(node->ResLastAll); - } - }, [&] () { // On descend - }, [&] () { // On ascend - }); - double maxTime = Min(maxMaxResTotal, 1.25 * maxAvgResTotal); - - double percentile = e.Get("ile")? FromString<double>(e.Get("ile")): 90; - WWW_CHECK(percentile >= 0.0 && percentile <= 100.0, "wrong percentile: %lf", percentile); - - ui64 row = 0; + { + ui64 tracksTotal = Tree.GetRoot()->TrackCount; + + double maxAvgResTotal = 0; + double maxMaxResTotal = 0; + Tree.Traverse([&] (TPatternNode* node) { + if (node->TrackCount > 0) { + maxAvgResTotal = Max(maxAvgResTotal, double(node->ResTotalSum) / node->TrackCount); + maxMaxResTotal = Max(maxMaxResTotal, double(node->ResTotalMax)); + Sort(node->ResTotalAll); + Sort(node->ResLastAll); + } + }, [&] () { // On descend + }, [&] () { // On ascend + }); + double maxTime = Min(maxMaxResTotal, 1.25 * maxAvgResTotal); + + double percentile = e.Get("ile")? FromString<double>(e.Get("ile")): 90; + WWW_CHECK(percentile >= 0.0 && percentile <= 100.0, "wrong percentile: %lf", percentile); + + ui64 row = 0; TVector<ui64> chain; - HTML(os) { - TABLE_CLASS("tracks-tree") { - TABLEHEAD() { - os << "<tr>"; - os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">#</td>"; - os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Pattern</td>"; - os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">"; - DIV_CLASS("rotate") { os << "Track Count"; } - os << "</td>"; - os << "<td colspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Share</td>"; - os << "<td colspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Total, ms</td>"; - os << "<td colspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Last, ms</td>"; - os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" class=\"timelinehead\" align=\"center\">Global Timeline</td>"; - os << "</tr><tr>"; - TABLEH() DIV_CLASS("rotate") { os << "Absolute"; } - TABLEH() DIV_CLASS("rotate") { os << "Relative"; } - TABLEH() DIV_CLASS("rotate") { os << "Average"; } - TABLEH() DIV_CLASS("rotate") { os << percentile << "%-ile"; } - TABLEH() DIV_CLASS("rotate") { os << "Average"; } - TABLEH() DIV_CLASS("rotate") { os << percentile << "%-ile"; } - os << "</tr>"; - } - TABLEBODY() { - if (tracksTotal == 0) { - return; - } - Tree.Traverse([&] (TPatternNode* node) { + HTML(os) { + TABLE_CLASS("tracks-tree") { + TABLEHEAD() { + os << "<tr>"; + os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">#</td>"; + os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Pattern</td>"; + os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">"; + DIV_CLASS("rotate") { os << "Track Count"; } + os << "</td>"; + os << "<td colspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Share</td>"; + os << "<td colspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Total, ms</td>"; + os << "<td colspan=\"2\" style=\"vertical-align:bottom\" align=\"center\">Last, ms</td>"; + os << "<td rowspan=\"2\" style=\"vertical-align:bottom\" class=\"timelinehead\" align=\"center\">Global Timeline</td>"; + os << "</tr><tr>"; + TABLEH() DIV_CLASS("rotate") { os << "Absolute"; } + TABLEH() DIV_CLASS("rotate") { os << "Relative"; } + TABLEH() DIV_CLASS("rotate") { os << "Average"; } + TABLEH() DIV_CLASS("rotate") { os << percentile << "%-ile"; } + TABLEH() DIV_CLASS("rotate") { os << "Average"; } + TABLEH() DIV_CLASS("rotate") { os << percentile << "%-ile"; } + os << "</tr>"; + } + TABLEBODY() { + if (tracksTotal == 0) { + return; + } + Tree.Traverse([&] (TPatternNode* node) { TString parentClass; - if (!chain.empty()) { - parentClass = " treegrid-parent-" + ToString(chain.back()); - } + if (!chain.empty()) { + parentClass = " treegrid-parent-" + ToString(chain.back()); + } TString selectedClass; - if (e.Get("pattern") == node->GetPath()) { - selectedClass = " danger"; - } - TABLER_CLASS("treegrid-" + ToString(++row) + parentClass + selectedClass) { - // Counting - ui64 tracksParent = node->Parent? node->Parent->TrackCount: tracksTotal; - double absShare = double(node->TrackCount) * 100 / tracksTotal; - double relShare = double(node->TrackCount) * 100 / tracksParent; - - // Resource total - double avgResTotal = double(node->ResTotalSum) / node->TrackCount; - size_t ileResTotalIdx = node->ResTotalAll.size() * percentile / 100; - if (ileResTotalIdx > 0) { - ileResTotalIdx--; - } - double ileResTotal = double(ileResTotalIdx >= node->ResTotalAll.size()? 0: node->ResTotalAll[ileResTotalIdx]); - double avgResTotalMs = avgResTotal * 1000.0 / NHPTimer::GetClockRate(); - double ileResTotalMs = ileResTotal * 1000.0 / NHPTimer::GetClockRate(); - - // Resource last - double avgResLast = double(node->ResLastSum) / node->TrackCount; - size_t ileResLastIdx = node->ResLastAll.size() * percentile / 100; - if (ileResLastIdx > 0) { - ileResLastIdx--; - } - double ileResLast = double(ileResLastIdx >= node->ResLastAll.size()? 0: node->ResLastAll[ileResLastIdx]); - double avgResLastMs = avgResLast * 1000.0 / NHPTimer::GetClockRate(); - double ileResLastMs = ileResLast * 1000.0 / NHPTimer::GetClockRate(); - - // Output - TABLED() { os << row; } - TABLED_CLASS("treegrid-element") { OutputPattern(os, e, node); } - TABLED() { os << node->TrackCount; } - TABLED() { OutputShare(os, absShare); } - TABLED() { OutputShare(os, relShare); } - TABLED() { os << FormatFloat(avgResTotalMs); } - TABLED() { os << FormatFloat(ileResTotalMs); } - TABLED() { os << FormatFloat(avgResLastMs); } - TABLED() { os << FormatFloat(ileResLastMs); } - TABLED() { OutputTimeline(os, MakeTimeline(node), maxTime); } - } - }, [&] () { // On descend - chain.push_back(row); - }, [&] () { // On ascend - chain.pop_back(); - }); - } - } - } - } - - // Chromium-compatible trace representation of tracks data + if (e.Get("pattern") == node->GetPath()) { + selectedClass = " danger"; + } + TABLER_CLASS("treegrid-" + ToString(++row) + parentClass + selectedClass) { + // Counting + ui64 tracksParent = node->Parent? node->Parent->TrackCount: tracksTotal; + double absShare = double(node->TrackCount) * 100 / tracksTotal; + double relShare = double(node->TrackCount) * 100 / tracksParent; + + // Resource total + double avgResTotal = double(node->ResTotalSum) / node->TrackCount; + size_t ileResTotalIdx = node->ResTotalAll.size() * percentile / 100; + if (ileResTotalIdx > 0) { + ileResTotalIdx--; + } + double ileResTotal = double(ileResTotalIdx >= node->ResTotalAll.size()? 0: node->ResTotalAll[ileResTotalIdx]); + double avgResTotalMs = avgResTotal * 1000.0 / NHPTimer::GetClockRate(); + double ileResTotalMs = ileResTotal * 1000.0 / NHPTimer::GetClockRate(); + + // Resource last + double avgResLast = double(node->ResLastSum) / node->TrackCount; + size_t ileResLastIdx = node->ResLastAll.size() * percentile / 100; + if (ileResLastIdx > 0) { + ileResLastIdx--; + } + double ileResLast = double(ileResLastIdx >= node->ResLastAll.size()? 0: node->ResLastAll[ileResLastIdx]); + double avgResLastMs = avgResLast * 1000.0 / NHPTimer::GetClockRate(); + double ileResLastMs = ileResLast * 1000.0 / NHPTimer::GetClockRate(); + + // Output + TABLED() { os << row; } + TABLED_CLASS("treegrid-element") { OutputPattern(os, e, node); } + TABLED() { os << node->TrackCount; } + TABLED() { OutputShare(os, absShare); } + TABLED() { OutputShare(os, relShare); } + TABLED() { os << FormatFloat(avgResTotalMs); } + TABLED() { os << FormatFloat(ileResTotalMs); } + TABLED() { os << FormatFloat(avgResLastMs); } + TABLED() { os << FormatFloat(ileResLastMs); } + TABLED() { OutputTimeline(os, MakeTimeline(node), maxTime); } + } + }, [&] () { // On descend + chain.push_back(row); + }, [&] () { // On ascend + chain.pop_back(); + }); + } + } + } + } + + // Chromium-compatible trace representation of tracks data void OutputChromeTrace(IOutputStream& os, const TCgiParameters& e) - { - Y_UNUSED(e); - TChromeTrace tr; - for (TPatternNode::TTrackEntry& entry: Tree.GetRoot()->Tracks) { - TTrack* track = entry.Track; - auto first = TTrackTr::begin(*track); - auto last = TTrackTr::rbegin(*track); - + { + Y_UNUSED(e); + TChromeTrace tr; + for (TPatternNode::TTrackEntry& entry: Tree.GetRoot()->Tracks) { + TTrack* track = entry.Track; + auto first = TTrackTr::begin(*track); + auto last = TTrackTr::rbegin(*track); + TString name = track->LastNode->GetPath(); - - const NLWTrace::TLogItem& firstItem = *first; - TThread::TId firstTid = first->ThreadId; - tr.Add(firstTid, firstItem.TimestampCycles, "b", "track", nullptr, name, track->TrackId); - - for (auto cur = TTrackTr::begin(*track), end = TTrackTr::end(*track); cur != end; ++cur) { - const NLWTrace::TLogItem& item = *cur; - - tr.Add(cur->ThreadId, item.TimestampCycles, "i", "event", &item, GetProbeName(item.Probe)); - + + const NLWTrace::TLogItem& firstItem = *first; + TThread::TId firstTid = first->ThreadId; + tr.Add(firstTid, firstItem.TimestampCycles, "b", "track", nullptr, name, track->TrackId); + + for (auto cur = TTrackTr::begin(*track), end = TTrackTr::end(*track); cur != end; ++cur) { + const NLWTrace::TLogItem& item = *cur; + + tr.Add(cur->ThreadId, item.TimestampCycles, "i", "event", &item, GetProbeName(item.Probe)); + TString sliceName = GetProbeName(item.Probe); - - auto next = cur + 1; - if (next != end) { - const NLWTrace::TLogItem& nextItem = *next; - tr.Add(cur->ThreadId, item.TimestampCycles, "b", "track", &item, sliceName, track->TrackId); - tr.Add(next->ThreadId, nextItem.TimestampCycles, "e", "track", &nextItem, sliceName, track->TrackId); - } else { - tr.Add(cur->ThreadId, item.TimestampCycles, "n", "track", &item, sliceName, track->TrackId); - } - } - - const NLWTrace::TLogItem& lastItem = *last; - tr.Add(last->ThreadId, lastItem.TimestampCycles, "e", "track", nullptr, name, track->TrackId); - } - tr.Output(os); - } - + + auto next = cur + 1; + if (next != end) { + const NLWTrace::TLogItem& nextItem = *next; + tr.Add(cur->ThreadId, item.TimestampCycles, "b", "track", &item, sliceName, track->TrackId); + tr.Add(next->ThreadId, nextItem.TimestampCycles, "e", "track", &nextItem, sliceName, track->TrackId); + } else { + tr.Add(cur->ThreadId, item.TimestampCycles, "n", "track", &item, sliceName, track->TrackId); + } + } + + const NLWTrace::TLogItem& lastItem = *last; + tr.Add(last->ThreadId, lastItem.TimestampCycles, "e", "track", nullptr, name, track->TrackId); + } + tr.Output(os); + } + void OutputSliceCovarianceMatrix(IOutputStream& os, const TCgiParameters& e) - { - Y_UNUSED(e); - TPatternNode* node = Tree.GetSelectedNode(); - if (!node) { - return; - } - - NAnalytics::TMatrix covMatrix = NAnalytics::CovarianceMatrix(node->Slices); - double var = covMatrix.CellSum(); - - double covMax = 0.0; - for (double x : covMatrix) { - if (covMax < x) { - covMax = x; - } - } - double dangerCov = covMax * 0.9 * 0.9; - double warnCov = covMax * 0.5 * 0.5; - - HTML(os) { - TABLE() { - TTimeline timeline = MakeTimeline(node); - TABLEHEAD() TABLER() { - TABLED(); - for (auto& e : timeline) TABLED() { - TPatternNode* subnode = e.first; - os << subnode->Name; - } - } - - auto tl = timeline.begin(); - TABLEBODY() for (size_t row = 0; row < covMatrix.Rows; row++) TABLER() { - TABLEH() { - if (tl != timeline.end()) { - TPatternNode* subnode = tl->first; - os << subnode->Name; - ++tl; - } - } - - for (size_t col = 0; col < covMatrix.Cols; col++) { - double cov = covMatrix.Cell(row, col); - TString tdClass = (cov >= dangerCov? "danger": (cov >= warnCov? "warning": "")); - TABLED_CLASS(tdClass) { - double sigmaX = (covMatrix.Cell(row, row) > 0? sqrt(covMatrix.Cell(row, row)): 0); - double sigmaY = (covMatrix.Cell(col, col) > 0? sqrt(covMatrix.Cell(col, col)): 0); - os << Sprintf("cov=%.3lf ms<sup>2</sup> (%.3lf ms) corr=%.1lf%% var_share=%.1lf%%", - cov, sqrt(abs(cov)), cov * 100.0 / sigmaX / sigmaY, cov * 100.0 / var); - } - } - } - } - } - } - -private: - TPatternNode* RollbackFind(TPatternNode* node) - { - for (;node != nullptr; node = node->Parent) { - if (node->Desc.Type == NT_PROBE) { - return node; - } - } - return nullptr; - } - + { + Y_UNUSED(e); + TPatternNode* node = Tree.GetSelectedNode(); + if (!node) { + return; + } + + NAnalytics::TMatrix covMatrix = NAnalytics::CovarianceMatrix(node->Slices); + double var = covMatrix.CellSum(); + + double covMax = 0.0; + for (double x : covMatrix) { + if (covMax < x) { + covMax = x; + } + } + double dangerCov = covMax * 0.9 * 0.9; + double warnCov = covMax * 0.5 * 0.5; + + HTML(os) { + TABLE() { + TTimeline timeline = MakeTimeline(node); + TABLEHEAD() TABLER() { + TABLED(); + for (auto& e : timeline) TABLED() { + TPatternNode* subnode = e.first; + os << subnode->Name; + } + } + + auto tl = timeline.begin(); + TABLEBODY() for (size_t row = 0; row < covMatrix.Rows; row++) TABLER() { + TABLEH() { + if (tl != timeline.end()) { + TPatternNode* subnode = tl->first; + os << subnode->Name; + ++tl; + } + } + + for (size_t col = 0; col < covMatrix.Cols; col++) { + double cov = covMatrix.Cell(row, col); + TString tdClass = (cov >= dangerCov? "danger": (cov >= warnCov? "warning": "")); + TABLED_CLASS(tdClass) { + double sigmaX = (covMatrix.Cell(row, row) > 0? sqrt(covMatrix.Cell(row, row)): 0); + double sigmaY = (covMatrix.Cell(col, col) > 0? sqrt(covMatrix.Cell(col, col)): 0); + os << Sprintf("cov=%.3lf ms<sup>2</sup> (%.3lf ms) corr=%.1lf%% var_share=%.1lf%%", + cov, sqrt(abs(cov)), cov * 100.0 / sigmaX / sigmaY, cov * 100.0 / var); + } + } + } + } + } + } + +private: + TPatternNode* RollbackFind(TPatternNode* node) + { + for (;node != nullptr; node = node->Parent) { + if (node->Desc.Type == NT_PROBE) { + return node; + } + } + return nullptr; + } + void OutputPattern(IOutputStream& os, const TCgiParameters& e, TPatternNode* node) - { - // Fill pattern name + { + // Fill pattern name TString patternName; TString patternTitle; - switch (node->Desc.Type) { - case NT_ROOT: - patternName = "All Tracks"; - break; - case NT_PROBE: - patternTitle = GetProbeName(node->Desc.Probe); - patternName = node->Desc.Probe->Event.Name; - break; - case NT_PARAM: - patternName.append(node->Desc.ParamName + " = " + node->Desc.ParamValue); - break; - } - - os << "<a href=\"" << MakeUrl(e, { - {"pattern", node->GetPath()}, - {"ptrn_anlz", e.Get("ptrn_anlz") ? e.Get("ptrn_anlz") : "resTotal"}, - {"linesfill", "y"}, - {"linessteps", "y"}, - {"pointsshow", "n"}, - {"sel_x1", e.Get("sel_x1") ? e.Get("sel_x1") : "0"}, - {"sel_x2", e.Get("sel_x2") ? e.Get("sel_x2") : "inf"}}) << "\"" - " title=\"" + patternTitle + "\">" << patternName << "</a>"; - - // Add/remove node menu - if (node->Desc.Type != NT_ROOT) { - os << "<div class=\"dropdown pull-right\" style=\"display:inline-block\">"; - if (node->Desc.Type == NT_PARAM) { - os<< "<button class=\"btn btn-xs btn-default\" type=\"button\"" - << "\" onClick=\"window.location.href='" - << MakeUrlEraseSub(e, "classify", node->Parent->GetPath() + "@" - + ToString(node->Desc.Rollbacks) + "." + node->Desc.ParamName) - << "';\">" - "<span class=\"glyphicon glyphicon-minus\"></span>" - "</button>"; - } - if (node->Classifier->GetChildType() != NT_PARAM) { - os << "<button class=\"btn btn-xs btn-default dropdown-toggle\" type=\"button\"" - " data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"true\">" - "<span class=\"glyphicon glyphicon-plus\"></span>" - "</button>" - "<ul class=\"dropdown-menu\">" - "<li class=\"dropdown-header\">Classify by param:</li>"; - int rollbacks = 0; - TPatternNode* probeNode = node; - while (probeNode = RollbackFind(probeNode)) { - const NLWTrace::TProbe* probe = probeNode->Desc.Probe; - os << "<li class=\"dropdown-header\">" << GetProbeName(probe) << "</li>"; - const NLWTrace::TSignature* sgn = &probe->Event.Signature; - for (size_t pi = 0; pi < sgn->ParamCount; pi++) { + switch (node->Desc.Type) { + case NT_ROOT: + patternName = "All Tracks"; + break; + case NT_PROBE: + patternTitle = GetProbeName(node->Desc.Probe); + patternName = node->Desc.Probe->Event.Name; + break; + case NT_PARAM: + patternName.append(node->Desc.ParamName + " = " + node->Desc.ParamValue); + break; + } + + os << "<a href=\"" << MakeUrl(e, { + {"pattern", node->GetPath()}, + {"ptrn_anlz", e.Get("ptrn_anlz") ? e.Get("ptrn_anlz") : "resTotal"}, + {"linesfill", "y"}, + {"linessteps", "y"}, + {"pointsshow", "n"}, + {"sel_x1", e.Get("sel_x1") ? e.Get("sel_x1") : "0"}, + {"sel_x2", e.Get("sel_x2") ? e.Get("sel_x2") : "inf"}}) << "\"" + " title=\"" + patternTitle + "\">" << patternName << "</a>"; + + // Add/remove node menu + if (node->Desc.Type != NT_ROOT) { + os << "<div class=\"dropdown pull-right\" style=\"display:inline-block\">"; + if (node->Desc.Type == NT_PARAM) { + os<< "<button class=\"btn btn-xs btn-default\" type=\"button\"" + << "\" onClick=\"window.location.href='" + << MakeUrlEraseSub(e, "classify", node->Parent->GetPath() + "@" + + ToString(node->Desc.Rollbacks) + "." + node->Desc.ParamName) + << "';\">" + "<span class=\"glyphicon glyphicon-minus\"></span>" + "</button>"; + } + if (node->Classifier->GetChildType() != NT_PARAM) { + os << "<button class=\"btn btn-xs btn-default dropdown-toggle\" type=\"button\"" + " data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"true\">" + "<span class=\"glyphicon glyphicon-plus\"></span>" + "</button>" + "<ul class=\"dropdown-menu\">" + "<li class=\"dropdown-header\">Classify by param:</li>"; + int rollbacks = 0; + TPatternNode* probeNode = node; + while (probeNode = RollbackFind(probeNode)) { + const NLWTrace::TProbe* probe = probeNode->Desc.Probe; + os << "<li class=\"dropdown-header\">" << GetProbeName(probe) << "</li>"; + const NLWTrace::TSignature* sgn = &probe->Event.Signature; + for (size_t pi = 0; pi < sgn->ParamCount; pi++) { TString param = sgn->ParamNames[pi]; if (TrackIds.contains(param) || IsFiltered(param)) { - continue; - } - os << "<li><a href=\"" - << MakeUrlAddSub(e, "classify", node->GetPath() + "@" + ToString(rollbacks) + "." + param) - << "\">" << param << "</a></li>"; - } - rollbacks++; - probeNode = probeNode->Parent; - } - os << "</ul>"; - } - os << "</div>"; - } - } - + continue; + } + os << "<li><a href=\"" + << MakeUrlAddSub(e, "classify", node->GetPath() + "@" + ToString(rollbacks) + "." + param) + << "\">" << param << "</a></li>"; + } + rollbacks++; + probeNode = probeNode->Parent; + } + os << "</ul>"; + } + os << "</div>"; + } + } + void OutputShare(IOutputStream& os, double share) - { - double lshare = share; - double rshare = 100 - lshare; - os << "<div class=\"progress\" style=\"margin-bottom:0px;position:relative\">" - "<div class=\"progress-bar progress-bar-success\" role=\"progressbar\"" - " aria-valuenow=\"" << lshare << "\"" - " aria-valuemin=\"0\"" - " aria-valuemax=\"100\"" - " style=\"width: " << lshare << "%;\">" - "</div>" - "<div class=\"progress-bar progress-bar-danger\" role=\"progressbar\"" - " aria-valuenow=\"" << rshare << "\"" - " aria-valuemin=\"0\"" - " aria-valuemax=\"100\"" - " style=\"width: " << rshare << "%;\">" - "</div>" - "<span style=\"position:absolute;left:0;width:100%;text-align:center;z-index:2;color:white\">" - << (share == 100? "100%": Sprintf("%2.1lf%%", share)) << - "</span>" - "</div>"; - } - + { + double lshare = share; + double rshare = 100 - lshare; + os << "<div class=\"progress\" style=\"margin-bottom:0px;position:relative\">" + "<div class=\"progress-bar progress-bar-success\" role=\"progressbar\"" + " aria-valuenow=\"" << lshare << "\"" + " aria-valuemin=\"0\"" + " aria-valuemax=\"100\"" + " style=\"width: " << lshare << "%;\">" + "</div>" + "<div class=\"progress-bar progress-bar-danger\" role=\"progressbar\"" + " aria-valuenow=\"" << rshare << "\"" + " aria-valuemin=\"0\"" + " aria-valuemax=\"100\"" + " style=\"width: " << rshare << "%;\">" + "</div>" + "<span style=\"position:absolute;left:0;width:100%;text-align:center;z-index:2;color:white\">" + << (share == 100? "100%": Sprintf("%2.1lf%%", share)) << + "</span>" + "</div>"; + } + using TTimeline = TVector<std::pair<TPatternNode*, double>>; - - TTimeline MakeTimeline(TPatternNode* node) - { - TTimeline ret; - if (node->TrackCount == 0) { - return ret; - } - ret.reserve(node->TimelineSum.size()); - for (double time : node->TimelineSum) { - ret.emplace_back(nullptr, double(time) / node->TrackCount); - } - TPatternNode* n = node; - for (auto i = ret.rbegin(), e = ret.rend(); i != e; ++i) { + + TTimeline MakeTimeline(TPatternNode* node) + { + TTimeline ret; + if (node->TrackCount == 0) { + return ret; + } + ret.reserve(node->TimelineSum.size()); + for (double time : node->TimelineSum) { + ret.emplace_back(nullptr, double(time) / node->TrackCount); + } + TPatternNode* n = node; + for (auto i = ret.rbegin(), e = ret.rend(); i != e; ++i) { WWW_CHECK(n, "internal bug: wrong timeline length at pattern node '%s'", node->GetPath().data()); - i->first = n; - n = n->Parent; - } - return ret; - } - + i->first = n; + n = n->Parent; + } + return ret; + } + void OutputTimeline(IOutputStream& os, const TTimeline& timeline, double maxTime) - { - static const char *barClass[] = { - "progress-bar-info", - "progress-bar-warning" - }; - if (timeline.empty()) { - return; - } - os << "<div class=\"progress\" style=\"margin-bottom:0px;color:black\">"; - double prevPos = 0.0; - double prevTime = 0.0; - size_t i = 0; - for (auto& e : timeline) { - TPatternNode* node = e.first; - double time = e.second; - double pos = time * 100 / maxTime; - if (pos > 100) { - pos = 100; - } - double width = pos - prevPos; - os << "<div class=\"progress-bar " << barClass[i % 2] << "\" role=\"progressbar\"" - " aria-valuenow=\"" << width << "\"" - " aria-valuemin=\"0\"" - " aria-valuemax=\"100\"" - " style=\"width:" << width << "%;color:black\"" - " title=\"" << FormatTimelineTooltip(time, prevTime, node) << "\">"; - if (width > 20) { // To ensure text will fit the bar - os << FormatCycles(time - prevTime); - } - os << "</div>"; - prevPos = pos; - prevTime = time; - i++; - } - os << "</div>"; - } - + { + static const char *barClass[] = { + "progress-bar-info", + "progress-bar-warning" + }; + if (timeline.empty()) { + return; + } + os << "<div class=\"progress\" style=\"margin-bottom:0px;color:black\">"; + double prevPos = 0.0; + double prevTime = 0.0; + size_t i = 0; + for (auto& e : timeline) { + TPatternNode* node = e.first; + double time = e.second; + double pos = time * 100 / maxTime; + if (pos > 100) { + pos = 100; + } + double width = pos - prevPos; + os << "<div class=\"progress-bar " << barClass[i % 2] << "\" role=\"progressbar\"" + " aria-valuenow=\"" << width << "\"" + " aria-valuemin=\"0\"" + " aria-valuemax=\"100\"" + " style=\"width:" << width << "%;color:black\"" + " title=\"" << FormatTimelineTooltip(time, prevTime, node) << "\">"; + if (width > 20) { // To ensure text will fit the bar + os << FormatCycles(time - prevTime); + } + os << "</div>"; + prevPos = pos; + prevTime = time; + i++; + } + os << "</div>"; + } + TString FormatTimelineTooltip(double time, double prevTime, TPatternNode* node) - { - return FormatCycles(time - prevTime) + ": " - + FormatCycles(prevTime) + " -> " + FormatCycles(time) - + "(" + node->Name + ")"; - } - + { + return FormatCycles(time - prevTime) + ": " + + FormatCycles(prevTime) + " -> " + FormatCycles(time) + + "(" + node->Name + ")"; + } + TString FormatFloat(double value) - { - if (value == 0.0) { - return "0"; - } - if (value > 1.0) { - if (value > 100.0) { - return Sprintf("%.0lf", value); - } - if (value > 10.0) { - return Sprintf("%.1lf", value); - } - return Sprintf("%.2lf", value); - } else if (value > 1e-3) { - if (value > 1e-1) { - return Sprintf("%.3lf", value); - } - if (value > 1e-2) { - return Sprintf("%.4lf", value); - } - return Sprintf("%.5lf", value); - } else if (value > 1e-6) { - if (value > 1e-4) { - return Sprintf("%.6lf", value); - } - if (value > 1e-5) { - return Sprintf("%.7lf", value); - } - return Sprintf("%.8lfus", value); - } else { - if (value > 1e-7) { - return Sprintf("%.9lfns", value); - } - if (value > 1e-8) { - return Sprintf("%.10lfns", value); - } - return Sprintf("%.2le", value); - } - } - + { + if (value == 0.0) { + return "0"; + } + if (value > 1.0) { + if (value > 100.0) { + return Sprintf("%.0lf", value); + } + if (value > 10.0) { + return Sprintf("%.1lf", value); + } + return Sprintf("%.2lf", value); + } else if (value > 1e-3) { + if (value > 1e-1) { + return Sprintf("%.3lf", value); + } + if (value > 1e-2) { + return Sprintf("%.4lf", value); + } + return Sprintf("%.5lf", value); + } else if (value > 1e-6) { + if (value > 1e-4) { + return Sprintf("%.6lf", value); + } + if (value > 1e-5) { + return Sprintf("%.7lf", value); + } + return Sprintf("%.8lfus", value); + } else { + if (value > 1e-7) { + return Sprintf("%.9lfns", value); + } + if (value > 1e-8) { + return Sprintf("%.10lfns", value); + } + return Sprintf("%.2le", value); + } + } + TString FormatCycles(double timeCycles) - { - double timeSec = timeCycles / NHPTimer::GetClockRate(); - if (timeSec > 1.0) { - if (timeSec > 100.0) { - return Sprintf("%.0lfs", timeSec); - } - if (timeSec > 10.0) { - return Sprintf("%.1lfs", timeSec); - } - return Sprintf("%.2lfs", timeSec); - } else if (timeSec > 1e-3) { - if (timeSec > 1e-1) { - return Sprintf("%.0lfms", timeSec * 1e3); - } - if (timeSec > 1e-2) { - return Sprintf("%.1lfms", timeSec * 1e3); - } - return Sprintf("%.2lfms", timeSec * 1e3); - } else if (timeSec > 1e-6) { - if (timeSec > 1e-4) { - return Sprintf("%.0lfus", timeSec * 1e6); - } - if (timeSec > 1e-5) { - return Sprintf("%.1lfus", timeSec * 1e6); - } - return Sprintf("%.2lfus", timeSec * 1e6); - } else { - if (timeSec > 1e-7) { - return Sprintf("%.0lfns", timeSec * 1e9); - } - if (timeSec > 1e-8) { - return Sprintf("%.1lfns", timeSec * 1e9); - } - return Sprintf("%.2lfns", timeSec * 1e9); - } - } - + { + double timeSec = timeCycles / NHPTimer::GetClockRate(); + if (timeSec > 1.0) { + if (timeSec > 100.0) { + return Sprintf("%.0lfs", timeSec); + } + if (timeSec > 10.0) { + return Sprintf("%.1lfs", timeSec); + } + return Sprintf("%.2lfs", timeSec); + } else if (timeSec > 1e-3) { + if (timeSec > 1e-1) { + return Sprintf("%.0lfms", timeSec * 1e3); + } + if (timeSec > 1e-2) { + return Sprintf("%.1lfms", timeSec * 1e3); + } + return Sprintf("%.2lfms", timeSec * 1e3); + } else if (timeSec > 1e-6) { + if (timeSec > 1e-4) { + return Sprintf("%.0lfus", timeSec * 1e6); + } + if (timeSec > 1e-5) { + return Sprintf("%.1lfus", timeSec * 1e6); + } + return Sprintf("%.2lfus", timeSec * 1e6); + } else { + if (timeSec > 1e-7) { + return Sprintf("%.0lfns", timeSec * 1e9); + } + if (timeSec > 1e-8) { + return Sprintf("%.1lfns", timeSec * 1e9); + } + return Sprintf("%.2lfns", timeSec * 1e9); + } + } + TString GetParam(const NLWTrace::TLogItem& item, TString* paramValues, const TString& paramName) - { - for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { - if (paramName == item.Probe->Event.Signature.ParamNames[pi]) { - return paramValues[pi]; - } - } + { + for (size_t pi = 0; pi < item.SavedParamsCount; pi++) { + if (paramName == item.Probe->Event.Signature.ParamNames[pi]) { + return paramValues[pi]; + } + } return TString(); - } - + } + TString GetGroup(const NLWTrace::TLogItem& item, TString* paramValues) - { - TStringStream ss; - bool first = true; + { + TStringStream ss; + bool first = true; for (const TString& groupParam : GroupBy) { - ss << (first? "": "|") << GetParam(item, paramValues, groupParam); - first = false; - } - return ss.Str(); - } - - void AddItemToTrack(TThread::TId tid, const NLWTrace::TLogItem& item) - { - // Ensure cyclic per thread lwtrace logs wont drop *inner* items of a track - // (note that some *starting* items can be dropped) - if (item.SavedParamsCount > 0 && !CutTs.Skip(item)) { + ss << (first? "": "|") << GetParam(item, paramValues, groupParam); + first = false; + } + return ss.Str(); + } + + void AddItemToTrack(TThread::TId tid, const NLWTrace::TLogItem& item) + { + // Ensure cyclic per thread lwtrace logs wont drop *inner* items of a track + // (note that some *starting* items can be dropped) + if (item.SavedParamsCount > 0 && !CutTs.Skip(item)) { TString paramValues[LWTRACE_MAX_PARAMS]; - item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); - Tracks[GetGroup(item, paramValues)].Items.emplace_back(tid, item); - } - } -}; - -NLWTrace::TProbeRegistry g_Probes; + item.Probe->Event.Signature.SerializeParams(item.Params, paramValues); + Tracks[GetGroup(item, paramValues)].Items.emplace_back(tid, item); + } + } +}; + +NLWTrace::TProbeRegistry g_Probes; TString g_sanitizerTest("TString g_sanitizerTest"); -NLWTrace::TManager g_SafeManager(g_Probes, false); -NLWTrace::TManager g_UnsafeManager(g_Probes, true); +NLWTrace::TManager g_SafeManager(g_Probes, false); +NLWTrace::TManager g_UnsafeManager(g_Probes, true); TDashboardRegistry g_DashboardRegistry; - -class TLWTraceMonPage : public NMonitoring::IMonPage { + +class TLWTraceMonPage : public NMonitoring::IMonPage { private: - NLWTrace::TManager* TraceMngr; + NLWTrace::TManager* TraceMngr; TString StartTime; - TTraceCleaner Cleaner; - TMutex SnapshotsMtx; + TTraceCleaner Cleaner; + TMutex SnapshotsMtx; THashMap<TString, TAtomicSharedPtr<NLWTrace::TLogPb>> Snapshots; public: - explicit TLWTraceMonPage(bool allowUnsafe = false) + explicit TLWTraceMonPage(bool allowUnsafe = false) : NMonitoring::IMonPage("trace", "Tracing") - , TraceMngr(&TraceManager(allowUnsafe)) - , Cleaner(TraceMngr) - { - time_t stime = TInstant::Now().TimeT(); - StartTime = CTimeR(&stime); - } + , TraceMngr(&TraceManager(allowUnsafe)) + , Cleaner(TraceMngr) + { + time_t stime = TInstant::Now().TimeT(); + StartTime = CTimeR(&stime); + } virtual void Output(NMonitoring::IMonHttpRequest& request) { - TStringStream out; + TStringStream out; try { - if (request.GetParams().Get("mode") == "") { - OutputTracesAndSnapshots(request, out); + if (request.GetParams().Get("mode") == "") { + OutputTracesAndSnapshots(request, out); } else if (request.GetParams().Get("mode") == "probes") { - OutputProbes(request, out); + OutputProbes(request, out); } else if (request.GetParams().Get("mode") == "dashboards") { OutputDashboards(request, out); } else if (request.GetParams().Get("mode") == "dashboard") { OutputDashboard(request, out); } else if (request.GetParams().Get("mode") == "log") { - OutputLog(request, out); + OutputLog(request, out); } else if (request.GetParams().Get("mode") == "query") { - OutputQuery(request, out); - } else if (request.GetParams().Get("mode") == "builder") { - OutputBuilder(request, out); - } else if (request.GetParams().Get("mode") == "analytics") { - OutputAnalytics(request, out); + OutputQuery(request, out); + } else if (request.GetParams().Get("mode") == "builder") { + OutputBuilder(request, out); + } else if (request.GetParams().Get("mode") == "analytics") { + OutputAnalytics(request, out); } else if (request.GetParams().Get("mode") == "new") { - PostNew(request, out); + PostNew(request, out); } else if (request.GetParams().Get("mode") == "delete") { - PostDelete(request, out); - } else if (request.GetParams().Get("mode") == "make_snapshot") { - PostSnapshot(request, out); - } else if (request.GetParams().Get("mode") == "settimeout") { - PostSetTimeout(request, out); + PostDelete(request, out); + } else if (request.GetParams().Get("mode") == "make_snapshot") { + PostSnapshot(request, out); + } else if (request.GetParams().Get("mode") == "settimeout") { + PostSetTimeout(request, out); } else { ythrow yexception() << "Bad request"; } - } catch (TPageGenBase& gen) { - out.Clear(); - out << gen.what(); - } catch (...) { - out.Clear(); - if (request.GetParams().Get("error") == "text") { - // Text error reply is helpful for ajax requests - out << NMonitoring::HTTPOKTEXT; - out << CurrentExceptionMessage(); - } else { - WWW_HTML(out) { - out << "<h2>Error</h2><pre>" - << CurrentExceptionMessage() - << Endl; - } - } - } + } catch (TPageGenBase& gen) { + out.Clear(); + out << gen.what(); + } catch (...) { + out.Clear(); + if (request.GetParams().Get("error") == "text") { + // Text error reply is helpful for ajax requests + out << NMonitoring::HTTPOKTEXT; + out << CurrentExceptionMessage(); + } else { + WWW_HTML(out) { + out << "<h2>Error</h2><pre>" + << CurrentExceptionMessage() + << Endl; + } + } + } request.Output() << out.Str(); - } - -private: + } + +private: void OutputNavbar(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { + { TString active = " class=\"active\""; - out << - "<nav class=\"navbar navbar-default\"><div class=\"container-fluid\">" - << NavbarHeader() << - "<ul class=\"nav navbar-nav\">" - "<li" << (request.GetParams().Get("mode") == ""? active: "") << "><a href=\"?mode=\">Traces</a></li>" - "<li" << (request.GetParams().Get("mode") == "probes"? active: "") << "><a href=\"?mode=probes\">Probes</a></li>" + out << + "<nav class=\"navbar navbar-default\"><div class=\"container-fluid\">" + << NavbarHeader() << + "<ul class=\"nav navbar-nav\">" + "<li" << (request.GetParams().Get("mode") == ""? active: "") << "><a href=\"?mode=\">Traces</a></li>" + "<li" << (request.GetParams().Get("mode") == "probes"? active: "") << "><a href=\"?mode=probes\">Probes</a></li>" "<li" << (request.GetParams().Get("mode") == "dashboards"? active: "") << "><a href=\"?mode=dashboards\">Dashboard</a></li>" - "<li" << (request.GetParams().Get("mode") == "builder"? active: "") << "><a href=\"?mode=builder\">Builder</a></li>" - "<li" << (request.GetParams().Get("mode") == "analytics"? active: "") << "><a href=\"?mode=analytics&id=\">Analytics</a></li>" + "<li" << (request.GetParams().Get("mode") == "builder"? active: "") << "><a href=\"?mode=builder\">Builder</a></li>" + "<li" << (request.GetParams().Get("mode") == "analytics"? active: "") << "><a href=\"?mode=analytics&id=\">Analytics</a></li>" "<li><a href=\"https://wiki.yandex-team.ru/development/poisk/arcadia/library/cpp/lwtrace/\" target=\"_blank\">Documentation</a></li>" - "</ul>" - "</div></nav>" - ; - } - - template <class TReader> - void ReadSnapshots(TReader& reader) const - { - TGuard<TMutex> g(SnapshotsMtx); - for (const auto& kv : Snapshots) { - reader.Push(kv.first, kv.second); - } - } - + "</ul>" + "</div></nav>" + ; + } + + template <class TReader> + void ReadSnapshots(TReader& reader) const + { + TGuard<TMutex> g(SnapshotsMtx); + for (const auto& kv : Snapshots) { + reader.Push(kv.first, kv.second); + } + } + void OutputTracesAndSnapshots(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - TLogSources logSources(Cleaner); - TraceMngr->ReadTraces(logSources); - ReadSnapshots(logSources); - - TStringStream ss; - TTracesHtmlPrinter printer(ss); - logSources.ForEach(printer); - WWW_HTML(out) { - OutputNavbar(request, out); - out << - "<table class=\"table table-striped\">" - "<tr><th>Start Time</th><th>Timeout</th><th>Name</th><th>Events</th><th>Threads</th><th></th><th></th><th></th><th></th><th></th></tr>" - << ss.Str() << - "</table>" - ; - out << "<hr/><p><strong>Start time:</strong> " << StartTime; - out << "<br/><strong>Build date:</strong> "; - out << __DATE__ << " " << __TIME__ << "</p>" << Endl; - } - } - + { + TLogSources logSources(Cleaner); + TraceMngr->ReadTraces(logSources); + ReadSnapshots(logSources); + + TStringStream ss; + TTracesHtmlPrinter printer(ss); + logSources.ForEach(printer); + WWW_HTML(out) { + OutputNavbar(request, out); + out << + "<table class=\"table table-striped\">" + "<tr><th>Start Time</th><th>Timeout</th><th>Name</th><th>Events</th><th>Threads</th><th></th><th></th><th></th><th></th><th></th></tr>" + << ss.Str() << + "</table>" + ; + out << "<hr/><p><strong>Start time:</strong> " << StartTime; + out << "<br/><strong>Build date:</strong> "; + out << __DATE__ << " " << __TIME__ << "</p>" << Endl; + } + } + void OutputProbes(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - TStringStream ss; - TProbesHtmlPrinter printer; - TraceMngr->ReadProbes(printer); - printer.Output(ss); - WWW_HTML(out) { - OutputNavbar(request, out); - out << ss.Str(); - } - } - + { + TStringStream ss; + TProbesHtmlPrinter printer; + TraceMngr->ReadProbes(printer); + printer.Output(ss); + WWW_HTML(out) { + OutputNavbar(request, out); + out << ss.Str(); + } + } + void OutputDashboards(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) { TStringStream ss; @@ -3910,7 +3910,7 @@ private: WWW_HTML(out) { OutputNavbar(request, out); out << ss.Str(); - } + } } void OutputDashboard(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) { @@ -3918,97 +3918,97 @@ private: ythrow yexception() << "Cgi-parameter 'name' is not specified"; } else { auto name = request.GetParams().Get("name"); - NLWTrace::TDashboard dash; + NLWTrace::TDashboard dash; if (!g_DashboardRegistry.Get(name, dash)) { ythrow yexception() << "Dashboard doesn't exist"; } WWW_HTML(out) { OutputNavbar(request, out); - out << "<style type='text/css'>html, body { height: 100%; }</style>"; - out << "<h2>" << dash.GetName() << "</h2>"; - if (dash.GetDescription()) { - out << "<h3>" << dash.GetDescription() << "</h3>"; - } - int height = 85; // % - int minHeight = 100; // px - out << "<table height='" << height << "%' width='100%' cellpadding='4'><tbody height='100%' width='100%'>"; - ui32 rows = 0; - auto maxRowSpan = [](const auto& row) { - ui32 rowSpan = 1; - for (const auto& cell : row.GetCells()) { - rowSpan = Max(rowSpan, cell.GetRowSpan()); - } - return rowSpan; - }; - for (const auto& row : dash.GetRows()) { - rows += maxRowSpan(row); - } - for (const auto& row : dash.GetRows()) { - int rowSpan = maxRowSpan(row); - out << "<tr align='left' valign='top' style='height:" << (height * rowSpan / rows) << "%; min-height:" << (minHeight * rowSpan)<< "px'>"; - for (const auto& cell : row.GetCells()) { - TString url = cell.GetUrl(); - TString title = cell.GetTitle(); - TString text = cell.GetText(); - auto rowSpan = Max<ui64>(1, cell.GetRowSpan()); - auto colSpan = Max<ui64>(1, cell.GetColSpan()); - if (url) { - if (title) { - out << "<td rowspan='" << rowSpan << "' colSpan='1'><a href=" << url << ">" << title << "</a><br>"; + out << "<style type='text/css'>html, body { height: 100%; }</style>"; + out << "<h2>" << dash.GetName() << "</h2>"; + if (dash.GetDescription()) { + out << "<h3>" << dash.GetDescription() << "</h3>"; + } + int height = 85; // % + int minHeight = 100; // px + out << "<table height='" << height << "%' width='100%' cellpadding='4'><tbody height='100%' width='100%'>"; + ui32 rows = 0; + auto maxRowSpan = [](const auto& row) { + ui32 rowSpan = 1; + for (const auto& cell : row.GetCells()) { + rowSpan = Max(rowSpan, cell.GetRowSpan()); + } + return rowSpan; + }; + for (const auto& row : dash.GetRows()) { + rows += maxRowSpan(row); + } + for (const auto& row : dash.GetRows()) { + int rowSpan = maxRowSpan(row); + out << "<tr align='left' valign='top' style='height:" << (height * rowSpan / rows) << "%; min-height:" << (minHeight * rowSpan)<< "px'>"; + for (const auto& cell : row.GetCells()) { + TString url = cell.GetUrl(); + TString title = cell.GetTitle(); + TString text = cell.GetText(); + auto rowSpan = Max<ui64>(1, cell.GetRowSpan()); + auto colSpan = Max<ui64>(1, cell.GetColSpan()); + if (url) { + if (title) { + out << "<td rowspan='" << rowSpan << "' colSpan='1'><a href=" << url << ">" << title << "</a><br>"; + } + out << "<iframe scrolling='no' width='" << 100 * colSpan << "%' height='" << height << "%' style='border: 0' src=" << url << "></iframe></td>"; + // Add fake cells to fix html table + for (ui32 left = 1; left < colSpan; ++left) { + out << "<td height='100%' rowspan='" << rowSpan << "' colSpan='1'>" + << "<iframe scrolling='no' width='100%' height='100%' style='border: 0' src=" << "" << "></iframe></td>"; } - out << "<iframe scrolling='no' width='" << 100 * colSpan << "%' height='" << height << "%' style='border: 0' src=" << url << "></iframe></td>"; - // Add fake cells to fix html table - for (ui32 left = 1; left < colSpan; ++left) { - out << "<td height='100%' rowspan='" << rowSpan << "' colSpan='1'>" - << "<iframe scrolling='no' width='100%' height='100%' style='border: 0' src=" << "" << "></iframe></td>"; - } - } else { - out << "<td style='font-size: 25px' align='left' rowspan='" << rowSpan << "' colSpan='" << colSpan << "'>" << text << "</td>"; + } else { + out << "<td style='font-size: 25px' align='left' rowspan='" << rowSpan << "' colSpan='" << colSpan << "'>" << text << "</td>"; } } - } + } out << "</tbody></table>"; } } } - static double ParseDouble(const TString& s) - { - if (s == "inf") { - return std::numeric_limits<double>::infinity(); - } else if (s == "-inf") { - return -std::numeric_limits<double>::infinity(); - } else { - return FromString<double>(s); - } - } - + static double ParseDouble(const TString& s) + { + if (s == "inf") { + return std::numeric_limits<double>::infinity(); + } else if (s == "-inf") { + return -std::numeric_limits<double>::infinity(); + } else { + return FromString<double>(s); + } + } + void OutputLog(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - if (request.GetParams().NumOfValues("id") == 0) { - ythrow yexception() << "Cgi-parameter 'id' is not specified"; - } else { + { + if (request.GetParams().NumOfValues("id") == 0) { + ythrow yexception() << "Cgi-parameter 'id' is not specified"; + } else { const TCgiParameters& e = request.GetParams(); - TStringStream ss; - if (e.Get("format") == "json") { - TLogJsonPrinter printer(ss); - printer.OutputHeader(); - TString id = e.Get("id"); - CheckAdHocTrace(id, TDuration::Minutes(1)); - TraceMngr->ReadLog(id, printer); - printer.OutputFooter(TraceMngr->GetTrace(id)); - out << HTTPOKJSON; - out << ss.Str(); - } if (e.Get("format") == "json2") { - TLogTextPrinter printer(e); - for (const TString& id : Subvalues(e, "id")) { - CheckAdHocTrace(id, TDuration::Minutes(1)); - TraceMngr->ReadLog(id, printer); - TraceMngr->ReadDepot(id, printer); - } - printer.OutputJson(ss); - out << HTTPOKJSON; - out << ss.Str(); + TStringStream ss; + if (e.Get("format") == "json") { + TLogJsonPrinter printer(ss); + printer.OutputHeader(); + TString id = e.Get("id"); + CheckAdHocTrace(id, TDuration::Minutes(1)); + TraceMngr->ReadLog(id, printer); + printer.OutputFooter(TraceMngr->GetTrace(id)); + out << HTTPOKJSON; + out << ss.Str(); + } if (e.Get("format") == "json2") { + TLogTextPrinter printer(e); + for (const TString& id : Subvalues(e, "id")) { + CheckAdHocTrace(id, TDuration::Minutes(1)); + TraceMngr->ReadLog(id, printer); + TraceMngr->ReadDepot(id, printer); + } + printer.OutputJson(ss); + out << HTTPOKJSON; + out << ss.Str(); } else if (e.Get("format") == "analytics" && e.Get("aggr") == "tracks") { TLogTrackExtractor logTrackExtractor(e, Subvalues(e, "f"), @@ -4027,8 +4027,8 @@ private: TLogTextPrinter printer(e); const TString& distBy = patternAnalyzer; - double sel_x1 = e.Get("sel_x1")? ParseDouble(e.Get("sel_x1")): NAN; - double sel_x2 = e.Get("sel_x2")? ParseDouble(e.Get("sel_x2")): NAN; + double sel_x1 = e.Get("sel_x1")? ParseDouble(e.Get("sel_x1")): NAN; + double sel_x2 = e.Get("sel_x2")? ParseDouble(e.Get("sel_x2")): NAN; TSampleOpts opts; opts.ShowProvider = (e.Get("show_provider") == "y"); if (e.Get("size_limit")) { @@ -4038,101 +4038,101 @@ private: printer.Output(ss); out << HTTPOKTEXT; out << ss.Str(); - } else { + } else { TLogTextPrinter printer(e); for (const TString& id : Subvalues(e, "id")) { - CheckAdHocTrace(id, TDuration::Minutes(1)); - TraceMngr->ReadLog(id, printer); - TraceMngr->ReadDepot(id, printer); - } - printer.Output(ss); - out << HTTPOKTEXT; - out << ss.Str(); - } - } - } - + CheckAdHocTrace(id, TDuration::Minutes(1)); + TraceMngr->ReadLog(id, printer); + TraceMngr->ReadDepot(id, printer); + } + printer.Output(ss); + out << HTTPOKTEXT; + out << ss.Str(); + } + } + } + void OutputQuery(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - if (request.GetParams().NumOfValues("id") == 0) { - ythrow yexception() << "Cgi-parameter 'id' is not specified"; - } else { + { + if (request.GetParams().NumOfValues("id") == 0) { + ythrow yexception() << "Cgi-parameter 'id' is not specified"; + } else { TString id = request.GetParams().Get("id"); - const NLWTrace::TQuery& query = TraceMngr->GetTrace(id)->GetQuery(); + const NLWTrace::TQuery& query = TraceMngr->GetTrace(id)->GetQuery(); TString queryStr = query.DebugString(); - WWW_HTML(out) { - out << "<h2>Trace Query: " << id << "</h2><pre>" << queryStr; - } + WWW_HTML(out) { + out << "<h2>Trace Query: " << id << "</h2><pre>" << queryStr; + } } } - + void OutputBuilder(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - Y_UNUSED(request); - WWW_HTML(out) { - OutputNavbar(request, out); - out << "<form class=\"form-horizontal\" action=\"?mode=new&ui=y\" method=\"POST\">"; - DIV_CLASS("form-group") { - LABEL_CLASS_FOR("col-sm-1 control-label", "inputId") { out << "Name"; } - DIV_CLASS("col-sm-11") { - out << "<input class=\"form-control\" id=\"inputId\" name=\"id\" placeholder=\"mytrace\">"; - } - } - DIV_CLASS("form-group") { - LABEL_CLASS_FOR("col-sm-1 control-label", "textareaQuery") { out << "Query"; } - DIV_CLASS("col-sm-11") { - out << "<textarea class=\"form-control\" id=\"textareaQuery\" name=\"query\" rows=\"10\"></textarea>"; - } - } - DIV_CLASS("form-group") { - DIV_CLASS("col-sm-offset-1 col-sm-11") { - out << "<button type=\"submit\" class=\"btn btn-default\">Trace</button>"; - } - } - out << "</form>"; - } - } - + { + Y_UNUSED(request); + WWW_HTML(out) { + OutputNavbar(request, out); + out << "<form class=\"form-horizontal\" action=\"?mode=new&ui=y\" method=\"POST\">"; + DIV_CLASS("form-group") { + LABEL_CLASS_FOR("col-sm-1 control-label", "inputId") { out << "Name"; } + DIV_CLASS("col-sm-11") { + out << "<input class=\"form-control\" id=\"inputId\" name=\"id\" placeholder=\"mytrace\">"; + } + } + DIV_CLASS("form-group") { + LABEL_CLASS_FOR("col-sm-1 control-label", "textareaQuery") { out << "Query"; } + DIV_CLASS("col-sm-11") { + out << "<textarea class=\"form-control\" id=\"textareaQuery\" name=\"query\" rows=\"10\"></textarea>"; + } + } + DIV_CLASS("form-group") { + DIV_CLASS("col-sm-offset-1 col-sm-11") { + out << "<button type=\"submit\" class=\"btn btn-default\">Trace</button>"; + } + } + out << "</form>"; + } + } + void OutputAnalytics(const NMonitoring::IMonHttpRequest& request, TStringStream& out) - { - using namespace NAnalytics; - const TCgiParameters& e = request.GetParams(); - - TLogSources logSources(Cleaner); - TraceMngr->ReadTraces(logSources); - ReadSnapshots(logSources); - - RequireMultipleSelection(out, e, "id", "Analyze ", ListTraces(logSources)); - - THolder<TLogFilter> logFilter; - TLogAnalyzer* logAnalyzer = nullptr; - TLogTrackExtractor* logTracks = nullptr; - if (request.GetParams().Get("aggr") == "tracks") { - logFilter.Reset(logTracks = new TLogTrackExtractor(e, - Subvalues(request.GetParams(), "f"), - Subvalues(request.GetParams(), "g") - )); + { + using namespace NAnalytics; + const TCgiParameters& e = request.GetParams(); + + TLogSources logSources(Cleaner); + TraceMngr->ReadTraces(logSources); + ReadSnapshots(logSources); + + RequireMultipleSelection(out, e, "id", "Analyze ", ListTraces(logSources)); + + THolder<TLogFilter> logFilter; + TLogAnalyzer* logAnalyzer = nullptr; + TLogTrackExtractor* logTracks = nullptr; + if (request.GetParams().Get("aggr") == "tracks") { + logFilter.Reset(logTracks = new TLogTrackExtractor(e, + Subvalues(request.GetParams(), "f"), + Subvalues(request.GetParams(), "g") + )); for (const TString& id : Subvalues(request.GetParams(), "id")) { - CheckAdHocTrace(id, TDuration::Minutes(1)); - TraceMngr->ReadLog(id, *logTracks); - TraceMngr->ReadDepot(id, *logTracks); - } - } else { - logFilter.Reset(logAnalyzer = new TLogAnalyzer( - Subvalues(request.GetParams(), "f"), - Subvalues(request.GetParams(), "g"), - request.GetParams().Get("cutts") == "y" - )); + CheckAdHocTrace(id, TDuration::Minutes(1)); + TraceMngr->ReadLog(id, *logTracks); + TraceMngr->ReadDepot(id, *logTracks); + } + } else { + logFilter.Reset(logAnalyzer = new TLogAnalyzer( + Subvalues(request.GetParams(), "f"), + Subvalues(request.GetParams(), "g"), + request.GetParams().Get("cutts") == "y" + )); for (const TString& id : Subvalues(request.GetParams(), "id")) { - CheckAdHocTrace(id, TDuration::Minutes(1)); - TraceMngr->ReadLog(id, *logAnalyzer); - TraceMngr->ReadDepot(id, *logAnalyzer); - } - } - - logFilter->FilterSelectors(out, e, "f"); - - OptionalMultipleSelection(out, e, "g", "group by", logFilter->ListParamNames()); + CheckAdHocTrace(id, TDuration::Minutes(1)); + TraceMngr->ReadLog(id, *logAnalyzer); + TraceMngr->ReadDepot(id, *logAnalyzer); + } + } + + logFilter->FilterSelectors(out, e, "f"); + + OptionalMultipleSelection(out, e, "g", "group by", logFilter->ListParamNames()); { auto paramNamesList = logFilter->ListParamNames(); if (e.Get("aggr") == "tracks") { @@ -4140,7 +4140,7 @@ private: } OptionalSelection(out, e, "s", "order by", paramNamesList); } - + if (e.Get("s")) { TVariants variants; variants.emplace_back("", "asc"); @@ -4149,130 +4149,130 @@ private: } TString aggr = e.Get("aggr"); - TVariants variants1; // MSVS2013 doesn't understand complex initializer lists - variants1.emplace_back("", "without aggregation"); - variants1.emplace_back("hist", "as histogram"); - variants1.emplace_back("tracks", "tracks"); - DropdownSelector<Link>(out, e, "aggr", e.Get("aggr"), "", variants1); - - unsigned refresh = e.Get("refresh")? - FromString<unsigned>(e.Get("refresh")): - 1000; - - if (aggr == "tracks") { - TVariants ileVars; - ileVars.emplace_back("0", "0"); - ileVars.emplace_back("25", "25"); - ileVars.emplace_back("50", "50"); - ileVars.emplace_back("75", "75"); - ileVars.emplace_back("", "90"); - ileVars.emplace_back("95", "95"); - ileVars.emplace_back("99", "99"); - ileVars.emplace_back("99.9", "99.9"); - ileVars.emplace_back("100", "100"); - DropdownSelector<Link>(out, e, "ile", e.Get("ile"), "and show", ileVars); - out << "%-ile. "; - TString patternAnalyzer; + TVariants variants1; // MSVS2013 doesn't understand complex initializer lists + variants1.emplace_back("", "without aggregation"); + variants1.emplace_back("hist", "as histogram"); + variants1.emplace_back("tracks", "tracks"); + DropdownSelector<Link>(out, e, "aggr", e.Get("aggr"), "", variants1); + + unsigned refresh = e.Get("refresh")? + FromString<unsigned>(e.Get("refresh")): + 1000; + + if (aggr == "tracks") { + TVariants ileVars; + ileVars.emplace_back("0", "0"); + ileVars.emplace_back("25", "25"); + ileVars.emplace_back("50", "50"); + ileVars.emplace_back("75", "75"); + ileVars.emplace_back("", "90"); + ileVars.emplace_back("95", "95"); + ileVars.emplace_back("99", "99"); + ileVars.emplace_back("99.9", "99.9"); + ileVars.emplace_back("100", "100"); + DropdownSelector<Link>(out, e, "ile", e.Get("ile"), "and show", ileVars); + out << "%-ile. "; + TString patternAnalyzer; TString distBy; TString distType; - if (e.Get("pattern")) { - TVariants analyzePatternVars; - analyzePatternVars.emplace_back("resTotal", "distribution by total"); - analyzePatternVars.emplace_back("resLast", "distribution by last"); - analyzePatternVars.emplace_back("covMatrix", "covariance matrix"); - DropdownSelector<Link>( - out, e, "ptrn_anlz", e.Get("ptrn_anlz"), - "Pattern", analyzePatternVars - ); - patternAnalyzer = e.Get("ptrn_anlz"); - - TVariants distTypeVars; - distTypeVars.emplace_back("", "as is"); - distTypeVars.emplace_back("-stack", "cumulative"); - DropdownSelector<Link>(out, e, "dist_type", e.Get("dist_type"), "", distTypeVars); - distType = e.Get("dist_type"); - } else { - out << "<i>Select pattern for more options</i>"; - } - logTracks->Run(); - - if (e.Get("download") == "y") { - out.Clear(); - out << - "HTTP/1.1 200 Ok\r\n" - "Content-Type: application/force-download\r\n" - "Content-Transfer-Encoding: binary\r\n" - "Content-Disposition: attachment; filename=\"trace_chrome.json\"\r\n" - "\r\n" - ; - logTracks->OutputChromeTrace(out, e); - return; - } - - NAnalytics::TTable distData; - bool showSample = false; + if (e.Get("pattern")) { + TVariants analyzePatternVars; + analyzePatternVars.emplace_back("resTotal", "distribution by total"); + analyzePatternVars.emplace_back("resLast", "distribution by last"); + analyzePatternVars.emplace_back("covMatrix", "covariance matrix"); + DropdownSelector<Link>( + out, e, "ptrn_anlz", e.Get("ptrn_anlz"), + "Pattern", analyzePatternVars + ); + patternAnalyzer = e.Get("ptrn_anlz"); + + TVariants distTypeVars; + distTypeVars.emplace_back("", "as is"); + distTypeVars.emplace_back("-stack", "cumulative"); + DropdownSelector<Link>(out, e, "dist_type", e.Get("dist_type"), "", distTypeVars); + distType = e.Get("dist_type"); + } else { + out << "<i>Select pattern for more options</i>"; + } + logTracks->Run(); + + if (e.Get("download") == "y") { + out.Clear(); + out << + "HTTP/1.1 200 Ok\r\n" + "Content-Type: application/force-download\r\n" + "Content-Transfer-Encoding: binary\r\n" + "Content-Disposition: attachment; filename=\"trace_chrome.json\"\r\n" + "\r\n" + ; + logTracks->OutputChromeTrace(out, e); + return; + } + + NAnalytics::TTable distData; + bool showSample = false; TLogTextPrinter printer(e); - - if (patternAnalyzer == "resTotal" || patternAnalyzer == "resLast") { - distBy = patternAnalyzer; - distData = logTracks->Distribution(distBy, "", "", e.Get("width")); - double sel_x1 = e.Get("sel_x1")? ParseDouble(e.Get("sel_x1")): NAN; - double sel_x2 = e.Get("sel_x2")? ParseDouble(e.Get("sel_x2")): NAN; - if (!isnan(sel_x1) && !isnan(sel_x2)) { - showSample = true; - TSampleOpts opts; - opts.ShowProvider = (e.Get("show_provider") == "y"); - if (e.Get("size_limit")) { - opts.SizeLimit = FromString<size_t>(e.Get("size_limit")); - } + + if (patternAnalyzer == "resTotal" || patternAnalyzer == "resLast") { + distBy = patternAnalyzer; + distData = logTracks->Distribution(distBy, "", "", e.Get("width")); + double sel_x1 = e.Get("sel_x1")? ParseDouble(e.Get("sel_x1")): NAN; + double sel_x2 = e.Get("sel_x2")? ParseDouble(e.Get("sel_x2")): NAN; + if (!isnan(sel_x1) && !isnan(sel_x2)) { + showSample = true; + TSampleOpts opts; + opts.ShowProvider = (e.Get("show_provider") == "y"); + if (e.Get("size_limit")) { + opts.SizeLimit = FromString<size_t>(e.Get("size_limit")); + } logTracks->OutputSample(distBy, sel_x1, sel_x2, opts, printer); - } - } - + } + } + TString selectors = out.Str(); - out.Clear(); - out << NMonitoring::HTTPOKHTML; - out << "<!DOCTYPE html>" << Endl; - HTML(out) { - HTML_TAG() { - HEAD() { - out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; - if (distBy) { - out << - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " var dataurl = null;\n" - " var datajson = " << ToJsonFlot(distData, distBy, {"_count_sum" + distType}) << ";\n" - " var refreshPeriod = 0;\n" - " var xn = \"" << distBy << "\";\n" - " var navigate = false;\n" - << NResource::Find("lwtrace/mon/static/analytics.js") << - " embededMode();" - " enableSelection();" - "});\n" - "</script>\n"; - } - // Show download button - out << - "<script type=\"text/javascript\">" - "$(function() {" - " $(\"#download-btn\").click(function(){window.location.href='" - << MakeUrlAdd(e, "download", "y") << - "';});" - " $(\"#download-btn\").removeClass(\"hidden\");" - "});" - "</script>\n"; - } - BODY() { - // Wrap selectors with navbar - { TSelectorsContainer sc(out); - out << selectors; - } - - logTracks->OutputTable(out, e); - if (distBy) { - out << NResource::Find("lwtrace/mon/static/analytics.flot.html") << Endl; - if (showSample) { + out.Clear(); + out << NMonitoring::HTTPOKHTML; + out << "<!DOCTYPE html>" << Endl; + HTML(out) { + HTML_TAG() { + HEAD() { + out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; + if (distBy) { + out << + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " var dataurl = null;\n" + " var datajson = " << ToJsonFlot(distData, distBy, {"_count_sum" + distType}) << ";\n" + " var refreshPeriod = 0;\n" + " var xn = \"" << distBy << "\";\n" + " var navigate = false;\n" + << NResource::Find("lwtrace/mon/static/analytics.js") << + " embededMode();" + " enableSelection();" + "});\n" + "</script>\n"; + } + // Show download button + out << + "<script type=\"text/javascript\">" + "$(function() {" + " $(\"#download-btn\").click(function(){window.location.href='" + << MakeUrlAdd(e, "download", "y") << + "';});" + " $(\"#download-btn\").removeClass(\"hidden\");" + "});" + "</script>\n"; + } + BODY() { + // Wrap selectors with navbar + { TSelectorsContainer sc(out); + out << selectors; + } + + logTracks->OutputTable(out, e); + if (distBy) { + out << NResource::Find("lwtrace/mon/static/analytics.flot.html") << Endl; + if (showSample) { static const THashSet<TString> keepParams = { "f", "g", @@ -4302,134 +4302,134 @@ private: out << "<pre>\n"; printer.Output(out); out << "</pre>\n"; - } - } - - if (patternAnalyzer == "covMatrix") { - logTracks->OutputSliceCovarianceMatrix(out, e); - } - } - } - } - } else { - double width = e.Get("width")? FromString<double>(e.Get("width")): 99; - - NAnalytics::TTable data; - if (aggr == "") { - data = logAnalyzer->GetTable(); - } else if (aggr == "hist") { - RequireSelection(out, e, "bn", "by", logFilter->ListParamNames()); - const NAnalytics::TTable& inputTable = logAnalyzer->GetTable(); + } + } + + if (patternAnalyzer == "covMatrix") { + logTracks->OutputSliceCovarianceMatrix(out, e); + } + } + } + } + } else { + double width = e.Get("width")? FromString<double>(e.Get("width")): 99; + + NAnalytics::TTable data; + if (aggr == "") { + data = logAnalyzer->GetTable(); + } else if (aggr == "hist") { + RequireSelection(out, e, "bn", "by", logFilter->ListParamNames()); + const NAnalytics::TTable& inputTable = logAnalyzer->GetTable(); TString bn = e.Get("bn"); - double b1 = e.Get("b1")? FromString<double>(e.Get("b1")): MinValue(bn, inputTable); - double b2 = e.Get("b2")? FromString<double>(e.Get("b2")): MaxValue(bn, inputTable); - if (isfinite(b1) && isfinite(b2)) { - WWW_CHECK(b1 <= b2, "invalid xrange [%le; %le]", b1, b2); - double dx = e.Get("dx")? FromString<double>(e.Get("dx")): (b2-b1)/width; - data = HistogramAll(inputTable, e.Get("bn"), b1, b2, dx); - } else { - // Empty table -- it's ok -- leave data table empty - } - } - + double b1 = e.Get("b1")? FromString<double>(e.Get("b1")): MinValue(bn, inputTable); + double b2 = e.Get("b2")? FromString<double>(e.Get("b2")): MaxValue(bn, inputTable); + if (isfinite(b1) && isfinite(b2)) { + WWW_CHECK(b1 <= b2, "invalid xrange [%le; %le]", b1, b2); + double dx = e.Get("dx")? FromString<double>(e.Get("dx")): (b2-b1)/width; + data = HistogramAll(inputTable, e.Get("bn"), b1, b2, dx); + } else { + // Empty table -- it's ok -- leave data table empty + } + } + TString xn = e.Get("xn"); - + TString outFormat = e.Get("out"); - TVariants variants2; - variants2.emplace_back("html", "table"); - variants2.emplace_back("flot", "chart"); - variants2.emplace_back("gantt", "gantt"); - variants2.emplace_back("text", "text"); - variants2.emplace_back("csv", "CSV"); - variants2.emplace_back("json_flot", "JSON"); - - RequireSelection(out, e, "out", "and show", variants2); - if (outFormat == "csv") { + TVariants variants2; + variants2.emplace_back("html", "table"); + variants2.emplace_back("flot", "chart"); + variants2.emplace_back("gantt", "gantt"); + variants2.emplace_back("text", "text"); + variants2.emplace_back("csv", "CSV"); + variants2.emplace_back("json_flot", "JSON"); + + RequireSelection(out, e, "out", "and show", variants2); + if (outFormat == "csv") { TString sep = e.Get("sep")? e.Get("sep"): TString("\t"); - out.Clear(); - out << NMonitoring::HTTPOKTEXT; - out << ToCsv(data, sep, e.Get("head") != "n"); - } else if (outFormat == "html") { + out.Clear(); + out << NMonitoring::HTTPOKTEXT; + out << ToCsv(data, sep, e.Get("head") != "n"); + } else if (outFormat == "html") { TString selectors = out.Str(); - out.Clear(); - WWW_HTML(out) { - // Wrap selectors with navbar - { TSelectorsContainer sc(out); - out << selectors; - } - out << ToHtml(data); - } - } else if (outFormat == "json_flot") { - SeriesSelectors(out, e, "xn", "yns", data); - out.Clear(); - out << NMonitoring::HTTPOKJSON; + out.Clear(); + WWW_HTML(out) { + // Wrap selectors with navbar + { TSelectorsContainer sc(out); + out << selectors; + } + out << ToHtml(data); + } + } else if (outFormat == "json_flot") { + SeriesSelectors(out, e, "xn", "yns", data); + out.Clear(); + out << NMonitoring::HTTPOKJSON; out << ToJsonFlot(data, xn, SplitString(e.Get("yns"), ":")); - } else if (outFormat == "flot") { - SeriesSelectors(out, e, "xn", "yns", data); + } else if (outFormat == "flot") { + SeriesSelectors(out, e, "xn", "yns", data); TString selectors = out.Str(); - + TVector<TString> ynos = SplitString(e.Get("yns"), ":"); - out.Clear(); - out << NMonitoring::HTTPOKHTML; - out << "<!DOCTYPE html>" << Endl; - HTML(out) { - HTML_TAG() { - HEAD() { - out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; - out << - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " var dataurl = \"" << EscapeJSONString(MakeUrl(e, "out", "json_flot")) << "\";\n" - " var refreshPeriod = " << refresh << ";\n" - " var xn = \"" << ParseName(xn) << "\";\n" - " var navigate = true;\n" - << NResource::Find("lwtrace/mon/static/analytics.js") << - "});\n" - "</script>\n" - ; - } - BODY() { - // Wrap selectors with navbar - { TSelectorsContainer sc(out); - out << selectors; - } - out << NResource::Find("lwtrace/mon/static/analytics.flot.html") << Endl; - } - } - } - } else if (outFormat == "gantt") { - TString selectors = out.Str(); - out.Clear(); - out << NMonitoring::HTTPOKHTML; - out << "<!DOCTYPE html>" << Endl; - HTML(out) { - HTML_TAG() { - HEAD() { - out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; - out << - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " var dataurl = \"" << EscapeJSONString(MakeUrl(e, {{"mode", "log"}, {"format", "json2"}, {"gantt",""}})) << "\";\n" - " var refreshPeriod = " << refresh << ";\n" - " var xn = \"" << ParseName(xn) << "\";\n" - " var navigate = true;\n" - << NResource::Find("lwtrace/mon/static/analytics.js") << - "});\n" - "</script>\n" - ; - } - BODY() { - // Wrap selectors with navbar - { TSelectorsContainer sc(out); - out << selectors; - } - out << NResource::Find("lwtrace/mon/static/analytics.gantt.html") << Endl; - } - } - } + out.Clear(); + out << NMonitoring::HTTPOKHTML; + out << "<!DOCTYPE html>" << Endl; + HTML(out) { + HTML_TAG() { + HEAD() { + out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; + out << + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " var dataurl = \"" << EscapeJSONString(MakeUrl(e, "out", "json_flot")) << "\";\n" + " var refreshPeriod = " << refresh << ";\n" + " var xn = \"" << ParseName(xn) << "\";\n" + " var navigate = true;\n" + << NResource::Find("lwtrace/mon/static/analytics.js") << + "});\n" + "</script>\n" + ; + } + BODY() { + // Wrap selectors with navbar + { TSelectorsContainer sc(out); + out << selectors; + } + out << NResource::Find("lwtrace/mon/static/analytics.flot.html") << Endl; + } + } + } + } else if (outFormat == "gantt") { + TString selectors = out.Str(); + out.Clear(); + out << NMonitoring::HTTPOKHTML; + out << "<!DOCTYPE html>" << Endl; + HTML(out) { + HTML_TAG() { + HEAD() { + out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; + out << + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " var dataurl = \"" << EscapeJSONString(MakeUrl(e, {{"mode", "log"}, {"format", "json2"}, {"gantt",""}})) << "\";\n" + " var refreshPeriod = " << refresh << ";\n" + " var xn = \"" << ParseName(xn) << "\";\n" + " var navigate = true;\n" + << NResource::Find("lwtrace/mon/static/analytics.js") << + "});\n" + "</script>\n" + ; + } + BODY() { + // Wrap selectors with navbar + { TSelectorsContainer sc(out); + out << selectors; + } + out << NResource::Find("lwtrace/mon/static/analytics.gantt.html") << Endl; + } + } + } } else if (outFormat = "text") { - out << " <input type='text' id='logsLimit' size='2' placeholder='Limit'>" << Endl; - out << + out << " <input type='text' id='logsLimit' size='2' placeholder='Limit'>" << Endl; + out << R"END(<script> { var url = new URL(window.location.href); @@ -4468,7 +4468,7 @@ private: HTML(out) { HTML_TAG() { HEAD() { - out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; + out << NResource::Find("lwtrace/mon/static/analytics.header.html") << Endl; } BODY() { // Wrap selectors with navbar @@ -4511,213 +4511,213 @@ private: } } } - } - } - } - + } + } + } + TDuration GetGetTimeout(const NMonitoring::IMonHttpRequest& request) - { - return (request.GetParams().Has("timeout")? - TDuration::Seconds(FromString<double>(request.GetParams().Get("timeout"))): - TDuration::Max()); - } - + { + return (request.GetParams().Has("timeout")? + TDuration::Seconds(FromString<double>(request.GetParams().Get("timeout"))): + TDuration::Max()); + } + void PostNew(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); + { + WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); const TString& id = request.GetPostParams().Get("id"); - bool ui = (request.GetParams().Get("ui") == "y"); - TDuration timeout = GetGetTimeout(request); - if (!CheckAdHocTrace(id, timeout)) { - NLWTrace::TQuery query; + bool ui = (request.GetParams().Get("ui") == "y"); + TDuration timeout = GetGetTimeout(request); + if (!CheckAdHocTrace(id, timeout)) { + NLWTrace::TQuery query; TString queryStr = request.GetPostParams().Get("query"); - if (!ui) { - queryStr = Base64Decode(queryStr); // Needed for trace.sh (historically) - } - WWW_CHECK(queryStr, "Empty trace query"); - bool parsed = NProtoBuf::TextFormat::ParseFromString(queryStr, &query); - WWW_CHECK(parsed, "Trace query text protobuf parse failed"); // TODO[serxa]: report error line/col and message - TraceMngr->New(id, query); - Cleaner.Postpone(id, timeout, false); - } else { + if (!ui) { + queryStr = Base64Decode(queryStr); // Needed for trace.sh (historically) + } + WWW_CHECK(queryStr, "Empty trace query"); + bool parsed = NProtoBuf::TextFormat::ParseFromString(queryStr, &query); + WWW_CHECK(parsed, "Trace query text protobuf parse failed"); // TODO[serxa]: report error line/col and message + TraceMngr->New(id, query); + Cleaner.Postpone(id, timeout, false); + } else { WWW_CHECK(!request.GetPostParams().Has("query"), "trace id '%s' is reserved for ad-hoc traces", id.data()); - } - if (ui) { - WWW_HTML(out) { - out << - "<div class=\"jumbotron alert-success\">" - "<h2>Trace created successfully</h2>" - "</div>" - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " setTimeout(function() {" - " window.location.replace('?');" - " }, 1000);" - "});\n" - "</script>\n"; - } - } else { - out << HTTPOKTEXT; - out << "OK\n"; - } - } - + } + if (ui) { + WWW_HTML(out) { + out << + "<div class=\"jumbotron alert-success\">" + "<h2>Trace created successfully</h2>" + "</div>" + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " setTimeout(function() {" + " window.location.replace('?');" + " }, 1000);" + "});\n" + "</script>\n"; + } + } else { + out << HTTPOKTEXT; + out << "OK\n"; + } + } + void PostDelete(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); + { + WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); const TString& id = request.GetPostParams().Get("id"); - bool ui = (request.GetParams().Get("ui") == "y"); - TraceMngr->Delete(id); - Cleaner.Forget(id); - if (ui) { - WWW_HTML(out) { - out << - "<div class=\"jumbotron alert-success\">" - "<h2>Trace deleted successfully</h2>" - "</div>" - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " setTimeout(function() {" - " window.location.replace('?');" - " }, 1000);" - "});\n" - "</script>\n"; - } - } else { - out << HTTPOKTEXT; - out << "OK\n"; - } - } - + bool ui = (request.GetParams().Get("ui") == "y"); + TraceMngr->Delete(id); + Cleaner.Forget(id); + if (ui) { + WWW_HTML(out) { + out << + "<div class=\"jumbotron alert-success\">" + "<h2>Trace deleted successfully</h2>" + "</div>" + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " setTimeout(function() {" + " window.location.replace('?');" + " }, 1000);" + "});\n" + "</script>\n"; + } + } else { + out << HTTPOKTEXT; + out << "OK\n"; + } + } + void PostSnapshot(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); + { + WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); const TString& id = request.GetPostParams().Get("id"); - bool ui = (request.GetParams().Get("ui") == "y"); - TInstant now = TInstant::Now(); - - TGuard<TMutex> g(SnapshotsMtx); - const NLWTrace::TSession* trace = TraceMngr->GetTrace(id); - struct tm tm0; + bool ui = (request.GetParams().Get("ui") == "y"); + TInstant now = TInstant::Now(); + + TGuard<TMutex> g(SnapshotsMtx); + const NLWTrace::TSession* trace = TraceMngr->GetTrace(id); + struct tm tm0; TString sid = id + Strftime("_%Y%m%d-%H%M%S", now.GmTime(&tm0)); - TAtomicSharedPtr<NLWTrace::TLogPb>& pbPtr = Snapshots[sid]; - pbPtr.Reset(new NLWTrace::TLogPb()); - trace->ToProtobuf(*pbPtr); - pbPtr->SetName(sid); - if (ui) { - WWW_HTML(out) { - out << - "<div class=\"jumbotron alert-success\">" - "<h2>Snapshot created successfully</h2>" - "</div>" - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " setTimeout(function() {" - " window.location.replace('?');" - " }, 1000);" - "});\n" - "</script>\n"; - } - } else { - out << HTTPOKTEXT; - out << "OK\n"; - } - } - + TAtomicSharedPtr<NLWTrace::TLogPb>& pbPtr = Snapshots[sid]; + pbPtr.Reset(new NLWTrace::TLogPb()); + trace->ToProtobuf(*pbPtr); + pbPtr->SetName(sid); + if (ui) { + WWW_HTML(out) { + out << + "<div class=\"jumbotron alert-success\">" + "<h2>Snapshot created successfully</h2>" + "</div>" + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " setTimeout(function() {" + " window.location.replace('?');" + " }, 1000);" + "});\n" + "</script>\n"; + } + } else { + out << HTTPOKTEXT; + out << "OK\n"; + } + } + void PostSetTimeout(const NMonitoring::IMonHttpRequest& request, IOutputStream& out) - { - WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); + { + WWW_CHECK(request.GetPostParams().Has("id"), "POST parameter 'id' is not specified"); const TString& id = request.GetPostParams().Get("id"); - TDuration timeout = GetGetTimeout(request); - bool ui = (request.GetParams().Get("ui") == "y"); - Cleaner.Postpone(id, timeout, true); - if (ui) { - WWW_HTML(out) { - out << - "<div class=\"jumbotron alert-success\">" - "<h2>Timeout changed successfully</h2>" - "</div>" - "<script type=\"text/javascript\">\n" - "$(function() {\n" - " setTimeout(function() {" - " window.location.replace('?');" - " }, 1000);" - "});\n" - "</script>\n"; - } - } else { - out << HTTPOKTEXT; - out << "OK\n"; - } - } + TDuration timeout = GetGetTimeout(request); + bool ui = (request.GetParams().Get("ui") == "y"); + Cleaner.Postpone(id, timeout, true); + if (ui) { + WWW_HTML(out) { + out << + "<div class=\"jumbotron alert-success\">" + "<h2>Timeout changed successfully</h2>" + "</div>" + "<script type=\"text/javascript\">\n" + "$(function() {\n" + " setTimeout(function() {" + " window.location.replace('?');" + " }, 1000);" + "});\n" + "</script>\n"; + } + } else { + out << HTTPOKTEXT; + out << "OK\n"; + } + } void RegisterDashboard(const TString& dashConfig) { g_DashboardRegistry.Register(dashConfig); } -private: - // Returns true iff trace is ad-hoc and ensures trace is created +private: + // Returns true iff trace is ad-hoc and ensures trace is created bool CheckAdHocTrace(const TString& id, TDuration timeout) - { - TAdHocTraceConfig cfg; - if (cfg.ParseId(id)) { - if (!TraceMngr->HasTrace(id)) { - TraceMngr->New(id, cfg.Query()); - } - Cleaner.Postpone(id, timeout, false); - return true; - } - return false; - } + { + TAdHocTraceConfig cfg; + if (cfg.ParseId(id)) { + if (!TraceMngr->HasTrace(id)) { + TraceMngr->New(id, cfg.Query()); + } + Cleaner.Postpone(id, timeout, false); + return true; + } + return false; + } }; -void RegisterPages(NMonitoring::TMonService2* mon, bool allowUnsafe) { +void RegisterPages(NMonitoring::TMonService2* mon, bool allowUnsafe) { THolder<NLwTraceMonPage::TLWTraceMonPage> p = MakeHolder<NLwTraceMonPage::TLWTraceMonPage>(allowUnsafe); - mon->Register(p.Release()); - -#define WWW_STATIC_FILE(file, type) \ - mon->Register(new TResourceMonPage(file, file, NMonitoring::TResourceMonPage::type)); - WWW_STATIC_FILE("lwtrace/mon/static/common.css", CSS); - WWW_STATIC_FILE("lwtrace/mon/static/common.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/css/bootstrap.min.css", CSS); - WWW_STATIC_FILE("lwtrace/mon/static/css/d3-gantt.css", CSS); - WWW_STATIC_FILE("lwtrace/mon/static/css/jquery.treegrid.css", CSS); - WWW_STATIC_FILE("lwtrace/mon/static/analytics.css", CSS); - WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.eot", FONT_EOT); - WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.svg", SVG); - WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.ttf", FONT_TTF); - WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.woff2", FONT_WOFF2); - WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.woff", FONT_WOFF); - WWW_STATIC_FILE("lwtrace/mon/static/img/collapse.png", PNG); - WWW_STATIC_FILE("lwtrace/mon/static/img/expand.png", PNG); - WWW_STATIC_FILE("lwtrace/mon/static/img/file.png", PNG); - WWW_STATIC_FILE("lwtrace/mon/static/img/folder.png", PNG); - WWW_STATIC_FILE("lwtrace/mon/static/js/bootstrap.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/d3.v4.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/d3-gantt.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/d3-tip-0.8.0-alpha.1.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/filesaver.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.extents.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.navigate.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.selection.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.treegrid.bootstrap3.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.treegrid.min.js", JAVASCRIPT); - WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.url.min.js", JAVASCRIPT); -#undef WWW_STATIC_FILE + mon->Register(p.Release()); + +#define WWW_STATIC_FILE(file, type) \ + mon->Register(new TResourceMonPage(file, file, NMonitoring::TResourceMonPage::type)); + WWW_STATIC_FILE("lwtrace/mon/static/common.css", CSS); + WWW_STATIC_FILE("lwtrace/mon/static/common.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/css/bootstrap.min.css", CSS); + WWW_STATIC_FILE("lwtrace/mon/static/css/d3-gantt.css", CSS); + WWW_STATIC_FILE("lwtrace/mon/static/css/jquery.treegrid.css", CSS); + WWW_STATIC_FILE("lwtrace/mon/static/analytics.css", CSS); + WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.eot", FONT_EOT); + WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.svg", SVG); + WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.ttf", FONT_TTF); + WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.woff2", FONT_WOFF2); + WWW_STATIC_FILE("lwtrace/mon/static/fonts/glyphicons-halflings-regular.woff", FONT_WOFF); + WWW_STATIC_FILE("lwtrace/mon/static/img/collapse.png", PNG); + WWW_STATIC_FILE("lwtrace/mon/static/img/expand.png", PNG); + WWW_STATIC_FILE("lwtrace/mon/static/img/file.png", PNG); + WWW_STATIC_FILE("lwtrace/mon/static/img/folder.png", PNG); + WWW_STATIC_FILE("lwtrace/mon/static/js/bootstrap.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/d3.v4.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/d3-gantt.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/d3-tip-0.8.0-alpha.1.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/filesaver.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.extents.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.navigate.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.flot.selection.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.treegrid.bootstrap3.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.treegrid.min.js", JAVASCRIPT); + WWW_STATIC_FILE("lwtrace/mon/static/js/jquery.url.min.js", JAVASCRIPT); +#undef WWW_STATIC_FILE } - -NLWTrace::TProbeRegistry& ProbeRegistry() { - return g_Probes; -} - -NLWTrace::TManager& TraceManager(bool allowUnsafe) { - return allowUnsafe? g_UnsafeManager: g_SafeManager; -} - + +NLWTrace::TProbeRegistry& ProbeRegistry() { + return g_Probes; +} + +NLWTrace::TManager& TraceManager(bool allowUnsafe) { + return allowUnsafe? g_UnsafeManager: g_SafeManager; +} + TDashboardRegistry& DashboardRegistry() { return g_DashboardRegistry; } -} // namespace NLwTraceMonPage +} // namespace NLwTraceMonPage |