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.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
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
19 * Copyright (C) 2016 ScyllaDB.
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>
33 extern seastar::logger seastar_logger
;
36 double_registration::double_registration(std::string what
): std::runtime_error(what
) {}
38 metric_groups::metric_groups() noexcept
: _impl(impl::create_metric_groups()) {
41 void metric_groups::clear() {
42 _impl
= impl::create_metric_groups();
45 metric_groups::metric_groups(std::initializer_list
<metric_group_definition
> mg
) : _impl(impl::create_metric_groups()) {
47 add_group(i
.name
, i
.metrics
);
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
);
54 metric_groups
& metric_groups::add_group(const group_name_type
& name
, const std::vector
<metric_definition
>& l
) {
55 _impl
->add_group(name
, l
);
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
) {
64 metric_group_definition::metric_group_definition(const group_name_type
& name
, std::initializer_list
<metric_definition
> l
) : name(name
), metrics(l
) {
67 metric_group_definition::~metric_group_definition() = default;
69 metric_groups::~metric_groups() = default;
70 metric_definition::metric_definition(metric_definition
&& m
) noexcept
: _impl(std::move(m
._impl
)) {
73 metric_definition::~metric_definition() = default;
75 metric_definition::metric_definition(impl::metric_definition_impl
const& m
) noexcept
:
76 _impl(std::make_unique
<impl::metric_definition_impl
>(m
)) {
79 bool label_instance::operator<(const label_instance
& id2
) const {
81 return std::tie(id1
.key(), id1
.value())
82 < std::tie(id2
.key(), id2
.value());
85 bool label_instance::operator==(const label_instance
& id2
) const {
87 return std::tie(id1
.key(), id1
.value())
88 == std::tie(id2
.key(), id2
.value());
92 static std::string
get_hostname() {
93 char hostname
[PATH_MAX
];
94 gethostname(hostname
, sizeof(hostname
));
95 hostname
[PATH_MAX
-1] = '\0';
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")
107 future
<> configure(const options
& opts
) {
109 c
.hostname
= opts
.metrics_hostname
.get_value();
110 return smp::invoke_on_all([c
] {
111 impl::get_local_impl()->set_config(c
);
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
);
119 const std::vector
<relabel_config
>& get_relabel_configs() {
120 return impl::get_local_impl()->get_relabel_configs();
124 static bool apply_relabeling(const relabel_config
& rc
, impl::metric_info
& info
) {
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
138 s
<< ((l
== "__name__") ? info
.id
.full_name() : val
->second
);
141 // regex_search forbid temporary strings
142 std::string tmps
= s
.str();
143 if (!std::regex_search(tmps
, match
, rc
.expr
.regex())) {
148 case relabel_config::relabel_action::drop
:
149 case relabel_config::relabel_action::keep
: {
150 info
.enabled
= rc
.action
== relabel_config::relabel_action::keep
;
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
;
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
);
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
;
177 bool label_instance::operator!=(const label_instance
& id2
) const {
179 return !(id1
== id2
);
183 * \brief get_unique_id generate a random id
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());
190 label
shard_label("shard");
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
;
198 _info
.original_labels
= id
.labels();
201 metric_value
metric_value::operator+(const metric_value
& c
) {
202 metric_value
res(*this);
204 case data_type::HISTOGRAM
:
205 std::get
<histogram
>(res
.u
) += std::get
<histogram
>(c
.u
);
208 std::get
<double>(res
.u
) += std::get
<double>(c
.u
);
214 void metric_value::ulong_conversion_error(double d
) {
215 throw std::range_error(format("cannot convert double value {} to unsigned long", d
));
218 metric_definition_impl::metric_definition_impl(
219 metric_name_type name
,
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();
230 if (labels
.find(shard_label
.name()) == labels
.end()) {
231 labels
[shard_label
.name()] = shard();
233 aggregate(_aggregate_labels
);
236 metric_definition_impl
& metric_definition_impl::operator ()(bool _enabled
) {
241 metric_definition_impl
& metric_definition_impl::operator ()(const label_instance
& label
) {
242 labels
[label
.key()] = label
.value();
246 metric_definition_impl
& metric_definition_impl::operator ()(skip_when_empty skip
) noexcept
{
247 _skip_when_empty
= skip
;
251 metric_definition_impl
& metric_definition_impl::set_type(const sstring
& type_name
) {
252 type
.type_name
= type_name
;
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(); });
263 metric_definition_impl
& metric_definition_impl::set_skip_when_empty(bool skip
) noexcept
{
264 _skip_when_empty
= skip_when_empty(skip
);
268 std::unique_ptr
<metric_groups_def
> create_metric_groups() {
269 return std::make_unique
<metric_groups_impl
>();
272 metric_groups_impl::~metric_groups_impl() {
273 for (const auto& i
: _registration
) {
274 unregister_metric(i
);
278 metric_groups_impl
& metric_groups_impl::add_metric(group_name_type name
, const metric_definition
& md
) {
280 metric_id
id(name
, md
._impl
->name
, md
._impl
->labels
);
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
);
284 _registration
.push_back(id
);
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()));
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
);
302 bool metric_id::operator<(
303 const metric_id
& id2
) const {
304 return as_tuple() < id2
.as_tuple();
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("+()"));
313 sstring
metric_id::full_name() const {
314 return safe_name(_group
+ "_" + _name
);
317 bool metric_id::operator==(
318 const metric_id
& id2
) const {
319 return as_tuple() == id2
.as_tuple();
322 // Unfortunately, metrics_impl can not be shared because it
323 // need to be available before the first users (reactor) will call it
325 shared_ptr
<impl
> get_local_impl() {
326 static thread_local
auto the_impl
= ::seastar::make_shared
<impl
>();
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()) {
337 if (i
->second
.empty()) {
338 get_value_map().erase(i
);
344 void unregister_metric(const metric_id
& id
) {
345 get_local_impl()->remove_registration(id
);
348 const value_map
& get_value_map() {
349 return get_local_impl()->get_value_map();
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
) {
361 values
.reserve(i
.size());
363 values
.emplace_back(v());
365 mv
.emplace_back(std::move(values
));
371 instance_id_type
shard() {
372 if (engine_is_ready()) {
373 return to_sstring(this_shard_id());
379 void impl::update_metrics_if_needed() {
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
>();
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());
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());
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
)});
406 // Maybe we didn't use all the original size
407 _current_metrics
.resize(i
);
413 shared_ptr
<metric_metadata
> impl::metadata() {
414 update_metrics_if_needed();
418 std::vector
<std::vector
<metric_function
>>& impl::functions() {
419 update_metrics_if_needed();
420 return _current_metrics
;
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());
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
);
435 if (metric
.info().type
!= type
.base_type
) {
436 throw std::runtime_error("registering metrics " + name
+ " registered with different type.");
438 metric
[rm
->info().id
.labels()] = rm
;
439 for (auto&& i
: rm
->info().id
.labels()) {
440 _labels
.insert(i
.first
);
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
;
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())) {
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
++);
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()) {
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.
485 seastar_logger
.error("Metrics: After relabeling, registering metrics twice for metrics : {}", family
.first
);
486 auto id
= get_unique_id();
488 conflicts
.metrics_relabeled_due_to_collision
++;
489 rm
->info().id
.labels()["err"] = id
;
492 family
.second
[lb
] = rm
;
495 return make_ready_future
<metric_relabeling_result
>(conflicts
);
499 const bool metric_disabled
= false;
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
;
505 if (action
== "report_when_empty") {
506 return relabel_config::relabel_action::report_when_empty
;
508 if (action
== "keep") {
509 return relabel_config::relabel_action::keep
;
511 if (action
== "drop") {
512 return relabel_config::relabel_action::drop
;
513 } if (action
== "drop_label") {
514 return relabel_config::relabel_action::drop_label
;
516 return relabel_config::relabel_action::replace
;
519 histogram
& histogram::operator+=(const histogram
& c
) {
520 if (c
.sample_count
== 0) {
523 for (size_t i
= 0; i
< c
.buckets
.size(); i
++) {
524 if (buckets
.size() <= i
) {
525 buckets
.push_back(c
.buckets
[i
]);
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");
530 buckets
[i
].count
+= c
.buckets
[i
].count
;
533 sample_count
+= c
.sample_count
;
534 sample_sum
+= c
.sample_sum
;
538 histogram
histogram::operator+(const histogram
& c
) const {
539 histogram res
= *this;
544 histogram
histogram::operator+(histogram
&& c
) const {