This is an automated email from the ASF dual-hosted git repository.

guangmingchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git


The following commit(s) were added to refs/heads/master by this push:
     new b4ce61f8 Fix format of prometheus service (#2899)
b4ce61f8 is described below

commit b4ce61f8f4935ec79d3d9584433f8da9f05edd1c
Author: Regal <141622927+zhengwei...@users.noreply.github.com>
AuthorDate: Wed Mar 12 09:33:33 2025 +0800

    Fix format of prometheus service (#2899)
    
    Fix PrometheusMetricsDumper to dump unique comment of a certain
    metric name to follow the prometheus specification.
    Also refine the prometheus ut case to support mvar output
    
    Co-authored-by: Zhengwei Zhu <zheng_we...@163.com>
---
 BUILD.bazel                                     |  1 +
 src/brpc/builtin/prometheus_metrics_service.cpp | 17 ++++++
 src/bvar/multi_dimension_inl.h                  | 53 +++++++++-------
 src/bvar/variable.h                             |  6 ++
 test/BUILD.bazel                                | 24 +++++++-
 test/brpc_channel_unittest.cpp                  |  4 --
 test/brpc_prometheus_metrics_unittest.cpp       | 81 ++++++++++++++++++++-----
 test/iobuf_unittest.cpp                         |  4 --
 8 files changed, 143 insertions(+), 47 deletions(-)

diff --git a/BUILD.bazel b/BUILD.bazel
index e383be4b..610d01d0 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -465,6 +465,7 @@ filegroup(
 proto_library(
     name = "brpc_idl_options_proto",
     srcs = [":brpc_idl_options_proto_srcs"],
+    strip_import_prefix = "src",
     visibility = ["//visibility:public"],
     deps = [
         "@com_google_protobuf//:descriptor_proto",
diff --git a/src/brpc/builtin/prometheus_metrics_service.cpp 
b/src/brpc/builtin/prometheus_metrics_service.cpp
index 88f675bb..4c5dd590 100644
--- a/src/brpc/builtin/prometheus_metrics_service.cpp
+++ b/src/brpc/builtin/prometheus_metrics_service.cpp
@@ -54,6 +54,8 @@ public:
     }
 
     bool dump(const std::string& name, const butil::StringPiece& desc) 
override;
+    bool dump_mvar(const std::string& name, const butil::StringPiece& desc) 
override;
+    bool dump_comment(const std::string& name, const std::string& type) 
override;
 
 private:
     DISALLOW_COPY_AND_ASSIGN(PrometheusMetricsDumper);
@@ -108,6 +110,21 @@ bool PrometheusMetricsDumper::dump(const std::string& name,
     return true;
 }
 
+bool PrometheusMetricsDumper::dump_mvar(const std::string& name, const 
butil::StringPiece& desc) {
+    if (!desc.empty() && desc[0] == '"') {
+        // there is no necessary to monitor string in prometheus
+        return true;
+    }
+    *_os << name << " " << desc << "\n";
+    return true;
+}
+
+bool PrometheusMetricsDumper::dump_comment(const std::string& name, const 
std::string& type) {
+    *_os << "# HELP " << name << '\n'
+         << "# TYPE " << name << " " << type << '\n';
+    return true;
+}
+
 const PrometheusMetricsDumper::SummaryItems*
 PrometheusMetricsDumper::ProcessLatencyRecorderSuffix(const 
butil::StringPiece& name,
                                                       const 
butil::StringPiece& desc) {
diff --git a/src/bvar/multi_dimension_inl.h b/src/bvar/multi_dimension_inl.h
index d1132c79..b7ef2b2d 100644
--- a/src/bvar/multi_dimension_inl.h
+++ b/src/bvar/multi_dimension_inl.h
@@ -252,7 +252,7 @@ size_t MultiDimension<T>::dump(Dumper* dumper, const 
DumpOptions* options) {
         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())) {
+        if (!dumper->dump_mvar(oss_key.str(), oss.str())) {
             continue;
         }
         n++;
@@ -269,20 +269,20 @@ size_t 
MultiDimension<bvar::LatencyRecorder>::dump(Dumper* dumper, const DumpOpt
         return 0;
     }
     size_t n = 0;
+    // To meet prometheus specification, we must guarantee no second TYPE line 
for one metric name
+
+    // latency comment
+    dumper->dump_comment(name() + "_latency", METRIC_TYPE_GAUGE);
     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()))) {
+        if (dumper->dump_mvar(oss_latency_key.str(), 
std::to_string(bvar->latency()))) {
             n++;
         }
         // latency_percentiles
@@ -291,53 +291,62 @@ size_t 
MultiDimension<bvar::LatencyRecorder>::dump(Dumper* dumper, const DumpOpt
         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)))) {
+            if (dumper->dump_mvar(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)))) {
+        if (dumper->dump_mvar(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)))) {
+        if (dumper->dump_mvar(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)) 
{
+    // max_latency comment
+    dumper->dump_comment(name() + "_max_latency", METRIC_TYPE_GAUGE);
+    for (auto &label_name : label_names) {
+        bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
+        if (!bvar) {
             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()))) {
+        if (dumper->dump_mvar(oss_max_latency_key.str(), 
std::to_string(bvar->max_latency()))) {
             n++;
         }
-        
-        // qps comment
-        if (!dumper->dump_comment(name() + "_qps", METRIC_TYPE_GAUGE)) {
+    }
+
+    // qps comment
+    dumper->dump_comment(name() + "_qps", METRIC_TYPE_GAUGE);
+    for (auto &label_name : label_names) {
+        bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
+        if (!bvar) {
             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()))) {
+        if (dumper->dump_mvar(oss_qps_key.str(), std::to_string(bvar->qps()))) 
{
             n++;
         }
+    }
 
-        // qps comment
-        if (!dumper->dump_comment(name() + "_count", METRIC_TYPE_COUNTER)) {
+    // count comment
+    dumper->dump_comment(name() + "_count", METRIC_TYPE_COUNTER);
+    for (auto &label_name : label_names) {
+        bvar::LatencyRecorder* bvar = get_stats_impl(label_name);
+        if (!bvar) {
             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()))) {
+        if (dumper->dump_mvar(oss_count_key.str(), 
std::to_string(bvar->count()))) {
             n++;
         }
     }
diff --git a/src/bvar/variable.h b/src/bvar/variable.h
index ce6ff4ac..86e9cd0c 100644
--- a/src/bvar/variable.h
+++ b/src/bvar/variable.h
@@ -53,6 +53,12 @@ public:
     virtual ~Dumper() { }
     virtual bool dump(const std::string& name,
                       const butil::StringPiece& description) = 0;
+    // Only for dumping value of multiple dimension var to prometheus service
+    virtual bool dump_mvar(const std::string& name,
+                           const butil::StringPiece& description) {
+        return true;
+    }
+    // Only for dumping comment of multiple dimension var to prometheus service
     virtual bool dump_comment(const std::string&, const std::string& /*type*/) 
{
         return true;
     }
diff --git a/test/BUILD.bazel b/test/BUILD.bazel
index 1e0ef966..1fd937b2 100644
--- a/test/BUILD.bazel
+++ b/test/BUILD.bazel
@@ -154,11 +154,12 @@ proto_library(
         [
             "*.proto",
         ],
-        exclude = [
-            "echo.proto",
-        ],
     ),
+    strip_import_prefix = "/test",
     visibility = ["//visibility:public"],
+    deps = [
+        "//:brpc_idl_options_proto",
+    ]
 )
 
 cc_proto_library(
@@ -241,6 +242,23 @@ cc_test(
     ],
 )
 
