Skip to content

Commit ed74e7d

Browse files
committed
feat statistics: support enums in MonotonicByLabelStorage
commit_hash:470cde4e42f55462f0a9269f6f687ecafe153425
1 parent d5dcd93 commit ed74e7d

File tree

3 files changed

+115
-12
lines changed

3 files changed

+115
-12
lines changed

core/include/userver/utils/statistics/by_label_storage.hpp

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,45 @@ template <typename Metric>
3434
concept ResettableMetric = requires(Metric& m) { ResetMetric(m); };
3535

3636
template <typename Field>
37-
concept StringViewCompatibleField =
38-
std::constructible_from<Field, std::string_view> && std::constructible_from<std::string_view, const Field&>;
37+
std::string_view FieldToStringView(const Field& field) {
38+
if constexpr (std::constructible_from<std::string_view, const Field&>) {
39+
return std::string_view{field};
40+
} else {
41+
// ADL lookup.
42+
return ToStringView(field);
43+
}
44+
}
45+
46+
template <typename Field>
47+
concept FieldConvertibleToStringView =
48+
std::constructible_from<std::string_view, const Field&> || requires(const Field& f) {
49+
{
50+
ToStringView(f)
51+
} -> std::same_as<std::string_view>;
52+
};
53+
54+
template <typename Field>
55+
concept FieldConstructibleFromStringView = std::constructible_from<Field, std::string_view>;
56+
57+
template <typename Labels>
58+
concept AllFieldsConvertibleToStringView = []<std::size_t... Is>(std::index_sequence<Is...>) {
59+
return (FieldConvertibleToStringView<boost::pfr::tuple_element_t<Is, Labels>> && ...);
60+
}(std::make_index_sequence<boost::pfr::tuple_size_v<Labels>>{});
3961

