aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpavook <pavook@yandex-team.com>2024-07-30 21:40:33 +0300
committerpavook <pavook@yandex-team.com>2024-07-30 21:52:37 +0300
commitf7ca71b582e2347ec55857b493d6bccf55bbc3df (patch)
tree3a136dbb465847d633e3764636b3d5f09d2e1f4c
parent79da6309e6e03ecbc7eb6f5083e2350c65496f68 (diff)
downloadydb-f7ca71b582e2347ec55857b493d6bccf55bbc3df.tar.gz
Add a safe class for managing statistic paths
2b600be732d64131e213852536c78b387535213f
-rw-r--r--yt/yt/core/misc/public.h8
-rw-r--r--yt/yt/core/misc/statistic_path-inl.h29
-rw-r--r--yt/yt/core/misc/statistic_path.cpp323
-rw-r--r--yt/yt/core/misc/statistic_path.h258
-rw-r--r--yt/yt/core/misc/unittests/statistic_path_ut.cpp216
-rw-r--r--yt/yt/core/misc/unittests/ya.make1
-rw-r--r--yt/yt/core/ya.make1
-rw-r--r--yt/yt/core/ytree/serialize.cpp12
-rw-r--r--yt/yt/core/ytree/serialize.h5
9 files changed, 853 insertions, 0 deletions
diff --git a/yt/yt/core/misc/public.h b/yt/yt/core/misc/public.h
index 7efd15b738..9cff9ba2cd 100644
--- a/yt/yt/core/misc/public.h
+++ b/yt/yt/core/misc/public.h
@@ -140,6 +140,14 @@ using TInternedObjectDataPtr = TIntrusivePtr<TInternedObjectData<T>>;
template <class T>
class TInternedObject;
+namespace NStatisticPath {
+
+class TStatisticPathLiteral;
+class TStatisticPath;
+struct TStatisticPathSerializer;
+
+} // namespace NStatisticPath
+
class TStatistics;
class TSummary;
diff --git a/yt/yt/core/misc/statistic_path-inl.h b/yt/yt/core/misc/statistic_path-inl.h
new file mode 100644
index 0000000000..d9821066a3
--- /dev/null
+++ b/yt/yt/core/misc/statistic_path-inl.h
@@ -0,0 +1,29 @@
+#ifndef STATISTIC_PATH_INL_H_
+ #error "Direct inclusion of this file is not allowed, include format.h"
+ // For the sake of sane code completion.
+ #include "statistic_path.h"
+#endif
+
+#include "serialize.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename C>
+void NStatisticPath::TStatisticPathSerializer::Save(C& context, const NStatisticPath::TStatisticPath& path)
+{
+ NYT::Save(context, path.Path());
+}
+
+template <typename C>
+void NStatisticPath::TStatisticPathSerializer::Load(C& context, NStatisticPath::TStatisticPath& path)
+{
+ NStatisticPath::TStatisticPathType tmp;
+ NYT::Load(context, tmp);
+ path = TStatisticPath(std::move(tmp));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/core/misc/statistic_path.cpp b/yt/yt/core/misc/statistic_path.cpp
new file mode 100644
index 0000000000..19f478d9f9
--- /dev/null
+++ b/yt/yt/core/misc/statistic_path.cpp
@@ -0,0 +1,323 @@
+#include "statistic_path.h"
+
+#include "error.h"
+
+#include <yt/yt/core/yson/format.h>
+
+namespace NYT::NStatisticPath {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError CheckStatisticPathLiteral(const TStatisticPathType& literal)
+{
+ if (literal.Empty()) {
+ return TError("Empty statistic path literal");
+ }
+ constexpr static TChar invalidCharacters[2]{Delimiter, 0};
+ constexpr static TBasicStringBuf<TChar> invalidCharacterBuf(invalidCharacters, 2);
+ if (auto invalidPos = literal.find_first_of(invalidCharacterBuf);
+ invalidPos != TStatisticPathType::npos) {
+ return TError("Invalid character found in a statistic path literal")
+ << TErrorAttribute("literal", literal)
+ << TErrorAttribute("invalid_character", static_cast<ui8>(literal[invalidPos]));
+ }
+ return {};
+}
+
+TErrorOr<TStatisticPathLiteral> ParseStatisticPathLiteral(const TStatisticPathType& literal)
+{
+ if (auto error = CheckStatisticPathLiteral(literal); !error.IsOK()) {
+ return error;
+ }
+ return TStatisticPathLiteral(literal, false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPathLiteral::TStatisticPathLiteral(const TStatisticPathType& literal, bool /*tag*/) noexcept
+ : Literal_(literal)
+{ }
+
+TStatisticPathLiteral::TStatisticPathLiteral(const TStatisticPathType& literal)
+ : TStatisticPathLiteral(ParseStatisticPathLiteral(literal).ValueOrThrow())
+{ }
+
+const TStatisticPathType& TStatisticPathLiteral::Literal() const noexcept
+{
+ return Literal_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPath::TStatisticPath(const TStatisticPathType& path)
+ : Path_(path)
+{ }
+
+TStatisticPath::TStatisticPath(TStatisticPathType&& path) noexcept
+ : Path_(std::move(path))
+{ }
+
+TStatisticPath::TStatisticPath(const TStatisticPathLiteral& literal)
+ : Path_(TStatisticPathType(Delimiter) + literal.Literal())
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError CheckStatisticPath(const TStatisticPathType& path)
+{
+ constexpr static TChar twoDelimiters[2]{Delimiter, Delimiter};
+ constexpr static TBasicStringBuf<TChar> adjacentDelimiters(twoDelimiters, 2);
+
+ TError error;
+ if (path.empty()) {
+ return {};
+ }
+
+ if (path.front() != Delimiter) {
+ error = TError("Statistic path must start with a delimiter");
+ } else if (path.back() == Delimiter) {
+ error = TError("Statistic path must not end with a delimiter");
+ } else if (path.Contains(0)) {
+ error = TError("Statistic path must not contain a null character");
+ } else if (path.Contains(adjacentDelimiters)) {
+ error = TError("Statistic path must not contain adjacent delimiters");
+ }
+
+ if (!error.IsOK()) {
+ return error
+ << TErrorAttribute("path", path);
+ }
+ return {};
+}
+
+TErrorOr<TStatisticPath> ParseStatisticPath(const TStatisticPathType& path)
+{
+ if (auto error = CheckStatisticPath(path); !error.IsOK()) {
+ return error;
+ }
+ return TStatisticPath(path);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const TStatisticPathType& TStatisticPath::Path() const noexcept
+{
+ return Path_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TStatisticPath::Empty() const noexcept
+{
+ return Path_.empty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TStatisticPath::Swap(TStatisticPath& other) noexcept
+{
+ Path_.swap(other.Path_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPath& TStatisticPath::Append(const TStatisticPathLiteral& literal)
+{
+ return Append(TStatisticPath(literal));
+}
+
+TStatisticPath& TStatisticPath::Append(const TStatisticPath& other)
+{
+ Path_.append(other.Path_);
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPath::const_iterator::value_type TStatisticPath::Front() const noexcept
+{
+ YT_VERIFY(!Empty());
+ return *begin();
+}
+
+TStatisticPath::const_iterator::value_type TStatisticPath::Back() const noexcept
+{
+ YT_VERIFY(!Empty());
+ return *(--end());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TStatisticPath::PopBack()
+{
+ YT_VERIFY(!Empty());
+ Path_.resize(Path_.size() - Back().Size() - 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TStatisticPath::StartsWith(const TStatisticPath& prefix) const
+{
+ // TODO(pavook) std::ranges::starts_with(*this, prefix) when C++23 arrives.
+ auto [selfIt, prefixIt] = std::ranges::mismatch(*this, prefix);
+ return prefixIt == prefix.end();
+}
+
+bool TStatisticPath::EndsWith(const TStatisticPath& suffix) const
+{
+ // TODO(pavook) std::ranges::ends_with(*this, suffix) when C++23 arrives.
+ auto [selfIt, suffixIt] = std::mismatch(rbegin(), rend(), suffix.rbegin(), suffix.rend());
+ return suffixIt == suffix.rend();
+}
+
+TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPathLiteral& rhs)
+{
+ return TStatisticPath(lhs).Append(rhs);
+}
+
+TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPath& rhs)
+{
+ return TStatisticPath(lhs).Append(rhs);
+}
+
+TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPathLiteral& rhs)
+{
+ return TStatisticPath(std::move(lhs)).Append(rhs);
+}
+
+TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPath& rhs)
+{
+ return TStatisticPath(std::move(lhs)).Append(rhs);
+}
+
+TStatisticPath operator/(const TStatisticPathLiteral& lhs, const TStatisticPathLiteral& rhs)
+{
+ return TStatisticPath(lhs).Append(rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept
+{
+ return lhs.Path() == rhs.Path();
+}
+
+std::strong_ordering operator<=>(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept
+{
+ return lhs.Path().compare(rhs.Path()) <=> 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NStatisticPathLiterals {
+
+TStatisticPathLiteral operator""_L(const char* str, size_t len)
+{
+ return TStatisticPathLiteral(TStatisticPathType(str, len));
+}
+
+} // namespace NStatisticPathLiterals
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPath::TIterator::TIterator(const TStatisticPath& path, TStatisticPath::TIterator::TBeginIteratorTag) noexcept
+ : Path_(path.Path())
+ , Entry_(Path_.begin(), Path_.begin())
+{
+ Advance();
+}
+
+TStatisticPath::TIterator::TIterator(const TStatisticPath& path, TStatisticPath::TIterator::TEndIteratorTag) noexcept
+ : Path_(path.Path())
+ , Entry_()
+{ }
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPath::TIterator::reference TStatisticPath::TIterator::operator*() const noexcept
+{
+ return Entry_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TStatisticPath::TIterator& TStatisticPath::TIterator::operator++() noexcept
+{
+ Advance();
+ return *this;
+}
+
+TStatisticPath::TIterator TStatisticPath::TIterator::operator++(int) noexcept
+{
+ TIterator result = *this;
+ Advance();
+ return result;
+}
+
+TStatisticPath::TIterator& TStatisticPath::TIterator::operator--() noexcept
+{
+ Retreat();
+ return *this;
+}
+
+TStatisticPath::TIterator TStatisticPath::TIterator::operator--(int) noexcept
+{
+ TIterator result = *this;
+ Retreat();
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TStatisticPath::TIterator::Advance() noexcept
+{
+ if (Entry_.end() == Path_.end()) {
+ Entry_ = value_type();
+ } else {
+ Entry_ = value_type(std::next(Entry_.end()), Path_.end()).NextTok(Delimiter);
+ }
+}
+
+void TStatisticPath::TIterator::Retreat() noexcept
+{
+ if (!Entry_) {
+ Entry_ = value_type(Path_.begin(), Path_.end()).RNextTok(Delimiter);
+ } else {
+ Entry_ = value_type(Path_.begin(), std::prev(Entry_.begin())).RNextTok(Delimiter);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TStatisticPath::TIterator& lhs, const TStatisticPath::TIterator& rhs) noexcept
+{
+ // TODO(pavook) should we compare sizes?
+ return lhs.Entry_.data() == rhs.Entry_.data();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(pavook) make it constant-time to comply with ranges::range semantic requirements?
+TStatisticPath::const_iterator TStatisticPath::begin() const noexcept
+{
+ return {*this, TStatisticPath::TIterator::TBeginIteratorTag{}};
+}
+
+TStatisticPath::const_iterator TStatisticPath::end() const noexcept
+{
+ return {*this, TStatisticPath::TIterator::TEndIteratorTag{}};
+}
+
+TStatisticPath::const_reverse_iterator TStatisticPath::rbegin() const noexcept
+{
+ return std::make_reverse_iterator(end());
+}
+
+TStatisticPath::const_reverse_iterator TStatisticPath::rend() const noexcept
+{
+ return std::make_reverse_iterator(begin());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NStatisticPath
diff --git a/yt/yt/core/misc/statistic_path.h b/yt/yt/core/misc/statistic_path.h
new file mode 100644
index 0000000000..8908e13089
--- /dev/null
+++ b/yt/yt/core/misc/statistic_path.h
@@ -0,0 +1,258 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/yson/public.h>
+#include <yt/yt/core/ytree/public.h>
+
+#include <util/generic/strbuf.h>
+#include <util/generic/string.h>
+
+namespace NYT {
+
+namespace NStatisticPath {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A type to store paths and literals as.
+using TStatisticPathType = TString;
+
+using TChar = TStatisticPathType::TChar;
+
+//! Delimiter character to use instead of `/`. We want it to compare "less" than any other character.
+//! See YT-22118 for motivation.
+static constexpr TChar Delimiter = '\x01';
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Checks that `literal` is a valid statistic path literal, i.e. `literal` isn't empty,
+//! doesn't contain `Delimiter` and doesn't contain `\0`.
+TError CheckStatisticPathLiteral(const TStatisticPathType& literal);
+
+class TStatisticPathLiteral
+{
+private:
+ TStatisticPathLiteral(const TStatisticPathType& literal, bool /*tag*/) noexcept;
+
+ friend TErrorOr<TStatisticPathLiteral> ParseStatisticPathLiteral(const TStatisticPathType& literal);
+
+public:
+ //! Can throw an exception in case `literal` is invalid (see `CheckStatisticPathLiteral`).
+ explicit TStatisticPathLiteral(const TStatisticPathType& literal);
+
+ friend class TStatisticPath;
+
+ [[nodiscard]] bool operator==(const TStatisticPathLiteral& other) const noexcept = default;
+
+ //! Returns the underlying string.
+ [[nodiscard]] const TStatisticPathType& Literal() const noexcept;
+
+private:
+ TStatisticPathType Literal_;
+};
+
+//! Checks that `literal` is a valid statistic path literal (see `CheckStatisticPathLiteral`) and
+//! returns TStatisticPathLiteral if it is. Returns an error with explanation otherwise.
+TErrorOr<TStatisticPathLiteral> ParseStatisticPathLiteral(const TStatisticPathType& literal);
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Checks that `path` is a valid statistic path (it can be constructed as a sequence of literals).
+//! Returns an error with explanation otherwise.
+TError CheckStatisticPath(const TStatisticPathType& path);
+
+//! Checks that `path` is a valid statistic path (it can be constructed as a sequence of literals),
+//! and returns a `TStatisticPath` built from it if it is. Returns an error with explanation otherwise.
+TErrorOr<TStatisticPath> ParseStatisticPath(const TStatisticPathType& path);
+
+// TODO(pavook) constexpr when constexpr std::string arrives.
+class TStatisticPath
+{
+private:
+ class TIterator
+ {
+ public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = TBasicStringBuf<TChar>;
+ using difference_type = value_type::difference_type;
+ using pointer = void;
+
+ // This is needed for iterator sanity as we store the value inside the iterator.
+ // This is allowed, as since C++20 ForwardIterator::reference doesn't have to be an actual reference.
+ using reference = const value_type;
+
+ reference operator*() const noexcept;
+
+ TIterator& operator++() noexcept;
+
+ TIterator operator++(int) noexcept;
+
+ TIterator& operator--() noexcept;
+
+ TIterator operator--(int) noexcept;
+
+ friend bool operator==(const TIterator& lhs, const TIterator& rhs) noexcept;
+
+ TIterator() noexcept = default;
+
+ TIterator(const TIterator& other) noexcept = default;
+
+ TIterator& operator=(const TIterator& other) noexcept = default;
+
+ private:
+ value_type Path_;
+ value_type Entry_;
+
+ struct TBeginIteratorTag
+ { };
+
+ struct TEndIteratorTag
+ { };
+
+ TIterator(const TStatisticPath& path, TBeginIteratorTag) noexcept;
+
+ TIterator(const TStatisticPath& path, TEndIteratorTag) noexcept;
+
+ void Advance() noexcept;
+
+ void Retreat() noexcept;
+
+ friend class TStatisticPath;
+ };
+
+ friend bool operator==(const TIterator& lhs, const TIterator& rhs) noexcept;
+
+public:
+ using iterator = TIterator;
+ using const_iterator = iterator;
+ using reverse_iterator = std::reverse_iterator<TIterator>;
+ using const_reverse_iterator = reverse_iterator;
+
+ //! Constructs an empty path. The underlying string will be empty.
+ TStatisticPath() noexcept = default;
+
+ //! Constructs a path from an existing literal. The literal will be prepended with a Delimiter.
+ TStatisticPath(const TStatisticPathLiteral& literal);
+
+ TStatisticPath(const TStatisticPath& other) = default;
+
+ TStatisticPath(TStatisticPath&& other) noexcept = default;
+
+ TStatisticPath& operator=(const TStatisticPath& other) = default;
+
+ TStatisticPath& operator=(TStatisticPath&& other) noexcept = default;
+
+ ~TStatisticPath() noexcept = default;
+
+ void Swap(TStatisticPath& other) noexcept;
+
+ //! Returns `true` if the path is empty.
+ [[nodiscard]] bool Empty() const noexcept;
+
+ //! Returns the underlying path string.
+ [[nodiscard]] const TStatisticPathType& Path() const noexcept;
+
+ //! Appends a literal to the end of the path, adding a Delimiter between them.
+ TStatisticPath& Append(const TStatisticPathLiteral& literal);
+
+ //! Appends another path to the end of the current path. Essentially concatenates the underlying strings.
+ TStatisticPath& Append(const TStatisticPath& other);
+
+ [[nodiscard]] const_iterator begin() const noexcept;
+
+ [[nodiscard]] const_iterator end() const noexcept;
+
+ [[nodiscard]] const_reverse_iterator rbegin() const noexcept;
+
+ [[nodiscard]] const_reverse_iterator rend() const noexcept;
+
+ //! Returns the first literal of the path. Verifies that the path is not empty.
+ [[nodiscard]] const_iterator::value_type Front() const noexcept;
+
+ //! Returns the last literal of the path. Verifies that the path is not empty.
+ [[nodiscard]] const_iterator::value_type Back() const noexcept;
+
+ //! Removes the last literal from the path. Verifies that the path is not empty.
+ void PopBack();
+
+ //! Returns `true` if `path` is a prefix of the current path.
+ [[nodiscard]] bool StartsWith(const TStatisticPath& path) const;
+
+ //! Returns `true` if `path` is a suffix of the current path.
+ [[nodiscard]] bool EndsWith(const TStatisticPath& path) const;
+
+private:
+ TStatisticPathType Path_;
+
+ TStatisticPath& AppendStr(const TStatisticPathType& path);
+
+ //! Private, build the path from literals instead.
+ explicit TStatisticPath(const TStatisticPathType& path);
+
+ //! Private, build the path from literals instead.
+ explicit TStatisticPath(TStatisticPathType&& path) noexcept;
+
+ friend struct TStatisticPathSerializer;
+
+ friend TErrorOr<TStatisticPath> ParseStatisticPath(const TStatisticPathType& path);
+};
+
+// TODO(pavook) `= default` when it stops crashing the compiler.
+[[nodiscard]] bool operator==(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept;
+
+[[nodiscard]] std::strong_ordering operator<=>(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept;
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`.
+[[nodiscard]] TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPathLiteral& rhs);
+
+//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`.
+[[nodiscard]] TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPath& rhs);
+
+//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`.
+[[nodiscard]] TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPathLiteral& rhs);
+
+//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`.
+[[nodiscard]] TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPath& rhs);
+
+//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`.
+[[nodiscard]] TStatisticPath operator/(const TStatisticPathLiteral& lhs, const TStatisticPathLiteral& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NStatisticPathLiterals {
+
+[[nodiscard]] TStatisticPathLiteral operator""_L(const char* str, size_t len);
+
+} // namespace NStatisticPathLiterals
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TStatisticPathSerializer
+{
+ template <class C>
+ static void Save(C& context, const NStatisticPath::TStatisticPath& path);
+
+ template <class C>
+ static void Load(C& context, NStatisticPath::TStatisticPath& path);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NStatisticPath
+
+template <class C>
+struct TSerializerTraits<NStatisticPath::TStatisticPath, C, void>
+{
+ using TSerializer = NStatisticPath::TStatisticPathSerializer;
+ using TComparer = TValueBoundComparer;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define STATISTIC_PATH_INL_H_
+#include "statistic_path-inl.h"
+#undef STATISTIC_PATH_INL_H_
diff --git a/yt/yt/core/misc/unittests/statistic_path_ut.cpp b/yt/yt/core/misc/unittests/statistic_path_ut.cpp
new file mode 100644
index 0000000000..e92d73c452
--- /dev/null
+++ b/yt/yt/core/misc/unittests/statistic_path_ut.cpp
@@ -0,0 +1,216 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/statistic_path.h>
+
+namespace NYT {
+namespace {
+
+using namespace NStatisticPath;
+using namespace NStatisticPathLiterals;
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Literal)
+{
+ EXPECT_EQ("ABC"_L.Literal(), "ABC");
+
+ EXPECT_TRUE(CheckStatisticPathLiteral("$a/b.c").IsOK());
+ EXPECT_FALSE(CheckStatisticPathLiteral(TString(Delimiter)).IsOK());
+ EXPECT_FALSE(CheckStatisticPathLiteral("\0").IsOK());
+ EXPECT_FALSE(CheckStatisticPathLiteral("").IsOK());
+
+ EXPECT_THROW(TStatisticPathLiteral("\0"), TErrorException);
+ EXPECT_THROW(TStatisticPathLiteral(TString(Delimiter)), TErrorException);
+ EXPECT_THROW(TStatisticPathLiteral(""), TErrorException);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Append)
+{
+ EXPECT_EQ(("A"_L / "BB"_L / "CCC"_L).Path(),
+ TString(Delimiter) + "A" + Delimiter + "BB" + Delimiter + "CCC");
+
+ EXPECT_EQ(("A"_L / "B"_L) / "C"_L, "A"_L / ("B"_L / "C"_L));
+
+ EXPECT_EQ("A"_L / TStatisticPath(), "A"_L);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Iterator)
+{
+ EXPECT_TRUE(std::bidirectional_iterator<TStatisticPath::const_iterator>);
+ EXPECT_TRUE(std::bidirectional_iterator<TStatisticPath::const_reverse_iterator>);
+ EXPECT_TRUE(std::ranges::range<TStatisticPath>);
+
+ TStatisticPath path = "A"_L / "BB"_L / "CCC"_L;
+
+ std::vector<TString> expected{"A", "BB", "CCC"};
+
+ {
+ std::vector<TString> actual(path.begin(), path.end());
+ EXPECT_EQ(actual, expected);
+ }
+
+ {
+ std::reverse(expected.begin(), expected.end());
+ std::vector<TString> actual(path.rbegin(), path.rend());
+ EXPECT_EQ(actual, expected);
+ }
+
+ {
+ // Post-increment, post-decrement validity test.
+ auto it = path.begin();
+ EXPECT_EQ(*(it++), "A");
+ EXPECT_EQ(*it, "BB");
+ EXPECT_EQ(*(it--), "BB");
+ EXPECT_EQ(*it, "A");
+ }
+
+ // Sanity check.
+ auto it = path.begin();
+ const auto& value = *it;
+ ++it;
+ EXPECT_NE(value, *it);
+
+ TStatisticPath emptyPath;
+ EXPECT_EQ(emptyPath.begin(), emptyPath.end());
+ EXPECT_TRUE(emptyPath.Empty());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Compare)
+{
+ EXPECT_EQ("A"_L / "B"_L, "A"_L / "B"_L);
+
+ EXPECT_LT("A"_L / "B"_L, "A"_L / "B"_L / "C"_L);
+
+ EXPECT_LT("A"_L / "B"_L, "A"_L / "C"_L);
+ EXPECT_LT("A"_L / "B"_L, "B"_L);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// We want a statistic path and it's "path prefix" to be adjacent according to
+// path comparison. See YT-22118 for motivation.
+TEST(TStatisticPathTest, Adjacent)
+{
+ for (TChar c = std::numeric_limits<TChar>::min();; ++c) {
+ auto literal = ParseStatisticPathLiteral(TString("A") + c + "B");
+ if (literal.IsOK()) {
+ EXPECT_LT("A"_L / "B"_L, TStatisticPath(literal.Value()));
+ }
+ if (c == std::numeric_limits<TChar>::max()) {
+ break;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Parse)
+{
+ EXPECT_EQ(ParseStatisticPath(TString(Delimiter) + "ABC" + Delimiter + "DEF").ValueOrThrow(), "ABC"_L / "DEF"_L);
+ EXPECT_EQ(ParseStatisticPath("").ValueOrThrow(), TStatisticPath());
+
+ EXPECT_FALSE(ParseStatisticPath(TString(Delimiter) + "A\0B"_sb).IsOK());
+ EXPECT_FALSE(ParseStatisticPath(TString(Delimiter) + "AB" + Delimiter).IsOK());
+ EXPECT_FALSE(ParseStatisticPath(TString(Delimiter) + "A" + Delimiter + Delimiter + "B").IsOK());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Constructor)
+{
+ EXPECT_EQ(TStatisticPath("abc"_L).Path(), TString(Delimiter) + "abc");
+
+ TStatisticPath defaultConstructed;
+ EXPECT_EQ(defaultConstructed.Path(), TString());
+ EXPECT_TRUE(defaultConstructed.Empty());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, StartsWith)
+{
+ // Simple prefix.
+ EXPECT_TRUE(("A"_L / "BB"_L / "CCC"_L).StartsWith("A"_L / "BB"_L));
+ // Equal paths.
+ EXPECT_TRUE(("A"_L / "BB"_L).StartsWith("A"_L / "BB"_L));
+ // Empty prefix.
+ EXPECT_TRUE(("A"_L / "B"_L).StartsWith({}));
+
+ // Reverse prefix.
+ EXPECT_FALSE(("A"_L / "BB"_L).StartsWith("A"_L / "BB"_L / "CCC"_L));
+ // There are string prefixes that are not path prefixes.
+ EXPECT_FALSE(("A"_L / "BB"_L / "CCC"_L).StartsWith("A"_L / "BB"_L / "C"_L));
+ // Sanity check.
+ EXPECT_FALSE(("A"_L / "B"_L).StartsWith("B"_L));
+}
+
+TEST(TStatisticPathTest, EndsWith)
+{
+ // Simple suffix.
+ EXPECT_TRUE(("A"_L / "BB"_L / "CCC"_L).EndsWith("BB"_L / "CCC"_L));
+ // Equal paths.
+ EXPECT_TRUE(("A"_L / "BB"_L).EndsWith("A"_L / "BB"_L));
+ // Empty prefix.
+ EXPECT_TRUE(("A"_L / "B"_L).EndsWith({}));
+
+ // Reverse suffix.
+ EXPECT_FALSE(("A"_L / "BB"_L).EndsWith("CCC"_L / "A"_L / "BB"_L));
+ // There are string suffixes that are not path suffixes.
+ EXPECT_FALSE(("CCC"_L / "A"_L / "BB"_L).EndsWith("C"_L / "A"_L / "BB"_L));
+ // Sanity check.
+ EXPECT_FALSE(("A"_L / "B"_L).EndsWith("A"_L));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, FrontBack)
+{
+ EXPECT_EQ(("A"_L / "BB"_L / "CCC"_L).Front(), "A");
+ EXPECT_EQ(("A"_L / "BB"_L / "CCC"_L).Back(), "CCC");
+ EXPECT_EQ(TStatisticPath("A"_L).Front(), "A");
+ EXPECT_EQ(TStatisticPath("A"_L).Back(), "A");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, PopBack)
+{
+ TStatisticPath path = "A"_L / "BB"_L / "CCC"_L;
+ EXPECT_EQ(path.Back(), "CCC");
+ path.PopBack();
+ EXPECT_EQ(path.Back(), "BB");
+ path.PopBack();
+ EXPECT_EQ(path.Back(), "A");
+ path.PopBack();
+ EXPECT_TRUE(path.Empty());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStatisticPathTest, Swap)
+{
+ TStatisticPath a = "A"_L / "BB"_L;
+ TStatisticPath b = "B"_L / "AA"_L;
+ TStatisticPath originalA = a;
+ TStatisticPath originalB = b;
+
+ a.Swap(b);
+ EXPECT_EQ(a, originalB);
+ EXPECT_EQ(b, originalA);
+
+ a.Swap(b);
+ EXPECT_EQ(a, originalA);
+ EXPECT_EQ(b, originalB);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/yt/yt/core/misc/unittests/ya.make b/yt/yt/core/misc/unittests/ya.make
index 7f8396c752..25a32157ec 100644
--- a/yt/yt/core/misc/unittests/ya.make
+++ b/yt/yt/core/misc/unittests/ya.make
@@ -65,6 +65,7 @@ SRCS(
sliding_window_ut.cpp
sync_cache_ut.cpp
spsc_queue_ut.cpp
+ statistic_path_ut.cpp
statistics_ut.cpp
string_ut.cpp
sync_expiring_cache_ut.cpp
diff --git a/yt/yt/core/ya.make b/yt/yt/core/ya.make
index 9ce00aca5b..d4a54b2ad7 100644
--- a/yt/yt/core/ya.make
+++ b/yt/yt/core/ya.make
@@ -152,6 +152,7 @@ SRCS(
misc/shutdown.cpp
misc/signal_registry.cpp
misc/slab_allocator.cpp
+ misc/statistic_path.cpp
misc/statistics.cpp
misc/string_helpers.cpp
misc/cache_config.cpp
diff --git a/yt/yt/core/ytree/serialize.cpp b/yt/yt/core/ytree/serialize.cpp
index f6a1b23ca6..8476a92b7e 100644
--- a/yt/yt/core/ytree/serialize.cpp
+++ b/yt/yt/core/ytree/serialize.cpp
@@ -167,6 +167,12 @@ void Serialize(IInputStream& input, IYsonConsumer* consumer)
Serialize(TYsonInput(&input), consumer);
}
+// TStatisticPath.
+void Serialize(const NStatisticPath::TStatisticPath& path, IYsonConsumer* consumer)
+{
+ consumer->OnStringScalar(path.Path());
+}
+
// Subtypes of google::protobuf::Message
void SerializeProtobufMessage(
const Message& message,
@@ -342,6 +348,12 @@ void Deserialize(TGuid& value, INodePtr node)
value = TGuid::FromString(node->AsString()->GetValue());
}
+// TStatisticPath.
+void Deserialize(NStatisticPath::TStatisticPath& value, INodePtr node)
+{
+ value = NStatisticPath::ParseStatisticPath(node->AsString()->GetValue()).ValueOrThrow();
+}
+
// Subtypes of google::protobuf::Message
void DeserializeProtobufMessage(
Message& message,
diff --git a/yt/yt/core/ytree/serialize.h b/yt/yt/core/ytree/serialize.h
index 2a9c0875ee..633507dce5 100644
--- a/yt/yt/core/ytree/serialize.h
+++ b/yt/yt/core/ytree/serialize.h
@@ -6,6 +6,7 @@
#include <yt/yt/core/misc/guid.h>
#include <yt/yt/core/misc/mpl.h>
+#include <yt/yt/core/misc/statistic_path.h>
#include <yt/yt/core/yson/writer.h>
@@ -161,6 +162,8 @@ void Serialize(
template <class T, class TTag>
void Serialize(const TStrongTypedef<T, TTag>& value, NYson::IYsonConsumer* consumer);
+void Serialize(const NStatisticPath::TStatisticPath& path, NYson::IYsonConsumer* consumer);
+
////////////////////////////////////////////////////////////////////////////////
template <class T>
@@ -260,6 +263,8 @@ void Deserialize(
template <class T, class TTag>
void Deserialize(TStrongTypedef<T, TTag>& value, INodePtr node);
+void Deserialize(NStatisticPath::TStatisticPath& path, INodePtr node);
+
////////////////////////////////////////////////////////////////////////////////
template <class T, class... TExtraArgs>