+cc_test(
+    name = "brpc_prometheus_test",
+    srcs = glob(
+        [
+            "brpc_prometheus_*_unittest.cpp",
+        ],
+    ),
+    copts = COPTS,
+    deps = [
+        ":cc_test_proto",
+        ":sstream_workaround",
+        "//:brpc",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
 refresh_compile_commands(
     name = "brpc_test_compdb",
     # Specify the targets of interest.
diff --git a/test/brpc_channel_unittest.cpp b/test/brpc_channel_unittest.cpp
index 93a4d3bf..66d1fbad 100644
--- a/test/brpc_channel_unittest.cpp
+++ b/test/brpc_channel_unittest.cpp
@@ -40,11 +40,7 @@
 #include "brpc/selective_channel.h"
 #include "brpc/socket_map.h"
 #include "brpc/controller.h"
-#if BAZEL_TEST
-#include "test/echo.pb.h"
-#else
 #include "echo.pb.h"
-#endif   // BAZEL_TEST
 #include "brpc/options.pb.h"
 
 namespace brpc {
diff --git a/test/brpc_prometheus_metrics_unittest.cpp 
b/test/brpc_prometheus_metrics_unittest.cpp
index 064c14f7..471fd40a 100644
--- a/test/brpc_prometheus_metrics_unittest.cpp
+++ b/test/brpc_prometheus_metrics_unittest.cpp
@@ -23,6 +23,7 @@
 #include "brpc/controller.h"
 #include "butil/strings/string_piece.h"
 #include "echo.pb.h"
+#include "bvar/multi_dimension.h"
 
 int main(int argc, char* argv[]) {
     testing::InitGoogleTest(&argc, argv);
@@ -45,7 +46,13 @@ enum STATE {
     HELP = 0,
     TYPE,
     GAUGE,
-    SUMMARY
+    SUMMARY,
+    COUNTER,
+    // When meets a line with a gauge/counter with labels, we have no
+    // idea the next line is a new HELP or the same gauge/counter just
+    // with different labels
+    HELP_OR_GAUGE,
+    HELP_OR_COUNTER,
 };
 
 TEST(PrometheusMetrics, sanity) {
@@ -54,10 +61,22 @@ TEST(PrometheusMetrics, sanity) {
     ASSERT_EQ(0, server.AddService(&echo_svc, 
brpc::SERVER_DOESNT_OWN_SERVICE));
     ASSERT_EQ(0, server.Start("127.0.0.1:8614", NULL));
 
-    brpc::Server server2;
-    DummyEchoServiceImpl echo_svc2;
-    ASSERT_EQ(0, server2.AddService(&echo_svc2, 
brpc::SERVER_DOESNT_OWN_SERVICE));
-    ASSERT_EQ(0, server2.Start("127.0.0.1:8615", NULL));
+    const std::list<std::string> labels = {"label1", "label2"};
+    bvar::MultiDimension<bvar::Adder<uint32_t> > my_madder("madder", labels);
+    bvar::Adder<uint32_t>* my_adder1 = my_madder.get_stats({"val1", "val2"});
+    ASSERT_TRUE(my_adder1);
+    *my_adder1 << 1 << 2;
+    bvar::Adder<uint32_t>* my_adder2 = my_madder.get_stats({"val2", "val3"});
+    ASSERT_TRUE(my_adder1);
+    *my_adder2 << 3 << 4;
+
+    bvar::MultiDimension<bvar::LatencyRecorder > my_mlat("mlat", labels);
+    bvar::LatencyRecorder* my_lat1 = my_mlat.get_stats({"val1", "val2"});
+    ASSERT_TRUE(my_lat1);
+    *my_lat1 << 1 << 2;
+    bvar::LatencyRecorder* my_lat2 = my_mlat.get_stats({"val2", "val3"});
+    ASSERT_TRUE(my_lat2);
+    *my_lat2 << 3 << 4;
 
     brpc::Channel channel;
     brpc::ChannelOptions channel_opts;
@@ -68,19 +87,22 @@ TEST(PrometheusMetrics, sanity) {
     channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
     ASSERT_FALSE(cntl.Failed());
     std::string res = cntl.response_attachment().to_string();
-
+    LOG(INFO) << "output:\n" << res;
     size_t start_pos = 0;
     size_t end_pos = 0;
+    size_t label_start = 0;
     STATE state = HELP;
     char name_help[128];
     char name_type[128];
     char type[16];
     int matched = 0;
-    int gauge_num = 0;
+    int num = 0;
     bool summary_sum_gathered = false;
     bool summary_count_gathered = false;
     bool has_ever_summary = false;
     bool has_ever_gauge = false;
+    bool has_ever_counter = false; // brought in by mvar latency recorder
+    std::unordered_set<std::string> metric_name_set;
 
     while ((end_pos = res.find('\n', start_pos)) != butil::StringPiece::npos) {
         res[end_pos] = '\0';       // safe;
@@ -98,21 +120,52 @@ TEST(PrometheusMetrics, sanity) {
                     state = GAUGE;
                 } else if (strcmp(type, "summary") == 0) {
                     state = SUMMARY;
+                } else if (strcmp(type, "counter") == 0) {
+                    state = COUNTER;
                 } else {
-                    ASSERT_TRUE(false);
+                    ASSERT_TRUE(false) << "invalid type: " << type;
                 }
+                ASSERT_EQ(0, metric_name_set.count(name_type)) << "second TYPE 
line for metric name "
+                    << name_type;
+                metric_name_set.insert(name_help);
                 break;
+            case HELP_OR_GAUGE:
+            case HELP_OR_COUNTER:
+                matched = sscanf(res.data() + start_pos, "# HELP %s", 
name_help);
+                // Try to figure out current line is a new COMMENT or not
+                if (matched == 1) {
+                    state = HELP;
+                } else {
+                    state = state == HELP_OR_GAUGE ? GAUGE : COUNTER;
+                }
+                res[end_pos] = '\n'; // revert to original
+                continue; // do not jump to next line
             case GAUGE:
-                matched = sscanf(res.data() + start_pos, "%s %d", name_type, 
&gauge_num);
+            case COUNTER:
+                matched = sscanf(res.data() + start_pos, "%s %d", name_type, 
&num);
                 ASSERT_EQ(2, matched);
-                ASSERT_STREQ(name_type, name_help);
-                state = HELP;
-                has_ever_gauge = true;
+                if (state == GAUGE) {
+                    has_ever_gauge = true;
+                }
+                if (state == COUNTER) {
+                    has_ever_counter = true;
+                }
+                label_start = butil::StringPiece(name_type).find("{");
+                if (label_start == strlen(name_help)) { // mvar
+                    ASSERT_EQ(name_type[strlen(name_type) - 1], '}');
+                    ASSERT_TRUE(strncmp(name_type, name_help, 
strlen(name_help)) == 0);
+                    state = state == GAUGE ? HELP_OR_GAUGE : HELP_OR_COUNTER;
+                } else if (label_start == butil::StringPiece::npos) { // var
+                    ASSERT_STREQ(name_type, name_help);
+                    state = HELP;
+                } else { // invalid
+                    ASSERT_TRUE(false);
+                }
                 break;
             case SUMMARY:
                 if (butil::StringPiece(res.data() + start_pos, end_pos - 
start_pos).find("quantile=")
                         == butil::StringPiece::npos) {
-                    matched = sscanf(res.data() + start_pos, "%s %d", 
name_type, &gauge_num);
+                    matched = sscanf(res.data() + start_pos, "%s %d", 
name_type, &num);
                     ASSERT_EQ(2, matched);
                     ASSERT_TRUE(strncmp(name_type, name_help, 
strlen(name_help)) == 0);
                     if (butil::StringPiece(name_type).ends_with("_sum")) {
@@ -138,7 +191,7 @@ TEST(PrometheusMetrics, sanity) {
         }
         start_pos = end_pos + 1;
     }
-    ASSERT_TRUE(has_ever_gauge && has_ever_summary);
+    ASSERT_TRUE(has_ever_gauge && has_ever_summary && has_ever_counter);
     ASSERT_EQ(0, server.Stop(0));
     ASSERT_EQ(0, server.Join());
 }
diff --git a/test/iobuf_unittest.cpp b/test/iobuf_unittest.cpp
index 09dd17da..da36c180 100644
--- a/test/iobuf_unittest.cpp
+++ b/test/iobuf_unittest.cpp
@@ -32,11 +32,7 @@
 #include <butil/fd_guard.h>
 #include <butil/errno.h>
 #include <butil/fast_rand.h>
-#if BAZEL_TEST
-#include "test/iobuf.pb.h"
-#else
 #include "iobuf.pb.h"
-#endif   // BAZEL_TEST
 
 namespace butil {
 namespace iobuf {


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org
For additional commands, e-mail: dev-h...@brpc.apache.org

Reply via email to