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