]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/src/core/prometheus.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / seastar / src / core / prometheus.cc
1 /*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18 /*
19 * Copyright (C) 2016 ScyllaDB
20 */
21
22 #include <seastar/core/prometheus.hh>
23 #include <google/protobuf/io/coded_stream.h>
24 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
25 #include "proto/metrics2.pb.h"
26 #include <sstream>
27
28 #include <seastar/core/scollectd_api.hh>
29 #include "core/scollectd-impl.hh"
30 #include <seastar/core/metrics_api.hh>
31 #include <seastar/http/function_handlers.hh>
32 #include <boost/algorithm/string/replace.hpp>
33 #include <boost/range/algorithm_ext/erase.hpp>
34 #include <boost/algorithm/string.hpp>
35 #include <boost/range/algorithm.hpp>
36 #include <boost/range/combine.hpp>
37 #include <seastar/core/thread.hh>
38
39 namespace seastar {
40
41 namespace prometheus {
42 namespace pm = io::prometheus::client;
43
44 namespace mi = metrics::impl;
45
46 /**
47 * Taken from an answer in stackoverflow:
48 * http://stackoverflow.com/questions/2340730/are-there-c-equivalents-for-the-protocol-buffers-delimited-i-o-functions-in-ja
49 */
50 static bool write_delimited_to(const google::protobuf::MessageLite& message,
51 google::protobuf::io::ZeroCopyOutputStream* rawOutput) {
52 google::protobuf::io::CodedOutputStream output(rawOutput);
53
54 const int size = message.ByteSize();
55 output.WriteVarint32(size);
56
57 uint8_t* buffer = output.GetDirectBufferForNBytesAndAdvance(size);
58 if (buffer != nullptr) {
59 message.SerializeWithCachedSizesToArray(buffer);
60 } else {
61 message.SerializeWithCachedSizes(&output);
62 if (output.HadError()) {
63 return false;
64 }
65 }
66
67 return true;
68 }
69
70 static pm::Metric* add_label(pm::Metric* mt, const metrics::impl::metric_id & id, const config& ctx) {
71 mt->mutable_label()->Reserve(id.labels().size() + 1);
72 if (ctx.label) {
73 auto label = mt->add_label();
74 label->set_name(ctx.label->key());
75 label->set_value(ctx.label->value());
76 }
77 for (auto &&i : id.labels()) {
78 auto label = mt->add_label();
79 label->set_name(i.first);
80 label->set_value(i.second);
81 }
82 return mt;
83 }
84
85 static void fill_metric(pm::MetricFamily& mf, const metrics::impl::metric_value& c,
86 const metrics::impl::metric_id & id, const config& ctx) {
87 switch (c.type()) {
88 case scollectd::data_type::DERIVE:
89 add_label(mf.add_metric(), id, ctx)->mutable_counter()->set_value(c.i());
90 mf.set_type(pm::MetricType::COUNTER);
91 break;
92 case scollectd::data_type::GAUGE:
93 add_label(mf.add_metric(), id, ctx)->mutable_gauge()->set_value(c.d());
94 mf.set_type(pm::MetricType::GAUGE);
95 break;
96 case scollectd::data_type::HISTOGRAM:
97 {
98 auto h = c.get_histogram();
99 auto mh = add_label(mf.add_metric(), id,ctx)->mutable_histogram();
100 mh->set_sample_count(h.sample_count);
101 mh->set_sample_sum(h.sample_sum);
102 for (auto b : h.buckets) {
103 auto bc = mh->add_bucket();
104 bc->set_cumulative_count(b.count);
105 bc->set_upper_bound(b.upper_bound);
106 }
107 mf.set_type(pm::MetricType::HISTOGRAM);
108 break;
109 }
110 default:
111 add_label(mf.add_metric(), id, ctx)->mutable_counter()->set_value(c.ui());
112 mf.set_type(pm::MetricType::COUNTER);
113 break;
114 }
115 }
116
117 static std::string to_str(seastar::metrics::impl::data_type dt) {
118 switch (dt) {
119 case seastar::metrics::impl::data_type::GAUGE:
120 return "gauge";
121 case seastar::metrics::impl::data_type::COUNTER:
122 return "counter";
123 case seastar::metrics::impl::data_type::HISTOGRAM:
124 return "histogram";
125 case seastar::metrics::impl::data_type::DERIVE:
126 // Prometheus server does not respect derive parameters
127 // So we report them as counter
128 return "counter";
129 default:
130 break;
131 }
132 return "untyped";
133 }
134
135 static std::string to_str(const seastar::metrics::impl::metric_value& v) {
136 switch (v.type()) {
137 case seastar::metrics::impl::data_type::GAUGE:
138 return std::to_string(v.d());
139 case seastar::metrics::impl::data_type::COUNTER:
140 return std::to_string(v.i());
141 case seastar::metrics::impl::data_type::DERIVE:
142 return std::to_string(v.ui());
143 default:
144 break;
145 }
146 return ""; // we should never get here but it makes the compiler happy
147 }
148
149 static void add_name(std::ostream& s, const sstring& name, const std::map<sstring, sstring>& labels, const config& ctx) {
150 s << name << "{";
151 const char* delimiter = "";
152 if (ctx.label) {
153 s << ctx.label->key() << "=\"" << ctx.label->value() << '"';
154 delimiter = ",";
155 }
156
157 if (!labels.empty()) {
158 for (auto l : labels) {
159 s << delimiter;
160 s << l.first << "=\"" << l.second << '"';
161 delimiter = ",";
162 }
163 }
164 s << "} ";
165
166 }
167
168 /*!
169 * \brief iterator for metric family
170 *
171 * In prometheus, a single shard collecct all the data from the other
172 * shards and report it.
173 *
174 * Each shard returns a value_copy struct that has a vector of vector values (a vector per metric family)
175 * and a vector of metadata (and insdie it a vector of metric metadata)
176 *
177 * The metrics are sorted by the metric family name.
178 *
179 * In prometheus, all the metrics that belongs to the same metric family are reported together.
180 *
181 * For efficiency the results from the metrics layer are kept in a vector.
182 *
183 * So we have a vector of shards of a vector of metric families of a vector of values.
184 *
185 * To produce the result, we use the metric_family_iterator that is created by metric_family_range.
186 *
187 * When iterating over the metrics we use two helper structure.
188 *
189 * 1. A map between metric family name and the total number of values (combine on all shards) and
190 * pointer to the metric family metadata.
191 * 2. A vector of positions to the current metric family for each shard.
192 *
193 * The metric_family_range returns a metric_family_iterator that goes over all the families.
194 *
195 * The iterator returns a metric_family object, that can report the metric_family name, the size (how many
196 * metrics in total belongs to the metric family) and a a foreach_metric method.
197 *
198 * The foreach_metric method can be used to perform an action on each of the metric that belongs to
199 * that metric family
200 *
201 * Iterating over the metrics is done:
202 * - go over each of the shard and each of the entry in the position vector:
203 * - if the current family (the metric family that we get from the shard and position) has the current name:
204 * - iterate over each of the metrics belong to that metric family:
205 *
206 * for example, if m is a metric_family_range
207 *
208 * for (auto&& i : m) {
209 * std::cout << i.name() << std::endl;
210 * i.foreach_metric([](const mi::metric_value& value, const mi::metric_info& value_info) {
211 * std::cout << value_info.id.labels().size() <<std::cout;
212 * });
213 * }
214 *
215 * Will print all the metric family names followed by the number of labels each metric has.
216 */
217 class metric_family_iterator;
218
219 class metric_family_range;
220
221 class metrics_families_per_shard {
222 using metrics_family_per_shard_data_container = std::vector<foreign_ptr<mi::values_reference>>;
223 metrics_family_per_shard_data_container _data;
224 using comp_function = std::function<bool(const sstring&, const mi::metric_family_metadata&)>;
225 /*!
226 * \brief find the last item in a range of metric family based on a comparator function
227 *
228 */
229 metric_family_iterator find_bound(const sstring& family_name, comp_function comp) const;
230
231 public:
232
233 using const_iterator = metrics_family_per_shard_data_container::const_iterator;
234 using iterator = metrics_family_per_shard_data_container::iterator;
235 using reference = metrics_family_per_shard_data_container::reference;
236 using const_reference = metrics_family_per_shard_data_container::const_reference;
237
238 /*!
239 * \brief find the first item following a metric family range.
240 * metric family are sorted, this will return the first item that is outside
241 * of the range
242 */
243 metric_family_iterator upper_bound(const sstring& family_name) const;
244
245 /*!
246 * \brief find the first item in a range of metric family.
247 * metric family are sorted, the first item, is the first to match the
248 * criteria.
249 */
250 metric_family_iterator lower_bound(const sstring& family_name) const;
251
252 /**
253 * \defgroup Variables Global variables
254 */
255
256 /*
257 * @defgroup Vector properties
258 * The following methods making metrics_families_per_shard act as
259 * a vector of foreign_ptr<mi::values_reference>
260 * @{
261 *
262 *
263 */
264 iterator begin() {
265 return _data.begin();
266 }
267
268 iterator end() {
269 return _data.end();
270 }
271
272 const_iterator begin() const {
273 return _data.begin();
274 }
275
276 const_iterator end() const {
277 return _data.end();
278 }
279
280 void resize(size_t new_size) {
281 _data.resize(new_size);
282 }
283
284 reference& operator[](size_t n) {
285 return _data[n];
286 }
287
288 const_reference& operator[](size_t n) const {
289 return _data[n];
290 }
291 /** @} */
292 };
293
294 static future<> get_map_value(metrics_families_per_shard& vec) {
295 vec.resize(smp::count);
296 return parallel_for_each(boost::irange(0u, smp::count), [&vec] (auto cpu) {
297 return smp::submit_to(cpu, [] {
298 return mi::get_values();
299 }).then([&vec, cpu] (auto res) {
300 vec[cpu] = std::move(res);
301 });
302 });
303 }
304
305
306 /*!
307 * \brief a facade class for metric family
308 */
309 class metric_family {
310 const sstring* _name = nullptr;
311 uint32_t _size = 0;
312 const mi::metric_family_info* _family_info = nullptr;
313 metric_family_iterator& _iterator_state;
314 metric_family(metric_family_iterator& state) : _iterator_state(state) {
315 }
316 metric_family(const sstring* name , uint32_t size, const mi::metric_family_info* family_info, metric_family_iterator& state) :
317 _name(name), _size(size), _family_info(family_info), _iterator_state(state) {
318 }
319 metric_family(const metric_family& info, metric_family_iterator& state) :
320 metric_family(info._name, info._size, info._family_info, state) {
321 }
322 public:
323 metric_family(const metric_family&) = delete;
324 metric_family(metric_family&&) = delete;
325
326 const sstring& name() const {
327 return *_name;
328 }
329
330 const uint32_t size() const {
331 return _size;
332 }
333
334 const mi::metric_family_info& metadata() const {
335 return *_family_info;
336 }
337
338 void foreach_metric(std::function<void(const mi::metric_value&, const mi::metric_info&)>&& f);
339
340 bool end() const {
341 return !_name || !_family_info;
342 }
343 friend class metric_family_iterator;
344 };
345
346 class metric_family_iterator {
347 const metrics_families_per_shard& _families;
348 std::vector<size_t> _positions;
349 metric_family _info;
350
351 void next() {
352 if (_positions.empty()) {
353 return;
354 }
355 const sstring *new_name = nullptr;
356 const mi::metric_family_info* new_family_info = nullptr;
357 _info._size = 0;
358 for (auto&& i : boost::combine(_positions, _families)) {
359 auto& pos_in_metric_per_shard = boost::get<0>(i);
360 auto& metric_family = boost::get<1>(i);
361 if (_info._name && pos_in_metric_per_shard < metric_family->metadata->size() &&
362 metric_family->metadata->at(pos_in_metric_per_shard).mf.name.compare(*_info._name) <= 0) {
363 pos_in_metric_per_shard++;
364 }
365 if (pos_in_metric_per_shard >= metric_family->metadata->size()) {
366 // no more metric family in this shard
367 continue;
368 }
369 auto& metadata = metric_family->metadata->at(pos_in_metric_per_shard);
370 int cmp = (!new_name) ? -1 : metadata.mf.name.compare(*new_name);
371 if (cmp < 0) {
372 new_name = &metadata.mf.name;
373 new_family_info = &metadata.mf;
374 _info._size = 0;
375 }
376 if (cmp <= 0) {
377 _info._size += metadata.metrics.size();
378 }
379 }
380 _info._name = new_name;
381 _info._family_info = new_family_info;
382 }
383
384 public:
385 metric_family_iterator() = delete;
386 metric_family_iterator(const metric_family_iterator& o) : _families(o._families), _positions(o._positions), _info(*this) {
387 next();
388 }
389
390 metric_family_iterator(metric_family_iterator&& o) : _families(o._families), _positions(std::move(o._positions)),
391 _info(*this) {
392 next();
393 }
394
395 metric_family_iterator(const metrics_families_per_shard& families,
396 unsigned shards)
397 : _families(families), _positions(shards, 0), _info(*this) {
398 next();
399 }
400
401 metric_family_iterator(const metrics_families_per_shard& families,
402 std::vector<size_t>&& positions)
403 : _families(families), _positions(std::move(positions)), _info(*this) {
404 next();
405 }
406
407 metric_family_iterator& operator++() {
408 next();
409 return *this;
410 }
411
412 metric_family_iterator operator++(int) {
413 metric_family_iterator previous(*this);
414 next();
415 return previous;
416 }
417
418 bool operator!=(const metric_family_iterator& o) const {
419 return !(*this == o);
420 }
421
422 bool operator==(const metric_family_iterator& o) const {
423 if (end()) {
424 return o.end();
425 }
426 if (o.end()) {
427 return false;
428 }
429 return name() == o.name();
430 }
431
432 metric_family& operator*() {
433 return _info;
434 }
435
436 metric_family* operator->() {
437 return &_info;
438 }
439 const sstring& name() const {
440 return *_info._name;
441 }
442
443 const uint32_t size() const {
444 return _info._size;
445 }
446
447 const mi::metric_family_info& metadata() const {
448 return *_info._family_info;
449 }
450
451 bool end() const {
452 return _positions.empty() || _info.end();
453 }
454
455 void foreach_metric(std::function<void(const mi::metric_value&, const mi::metric_info&)>&& f) {
456 // iterating over the shard vector and the position vector
457 for (auto&& i : boost::combine(_positions, _families)) {
458 auto& pos_in_metric_per_shard = boost::get<0>(i);
459 auto& metric_family = boost::get<1>(i);
460 if (pos_in_metric_per_shard >= metric_family->metadata->size()) {
461 // no more metric family in this shard
462 continue;
463 }
464 auto& metadata = metric_family->metadata->at(pos_in_metric_per_shard);
465 // the the name is different, that means that on this shard, the metric family
466 // does not exist, because everything is sorted by metric family name, this is fine.
467 if (metadata.mf.name == name()) {
468 const mi::value_vector& values = metric_family->values[pos_in_metric_per_shard];
469 const mi::metric_metadata_vector& metrics_metadata = metadata.metrics;
470 for (auto&& vm : boost::combine(values, metrics_metadata)) {
471 auto& value = boost::get<0>(vm);
472 auto& metric_metadata = boost::get<1>(vm);
473 f(value, metric_metadata);
474 }
475 }
476 }
477 }
478
479 };
480
481 void metric_family::foreach_metric(std::function<void(const mi::metric_value&, const mi::metric_info&)>&& f) {
482 _iterator_state.foreach_metric(std::move(f));
483 }
484
485 class metric_family_range {
486 metric_family_iterator _begin;
487 metric_family_iterator _end;
488 public:
489 metric_family_range(const metrics_families_per_shard& families) : _begin(families, smp::count),
490 _end(metric_family_iterator(families, 0))
491 {
492 }
493
494 metric_family_range(const metric_family_iterator& b, const metric_family_iterator& e) : _begin(b), _end(e)
495 {
496 }
497
498 metric_family_iterator begin() const {
499 return _begin;
500 }
501
502 metric_family_iterator end() const {
503 return _end;
504 }
505 };
506
507 metric_family_iterator metrics_families_per_shard::find_bound(const sstring& family_name, comp_function comp) const {
508 std::vector<size_t> positions;
509 positions.reserve(smp::count);
510
511 for (auto& shard_info : _data) {
512 std::vector<mi::metric_family_metadata>& metadata = *(shard_info->metadata);
513 std::vector<mi::metric_family_metadata>::iterator it_b = boost::range::upper_bound(metadata, family_name, comp);
514 positions.emplace_back(it_b - metadata.begin());
515 }
516 return metric_family_iterator(*this, std::move(positions));
517
518 }
519
520 metric_family_iterator metrics_families_per_shard::lower_bound(const sstring& family_name) const {
521 return find_bound(family_name, [](const sstring& a, const mi::metric_family_metadata& b) {
522 //sstring doesn't have a <= operator
523 return a < b.mf.name || a == b.mf.name;
524 });
525 }
526
527 metric_family_iterator metrics_families_per_shard::upper_bound(const sstring& family_name) const {
528 return find_bound(family_name, [](const sstring& a, const mi::metric_family_metadata& b) {
529 return a < b.mf.name;
530 });
531 }
532
533 /*!
534 * \brief a helper function to get metric family range
535 * if metric_family_name is empty will return everything, if not, it will return
536 * the range of metric family that match the metric_family_name.
537 *
538 * if prefix is true the match will be based on prefix
539 */
540 metric_family_range get_range(const metrics_families_per_shard& mf, const sstring& metric_family_name, bool prefix) {
541 if (metric_family_name == "") {
542 return metric_family_range(mf);
543 }
544 auto upper_bount_prefix = metric_family_name;
545 ++upper_bount_prefix.back();
546 if (prefix) {
547 return metric_family_range(mf.lower_bound(metric_family_name), mf.lower_bound(upper_bount_prefix));
548 }
549 auto lb = mf.lower_bound(metric_family_name);
550 if (lb.end() || lb->name() != metric_family_name) {
551 return metric_family_range(lb, lb); // just return an empty range
552 }
553 auto up = lb;
554 ++up;
555 return metric_family_range(lb, up);
556
557 }
558
559 future<> write_text_representation(output_stream<char>& out, const config& ctx, const metric_family_range& m) {
560 return seastar::async([&ctx, &out, &m] () mutable {
561 bool found = false;
562 for (metric_family& metric_family : m) {
563 auto name = ctx.prefix + "_" + metric_family.name();
564 found = false;
565 metric_family.foreach_metric([&out, &ctx, &found, &name, &metric_family](auto value, auto value_info) mutable {
566 std::stringstream s;
567 if (!found) {
568 if (metric_family.metadata().d.str() != "") {
569 s << "# HELP " << name << " " << metric_family.metadata().d.str() << "\n";
570 }
571 s << "# TYPE " << name << " " << to_str(metric_family.metadata().type) << "\n";
572 found = true;
573 }
574 if (value.type() == mi::data_type::HISTOGRAM) {
575 auto&& h = value.get_histogram();
576 std::map<sstring, sstring> labels = value_info.id.labels();
577 add_name(s, name + "_sum", labels, ctx);
578 s << h.sample_sum;
579 s << "\n";
580 add_name(s, name + "_count", labels, ctx);
581 s << h.sample_count;
582 s << "\n";
583
584 auto& le = labels["le"];
585 auto bucket = name + "_bucket";
586 for (auto i : h.buckets) {
587 le = std::to_string(i.upper_bound);
588 add_name(s, bucket, labels, ctx);
589 s << i.count;
590 s << "\n";
591 }
592 labels["le"] = "+Inf";
593 add_name(s, bucket, labels, ctx);
594 s << h.sample_count;
595 s << "\n";
596 } else {
597 add_name(s, name, value_info.id.labels(), ctx);
598 s << to_str(value);
599 s << "\n";
600 }
601 out.write(s.str()).get();
602 thread::maybe_yield();
603 });
604 }
605 });
606 }
607
608 future<> write_protobuf_representation(output_stream<char>& out, const config& ctx, metric_family_range& m) {
609 return do_for_each(m, [&ctx, &out](metric_family& metric_family) mutable {
610 std::string s;
611 google::protobuf::io::StringOutputStream os(&s);
612
613 auto& name = metric_family.name();
614 pm::MetricFamily mtf;
615
616 mtf.set_name(ctx.prefix + "_" + name);
617 mtf.mutable_metric()->Reserve(metric_family.size());
618 metric_family.foreach_metric([&mtf, &ctx](auto value, auto value_info) {
619 fill_metric(mtf, value, value_info.id, ctx);
620 });
621 if (!write_delimited_to(mtf, &os)) {
622 seastar_logger.warn("Failed to write protobuf metrics");
623 }
624 return out.write(s);
625 });
626 }
627
628 bool is_accept_text(const std::string& accept) {
629 std::vector<std::string> strs;
630 boost::split(strs, accept, boost::is_any_of(","));
631 for (auto i : strs) {
632 boost::trim(i);
633 if (boost::starts_with(i, "application/vnd.google.protobuf;")) {
634 return false;
635 }
636 }
637 return true;
638 }
639
640 class metrics_handler : public handler_base {
641 sstring _prefix;
642 config _ctx;
643
644 /*!
645 * \brief tries to trim an asterisk from the end of the string
646 * return true if an asterisk exists.
647 */
648 bool trim_asterisk(sstring& name) {
649 if (name.size() && name.back() == '*') {
650 name.resize(name.length() - 1);
651 return true;
652 }
653 // Prometheus uses url encoding for the path so '*' is encoded as '%2A'
654 if (boost::algorithm::ends_with(name, "%2A")) {
655 // This assert is obviously true. It is in here just to
656 // silence a bogus gcc warning:
657 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89337
658 assert(name.length() >= 3);
659 name.resize(name.length() - 3);
660 return true;
661 }
662 return false;
663 }
664 public:
665 metrics_handler(config ctx) : _ctx(ctx) {}
666
667 future<std::unique_ptr<httpd::reply>> handle(const sstring& path,
668 std::unique_ptr<httpd::request> req, std::unique_ptr<httpd::reply> rep) override {
669 auto text = is_accept_text(req->get_header("Accept"));
670 sstring metric_family_name = req->get_query_param("name");
671 bool prefix = trim_asterisk(metric_family_name);
672
673 rep->write_body((text) ? "txt" : "proto", [this, text, metric_family_name, prefix] (output_stream<char>&& s) {
674 return do_with(metrics_families_per_shard(), output_stream<char>(std::move(s)),
675 [this, text, prefix, &metric_family_name] (metrics_families_per_shard& families, output_stream<char>& s) mutable {
676 return get_map_value(families).then([&s, &families, this, text, prefix, &metric_family_name]() mutable {
677 return do_with(get_range(families, metric_family_name, prefix),
678 [&s, this, text](metric_family_range& m) {
679 return (text) ? write_text_representation(s, _ctx, m) :
680 write_protobuf_representation(s, _ctx, m);
681 });
682 }).finally([&s] () mutable {
683 return s.close();
684 });
685 });
686 });
687 return make_ready_future<std::unique_ptr<httpd::reply>>(std::move(rep));
688 }
689 };
690
691
692
693 future<> add_prometheus_routes(http_server& server, config ctx) {
694 server._routes.put(GET, "/metrics", new metrics_handler(ctx));
695 return make_ready_future<>();
696 }
697
698 future<> add_prometheus_routes(distributed<http_server>& server, config ctx) {
699 return server.invoke_on_all([ctx](http_server& s) {
700 return add_prometheus_routes(s, ctx);
701 });
702 }
703
704 future<> start(httpd::http_server_control& http_server, config ctx) {
705 return add_prometheus_routes(http_server.server(), ctx);
706 }
707
708 }
709 }