blob: 1a3aa51cd2b119c5213a580f5a9d5a87ad4d9763 [file] [log] [blame]
// 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