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/// @ingroup userver_universal
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/// @ref 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/// For introduction to metrics see @ref scripts/docs/en/userver/metrics.md
91class Writer final {
92public:
93 /// Path parts delimiter. In other words, writer["a"]["b"] becomes "a.b"
94 static inline constexpr char kDelimiter = '.';
95
96 Writer() = delete;
97 Writer(Writer&& other) = delete;
98 Writer(const Writer&) = delete;
99 Writer& operator=(Writer&&) = delete;
100 Writer& operator=(const Writer&) = delete;
101
102 ~Writer();
103
104 /// Returns a Writer with a ('.' + path) appended
105 [[nodiscard]] Writer operator[](std::string_view path) &;
106
107 /// Returns a Writer with a ('.' + path) appended
108 [[nodiscard]] Writer operator[](std::string_view path) &&;
109
110 /// Write metric value to metrics builder via using DumpMetric
111 /// function.
112 template <class T>
113 void operator=(const T& value) {
114 if constexpr (std::is_arithmetic_v<T> || std::is_same_v<std::decay_t<T>, Rate> ||
115 std::is_same_v<std::decay_t<T>, HistogramView> || std::is_same_v<std::decay_t<T>, MetricValue>)
116 {
117 Write(value);
118 } else {
119 if (state_) {
120 static_assert(
121 kHasWriterSupport<T>,
122 "Cast the metric to an arithmetic type or provide a "
123 "`void DumpMetric(utils::statistics::Writer& writer, "
124 "const Metric& value)` function for the `Metric` type"
125 );
126 DumpMetric(*this, value);
127 }
128 }
129 }
130
131 /// Write metric value with labels to metrics builder
132 template <class T>
133 void ValueWithLabels(const T& value, LabelsSpan labels) {
134 auto new_writer = MakeChild();
135 new_writer.AppendLabelsSpan(labels);
136 new_writer = value;
137 }
138
139 /// Write metric value with labels to metrics builder
140 template <class T>
141 void ValueWithLabels(const T& value, std::initializer_list<LabelView> il) {
142 ValueWithLabels(value, LabelsSpan{il});
143 }
144
145 /// Write metric value with label to metrics builder
146 template <class T>
147 void ValueWithLabels(const T& value, const LabelView& label) {
148 ValueWithLabels(value, LabelsSpan{&label, &label + 1});
149 }
150
151 /// @cond
152 /// func must be called even for filtered out Writers, e.g. to always collect
153 /// totals
154 template <class Func>
155 void WithLabels(utils::impl::InternalTag, LabelsSpan labels, Func func) {
156 auto new_writer = MakeChild();
157 new_writer.AppendLabelsSpan(labels);
158 func(new_writer);
159 }
160
161 template <class Func>
162 void WithLabels(utils::impl::InternalTag, std::initializer_list<LabelView> il, Func func) {
163 WithLabels(utils::impl::InternalTag{}, LabelsSpan{il}, func);
164 }
165
166 template <class Func>
167 void WithLabels(utils::impl::InternalTag, const LabelView& label, Func func) {
168 WithLabels(utils::impl::InternalTag{}, LabelsSpan{&label, &label + 1}, func);
169 }
170 /// @endcond
171
172 /// Returns true if this writer would actually write data. Returns false if
173 /// the data is not required by request and metrics construction could be
174 /// skipped.
175 explicit operator bool() const noexcept { return !!state_; }
176
177 /// @cond
178 explicit Writer(impl::WriterState* state) noexcept;
179 explicit Writer(impl::WriterState& state, LabelsSpan labels);
180 /// @endcond
181
182private:
183 using ULongLong = unsigned long long;
184
185 using PathSizeType = std::uint16_t;
186 using LabelsSizeType = std::uint8_t;
187
188 void Write(unsigned long long value);
189 void Write(long long value);
190 void Write(double value);
191 void Write(Rate value);
192 void Write(HistogramView value);
193 void Write(MetricValue value);
194
195 void Write(float value) { Write(static_cast<double>(value)); }
196
197 void Write(unsigned long value) { Write(static_cast<ULongLong>(value)); }
198 void Write(long value) { Write(static_cast<long long>(value)); }
199 void Write(unsigned int value) { Write(static_cast<long long>(value)); }
200 void Write(int value) { Write(static_cast<long long>(value)); }
201 void Write(unsigned short value) { Write(static_cast<long long>(value)); }
202 void Write(short value) { Write(static_cast<long long>(value)); }
203
204 Writer MakeChild();
205
206 struct MoveTag {};
207 Writer(Writer& other, MoveTag) noexcept;
208
209 Writer MoveOut() noexcept { return Writer{*this, MoveTag{}}; }
210
211 void ResetState() noexcept;
212 void ValidateUsage();
213
214 void AppendPath(std::string_view path);
215 void AppendLabelsSpan(LabelsSpan labels);
216
217 impl::WriterState* state_;
218 const PathSizeType initial_path_size_;
219 PathSizeType current_path_size_;
220 const LabelsSizeType initial_labels_size_;
221 LabelsSizeType current_labels_size_;
222};
223
224template <class Metric>
225void DumpMetric(Writer& writer, const std::atomic<Metric>& m) {
226 static_assert(std::atomic<Metric>::is_always_lock_free, "std::atomic misuse");
227 writer = m.load();
228}
229
230} // namespace utils::statistics
231
232USERVER_NAMESPACE_END