// 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. // Date: 2021/11/17 10:57:43 #ifndef BVAR_MULTI_DIMENSION_INL_H #define BVAR_MULTI_DIMENSION_INL_H #include namespace bvar { DECLARE_int32(bvar_latency_p1); DECLARE_int32(bvar_latency_p2); DECLARE_int32(bvar_latency_p3); static const std::string ALLOW_UNUSED METRIC_TYPE_COUNTER = "counter"; static const std::string ALLOW_UNUSED METRIC_TYPE_SUMMARY = "summary"; static const std::string ALLOW_UNUSED METRIC_TYPE_HISTOGRAM = "histogram"; static const std::string ALLOW_UNUSED METRIC_TYPE_GAUGE = "gauge"; template inline MultiDimension::MultiDimension(const key_type& labels) : Base(labels) { _metric_map.Modify(init_flatmap); } template inline MultiDimension::MultiDimension(const butil::StringPiece& name, const key_type& labels) : Base(labels) { _metric_map.Modify(init_flatmap); this->expose(name); } template inline MultiDimension::MultiDimension(const butil::StringPiece& prefix, const butil::StringPiece& name, const key_type& labels) : Base(labels) { _metric_map.Modify(init_flatmap); this->expose_as(prefix, name); } template MultiDimension::~MultiDimension() { delete_stats(); hide(); } template inline size_t MultiDimension::init_flatmap(MetricMap& bg) { // size = 1 << 13 CHECK_EQ(0, bg.init(8192, 80)); return (size_t)1; } template inline size_t MultiDimension::count_stats() { MetricMapScopedPtr metric_map_ptr; if (_metric_map.Read(&metric_map_ptr) != 0) { LOG(ERROR) << "Fail to read dbd"; return 0; } return metric_map_ptr->size(); } template inline void MultiDimension::delete_stats(const key_type& labels_value) { if (is_valid_lables_value(labels_value)) { // Because there are two copies(foreground and background) in DBD, we need to use an empty tmp_metric, // get the deleted value of second copy into tmp_metric, which can prevent the bvar object from being deleted twice. op_value_type tmp_metric = NULL; auto erase_fn = [&labels_value, &tmp_metric](MetricMap& bg) { auto it = bg.seek(labels_value); if (it != NULL) { tmp_metric = *it; bg.erase(labels_value); return 1; } return 0; }; _metric_map.Modify(erase_fn); if (tmp_metric) { delete tmp_metric; } } } template inline void MultiDimension::delete_stats() { // Because there are two copies(foreground and background) in DBD, we need to use an empty tmp_map, // swap two copies with empty, and get the value of second copy into tmp_map, // then traversal tmp_map and delete bvar object, // which can prevent the bvar object from being deleted twice. MetricMap tmp_map; auto clear_fn = [&tmp_map](MetricMap& map) { if (!tmp_map.empty()) { tmp_map.clear(); } tmp_map.swap(map); return (size_t)1; }; int ret = _metric_map.Modify(clear_fn); CHECK_EQ(1, ret); for (auto &kv : tmp_map) { delete kv.second; } } template inline void MultiDimension::list_stats(std::vector* names) { if (names == NULL) { return; } names->clear(); MetricMapScopedPtr metric_map_ptr; if (_metric_map.Read(&metric_map_ptr) != 0) { LOG(ERROR) << "Fail to read dbd"; return; } names->reserve(metric_map_ptr->size()); for (auto it = metric_map_ptr->begin(); it != metric_map_ptr->end(); ++it) { names->emplace_back(it->first); } } template inline T* MultiDimension::get_stats_impl(const key_type& labels_value) { if (!is_valid_lables_value(labels_value)) { return nullptr; } MetricMapScopedPtr metric_map_ptr; if (_metric_map.Read(&metric_map_ptr) != 0) { LOG(ERROR) << "Fail to read dbd"; return nullptr; } auto it = metric_map_ptr->seek(labels_value); if (it == nullptr) { return nullptr; } return (*it); } template inline T* MultiDimension::get_stats_impl(const key_type& labels_value, STATS_OP stats_op, bool* do_write) { if (!is_valid_lables_value(labels_value)) { return nullptr; } { MetricMapScopedPtr metric_map_ptr; if (_metric_map.Read(&metric_map_ptr) != 0) { LOG(ERROR) << "Fail to read dbd"; return nullptr; } auto it = metric_map_ptr->seek(labels_value); if (it != NULL) { return (*it); } else if (READ_ONLY == stats_op) { return nullptr; } if (metric_map_ptr->size() > MAX_MULTI_DIMENSION_STATS_COUNT) { LOG(ERROR) << "Too many stats seen, overflow detected, max stats count:" << MAX_MULTI_DIMENSION_STATS_COUNT; return nullptr; } } // Because DBD has two copies(foreground and background) MetricMap, both copies need to be modify, // In order to avoid new duplicate bvar object, need use cache_metric to cache the new bvar object, // In this way, when modifying the second copy, can directly use the cache_metric bvar object. op_value_type cache_metric = NULL; auto insert_fn = [&labels_value, &cache_metric, &do_write](MetricMap& bg) { auto bg_metric = bg.seek(labels_value); if (NULL != bg_metric) { cache_metric = *bg_metric; return 0; } if (do_write) { *do_write = true; } if (NULL != cache_metric) { bg.insert(labels_value, cache_metric); } else { T* add_metric = new T(); bg.insert(labels_value, add_metric); cache_metric = add_metric; } return 1; }; _metric_map.Modify(insert_fn); return cache_metric; } template inline bool MultiDimension::has_stats(const key_type& labels_value) { return get_stats_impl(labels_value) != nullptr; } template inline size_t MultiDimension::dump(Dumper* dumper, const DumpOptions* options) { std::vector label_names; list_stats(&label_names); if (label_names.empty() || !dumper->dump_comment(name(), METRIC_TYPE_GAUGE)) { return 0; } size_t n = 0; for (auto &label_name : label_names) { T* bvar = get_stats_impl(label_name); if (!bvar) { continue; } std::ostringstream oss; bvar->describe(oss, options->quote_string); std::ostringstream oss_key; make_dump_key(oss_key, label_name); if (!dumper->dump(oss_key.str(), oss.str())) { continue; } n++; } return n; } template <> inline size_t MultiDimension::dump(Dumper* dumper, const DumpOptions*) { std::vector label_names; list_stats(&label_names); if (label_names.empty()) { return 0; } size_t n = 0; for (auto &label_name : label_names) { bvar::LatencyRecorder* bvar = get_stats_impl(label_name); if (!bvar) { continue; } // latency comment if (!dumper->dump_comment(name() + "_latency", METRIC_TYPE_GAUGE)) { continue; } // latency std::ostringstream oss_latency_key; make_dump_key(oss_latency_key, label_name, "_latency"); if (dumper->dump(oss_latency_key.str(), std::to_string(bvar->latency()))) { n++; } // latency_percentiles // p1/p2/p3 int latency_percentiles[3] {FLAGS_bvar_latency_p1, FLAGS_bvar_latency_p2, FLAGS_bvar_latency_p3}; for (auto lp : latency_percentiles) { std::ostringstream oss_lp_key; make_dump_key(oss_lp_key, label_name, "_latency", lp); if (dumper->dump(oss_lp_key.str(), std::to_string(bvar->latency_percentile(lp / 100.0)))) { n++; } } // 999 std::ostringstream oss_p999_key; make_dump_key(oss_p999_key, label_name, "_latency", 999); if (dumper->dump(oss_p999_key.str(), std::to_string(bvar->latency_percentile(0.999)))) { n++; } // 9999 std::ostringstream oss_p9999_key; make_dump_key(oss_p9999_key, label_name, "_latency", 9999); if (dumper->dump(oss_p9999_key.str(), std::to_string(bvar->latency_percentile(0.9999)))) { n++; } // max_latency comment if (!dumper->dump_comment(name() + "_max_latency", METRIC_TYPE_GAUGE)) { continue; } // max_latency std::ostringstream oss_max_latency_key; make_dump_key(oss_max_latency_key, label_name, "_max_latency"); if (dumper->dump(oss_max_latency_key.str(), std::to_string(bvar->max_latency()))) { n++; } // qps comment if (!dumper->dump_comment(name() + "_qps", METRIC_TYPE_GAUGE)) { continue; } // qps std::ostringstream oss_qps_key; make_dump_key(oss_qps_key, label_name, "_qps"); if (dumper->dump(oss_qps_key.str(), std::to_string(bvar->qps()))) { n++; } // qps comment if (!dumper->dump_comment(name() + "_count", METRIC_TYPE_COUNTER)) { continue; } // count std::ostringstream oss_count_key; make_dump_key(oss_count_key, label_name, "_count"); if (dumper->dump(oss_count_key.str(), std::to_string(bvar->count()))) { n++; } } return n; } template inline void MultiDimension::make_dump_key(std::ostream& os, const key_type& labels_value, const std::string& suffix, const int quantile) { os << name(); if (!suffix.empty()) { os << suffix; } make_labels_kvpair_string(os, labels_value, quantile); } template inline void MultiDimension::make_labels_kvpair_string(std::ostream& os, const key_type& labels_value, const int quantile) { os << "{"; auto label_key = _labels.cbegin(); auto label_value = labels_value.cbegin(); char comma[2] = {'\0', '\0'}; for (; label_key != _labels.cend() && label_value != labels_value.cend(); label_key++, label_value++) { os << comma << label_key->c_str() << "=\"" << label_value->c_str() << "\""; comma[0] = ','; } if (quantile > 0) { os << ",quantile=\"" << quantile << "\""; } os << "}"; } template inline bool MultiDimension::is_valid_lables_value(const key_type& labels_value) const { if (count_labels() != labels_value.size()) { LOG(ERROR) << "Invalid labels count"; return false; } return true; } template inline void MultiDimension::describe(std::ostream& os) { os << "{\"name\" : \"" << _name << "\", \"labels\" : ["; char comma[3] = {'\0', ' ', '\0'}; for (auto &label : _labels) { os << comma << "\"" << label << "\""; comma[0] = ','; } os << "], \"stats_count\" : " << count_stats() << "}"; } } // namespace bvar #endif // BVAR_MULTI_DIMENSION_INL_H