/proc/self/cwd/source/server/admin/prometheus_stats.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/server/admin/prometheus_stats.h" |
2 | | |
3 | | #include <cmath> |
4 | | |
5 | | #include "source/common/common/empty_string.h" |
6 | | #include "source/common/common/macros.h" |
7 | | #include "source/common/common/regex.h" |
8 | | #include "source/common/stats/histogram_impl.h" |
9 | | #include "source/common/upstream/host_utility.h" |
10 | | |
11 | | #include "absl/strings/str_cat.h" |
12 | | #include "absl/strings/str_replace.h" |
13 | | |
14 | | namespace Envoy { |
15 | | namespace Server { |
16 | | |
17 | | namespace { |
18 | | |
19 | 0 | const Regex::CompiledGoogleReMatcher& promRegex() { |
20 | 0 | CONSTRUCT_ON_FIRST_USE(Regex::CompiledGoogleReMatcherNoSafetyChecks, "[^a-zA-Z0-9_]"); |
21 | 0 | } |
22 | | |
23 | | /** |
24 | | * Take a string and sanitize it according to Prometheus conventions. |
25 | | */ |
26 | 0 | std::string sanitizeName(const absl::string_view name) { |
27 | | // The name must match the regex [a-zA-Z_][a-zA-Z0-9_]* as required by |
28 | | // prometheus. Refer to https://prometheus.io/docs/concepts/data_model/. |
29 | | // The initial [a-zA-Z_] constraint is always satisfied by the namespace prefix. |
30 | 0 | return promRegex().replaceAll(name, "_"); |
31 | 0 | } |
32 | | |
33 | | /** |
34 | | * Take tag values and sanitize it for text serialization, according to |
35 | | * Prometheus conventions. |
36 | | */ |
37 | 0 | std::string sanitizeValue(const absl::string_view value) { |
38 | | // Removes problematic characters from Prometheus tag values to prevent |
39 | | // text serialization issues. This matches the prometheus text formatting code: |
40 | | // https://github.com/prometheus/common/blob/88f1636b699ae4fb949d292ffb904c205bf542c9/expfmt/text_create.go#L419-L420. |
41 | | // The goal is to replace '\' with "\\", newline with "\n", and '"' with "\"". |
42 | 0 | return absl::StrReplaceAll(value, { |
43 | 0 | {R"(\)", R"(\\)"}, |
44 | 0 | {"\n", R"(\n)"}, |
45 | 0 | {R"(")", R"(\")"}, |
46 | 0 | }); |
47 | 0 | } |
48 | | |
49 | | /* |
50 | | * Comparator for Stats::Metric that does not require a string representation |
51 | | * to make the comparison, for memory efficiency. |
52 | | */ |
53 | | struct MetricLessThan { |
54 | 0 | bool operator()(const Stats::Metric* a, const Stats::Metric* b) const { |
55 | 0 | ASSERT(&a->constSymbolTable() == &b->constSymbolTable()); |
56 | 0 | return a->constSymbolTable().lessThan(a->statName(), b->statName()); |
57 | 0 | } |
58 | | }; |
59 | | |
60 | | struct PrimitiveMetricSnapshotLessThan { |
61 | | bool operator()(const Stats::PrimitiveMetricMetadata* a, |
62 | 0 | const Stats::PrimitiveMetricMetadata* b) { |
63 | 0 | return a->name() < b->name(); |
64 | 0 | } |
65 | | }; |
66 | | |
67 | | std::string generateNumericOutput(uint64_t value, const Stats::TagVector& tags, |
68 | 0 | const std::string& prefixed_tag_extracted_name) { |
69 | 0 | const std::string formatted_tags = PrometheusStatsFormatter::formattedTags(tags); |
70 | 0 | return fmt::format("{0}{{{1}}} {2}\n", prefixed_tag_extracted_name, formatted_tags, value); |
71 | 0 | } |
72 | | |
73 | | /* |
74 | | * Return the prometheus output for a numeric Stat (Counter or Gauge). |
75 | | */ |
76 | | template <class StatType> |
77 | | std::string generateStatNumericOutput(const StatType& metric, |
78 | 0 | const std::string& prefixed_tag_extracted_name) { |
79 | 0 | return generateNumericOutput(metric.value(), metric.tags(), prefixed_tag_extracted_name); |
80 | 0 | } Unexecuted instantiation: prometheus_stats.cc:std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > Envoy::Server::(anonymous namespace)::generateStatNumericOutput<Envoy::Stats::Counter>(Envoy::Stats::Counter const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Unexecuted instantiation: prometheus_stats.cc:std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > Envoy::Server::(anonymous namespace)::generateStatNumericOutput<Envoy::Stats::Gauge>(Envoy::Stats::Gauge const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) |
81 | | |
82 | | /* |
83 | | * Returns the prometheus output for a TextReadout in gauge format. |
84 | | * It is a workaround of a limitation of prometheus which stores only numeric metrics. |
85 | | * The output is a gauge named the same as a given text-readout. The value of returned gauge is |
86 | | * always equal to 0. Returned gauge contains all tags of a given text-readout and one additional |
87 | | * tag {"text_value":"textReadout.value"}. |
88 | | */ |
89 | | std::string generateTextReadoutOutput(const Stats::TextReadout& text_readout, |
90 | 0 | const std::string& prefixed_tag_extracted_name) { |
91 | 0 | auto tags = text_readout.tags(); |
92 | 0 | tags.push_back(Stats::Tag{"text_value", text_readout.value()}); |
93 | 0 | const std::string formattedTags = PrometheusStatsFormatter::formattedTags(tags); |
94 | 0 | return fmt::format("{0}{{{1}}} 0\n", prefixed_tag_extracted_name, formattedTags); |
95 | 0 | } |
96 | | |
97 | | /* |
98 | | * Returns the prometheus output for a histogram. The output is a multi-line string (with embedded |
99 | | * newlines) that contains all the individual bucket counts and sum/count for a single histogram |
100 | | * (metric_name plus all tags). |
101 | | */ |
102 | | std::string generateHistogramOutput(const Stats::ParentHistogram& histogram, |
103 | 0 | const std::string& prefixed_tag_extracted_name) { |
104 | 0 | const std::string tags = PrometheusStatsFormatter::formattedTags(histogram.tags()); |
105 | 0 | const std::string hist_tags = histogram.tags().empty() ? EMPTY_STRING : (tags + ","); |
106 | |
|
107 | 0 | const Stats::HistogramStatistics& stats = histogram.cumulativeStatistics(); |
108 | 0 | Stats::ConstSupportedBuckets& supported_buckets = stats.supportedBuckets(); |
109 | 0 | const std::vector<uint64_t>& computed_buckets = stats.computedBuckets(); |
110 | 0 | std::string output; |
111 | 0 | for (size_t i = 0; i < supported_buckets.size(); ++i) { |
112 | 0 | double bucket = supported_buckets[i]; |
113 | 0 | uint64_t value = computed_buckets[i]; |
114 | | // We want to print the bucket in a fixed point (non-scientific) format. The fmt library |
115 | | // doesn't have a specific modifier to format as a fixed-point value only so we use the |
116 | | // 'g' operator which prints the number in general fixed point format or scientific format |
117 | | // with precision 50 to round the number up to 32 significant digits in fixed point format |
118 | | // which should cover pretty much all cases |
119 | 0 | output.append(fmt::format("{0}_bucket{{{1}le=\"{2:.32g}\"}} {3}\n", prefixed_tag_extracted_name, |
120 | 0 | hist_tags, bucket, value)); |
121 | 0 | } |
122 | |
|
123 | 0 | output.append(fmt::format("{0}_bucket{{{1}le=\"+Inf\"}} {2}\n", prefixed_tag_extracted_name, |
124 | 0 | hist_tags, stats.sampleCount())); |
125 | 0 | output.append(fmt::format("{0}_sum{{{1}}} {2:.32g}\n", prefixed_tag_extracted_name, tags, |
126 | 0 | stats.sampleSum())); |
127 | 0 | output.append(fmt::format("{0}_count{{{1}}} {2}\n", prefixed_tag_extracted_name, tags, |
128 | 0 | stats.sampleCount())); |
129 | |
|
130 | 0 | return output; |
131 | 0 | }; |
132 | | |
133 | | /** |
134 | | * Processes a stat type (counter, gauge, histogram) by generating all output lines, sorting |
135 | | * them by tag-extracted metric name, and then outputting them in the correct sorted order into |
136 | | * response. |
137 | | * |
138 | | * @param response The buffer to put the output into. |
139 | | * @param used_only Whether to only output stats that are used. |
140 | | * @param regex A filter on which stats to output. |
141 | | * @param metrics The metrics to output stats for. This must contain all stats of the given type |
142 | | * to be included in the same output. |
143 | | * @param generate_output A function which returns the output text for this metric. |
144 | | * @param type The name of the prometheus metric type for used in TYPE annotations. |
145 | | */ |
146 | | template <class StatType> |
147 | | uint64_t outputStatType( |
148 | | Buffer::Instance& response, const StatsParams& params, |
149 | | const std::vector<Stats::RefcountPtr<StatType>>& metrics, |
150 | | const std::function<std::string( |
151 | | const StatType& metric, const std::string& prefixed_tag_extracted_name)>& generate_output, |
152 | 0 | absl::string_view type, const Stats::CustomStatNamespaces& custom_namespaces) { |
153 | | |
154 | | /* |
155 | | * From |
156 | | * https:*github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#grouping-and-sorting: |
157 | | * |
158 | | * All lines for a given metric must be provided as one single group, with the optional HELP and |
159 | | * TYPE lines first (in no particular order). Beyond that, reproducible sorting in repeated |
160 | | * expositions is preferred but not required, i.e. do not sort if the computational cost is |
161 | | * prohibitive. |
162 | | */ |
163 | | |
164 | | // This is an unsorted collection of dumb-pointers (no need to increment then decrement every |
165 | | // refcount; ownership is held throughout by `metrics`). It is unsorted for efficiency, but will |
166 | | // be sorted before producing the final output to satisfy the "preferred" ordering from the |
167 | | // prometheus spec: metrics will be sorted by their tags' textual representation, which will be |
168 | | // consistent across calls. |
169 | 0 | using StatTypeUnsortedCollection = std::vector<const StatType*>; |
170 | | |
171 | | // Return early to avoid crashing when getting the symbol table from the first metric. |
172 | 0 | if (metrics.empty()) { |
173 | 0 | return 0; |
174 | 0 | } |
175 | | |
176 | | // There should only be one symbol table for all of the stats in the admin |
177 | | // interface. If this assumption changes, the name comparisons in this function |
178 | | // will have to change to compare to convert all StatNames to strings before |
179 | | // comparison. |
180 | 0 | const Stats::SymbolTable& global_symbol_table = metrics.front()->constSymbolTable(); |
181 | | |
182 | | // Sorted collection of metrics sorted by their tagExtractedName, to satisfy the requirements |
183 | | // of the exposition format. |
184 | 0 | std::map<Stats::StatName, StatTypeUnsortedCollection, Stats::StatNameLessThan> groups( |
185 | 0 | global_symbol_table); |
186 | |
|
187 | 0 | for (const auto& metric : metrics) { |
188 | 0 | ASSERT(&global_symbol_table == &metric->constSymbolTable()); |
189 | 0 | if (!params.shouldShowMetric(*metric)) { |
190 | 0 | continue; |
191 | 0 | } |
192 | 0 | groups[metric->tagExtractedStatName()].push_back(metric.get()); |
193 | 0 | } |
194 | | |
195 | 0 | auto result = groups.size(); |
196 | 0 | for (auto& group : groups) { |
197 | 0 | const absl::optional<std::string> prefixed_tag_extracted_name = |
198 | 0 | PrometheusStatsFormatter::metricName(global_symbol_table.toString(group.first), |
199 | 0 | custom_namespaces); |
200 | 0 | if (!prefixed_tag_extracted_name.has_value()) { |
201 | 0 | --result; |
202 | 0 | continue; |
203 | 0 | } |
204 | 0 | response.add(fmt::format("# TYPE {0} {1}\n", prefixed_tag_extracted_name.value(), type)); |
205 | | |
206 | | // Sort before producing the final output to satisfy the "preferred" ordering from the |
207 | | // prometheus spec: metrics will be sorted by their tags' textual representation, which will |
208 | | // be consistent across calls. |
209 | 0 | std::sort(group.second.begin(), group.second.end(), MetricLessThan()); |
210 | |
|
211 | 0 | for (const auto& metric : group.second) { |
212 | 0 | response.add(generate_output(*metric, prefixed_tag_extracted_name.value())); |
213 | 0 | } |
214 | 0 | } |
215 | 0 | return result; |
216 | 0 | } Unexecuted instantiation: prometheus_stats.cc:unsigned long Envoy::Server::(anonymous namespace)::outputStatType<Envoy::Stats::Counter>(Envoy::Buffer::Instance&, Envoy::Server::StatsParams const&, std::__1::vector<Envoy::Stats::RefcountPtr<Envoy::Stats::Counter>, std::__1::allocator<Envoy::Stats::RefcountPtr<Envoy::Stats::Counter> > > const&, std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > (Envoy::Stats::Counter const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)> const&, std::__1::basic_string_view<char, std::__1::char_traits<char> >, Envoy::Stats::CustomStatNamespaces const&) Unexecuted instantiation: prometheus_stats.cc:unsigned long Envoy::Server::(anonymous namespace)::outputStatType<Envoy::Stats::Gauge>(Envoy::Buffer::Instance&, Envoy::Server::StatsParams const&, std::__1::vector<Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge>, std::__1::allocator<Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge> > > const&, std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > (Envoy::Stats::Gauge const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)> const&, std::__1::basic_string_view<char, std::__1::char_traits<char> >, Envoy::Stats::CustomStatNamespaces const&) Unexecuted instantiation: prometheus_stats.cc:unsigned long Envoy::Server::(anonymous namespace)::outputStatType<Envoy::Stats::TextReadout>(Envoy::Buffer::Instance&, Envoy::Server::StatsParams const&, std::__1::vector<Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout>, std::__1::allocator<Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout> > > const&, std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > (Envoy::Stats::TextReadout const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)> const&, std::__1::basic_string_view<char, std::__1::char_traits<char> >, Envoy::Stats::CustomStatNamespaces const&) Unexecuted instantiation: prometheus_stats.cc:unsigned long Envoy::Server::(anonymous namespace)::outputStatType<Envoy::Stats::ParentHistogram>(Envoy::Buffer::Instance&, Envoy::Server::StatsParams const&, std::__1::vector<Envoy::Stats::RefcountPtr<Envoy::Stats::ParentHistogram>, std::__1::allocator<Envoy::Stats::RefcountPtr<Envoy::Stats::ParentHistogram> > > const&, std::__1::function<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > (Envoy::Stats::ParentHistogram const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)> const&, std::__1::basic_string_view<char, std::__1::char_traits<char> >, Envoy::Stats::CustomStatNamespaces const&) |
217 | | |
218 | | template <class StatType> |
219 | | uint64_t outputPrimitiveStatType(Buffer::Instance& response, const StatsParams& params, |
220 | | const std::vector<StatType>& metrics, absl::string_view type, |
221 | 0 | const Stats::CustomStatNamespaces& custom_namespaces) { |
222 | | |
223 | | /* |
224 | | * From |
225 | | * https:*github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#grouping-and-sorting: |
226 | | * |
227 | | * All lines for a given metric must be provided as one single group, with the optional HELP and |
228 | | * TYPE lines first (in no particular order). Beyond that, reproducible sorting in repeated |
229 | | * expositions is preferred but not required, i.e. do not sort if the computational cost is |
230 | | * prohibitive. |
231 | | */ |
232 | | |
233 | | // This is an unsorted collection of dumb-pointers (no need to increment then decrement every |
234 | | // refcount; ownership is held throughout by `metrics`). It is unsorted for efficiency, but will |
235 | | // be sorted before producing the final output to satisfy the "preferred" ordering from the |
236 | | // prometheus spec: metrics will be sorted by their tags' textual representation, which will be |
237 | | // consistent across calls. |
238 | 0 | using StatTypeUnsortedCollection = std::vector<const StatType*>; |
239 | | |
240 | | // Return early to avoid crashing when getting the symbol table from the first metric. |
241 | 0 | if (metrics.empty()) { |
242 | 0 | return 0; |
243 | 0 | } |
244 | | |
245 | | // Sorted collection of metrics sorted by their tagExtractedName, to satisfy the requirements |
246 | | // of the exposition format. |
247 | 0 | std::map<std::string, StatTypeUnsortedCollection> groups; |
248 | |
|
249 | 0 | for (const auto& metric : metrics) { |
250 | 0 | if (!params.shouldShowMetric(metric)) { |
251 | 0 | continue; |
252 | 0 | } |
253 | 0 | groups[metric.tagExtractedName()].push_back(&metric); |
254 | 0 | } |
255 | |
|
256 | 0 | auto result = groups.size(); |
257 | 0 | for (auto& group : groups) { |
258 | 0 | const absl::optional<std::string> prefixed_tag_extracted_name = |
259 | 0 | PrometheusStatsFormatter::metricName(group.first, custom_namespaces); |
260 | 0 | if (!prefixed_tag_extracted_name.has_value()) { |
261 | 0 | --result; |
262 | 0 | continue; |
263 | 0 | } |
264 | 0 | response.add(fmt::format("# TYPE {0} {1}\n", prefixed_tag_extracted_name.value(), type)); |
265 | | |
266 | | // Sort before producing the final output to satisfy the "preferred" ordering from the |
267 | | // prometheus spec: metrics will be sorted by their tags' textual representation, which will |
268 | | // be consistent across calls. |
269 | 0 | std::sort(group.second.begin(), group.second.end(), PrimitiveMetricSnapshotLessThan()); |
270 | |
|
271 | 0 | for (const auto& metric : group.second) { |
272 | 0 | response.add(generateNumericOutput(metric->value(), metric->tags(), |
273 | 0 | prefixed_tag_extracted_name.value())); |
274 | 0 | } |
275 | 0 | } |
276 | 0 | return result; |
277 | 0 | } Unexecuted instantiation: prometheus_stats.cc:unsigned long Envoy::Server::(anonymous namespace)::outputPrimitiveStatType<Envoy::Stats::PrimitiveCounterSnapshot>(Envoy::Buffer::Instance&, Envoy::Server::StatsParams const&, std::__1::vector<Envoy::Stats::PrimitiveCounterSnapshot, std::__1::allocator<Envoy::Stats::PrimitiveCounterSnapshot> > const&, std::__1::basic_string_view<char, std::__1::char_traits<char> >, Envoy::Stats::CustomStatNamespaces const&) Unexecuted instantiation: prometheus_stats.cc:unsigned long Envoy::Server::(anonymous namespace)::outputPrimitiveStatType<Envoy::Stats::PrimitiveGaugeSnapshot>(Envoy::Buffer::Instance&, Envoy::Server::StatsParams const&, std::__1::vector<Envoy::Stats::PrimitiveGaugeSnapshot, std::__1::allocator<Envoy::Stats::PrimitiveGaugeSnapshot> > const&, std::__1::basic_string_view<char, std::__1::char_traits<char> >, Envoy::Stats::CustomStatNamespaces const&) |
278 | | |
279 | | /* |
280 | | * Returns the prometheus output for a summary. The output is a multi-line string (with embedded |
281 | | * newlines) that contains all the individual quantile values and sum/count for a single histogram |
282 | | * (metric_name plus all tags). |
283 | | */ |
284 | | std::string generateSummaryOutput(const Stats::ParentHistogram& histogram, |
285 | 0 | const std::string& prefixed_tag_extracted_name) { |
286 | 0 | const std::string tags = PrometheusStatsFormatter::formattedTags(histogram.tags()); |
287 | 0 | const std::string hist_tags = histogram.tags().empty() ? EMPTY_STRING : (tags + ","); |
288 | |
|
289 | 0 | const Stats::HistogramStatistics& stats = histogram.intervalStatistics(); |
290 | 0 | Stats::ConstSupportedBuckets& supported_quantiles = stats.supportedQuantiles(); |
291 | 0 | const std::vector<double>& computed_quantiles = stats.computedQuantiles(); |
292 | 0 | std::string output; |
293 | 0 | for (size_t i = 0; i < supported_quantiles.size(); ++i) { |
294 | 0 | double quantile = supported_quantiles[i]; |
295 | 0 | double value = computed_quantiles[i]; |
296 | 0 | output.append(fmt::format("{0}{{{1}quantile=\"{2}\"}} {3:.32g}\n", prefixed_tag_extracted_name, |
297 | 0 | hist_tags, quantile, value)); |
298 | 0 | } |
299 | |
|
300 | 0 | output.append(fmt::format("{0}_sum{{{1}}} {2:.32g}\n", prefixed_tag_extracted_name, tags, |
301 | 0 | stats.sampleSum())); |
302 | 0 | output.append(fmt::format("{0}_count{{{1}}} {2}\n", prefixed_tag_extracted_name, tags, |
303 | 0 | stats.sampleCount())); |
304 | |
|
305 | 0 | return output; |
306 | 0 | }; |
307 | | |
308 | | } // namespace |
309 | | |
310 | 0 | std::string PrometheusStatsFormatter::formattedTags(const std::vector<Stats::Tag>& tags) { |
311 | 0 | std::vector<std::string> buf; |
312 | 0 | buf.reserve(tags.size()); |
313 | 0 | for (const Stats::Tag& tag : tags) { |
314 | 0 | buf.push_back(fmt::format("{}=\"{}\"", sanitizeName(tag.name_), sanitizeValue(tag.value_))); |
315 | 0 | } |
316 | 0 | return absl::StrJoin(buf, ","); |
317 | 0 | } |
318 | | |
319 | 0 | absl::Status PrometheusStatsFormatter::validateParams(const StatsParams& params) { |
320 | 0 | absl::Status result; |
321 | 0 | switch (params.histogram_buckets_mode_) { |
322 | 0 | case Utility::HistogramBucketsMode::Summary: |
323 | 0 | case Utility::HistogramBucketsMode::Unset: |
324 | 0 | case Utility::HistogramBucketsMode::Cumulative: |
325 | 0 | result = absl::OkStatus(); |
326 | 0 | break; |
327 | 0 | case Utility::HistogramBucketsMode::Detailed: |
328 | 0 | case Utility::HistogramBucketsMode::Disjoint: |
329 | 0 | result = absl::InvalidArgumentError("unsupported prometheus histogram bucket mode"); |
330 | 0 | break; |
331 | 0 | } |
332 | 0 | return result; |
333 | 0 | } |
334 | | |
335 | | absl::optional<std::string> |
336 | | PrometheusStatsFormatter::metricName(const std::string& extracted_name, |
337 | 0 | const Stats::CustomStatNamespaces& custom_namespaces) { |
338 | 0 | const absl::optional<absl::string_view> custom_namespace_stripped = |
339 | 0 | custom_namespaces.stripRegisteredPrefix(extracted_name); |
340 | 0 | if (custom_namespace_stripped.has_value()) { |
341 | | // This case the name has a custom namespace, and it is a custom metric. |
342 | 0 | const std::string sanitized_name = sanitizeName(custom_namespace_stripped.value()); |
343 | | // We expose these metrics without modifying (e.g. without "envoy_"), |
344 | | // so we have to check the "user-defined" stat name complies with the Prometheus naming |
345 | | // convention. Specifically the name must start with the "[a-zA-Z_]" pattern. |
346 | | // All the characters in sanitized_name are already in "[a-zA-Z0-9_]" pattern |
347 | | // thanks to sanitizeName above, so the only thing we have to do is check |
348 | | // if it does not start with digits. |
349 | 0 | if (sanitized_name.empty() || absl::ascii_isdigit(sanitized_name.front())) { |
350 | 0 | return absl::nullopt; |
351 | 0 | } |
352 | 0 | return sanitized_name; |
353 | 0 | } |
354 | | |
355 | | // If it does not have a custom namespace, add namespacing prefix to avoid conflicts, as per best |
356 | | // practice: https://prometheus.io/docs/practices/naming/#metric-names Also, naming conventions on |
357 | | // https://prometheus.io/docs/concepts/data_model/ |
358 | 0 | return absl::StrCat("envoy_", sanitizeName(extracted_name)); |
359 | 0 | } |
360 | | |
361 | | uint64_t PrometheusStatsFormatter::statsAsPrometheus( |
362 | | const std::vector<Stats::CounterSharedPtr>& counters, |
363 | | const std::vector<Stats::GaugeSharedPtr>& gauges, |
364 | | const std::vector<Stats::ParentHistogramSharedPtr>& histograms, |
365 | | const std::vector<Stats::TextReadoutSharedPtr>& text_readouts, |
366 | | const Upstream::ClusterManager& cluster_manager, Buffer::Instance& response, |
367 | 0 | const StatsParams& params, const Stats::CustomStatNamespaces& custom_namespaces) { |
368 | |
|
369 | 0 | uint64_t metric_name_count = 0; |
370 | 0 | metric_name_count += outputStatType<Stats::Counter>(response, params, counters, |
371 | 0 | generateStatNumericOutput<Stats::Counter>, |
372 | 0 | "counter", custom_namespaces); |
373 | |
|
374 | 0 | metric_name_count += outputStatType<Stats::Gauge>(response, params, gauges, |
375 | 0 | generateStatNumericOutput<Stats::Gauge>, |
376 | 0 | "gauge", custom_namespaces); |
377 | | |
378 | | // TextReadout stats are returned in gauge format, so "gauge" type is set intentionally. |
379 | 0 | metric_name_count += outputStatType<Stats::TextReadout>( |
380 | 0 | response, params, text_readouts, generateTextReadoutOutput, "gauge", custom_namespaces); |
381 | | |
382 | | // validation of bucket modes is handled separately |
383 | 0 | switch (params.histogram_buckets_mode_) { |
384 | 0 | case Utility::HistogramBucketsMode::Summary: |
385 | 0 | metric_name_count += outputStatType<Stats::ParentHistogram>( |
386 | 0 | response, params, histograms, generateSummaryOutput, "summary", custom_namespaces); |
387 | 0 | break; |
388 | 0 | case Utility::HistogramBucketsMode::Unset: |
389 | 0 | case Utility::HistogramBucketsMode::Cumulative: |
390 | 0 | metric_name_count += outputStatType<Stats::ParentHistogram>( |
391 | 0 | response, params, histograms, generateHistogramOutput, "histogram", custom_namespaces); |
392 | 0 | break; |
393 | | // "Detailed" and "Disjoint" don't make sense for prometheus histogram semantics |
394 | 0 | case Utility::HistogramBucketsMode::Detailed: |
395 | 0 | case Utility::HistogramBucketsMode::Disjoint: |
396 | 0 | IS_ENVOY_BUG("unsupported prometheus histogram bucket mode"); |
397 | 0 | break; |
398 | 0 | } |
399 | | |
400 | | // Note: This assumes that there is no overlap in stat name between per-endpoint stats and all |
401 | | // other stats. If this is not true, then the counters/gauges for per-endpoint need to be combined |
402 | | // with the above counter/gauge calls so that stats can be properly grouped. |
403 | 0 | std::vector<Stats::PrimitiveCounterSnapshot> host_counters; |
404 | 0 | std::vector<Stats::PrimitiveGaugeSnapshot> host_gauges; |
405 | 0 | Upstream::HostUtility::forEachHostMetric( |
406 | 0 | cluster_manager, |
407 | 0 | [&](Stats::PrimitiveCounterSnapshot&& metric) { |
408 | 0 | host_counters.emplace_back(std::move(metric)); |
409 | 0 | }, |
410 | 0 | [&](Stats::PrimitiveGaugeSnapshot&& metric) { host_gauges.emplace_back(std::move(metric)); }); |
411 | |
|
412 | 0 | metric_name_count += |
413 | 0 | outputPrimitiveStatType(response, params, host_counters, "counter", custom_namespaces); |
414 | |
|
415 | 0 | metric_name_count += |
416 | 0 | outputPrimitiveStatType(response, params, host_gauges, "gauge", custom_namespaces); |
417 | |
|
418 | 0 | return metric_name_count; |
419 | 0 | } |
420 | | |
421 | | } // namespace Server |
422 | | } // namespace Envoy |