4062
template <typename Labels>
41-
concept AllFieldsAreStringViewCompatible = []<std::size_t... Is>(std::index_sequence<Is...>) {
42-
return (StringViewCompatibleField<boost::pfr::tuple_element_t<Is, Labels>> && ...);
63+
concept AllFieldsConstructibleFromStringView = []<std::size_t... Is>(std::index_sequence<Is...>) {
64+
return (FieldConstructibleFromStringView<boost::pfr::tuple_element_t<Is, Labels>> && ...);
4365
}(std::make_index_sequence<boost::pfr::tuple_size_v<Labels>>{});
4466

4567
template <typename Labels>
46-
concept LabelsAggregate = std::is_aggregate_v<Labels> && AllFieldsAreStringViewCompatible<Labels>;
68+
concept LabelsAggregate = std::is_aggregate_v<Labels> && AllFieldsConvertibleToStringView<Labels>;
4769

4870
template <typename Labels>
4971
auto LabelsStructToViewArray(const Labels& labels) {
5072
constexpr std::size_t kN = boost::pfr::tuple_size_v<Labels>;
5173
std::array<std::string_view, kN> result{};
5274
boost::pfr::for_each_field(labels, [&result](const auto& field, std::size_t i) {
53-
result[i] = std::string_view{field};
75+
result[i] = FieldToStringView(field);
5476
});
5577
return result;
5678
}
@@ -157,10 +179,13 @@ struct ByLabelEntryEqual {
157179
///
158180
/// See @ref scripts/docs/en/userver/metrics.md .
159181
///
160-
/// `Labels` must be an aggregate type where all fields are interconvertible
161-
/// with `std::string_view`, i.e. constructible from `std::string_view` and
162-
/// convertible to `std::string_view`. This includes `std::string_view` itself,
163-
/// `utils::Required<std::string_view>`, `std::string`, @ref utils::StrongTypedef, etc.
182+
/// `Labels` must be an aggregate type where all fields are convertible to `std::string_view`, by at least one of:
183+
///
184+
/// * a (possibly `explicit`) conversion operator to `std::string_view`;
185+
/// * an ADL-found `std::string_view ToStringView(const Field&)` function.
186+
///
187+
/// This includes `std::string_view` itself, `utils::Required<std::string_view>`,
188+
/// `std::string`, @ref utils::StrongTypedef, code-generated enums, etc.
164189
///
165190
/// Label names are taken from `Labels` field names.
166191
///
@@ -203,7 +228,19 @@ struct ByLabelEntryEqual {
203228
/// `MonotonicByLabelStorage` is also composable the other way around, it can be included in larger metric structures
204229
/// as a field.
205230
///
206-
/// @tparam Labels An aggregate type with `std::string_view` fields.
231+
/// ## Usage of MonotonicByLabelStorage with enum labels
232+
///
233+
/// An enum can be used as a label as long as it has `ToStringView` defined. Usage example:
234+
///
235+
/// @snippet core/src/utils/statistics/by_label_storage_test.cpp enum label ToStringView
236+
///
237+
/// @snippet core/src/utils/statistics/by_label_storage_test.cpp enum label labels
238+
///
239+
/// @snippet core/src/utils/statistics/by_label_storage_test.cpp enum label metric tag
240+
///
241+
/// @snippet core/src/utils/statistics/by_label_storage_test.cpp enum label usage
242+
///
243+
/// @tparam Labels An aggregate type whose fields are convertible to `std::string_view`.
207244
/// @tparam Metric The metric type. Must support `DumpMetric(Writer&, const Metric&)`.
208245
template <typename Labels, typename Metric>
209246
requires impl::LabelsAggregate<Labels> && impl::DumpableMetric<Metric>
@@ -249,8 +286,13 @@ class MonotonicByLabelStorage final {
249286
}
250287

251288
/// @brief Visit all stored metrics.
289+
///
290+
/// Requires that all fields of `Labels` are constructible from `std::string_view`.
291+
///
252292
/// @param func Callable accepting `(const Labels&, const Metric&)`.
253-
void VisitAll(std::invocable<const Labels&, const Metric&> auto func) const {
293+
void VisitAll(std::invocable<const Labels&, const Metric&> auto func) const
294+
requires impl::AllFieldsConstructibleFromStringView<Labels>
295+
{
254296
set_.Visit([&func](const Entry& entry) { func(impl::LabelsArrayToStruct<Labels>(entry.labels), entry.metric); }
255297
);
256298
}

core/src/utils/statistics/by_label_storage_test.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,4 +468,51 @@ UTEST(MonotonicByLabelStorage, SnippetCompositeMetricUsage) {
468468
EXPECT_EQ(snapshot.SingleMetric("errors", {{"endpoint", "/order"}}), Rate{1});
469469
}
470470

471+
namespace {
472+
473+
/// [enum label ToStringView]
474+
enum class RequestType : std::uint8_t { kRead, kWrite };
475+
476+
[[maybe_unused]] std::string_view ToStringView(RequestType type) {
477+
switch (type) {
478+
case RequestType::kRead:
479+
return "read";
480+
case RequestType::kWrite:
481+
return "write";
482+
}
483+
UINVARIANT(false, "Unknown RequestType");
484+
}
485+
/// [enum label ToStringView]
486+
487+
} // namespace
488+
489+
/// [enum label labels]
490+
struct TypedRequestLabels {
491+
utils::Required<RequestType> type;
492+
};
493+
/// [enum label labels]
494+
495+
namespace {
496+
497+
/// [enum label metric tag]
498+
// Needs to be `inline` if defined in a header.
499+
const utils::statistics::MetricTag<
500+
utils::statistics::MonotonicByLabelStorage<TypedRequestLabels, utils::statistics::RateCounter>>
501+
kTypedRequestsMetric{"my-service.typed-requests"};
502+
/// [enum label metric tag]
503+
504+
} // namespace
505+
506+
UTEST(MonotonicByLabelStorage, SnippetEnumLabelWithToStringView) {
507+
utils::statistics::MetricsStorage metrics_storage;
508+
509+
/// [enum label usage]
510+
auto& storage = metrics_storage.GetMetric(kTypedRequestsMetric);
511+
++storage[{.type = RequestType::kRead}];
512+
/// [enum label usage]
513+
514+
const auto snapshot = MakeSnapshot(storage);
515+
EXPECT_EQ(snapshot.SingleMetric("", {{"type", "read"}}), Rate{1});
516+
}
517+
471518
USERVER_NAMESPACE_END

universal/include/userver/utils/required.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/// @brief @copybrief utils::Required
55

66
#include <concepts>
7+
#include <string_view>
78
#include <type_traits>
89
#include <utility>
910

@@ -80,6 +81,19 @@ class Required final {
8081
T value_;
8182
};
8283

84+
/// @brief Convert `Required<T>` to `std::string_view` via ADL-found `ToStringView` on the inner value.
85+
///
86+
/// Participates in overload resolution only when `ToStringView(*req)` is valid.
87+
template <typename T>
88+
requires requires(const T& v) {
89+
{
90+
ToStringView(v)
91+
} -> std::same_as<std::string_view>;
92+
}
93+
std::string_view ToStringView(const Required<T>& req) {
94+
return ToStringView(*req);
95+
}
96+
8397
} // namespace utils
8498

8599
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)