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