| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "util/metrics.h" |
| |
| #include <glog/logging.h> |
| #include <rapidjson/encodings.h> |
| #include <rapidjson/stringbuffer.h> |
| #include <rapidjson/writer.h> |
| |
| #include <initializer_list> |
| |
| #include "common/config.h" |
| |
| namespace doris { |
| |
| std::ostream& operator<<(std::ostream& os, MetricType type) { |
| switch (type) { |
| case MetricType::COUNTER: |
| os << "counter"; |
| break; |
| case MetricType::GAUGE: |
| os << "gauge"; |
| break; |
| case MetricType::HISTOGRAM: |
| os << "histogram"; |
| break; |
| case MetricType::SUMMARY: |
| os << "summary"; |
| break; |
| case MetricType::UNTYPED: |
| os << "untyped"; |
| break; |
| default: |
| os << "unknown"; |
| break; |
| } |
| return os; |
| } |
| |
| const char* unit_name(MetricUnit unit) { |
| switch (unit) { |
| case MetricUnit::NANOSECONDS: |
| return "nanoseconds"; |
| case MetricUnit::MICROSECONDS: |
| return "microseconds"; |
| case MetricUnit::MILLISECONDS: |
| return "milliseconds"; |
| case MetricUnit::SECONDS: |
| return "seconds"; |
| case MetricUnit::BYTES: |
| return "bytes"; |
| case MetricUnit::ROWS: |
| return "rows"; |
| case MetricUnit::PERCENT: |
| return "percent"; |
| case MetricUnit::REQUESTS: |
| return "requests"; |
| case MetricUnit::OPERATIONS: |
| return "operations"; |
| case MetricUnit::BLOCKS: |
| return "blocks"; |
| case MetricUnit::ROWSETS: |
| return "rowsets"; |
| case MetricUnit::CONNECTIONS: |
| return "rowsets"; |
| default: |
| return "nounit"; |
| } |
| } |
| |
| std::string labels_to_string(std::initializer_list<const Labels*> multi_labels) { |
| bool all_empty = true; |
| for (const auto& labels : multi_labels) { |
| if (!labels->empty()) { |
| all_empty = false; |
| break; |
| } |
| } |
| if (all_empty) { |
| return std::string(); |
| } |
| |
| std::stringstream ss; |
| ss << "{"; |
| int i = 0; |
| for (auto labels : multi_labels) { |
| for (const auto& label : *labels) { |
| if (i++ > 0) { |
| ss << ","; |
| } |
| ss << label.first << "=\"" << label.second << "\""; |
| } |
| } |
| ss << "}"; |
| |
| return ss.str(); |
| } |
| |
| std::string Metric::to_prometheus(const std::string& display_name, const Labels& entity_labels, |
| const Labels& metric_labels) const { |
| std::stringstream ss; |
| ss << display_name // metric name |
| << labels_to_string({&entity_labels, &metric_labels}) // metric labels |
| << " " << to_string() << "\n"; // metric value |
| return ss.str(); |
| } |
| |
| std::map<std::string, double> HistogramMetric::_s_output_percentiles = { |
| {"0.50", 50.0}, {"0.75", 75.0}, {"0.90", 90.0}, {"0.95", 95.0}, {"0.99", 99.0}}; |
| void HistogramMetric::clear() { |
| std::lock_guard<std::mutex> l(_lock); |
| _stats.clear(); |
| } |
| |
| bool HistogramMetric::is_empty() const { |
| return _stats.is_empty(); |
| } |
| |
| void HistogramMetric::add(const uint64_t& value) { |
| _stats.add(value); |
| } |
| |
| void HistogramMetric::merge(const HistogramMetric& other) { |
| std::lock_guard<std::mutex> l(_lock); |
| _stats.merge(other._stats); |
| } |
| |
| void HistogramMetric::set_histogram(const HistogramStat& stats) { |
| std::lock_guard<std::mutex> l(_lock); |
| _stats.clear(); |
| _stats.merge(stats); |
| } |
| |
| double HistogramMetric::median() const { |
| return _stats.median(); |
| } |
| |
| double HistogramMetric::percentile(double p) const { |
| return _stats.percentile(p); |
| } |
| |
| double HistogramMetric::average() const { |
| return _stats.average(); |
| } |
| |
| double HistogramMetric::standard_deviation() const { |
| return _stats.standard_deviation(); |
| } |
| |
| std::string HistogramMetric::to_string() const { |
| return _stats.to_string(); |
| } |
| |
| std::string HistogramMetric::to_prometheus(const std::string& display_name, |
| const Labels& entity_labels, |
| const Labels& metric_labels) const { |
| // TODO: Use std::string concate for better performance. |
| std::stringstream ss; |
| for (const auto& percentile : _s_output_percentiles) { |
| auto quantile_lable = Labels({{"quantile", percentile.first}}); |
| ss << display_name << labels_to_string({&entity_labels, &metric_labels, &quantile_lable}) |
| << " " << _stats.percentile(percentile.second) << "\n"; |
| } |
| ss << display_name << "_sum" << labels_to_string({&entity_labels, &metric_labels}) << " " |
| << _stats.sum() << "\n"; |
| ss << display_name << "_count" << labels_to_string({&entity_labels, &metric_labels}) << " " |
| << _stats.num() << "\n"; |
| ss << display_name << "_max" << labels_to_string({&entity_labels, &metric_labels}) << " " |
| << _stats.max() << "\n"; |
| ss << display_name << "_min" << labels_to_string({&entity_labels, &metric_labels}) << " " |
| << _stats.min() << "\n"; |
| ss << display_name << "_average" << labels_to_string({&entity_labels, &metric_labels}) << " " |
| << _stats.average() << "\n"; |
| ss << display_name << "_median" << labels_to_string({&entity_labels, &metric_labels}) << " " |
| << _stats.median() << "\n"; |
| ss << display_name << "_standard_deviation" |
| << labels_to_string({&entity_labels, &metric_labels}) << " " << _stats.standard_deviation() |
| << "\n"; |
| |
| return ss.str(); |
| } |
| |
| rj::Value HistogramMetric::to_json_value(rj::Document::AllocatorType& allocator) const { |
| rj::Value json_value(rj::kObjectType); |
| json_value.AddMember("total_count", rj::Value(_stats.num()), allocator); |
| json_value.AddMember("min", rj::Value(_stats.min()), allocator); |
| json_value.AddMember("average", rj::Value(_stats.average()), allocator); |
| json_value.AddMember("median", rj::Value(_stats.median()), allocator); |
| for (const auto& percentile : _s_output_percentiles) { |
| json_value.AddMember( |
| rj::Value(std::string("percentile_").append(percentile.first.substr(2)).c_str(), |
| allocator), |
| rj::Value(_stats.percentile(percentile.second)), allocator); |
| } |
| json_value.AddMember("standard_deviation", rj::Value(_stats.standard_deviation()), allocator); |
| json_value.AddMember("max", rj::Value(_stats.max()), allocator); |
| json_value.AddMember("total_sum", rj::Value(_stats.sum()), allocator); |
| |
| return json_value; |
| } |
| |
| std::string MetricPrototype::simple_name() const { |
| return group_name.empty() ? name : group_name; |
| } |
| |
| std::string MetricPrototype::combine_name(const std::string& registry_name) const { |
| return (registry_name.empty() ? std::string() : registry_name + "_") + simple_name(); |
| } |
| |
| std::string MetricPrototype::to_prometheus(const std::string& registry_name) const { |
| std::stringstream ss; |
| ss << "# TYPE " << combine_name(registry_name) << " " << type << "\n"; |
| return ss.str(); |
| } |
| |
| void MetricEntity::deregister_metric(const MetricPrototype* metric_type) { |
| std::lock_guard<std::mutex> l(_lock); |
| auto metric = _metrics.find(metric_type); |
| if (metric != _metrics.end()) { |
| delete metric->second; |
| _metrics.erase(metric); |
| } |
| } |
| |
| Metric* MetricEntity::get_metric(const std::string& name, const std::string& group_name) const { |
| MetricPrototype dummy(MetricType::UNTYPED, MetricUnit::NOUNIT, name, "", group_name); |
| std::lock_guard<std::mutex> l(_lock); |
| auto it = _metrics.find(&dummy); |
| if (it == _metrics.end()) { |
| return nullptr; |
| } |
| return it->second; |
| } |
| |
| void MetricEntity::register_hook(const std::string& name, const std::function<void()>& hook) { |
| std::lock_guard<std::mutex> l(_lock); |
| #ifndef BE_TEST |
| DCHECK(_hooks.find(name) == _hooks.end()) << "hook is already exist! " << _name << ":" << name; |
| #endif |
| _hooks.emplace(name, hook); |
| } |
| |
| void MetricEntity::deregister_hook(const std::string& name) { |
| std::lock_guard<std::mutex> l(_lock); |
| _hooks.erase(name); |
| } |
| |
| void MetricEntity::trigger_hook_unlocked(bool force) const { |
| // When 'enable_metric_calculator' is true, hooks will be triggered by a background thread, |
| // see 'calculate_metrics' in daemon.cpp for more details. |
| if (!force && config::enable_metric_calculator) { |
| return; |
| } |
| for (const auto& hook : _hooks) { |
| hook.second(); |
| } |
| } |
| |
| MetricRegistry::~MetricRegistry() {} |
| |
| std::shared_ptr<MetricEntity> MetricRegistry::register_entity(const std::string& name, |
| const Labels& labels, |
| MetricEntityType type) { |
| std::shared_ptr<MetricEntity> entity = std::make_shared<MetricEntity>(type, name, labels); |
| std::lock_guard<std::mutex> l(_lock); |
| auto inserted_entity = _entities.insert(std::make_pair(entity, 1)); |
| if (!inserted_entity.second) { |
| // If exist, increase the registered count |
| inserted_entity.first->second++; |
| } |
| return inserted_entity.first->first; |
| } |
| |
| void MetricRegistry::deregister_entity(const std::shared_ptr<MetricEntity>& entity) { |
| std::lock_guard<std::mutex> l(_lock); |
| auto found_entity = _entities.find(entity); |
| if (found_entity != _entities.end()) { |
| // Decrease the registered count |
| --found_entity->second; |
| if (found_entity->second == 0) { |
| // Only erase it when registered count is zero |
| _entities.erase(found_entity); |
| } |
| } |
| } |
| |
| std::shared_ptr<MetricEntity> MetricRegistry::get_entity(const std::string& name, |
| const Labels& labels, |
| MetricEntityType type) { |
| std::shared_ptr<MetricEntity> dummy = std::make_shared<MetricEntity>(type, name, labels); |
| |
| std::lock_guard<std::mutex> l(_lock); |
| auto entity = _entities.find(dummy); |
| if (entity == _entities.end()) { |
| return std::shared_ptr<MetricEntity>(); |
| } |
| return entity->first; |
| } |
| |
| void MetricRegistry::trigger_all_hooks(bool force) const { |
| std::lock_guard<std::mutex> l(_lock); |
| for (const auto& entity : _entities) { |
| std::lock_guard<std::mutex> l(entity.first->_lock); |
| entity.first->trigger_hook_unlocked(force); |
| } |
| } |
| |
| std::string MetricRegistry::to_prometheus(bool with_tablet_metrics) const { |
| // Reorder by MetricPrototype |
| EntityMetricsByType entity_metrics_by_types; |
| std::lock_guard<std::mutex> l1(_lock); |
| for (const auto& entity : _entities) { |
| if (entity.first->_type == MetricEntityType::kTablet && !with_tablet_metrics) { |
| continue; |
| } |
| std::lock_guard<std::mutex> l2(entity.first->_lock); |
| entity.first->trigger_hook_unlocked(false); |
| for (const auto& metric : entity.first->_metrics) { |
| std::pair<MetricEntity*, Metric*> new_elem = |
| std::make_pair(entity.first.get(), metric.second); |
| auto found = entity_metrics_by_types.find(metric.first); |
| if (found == entity_metrics_by_types.end()) { |
| entity_metrics_by_types.emplace( |
| metric.first, std::vector<std::pair<MetricEntity*, Metric*>>({new_elem})); |
| } else { |
| found->second.emplace_back(new_elem); |
| } |
| } |
| } |
| |
| // Output |
| std::stringstream ss; |
| std::string last_group_name; |
| for (const auto& entity_metrics_by_type : entity_metrics_by_types) { |
| if (last_group_name.empty() || |
| last_group_name != entity_metrics_by_type.first->group_name) { |
| ss << entity_metrics_by_type.first->to_prometheus(_name); // metric TYPE line |
| } |
| last_group_name = entity_metrics_by_type.first->group_name; |
| std::string display_name = entity_metrics_by_type.first->combine_name(_name); |
| for (const auto& entity_metric : entity_metrics_by_type.second) { |
| ss << entity_metric.second->to_prometheus(display_name, // metric key-value line |
| entity_metric.first->_labels, |
| entity_metrics_by_type.first->labels); |
| } |
| } |
| |
| return ss.str(); |
| } |
| |
| std::string MetricRegistry::to_json(bool with_tablet_metrics) const { |
| rj::Document doc {rj::kArrayType}; |
| rj::Document::AllocatorType& allocator = doc.GetAllocator(); |
| std::lock_guard<std::mutex> l(_lock); |
| for (const auto& entity : _entities) { |
| if (entity.first->_type == MetricEntityType::kTablet && !with_tablet_metrics) { |
| continue; |
| } |
| std::lock_guard<std::mutex> l(entity.first->_lock); |
| entity.first->trigger_hook_unlocked(false); |
| for (const auto& metric : entity.first->_metrics) { |
| rj::Value metric_obj(rj::kObjectType); |
| // tags |
| rj::Value tag_obj(rj::kObjectType); |
| tag_obj.AddMember("metric", rj::Value(metric.first->simple_name().c_str(), allocator), |
| allocator); |
| // MetricPrototype's labels |
| for (auto& label : metric.first->labels) { |
| tag_obj.AddMember(rj::Value(label.first.c_str(), allocator), |
| rj::Value(label.second.c_str(), allocator), allocator); |
| } |
| // MetricEntity's labels |
| for (auto& label : entity.first->_labels) { |
| tag_obj.AddMember(rj::Value(label.first.c_str(), allocator), |
| rj::Value(label.second.c_str(), allocator), allocator); |
| } |
| metric_obj.AddMember("tags", tag_obj, allocator); |
| // unit |
| rj::Value unit_val(unit_name(metric.first->unit), allocator); |
| metric_obj.AddMember("unit", unit_val, allocator); |
| // value |
| metric_obj.AddMember("value", metric.second->to_json_value(allocator), allocator); |
| doc.PushBack(metric_obj, allocator); |
| } |
| } |
| |
| rj::StringBuffer strBuf; |
| rj::Writer<rj::StringBuffer> writer(strBuf); |
| doc.Accept(writer); |
| return strBuf.GetString(); |
| } |
| |
| std::string MetricRegistry::to_core_string() const { |
| std::stringstream ss; |
| std::lock_guard<std::mutex> l(_lock); |
| for (const auto& entity : _entities) { |
| std::lock_guard<std::mutex> l(entity.first->_lock); |
| entity.first->trigger_hook_unlocked(false); |
| for (const auto& metric : entity.first->_metrics) { |
| if (metric.first->is_core_metric) { |
| ss << metric.first->combine_name(_name) << " LONG " << metric.second->to_string() |
| << "\n"; |
| } |
| } |
| } |
| |
| return ss.str(); |
| } |
| |
| } // namespace doris |