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()
28 noexcept -> 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/main.cpp TCP sample - Stats definition
81///
82/// The DumpMetric function may look like:
83/// @snippet samples/tcp_full_duplex_service/main.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> ||
116 std::is_same_v<std::decay_t<T>, HistogramView> || std::is_same_v<std::decay_t<T>, MetricValue>)
117 {
118 Write(value);
119 } else {
120 if (state_) {
121 static_assert(
122 kHasWriterSupport<T>,
123 "Cast the metric to an arithmetic type or provide a "
124 "`void DumpMetric(utils::statistics::Writer& writer, "
125 "const Metric& value)` function for the `Metric` type"
126 );
127 DumpMetric(*this, value);
128 }
129 }
130 }
131
132 /// Write metric value with labels to metrics builder
133 template <class T>
134 void ValueWithLabels(const T& value, LabelsSpan labels) {
135 auto new_writer = MakeChild();
136 new_writer.AppendLabelsSpan(labels);
137 new_writer = value;
138 }
139
140 /// Write metric value with labels to metrics builder
141 template <class T>
142 void ValueWithLabels(const T& value, std::initializer_list<LabelView> il) {
143 ValueWithLabels(value, LabelsSpan{il});
144 }
145
146 /// Write metric value with label to metrics builder
147 template <class T>
148 void ValueWithLabels(const T& value, const LabelView& label) {
149 ValueWithLabels(value, LabelsSpan{&label, &label + 1});
150 }
151
152 /// @cond
153 /// func must be called even for filtered out Writers, e.g. to always collect
154 /// totals
155 template <class Func>
156 void WithLabels(utils::impl::InternalTag, LabelsSpan labels, Func func) {
157 auto new_writer = MakeChild();
158 new_writer.AppendLabelsSpan(labels);
159 func(new_writer);
160 }
161
162 template <class Func>
163 void WithLabels(utils::impl::InternalTag, std::initializer_list<LabelView> il, Func func) {
164 WithLabels(utils::impl::InternalTag{}, LabelsSpan{il}, func);
165 }
166
167 template <class Func>
168 void WithLabels(utils::impl::InternalTag, const LabelView& label, Func func) {
169 WithLabels(utils::impl::InternalTag{}, LabelsSpan{&label, &label + 1}, func);
170 }
171 /// @endcond
172
173 /// Returns true if this writer would actually write data. Returns false if
174 /// the data is not required by request and metrics construction could be
175 /// skipped.
176 explicit operator bool() const noexcept { return !!state_; }
177
178 /// @cond
179 explicit Writer(impl::WriterState* state) noexcept;
180 explicit Writer(impl::WriterState& state, LabelsSpan labels);
181 /// @endcond
182
183private:
184 using ULongLong = unsigned long long;
185
186 using PathSizeType = std::uint16_t;
187 using LabelsSizeType = std::uint8_t;
188
189 void Write(unsigned long long value);
190 void Write(long long value);
191 void Write(double value);
192 void Write(Rate value);
193 void Write(HistogramView value);
194 void Write(MetricValue value);
195
196 void Write(float value) { Write(static_cast<double>(value)); }
197
198 void Write(unsigned long value) { Write(static_cast<ULongLong>(value)); }
199 void Write(long value) { Write(static_cast<long long>(value)); }
200 void Write(unsigned int value) { Write(static_cast<long long>(value)); }
201 void Write(int value) { Write(static_cast<long long>(value)); }
202 void Write(unsigned short value) { Write(static_cast<long long>(value)); }
203 void Write(short value) { Write(static_cast<long long>(value)); }
204
205 Writer MakeChild();
206
207 struct MoveTag {};
208 Writer(Writer& other, MoveTag) noexcept;
209
210 Writer MoveOut() noexcept { return Writer{*this, MoveTag{}}; }
211
212 void ResetState() noexcept;
213 void ValidateUsage();
214
215 void AppendPath(std::string_view path);
216 void AppendLabelsSpan(LabelsSpan labels);
217
218 impl::WriterState* state_;
219 const PathSizeType initial_path_size_;
220 PathSizeType current_path_size_;
221 const LabelsSizeType initial_labels_size_;
222 LabelsSizeType current_labels_size_;
223};
224
225template <class Metric>
226void DumpMetric(Writer& writer, const std::atomic<Metric>& m) {
227 static_assert(std::atomic<Metric>::is_always_lock_free, "std::atomic misuse");
228 writer = m.load();
229}
230
231} // namespace utils::statistics
232
233USERVER_NAMESPACE_END