userver: userver/utils/statistics/writer.hpp Source File
Loading...
Searching...
No Matches
writer.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/statistics/writer.hpp
4/// @brief @copybrief utils::statistics::Writer
5
6#include <atomic>
7#include <string_view>
8#include <type_traits>
9
10#include <userver/utils/impl/internal_tag.hpp>
11#include <userver/utils/statistics/histogram_view.hpp>
12#include <userver/utils/statistics/labels.hpp>
13#include <userver/utils/statistics/rate.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace utils::statistics {
18
19class Writer;
20class MetricValue;
21
22namespace impl {
23
24struct WriterState;
25
26template <class Metric>
27constexpr auto HasDumpMetricWriter() noexcept
28 -> decltype(DumpMetric(std::declval<Writer&>(), std::declval<const Metric&>()), std::true_type{}) {
29 return {};
30}
31
32template <class Metric, class... Args>
33constexpr auto HasDumpMetricWriter(Args...) noexcept {
34 return std::is_arithmetic_v<Metric>;
35}
36
37} // namespace impl
38
39/// @brief Returns true, if the `Metric` could be written by
40/// utils::statistics::Writer.
41///
42/// In other words, checks that the DumpMetric for the `Metric` is provided or
43/// that the metric could be written without providing one.
44template <class Metric>
46
47// clang-format off
48
49/// @brief Class for writing metrics that is provided by utils::statistics::Storage.
50///
51/// Usage is quite straightforward:
52///
53/// @snippet core/src/utils/statistics/pretty_format_test.cpp Writer basic sample
54///
55/// The above sample would produce the following metrics:
56///
57/// @snippet core/src/utils/statistics/pretty_format_test.cpp metrics pretty
58///
59/// The Writer can be customized for writing custom metric types by providing a
60/// `void DumpMetric(utils::statistics::Writer& writer, const CustomMetric& value)`
61/// function:
62///
63/// @snippet core/src/utils/statistics/writer_test.cpp DumpMetric basic
64///
65/// DumpMetric functions nest well, labels are propagated to the nested
66/// DumpMetric:
67///
68/// @snippet core/src/utils/statistics/writer_test.cpp DumpMetric nested
69///
70/// To use the above writers register the metric writer in
71/// utils::statistics::Storage component:
72///
73/// @snippet core/src/utils/statistics/writer_test.cpp DumpMetric RegisterWriter
74///
75/// The above metrics in Graphite format would look like:
76/// @snippet core/src/utils/statistics/writer_test.cpp metrics graphite
77///
78/// The Writer is usable by the utils::statistics::MetricTag. For example, for
79/// the following structure:
80/// @snippet samples/tcp_full_duplex_service/tcp_full_duplex_service.cpp TCP sample - Stats definition
81///
82/// The DumpMetric function may look like:
83/// @snippet samples/tcp_full_duplex_service/tcp_full_duplex_service.cpp TCP sample - Stats tag
84///
85/// For information on metrics testing in testsuite refer to
86/// @ref TESTSUITE_METRICS_TESTING "Testsuite - Metrics".
87///
88/// For metrics testing in unit-tests see utils::statistics::Snapshot.
89
90// clang-format on
91
92class Writer final {
93public:
94 /// Path parts delimiter. In other words, writer["a"]["b"] becomes "a.b"
95 static inline constexpr char kDelimiter = '.';
96
97 Writer() = delete;
98 Writer(Writer&& other) = delete;
99 Writer(const Writer&) = delete;
100 Writer& operator=(Writer&&) = delete;
101 Writer& operator=(const Writer&) = delete;
102
103 ~Writer();
104
105 /// Returns a Writer with a ('.' + path) appended
106 [[nodiscard]] Writer operator[](std::string_view path) &;
107
108 /// Returns a Writer with a ('.' + path) appended
109 [[nodiscard]] Writer operator[](std::string_view path) &&;
110
111 /// Write metric value to metrics builder via using DumpMetric
112 /// function.
113 template <class T>
114 void operator=(const T& value) {
115 if constexpr (std::is_arithmetic_v<T> || std::is_same_v<std::decay_t<T>, Rate> || std::is_same_v<std::decay_t<T>, HistogramView> || std::is_same_v<std::decay_t<T>, MetricValue>) {
116 Write(value);
117 } else {
118 if (state_) {
119 static_assert(
120 kHasWriterSupport<T>,
121 "Cast the metric to an arithmetic type or provide a "
122 "`void DumpMetric(utils::statistics::Writer& writer, "
123 "const Metric& value)` function for the `Metric` type"
124 );
125 DumpMetric(*this, value);
126 }
127 }
128 }
129
130 /// Write metric value with labels to metrics builder
131 template <class T>
132 void ValueWithLabels(const T& value, LabelsSpan labels) {
133 auto new_writer = MakeChild();
134 new_writer.AppendLabelsSpan(labels);
135 new_writer = value;
136 }
137
138 /// Write metric value with labels to metrics builder
139 template <class T>
140 void ValueWithLabels(const T& value, std::initializer_list<LabelView> il) {
141 ValueWithLabels(value, LabelsSpan{il});
142 }
143
144 /// Write metric value with label to metrics builder
145 template <class T>
146 void ValueWithLabels(const T& value, const LabelView& label) {
147 ValueWithLabels(value, LabelsSpan{&label, &label + 1});
148 }
149
150 /// @cond
151 /// func must be called even for filtered out Writers, e.g. to always collect
152 /// totals
153 template <class Func>
154 void WithLabels(utils::impl::InternalTag, LabelsSpan labels, Func func) {
155 auto new_writer = MakeChild();
156 new_writer.AppendLabelsSpan(labels);
157 func(new_writer);
158 }
159
160 template <class Func>
161 void WithLabels(utils::impl::InternalTag, std::initializer_list<LabelView> il, Func func) {
162 WithLabels(utils::impl::InternalTag{}, LabelsSpan{il}, func);
163 }
164
165 template <class Func>
166 void WithLabels(utils::impl::InternalTag, const LabelView& label, Func func) {
167 WithLabels(utils::impl::InternalTag{}, LabelsSpan{&label, &label + 1}, func);
168 }
169 /// @endcond
170
171 /// Returns true if this writer would actually write data. Returns false if
172 /// the data is not required by request and metrics construction could be
173 /// skipped.
174 explicit operator bool() const noexcept { return !!state_; }
175
176 /// @cond
177 explicit Writer(impl::WriterState* state) noexcept;
178 explicit Writer(impl::WriterState& state, LabelsSpan labels);
179 /// @endcond
180
181private:
182 using ULongLong = unsigned long long;
183
184 using PathSizeType = std::uint16_t;
185 using LabelsSizeType = std::uint8_t;
186
187 void Write(unsigned long long value);
188 void Write(long long value);
189 void Write(double value);
190 void Write(Rate value);
191 void Write(HistogramView value);
192 void Write(MetricValue value);
193
194 void Write(float value) { Write(static_cast<double>(value)); }
195
196 void Write(unsigned long value) { Write(static_cast<ULongLong>(value)); }
197 void Write(long value) { Write(static_cast<long long>(value)); }
198 void Write(unsigned int value) { Write(static_cast<long long>(value)); }
199 void Write(int value) { Write(static_cast<long long>(value)); }
200 void Write(unsigned short value) { Write(static_cast<long long>(value)); }
201 void Write(short value) { Write(static_cast<long long>(value)); }
202
203 Writer MakeChild();
204
205 struct MoveTag {};
206 Writer(Writer& other, MoveTag) noexcept;
207
208 Writer MoveOut() noexcept { return Writer{*this, MoveTag{}}; }
209
210 void ResetState() noexcept;
211 void ValidateUsage();
212
213 void AppendPath(std::string_view path);
214 void AppendLabelsSpan(LabelsSpan labels);
215
216 impl::WriterState* state_;
217 const PathSizeType initial_path_size_;
218 PathSizeType current_path_size_;
219 const LabelsSizeType initial_labels_size_;
220 LabelsSizeType current_labels_size_;
221};
222
223template <class Metric>
224void DumpMetric(Writer& writer, const std::atomic<Metric>& m) {
225 static_assert(std::atomic<Metric>::is_always_lock_free, "std::atomic misuse");
226 writer = m.load();
227}
228
229} // namespace utils::statistics
230
231USERVER_NAMESPACE_END