]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/src/core/metrics.cc
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / seastar / src / core / metrics.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/metrics.hh>
23 #include <seastar/core/metrics_api.hh>
24 #include <seastar/core/relabel_config.hh>
25 #include <seastar/core/reactor.hh>
26 #include <boost/range/algorithm.hpp>
27 #include <boost/algorithm/string.hpp>
28 #include <boost/algorithm/string/replace.hpp>
29 #include <boost/range/algorithm_ext/erase.hpp>
30 #include <random>
31
32 namespace seastar {
33 extern seastar::logger seastar_logger;
34 namespace metrics {
35
36 double_registration::double_registration(std::string what): std::runtime_error(what) {}
37
38 metric_groups::metric_groups() noexcept : _impl(impl::create_metric_groups()) {
39 }
40
41 void metric_groups::clear() {
42 _impl = impl::create_metric_groups();
43 }
44
45 metric_groups::metric_groups(std::initializer_list<metric_group_definition> mg) : _impl(impl::create_metric_groups()) {
46 for (auto&& i : mg) {
47 add_group(i.name, i.metrics);
48 }
49 }
50 metric_groups& metric_groups::add_group(const group_name_type& name, const std::initializer_list<metric_definition>& l) {
51 _impl->add_group(name, l);
52 return *this;
53 }
54 metric_groups& metric_groups::add_group(const group_name_type& name, const std::vector<metric_definition>& l) {
55 _impl->add_group(name, l);
56 return *this;
57 }
58 metric_group::metric_group() noexcept = default;
59 metric_group::~metric_group() = default;
60 metric_group::metric_group(const group_name_type& name, std::initializer_list<metric_definition> l) {
61 add_group(name, l);
62 }
63
64 metric_group_definition::metric_group_definition(const group_name_type& name, std::initializer_list<metric_definition> l) : name(name), metrics(l) {
65 }
66
67 metric_group_definition::~metric_group_definition() = default;
68
69 metric_groups::~metric_groups() = default;
70 metric_definition::metric_definition(metric_definition&& m) noexcept : _impl(std::move(m._impl)) {
71 }
72
73 metric_definition::~metric_definition() = default;
74
75 metric_definition::metric_definition(impl::metric_definition_impl const& m) noexcept :
76 _impl(std::make_unique<impl::metric_definition_impl>(m)) {
77 }
78
79 bool label_instance::operator<(const label_instance& id2) const {
80 auto& id1 = *this;
81 return std::tie(id1.key(), id1.value())
82 < std::tie(id2.key(), id2.value());
83 }
84
85 bool label_instance::operator==(const label_instance& id2) const {
86 auto& id1 = *this;
87 return std::tie(id1.key(), id1.value())
88 == std::tie(id2.key(), id2.value());
89 }
90
91
92 static std::string get_hostname() {
93 char hostname[PATH_MAX];
94 gethostname(hostname, sizeof(hostname));
95 hostname[PATH_MAX-1] = '\0';
96 return hostname;
97 }
98
99
100 options::options(program_options::option_group* parent_group)
101 : program_options::option_group(parent_group, "Metrics options")
102 , metrics_hostname(*this, "metrics-hostname", get_hostname(),
103 "set the hostname used by the metrics, if not set, the local hostname will be used")
104 {
105 }
106
107 future<> configure(const options& opts) {
108 impl::config c;
109 c.hostname = opts.metrics_hostname.get_value();
110 return smp::invoke_on_all([c] {
111 impl::get_local_impl()->set_config(c);
112 });
113 }
114
115 future<metric_relabeling_result> set_relabel_configs(const std::vector<relabel_config>& relabel_configs) {
116 return impl::get_local_impl()->set_relabel_configs(relabel_configs);
117 }
118
119 const std::vector<relabel_config>& get_relabel_configs() {
120 return impl::get_local_impl()->get_relabel_configs();
121 }
122
123
124 static bool apply_relabeling(const relabel_config& rc, impl::metric_info& info) {
125 std::stringstream s;
126 bool first = true;
127 for (auto&& l: rc.source_labels) {
128 auto val = info.id.labels().find(l);
129 if (l != "__name__" && val == info.id.labels().end()) {
130 //If not all the labels are found nothing todo
131 return false;
132 }
133 if (first) {
134 first = false;
135 } else {
136 s << rc.separator;
137 }
138 s << ((l == "__name__") ? info.id.full_name() : val->second);
139 }
140 std::smatch match;
141 // regex_search forbid temporary strings
142 std::string tmps = s.str();
143 if (!std::regex_search(tmps, match, rc.expr.regex())) {
144 return false;
145 }
146
147 switch (rc.action) {
148 case relabel_config::relabel_action::drop:
149 case relabel_config::relabel_action::keep: {
150 info.enabled = rc.action == relabel_config::relabel_action::keep;
151 return true;
152 }
153 case relabel_config::relabel_action::report_when_empty:
154 case relabel_config::relabel_action::skip_when_empty: {
155 info.should_skip_when_empty = (rc.action == relabel_config::relabel_action::skip_when_empty) ? skip_when_empty::yes : skip_when_empty::no;
156 return false;
157 }
158 case relabel_config::relabel_action::drop_label: {
159 if (info.id.labels().find(rc.target_label) != info.id.labels().end()) {
160 info.id.labels().erase(rc.target_label);
161 }
162 return true;
163 };
164 case relabel_config::relabel_action::replace: {
165 if (!rc.target_label.empty()) {
166 std::string fmt_s = match.format(rc.replacement);
167 info.id.labels()[rc.target_label] = fmt_s;
168 }
169 return true;
170 }
171 default:
172 break;
173 }
174 return true;
175 }
176
177 bool label_instance::operator!=(const label_instance& id2) const {
178 auto& id1 = *this;
179 return !(id1 == id2);
180 }
181
182 /*!
183 * \brief get_unique_id generate a random id
184 */
185 static std::string get_unique_id() {
186 std::random_device rd;
187 return std::to_string(rd()) + "-" + std::to_string(rd()) + "-" + std::to_string(rd()) + "-" + std::to_string(rd());
188 }
189
190 label shard_label("shard");
191 namespace impl {
192
193 registered_metric::registered_metric(metric_id id, metric_function f, bool enabled, skip_when_empty skip) :
194 _f(f), _impl(get_local_impl()) {
195 _info.enabled = enabled;
196 _info.should_skip_when_empty = skip;
197 _info.id = id;
198 _info.original_labels = id.labels();
199 }
200
201 metric_value metric_value::operator+(const metric_value& c) {
202 metric_value res(*this);
203 switch (_type) {
204 case data_type::HISTOGRAM:
205 std::get<histogram>(res.u) += std::get<histogram>(c.u);
206 break;
207 default:
208 std::get<double>(res.u) += std::get<double>(c.u);
209 break;
210 }
211 return res;
212 }
213
214 void metric_value::ulong_conversion_error(double d) {
215 throw std::range_error(format("cannot convert double value {} to unsigned long", d));
216 }
217
218 metric_definition_impl::metric_definition_impl(
219 metric_name_type name,
220 metric_type type,
221 metric_function f,
222 description d,
223 std::vector<label_instance> _labels,
224 std::vector<label> _aggregate_labels)
225 : name(name), type(type), f(f)
226 , d(d), enabled(true) {
227 for (auto i: _labels) {
228 labels[i.key()] = i.value();
229 }
230 if (labels.find(shard_label.name()) == labels.end()) {
231 labels[shard_label.name()] = shard();
232 }
233 aggregate(_aggregate_labels);
234 }
235
236 metric_definition_impl& metric_definition_impl::operator ()(bool _enabled) {
237 enabled = _enabled;
238 return *this;
239 }
240
241 metric_definition_impl& metric_definition_impl::operator ()(const label_instance& label) {
242 labels[label.key()] = label.value();
243 return *this;
244 }
245
246 metric_definition_impl& metric_definition_impl::operator ()(skip_when_empty skip) noexcept {
247 _skip_when_empty = skip;
248 return *this;
249 }
250
251 metric_definition_impl& metric_definition_impl::set_type(const sstring& type_name) {
252 type.type_name = type_name;
253 return *this;
254 }
255
256 metric_definition_impl& metric_definition_impl::aggregate(const std::vector<label>& _labels) noexcept {
257 aggregate_labels.reserve(_labels.size());
258 std::transform(_labels.begin(), _labels.end(),std::back_inserter(aggregate_labels),
259 [](const label& l) { return l.name(); });
260 return *this;
261 }
262
263 metric_definition_impl& metric_definition_impl::set_skip_when_empty(bool skip) noexcept {
264 _skip_when_empty = skip_when_empty(skip);
265 return *this;
266 }
267
268 std::unique_ptr<metric_groups_def> create_metric_groups() {
269 return std::make_unique<metric_groups_impl>();
270 }
271
272 metric_groups_impl::~metric_groups_impl() {
273 for (const auto& i : _registration) {
274 unregister_metric(i);
275 }
276 }
277
278 metric_groups_impl& metric_groups_impl::add_metric(group_name_type name, const metric_definition& md) {
279
280 metric_id id(name, md._impl->name, md._impl->labels);
281
282 get_local_impl()->add_registration(id, md._impl->type, md._impl->f, md._impl->d, md._impl->enabled, md._impl->_skip_when_empty, md._impl->aggregate_labels);
283
284 _registration.push_back(id);
285 return *this;
286 }
287
288 metric_groups_impl& metric_groups_impl::add_group(group_name_type name, const std::vector<metric_definition>& l) {
289 for (auto i = l.begin(); i != l.end(); ++i) {
290 add_metric(name, *(i->_impl.get()));
291 }
292 return *this;
293 }
294
295 metric_groups_impl& metric_groups_impl::add_group(group_name_type name, const std::initializer_list<metric_definition>& l) {
296 for (auto i = l.begin(); i != l.end(); ++i) {
297 add_metric(name, *i);
298 }
299 return *this;
300 }
301
302 bool metric_id::operator<(
303 const metric_id& id2) const {
304 return as_tuple() < id2.as_tuple();
305 }
306
307 static std::string safe_name(const sstring& name) {
308 auto rep = boost::replace_all_copy(boost::replace_all_copy(name, "-", "_"), " ", "_");
309 boost::remove_erase_if(rep, boost::is_any_of("+()"));
310 return rep;
311 }
312
313 sstring metric_id::full_name() const {
314 return safe_name(_group + "_" + _name);
315 }
316
317 bool metric_id::operator==(
318 const metric_id & id2) const {
319 return as_tuple() == id2.as_tuple();
320 }
321
322 // Unfortunately, metrics_impl can not be shared because it
323 // need to be available before the first users (reactor) will call it
324
325 shared_ptr<impl> get_local_impl() {
326 static thread_local auto the_impl = ::seastar::make_shared<impl>();
327 return the_impl;
328 }
329 void impl::remove_registration(const metric_id& id) {
330 auto i = get_value_map().find(id.full_name());
331 if (i != get_value_map().end()) {
332 auto j = i->second.find(id.labels());
333 if (j != i->second.end()) {
334 j->second = nullptr;
335 i->second.erase(j);
336 }
337 if (i->second.empty()) {
338 get_value_map().erase(i);
339 }
340 dirty();
341 }
342 }
343
344 void unregister_metric(const metric_id & id) {
345 get_local_impl()->remove_registration(id);
346 }
347
348 const value_map& get_value_map() {
349 return get_local_impl()->get_value_map();
350 }
351
352 foreign_ptr<values_reference> get_values() {
353 shared_ptr<values_copy> res_ref = ::seastar::make_shared<values_copy>();
354 auto& res = *(res_ref.get());
355 auto& mv = res.values;
356 res.metadata = get_local_impl()->metadata();
357 auto & functions = get_local_impl()->functions();
358 mv.reserve(functions.size());
359 for (auto&& i : functions) {
360 value_vector values;
361 values.reserve(i.size());
362 for (auto&& v : i) {
363 values.emplace_back(v());
364 }
365 mv.emplace_back(std::move(values));
366 }
367 return res_ref;
368 }
369
370
371 instance_id_type shard() {
372 if (engine_is_ready()) {
373 return to_sstring(this_shard_id());
374 }
375
376 return sstring("0");
377 }
378
379 void impl::update_metrics_if_needed() {
380 if (_dirty) {
381 // Forcing the metadata to an empty initialization
382 // Will prevent using corrupted data if an exception is thrown
383 _metadata = ::seastar::make_shared<metric_metadata>();
384
385 auto mt_ref = ::seastar::make_shared<metric_metadata>();
386 auto &mt = *(mt_ref.get());
387 mt.reserve(_value_map.size());
388 _current_metrics.resize(_value_map.size());
389 size_t i = 0;
390 for (auto&& mf : _value_map) {
391 metric_metadata_vector metrics;
392 _current_metrics[i].clear();
393 for (auto&& m : mf.second) {
394 if (m.second && m.second->is_enabled()) {
395 metrics.emplace_back(m.second->info());
396 _current_metrics[i].emplace_back(m.second->get_function());
397 }
398 }
399 if (!metrics.empty()) {
400 // If nothing was added, no need to add the metric_family
401 // and no need to progress
402 mt.emplace_back(metric_family_metadata{mf.second.info(), std::move(metrics)});
403 i++;
404 }
405 }
406 // Maybe we didn't use all the original size
407 _current_metrics.resize(i);
408 _metadata = mt_ref;
409 _dirty = false;
410 }
411 }
412
413 shared_ptr<metric_metadata> impl::metadata() {
414 update_metrics_if_needed();
415 return _metadata;
416 }
417
418 std::vector<std::vector<metric_function>>& impl::functions() {
419 update_metrics_if_needed();
420 return _current_metrics;
421 }
422
423 void impl::add_registration(const metric_id& id, const metric_type& type, metric_function f, const description& d, bool enabled, skip_when_empty skip, const std::vector<std::string>& aggregate_labels) {
424 auto rm = ::seastar::make_shared<registered_metric>(id, f, enabled, skip);
425 for (auto&& rl : _relabel_configs) {
426 apply_relabeling(rl, rm->info());
427 }
428
429 sstring name = id.full_name();
430 if (_value_map.find(name) != _value_map.end()) {
431 auto& metric = _value_map[name];
432 if (metric.find(rm->info().id.labels()) != metric.end()) {
433 throw double_registration("registering metrics twice for metrics: " + name);
434 }
435 if (metric.info().type != type.base_type) {
436 throw std::runtime_error("registering metrics " + name + " registered with different type.");
437 }
438 metric[rm->info().id.labels()] = rm;
439 for (auto&& i : rm->info().id.labels()) {
440 _labels.insert(i.first);
441 }
442 } else {
443 _value_map[name].info().type = type.base_type;
444 _value_map[name].info().d = d;
445 _value_map[name].info().inherit_type = type.type_name;
446 _value_map[name].info().name = id.full_name();
447 _value_map[name].info().aggregate_labels = aggregate_labels;
448 _value_map[name][rm->info().id.labels()] = rm;
449 }
450 dirty();
451 }
452
453 future<metric_relabeling_result> impl::set_relabel_configs(const std::vector<relabel_config>& relabel_configs) {
454 _relabel_configs = relabel_configs;
455 metric_relabeling_result conflicts{0};
456 for (auto&& family : _value_map) {
457 std::vector<shared_ptr<registered_metric>> rms;
458 for (auto&& metric = family.second.begin(); metric != family.second.end();) {
459 metric->second->info().id.labels().clear();
460 metric->second->info().id.labels() = metric->second->info().original_labels;
461 for (auto rl : _relabel_configs) {
462 if (apply_relabeling(rl, metric->second->info())) {
463 dirty();
464 }
465 }
466 if (metric->first != metric->second->info().id.labels()) {
467 // If a metric labels were changed, we should remove it from the map, and place it back again
468 rms.push_back(metric->second);
469 family.second.erase(metric++);
470 dirty();
471 } else {
472 ++metric;
473 }
474 }
475 for (auto rm : rms) {
476 labels_type lb = rm->info().id.labels();
477 if (family.second.find(rm->info().id.labels()) != family.second.end()) {
478 /*
479 You can not have a two metrics with the same name and label, there is a problem with the configuration.
480 On startup we would have throw an exception and stop.
481 But during normal run, we don't want to crash the server just because a metric reconfiguration.
482 We cannot throw away the metric.
483 Instead we add it with an extra label, allowing the user to reconfigure.
484 */
485 seastar_logger.error("Metrics: After relabeling, registering metrics twice for metrics : {}", family.first);
486 auto id = get_unique_id();
487 lb["err"] = id;
488 conflicts.metrics_relabeled_due_to_collision++;
489 rm->info().id.labels()["err"] = id;
490 }
491
492 family.second[lb] = rm;
493 }
494 }
495 return make_ready_future<metric_relabeling_result>(conflicts);
496 }
497 }
498
499 const bool metric_disabled = false;
500
501 relabel_config::relabel_action relabel_config_action(const std::string& action) {
502 if (action == "skip_when_empty") {
503 return relabel_config::relabel_action::skip_when_empty;
504 }
505 if (action == "report_when_empty") {
506 return relabel_config::relabel_action::report_when_empty;
507 }
508 if (action == "keep") {
509 return relabel_config::relabel_action::keep;
510 }
511 if (action == "drop") {
512 return relabel_config::relabel_action::drop;
513 } if (action == "drop_label") {
514 return relabel_config::relabel_action::drop_label;
515 }
516 return relabel_config::relabel_action::replace;
517 }
518
519 histogram& histogram::operator+=(const histogram& c) {
520 if (c.sample_count == 0) {
521 return *this;
522 }
523 for (size_t i = 0; i < c.buckets.size(); i++) {
524 if (buckets.size() <= i) {
525 buckets.push_back(c.buckets[i]);
526 } else {
527 if (buckets[i].upper_bound != c.buckets[i].upper_bound) {
528 throw std::out_of_range("Trying to add histogram with different bucket limits");
529 }
530 buckets[i].count += c.buckets[i].count;
531 }
532 }
533 sample_count += c.sample_count;
534 sample_sum += c.sample_sum;
535 return *this;
536 }
537
538 histogram histogram::operator+(const histogram& c) const {
539 histogram res = *this;
540 res += c;
541 return res;
542 }
543
544 histogram histogram::operator+(histogram&& c) const {
545 c += *this;
546 return std::move(c);
547 }
548
549 }
550 }