This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new 00efc6826 KUDU-3375 Expose Prometheus Metrics in Kudu
00efc6826 is described below
commit 00efc6826ac9a1f5d10750296c7357790a041fec
Author: mammadli.khazar <[email protected]>
AuthorDate: Tue Oct 11 15:41:43 2022 +0200
KUDU-3375 Expose Prometheus Metrics in Kudu
This patch introduces an endpoint which emits server level metrics in
Prometheus Text Explosion format for Kudu. The endpoint can be found at
/metrics_prometheus path of the webserver.
More information on the Prometheus format can be found at:
https://prometheus.io/docs/instrumenting/exposition_formats
Prefix kudu_ has been added to all of the metric names according to
Prometheus naming convention to specify the domain and differentiate
from any other metrics belonging to different services. Following the
kudu_ prefix either master_ or tserver_ prefix is attached based on the
server type that is exposing the metrics. In order to differentiate
between different master and tablet servers running on the same host
Prometheus automatically attaches an instance label which holds the
"hostname:portname".
eg: kudu_master_metric{instance="localhost:8765", job="kudu-master",
unit_type="unit"}
Not all of the Kudu metric names follow the Prometheus metric naming
recommendations which state metric units should be attached at the end
of the metric name. In Kudu some metrics have these units at the end of
the metric name, some in the middle of the metric and for some metrics
it is unclear what the metric unit is just by looking at the metric
name. Due to this and also because Kudu supports more Metric Units
than base Prometheus Metric units, metric units are represented as
Prometheus labels for each metric. This also makes it possible to keep
time based units more accurate as Milliseconds, Nanoseconds and etc.
do not need to be converted to seconds which is the base time unit in
Prometheus.
More information on Prometheus Metric units and naming conventions
can be found at:
https://prometheus.io/docs/practices/naming/#metric-names
Tested by feeding all of the generated metrics to a prometheus server
running on localhost. Added unit tests to each metric type.
Change-Id: I4b2d01bd56f6f0f1b6f31cbce15e671d16521739
Reviewed-on: http://gerrit.cloudera.org:8080/19133
Tested-by: Kudu Jenkins
Reviewed-by: Alexey Serbin <[email protected]>
---
src/kudu/server/default_path_handlers.cc | 22 ++-
src/kudu/server/default_path_handlers.h | 4 +-
src/kudu/server/server_base.cc | 1 +
src/kudu/util/CMakeLists.txt | 1 +
src/kudu/util/metrics-test.cc | 124 +++++++++++++++
src/kudu/util/metrics.cc | 172 +++++++++++++++++++++
src/kudu/util/metrics.h | 80 ++++++++--
.../prometheus_writer.cc} | 25 +--
.../prometheus_writer.h} | 28 ++--
9 files changed, 405 insertions(+), 52 deletions(-)
diff --git a/src/kudu/server/default_path_handlers.cc
b/src/kudu/server/default_path_handlers.cc
index 682db402d..7b2ecc2f7 100644
--- a/src/kudu/server/default_path_handlers.cc
+++ b/src/kudu/server/default_path_handlers.cc
@@ -25,11 +25,13 @@
#include <functional>
#include <memory>
#include <string>
+#include <type_traits>
#include <unordered_map>
#include <vector>
#include <gflags/gflags.h>
#include <glog/logging.h>
+#include "kudu/util/prometheus_writer.h"
#include "kudu/util/version_info.h"
#include "kudu/util/version_info.pb.h"
@@ -510,6 +512,13 @@ static void WriteMetricsAsJson(const MetricRegistry* const
metrics,
}
}
+static void WriteMetricsAsPrometheus(const MetricRegistry* const metrics,
+ const Webserver::WebRequest& /*req*/,
+ Webserver::PrerenderedWebResponse* resp) {
+ PrometheusWriter writer(&resp->output);
+ WARN_NOT_OK(metrics->WriteAsPrometheus(&writer), "Couldn't write Prometheus
metrics over HTTP");
+}
+
void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry*
const metrics) {
auto callback = [metrics](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
@@ -518,7 +527,7 @@ void RegisterMetricsJsonHandler(Webserver* webserver, const
MetricRegistry* cons
bool not_styled = false;
bool not_on_nav_bar = false;
bool is_on_nav_bar = true;
- webserver->RegisterPrerenderedPathHandler("/metrics", "Metrics", callback,
+ webserver->RegisterPrerenderedPathHandler("/metrics", "JSON Metrics",
callback,
not_styled, is_on_nav_bar);
// The old name -- this is preserved for compatibility with older releases of
@@ -527,4 +536,15 @@ void RegisterMetricsJsonHandler(Webserver* webserver,
const MetricRegistry* cons
not_styled, not_on_nav_bar);
}
+void RegisterMetricsPrometheusHandler(Webserver* webserver, const
MetricRegistry* const metrics) {
+ auto callback = [metrics](const Webserver::WebRequest& req,
+ Webserver::PrerenderedWebResponse* resp) {
+ WriteMetricsAsPrometheus(metrics, req, resp);
+ };
+ constexpr bool not_styled = false;
+ constexpr bool is_on_nav_bar = true;
+ webserver->RegisterPrerenderedPathHandler("/metrics_prometheus", "Prometheus
Metrics", callback,
+ not_styled, is_on_nav_bar);
+}
+
} // namespace kudu
diff --git a/src/kudu/server/default_path_handlers.h
b/src/kudu/server/default_path_handlers.h
index 1a5891546..aadf00fd9 100644
--- a/src/kudu/server/default_path_handlers.h
+++ b/src/kudu/server/default_path_handlers.h
@@ -32,8 +32,10 @@ void AddPreInitializedDefaultPathHandlers(Webserver*
webserver);
void AddPostInitializedDefaultPathHandlers(Webserver* webserver);
// Adds an endpoint to get metrics in JSON format.
-void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry*
const metrics);
+void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry*
metrics);
+// Adds an endpoint to get metrics in Prometheus format.
+void RegisterMetricsPrometheusHandler(Webserver* webserver, const
MetricRegistry* metrics);
} // namespace kudu
#endif // KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc
index b3aee0960..5c9029534 100644
--- a/src/kudu/server/server_base.cc
+++ b/src/kudu/server/server_base.cc
@@ -969,6 +969,7 @@ Status ServerBase::Start() {
AddPostInitializedDefaultPathHandlers(web_server_.get());
AddRpczPathHandlers(messenger_, web_server_.get());
RegisterMetricsJsonHandler(web_server_.get(), metric_registry_.get());
+ RegisterMetricsPrometheusHandler(web_server_.get(),
metric_registry_.get());
TracingPathHandlers::RegisterHandlers(web_server_.get());
web_server_->set_footer_html(FooterHtml());
web_server_->SetStartupComplete(true);
diff --git a/src/kudu/util/CMakeLists.txt b/src/kudu/util/CMakeLists.txt
index e1713e16d..3a1fab523 100644
--- a/src/kudu/util/CMakeLists.txt
+++ b/src/kudu/util/CMakeLists.txt
@@ -221,6 +221,7 @@ set(UTIL_SRCS
pb_util.cc
pb_util-internal.cc
process_memory.cc
+ prometheus_writer.cc
random_util.cc
rolling_log.cc
rw_mutex.cc
diff --git a/src/kudu/util/metrics-test.cc b/src/kudu/util/metrics-test.cc
index e80a2d59a..2ab2d15d3 100644
--- a/src/kudu/util/metrics-test.cc
+++ b/src/kudu/util/metrics-test.cc
@@ -41,6 +41,7 @@
#include "kudu/util/jsonwriter.h"
#include "kudu/util/monotime.h"
#include "kudu/util/oid_generator.h"
+#include "kudu/util/prometheus_writer.h"
#include "kudu/util/random.h"
#include "kudu/util/status.h"
#include "kudu/util/test_macros.h"
@@ -131,6 +132,22 @@ TEST_F(MetricsTest, ResetCounter) {
ASSERT_EQ(0, c->value());
}
+TEST_F(MetricsTest, CounterPrometheusTest) {
+ scoped_refptr<Counter> requests =
+ new Counter(&METRIC_test_counter);
+
+ std::ostringstream output;
+ string prefix;
+ PrometheusWriter writer(&output);
+ ASSERT_OK(requests->WriteAsPrometheus(&writer, prefix));
+
+ const string expected_output = "# HELP test_counter Description of test
counter\n"
+ "# TYPE test_counter counter\n"
+ "test_counter{unit_type=\"requests\"} 0\n";
+
+ ASSERT_EQ(expected_output, output.str());
+}
+
METRIC_DEFINE_gauge_string(test_entity, test_string_gauge, "Test string Gauge",
MetricUnit::kState, "Description of string Gauge",
kudu::MetricLevel::kInfo);
@@ -186,6 +203,18 @@ TEST_F(MetricsTest, SimpleStringGaugeForMergeTest) {
state_for_merge->unique_values());
}
+TEST_F(MetricsTest, StringGaugePrometheusTest) {
+ scoped_refptr<StringGauge> state =
+ new StringGauge(&METRIC_test_string_gauge, "Healthy");
+
+ std::ostringstream output;
+ string prefix;
+ PrometheusWriter writer(&output);
+ ASSERT_OK(state->WriteAsPrometheus(&writer, prefix));
+ // String Gauges are not representable in Prometheus, empty output is
expected
+ ASSERT_EQ("", output.str());
+}
+
METRIC_DEFINE_gauge_double(test_entity, test_mean_gauge, "Test mean Gauge",
MetricUnit::kUnits, "Description of mean Gauge",
kudu::MetricLevel::kInfo);
@@ -260,10 +289,33 @@ TEST_F(MetricsTest, TestMeanGaugeJsonPrint) {
ASSERT_EQ(value, kTotalSum/kTotalCount);
}
+
+TEST_F(MetricsTest, MeanGaugePrometheusTest) {
+ scoped_refptr<MeanGauge> average_usage =
+ METRIC_test_mean_gauge.InstantiateMeanGauge(entity_);
+
+ std::ostringstream output;
+ string prefix;
+ PrometheusWriter writer(&output);
+ ASSERT_OK(average_usage->WriteAsPrometheus(&writer, prefix));
+
+ const string expected_output = "# HELP test_mean_gauge Description of mean
Gauge\n"
+ "# TYPE test_mean_gauge gauge\n"
+ "test_mean_gauge{unit_type=\"units\"} 0\n"
+ "test_mean_gauge_count{unit_type=\"units\"}
0\n"
+ "test_mean_gauge_sum{unit_type=\"units\"}
0\n";
+
+ ASSERT_EQ(expected_output, output.str());
+}
+
METRIC_DEFINE_gauge_uint64(test_entity, test_gauge, "Test uint64 Gauge",
MetricUnit::kBytes, "Description of Test Gauge",
kudu::MetricLevel::kInfo);
+METRIC_DEFINE_gauge_bool(test_entity, test_gauge_bool, "Test boolean Gauge",
+ MetricUnit::kState, "Description of Test boolean
Gauge",
+ kudu::MetricLevel::kInfo);
+
TEST_F(MetricsTest, SimpleAtomicGaugeTest) {
scoped_refptr<AtomicGauge<uint64_t> > mem_usage =
METRIC_test_gauge.Instantiate(entity_, 0);
@@ -323,6 +375,38 @@ TEST_F(MetricsTest, SimpleAtomicGaugeMinTypeMergeTest) {
ASSERT_EQ(1, start_time_for_merge->value());
}
+TEST_F(MetricsTest, AtomicGaugePrometheusTest) {
+ scoped_refptr<AtomicGauge<uint64_t> > mem_usage =
+ METRIC_test_gauge.Instantiate(entity_, 0);
+
+ std::ostringstream output;
+ string prefix;
+ PrometheusWriter writer(&output);
+ ASSERT_OK(mem_usage->WriteAsPrometheus(&writer, prefix));
+
+ const string expected_output = "# HELP test_gauge Description of Test
Gauge\n"
+ "# TYPE test_gauge gauge\n"
+ "test_gauge{unit_type=\"bytes\"} 0\n";
+
+ ASSERT_EQ(expected_output, output.str());
+}
+
+TEST_F(MetricsTest, AtomicGaugeBooleanPrometheusTest) {
+ scoped_refptr<AtomicGauge<bool> > clock_extrapolating =
+ METRIC_test_gauge_bool.Instantiate(entity_, false);
+
+ std::ostringstream output;
+ string prefix;
+ PrometheusWriter writer(&output);
+ ASSERT_OK(clock_extrapolating->WriteAsPrometheus(&writer, prefix));
+
+ const string expected_output = "# HELP test_gauge_bool Description of Test
boolean Gauge\n"
+ "# TYPE test_gauge_bool gauge\n"
+ "test_gauge_bool{unit_type=\"state\"} 0\n";
+
+ ASSERT_EQ(expected_output, output.str());
+}
+
METRIC_DEFINE_gauge_int64(test_entity, test_func_gauge, "Test Function Gauge",
MetricUnit::kBytes, "Test Gauge 2",
kudu::MetricLevel::kInfo);
@@ -462,6 +546,24 @@ TEST_F(MetricsTest, AutoDetachToConstant) {
ASSERT_EQ(12345, gauge->value());
}
+TEST_F(MetricsTest, FunctionGaugePrometheusTest) {
+ int metric_val = 1000;
+ scoped_refptr<FunctionGauge<int64_t> > gauge =
+ METRIC_test_func_gauge.InstantiateFunctionGauge(
+ entity_, [&metric_val]() { return MyFunction(&metric_val); });
+
+ std::ostringstream output;
+ string prefix;
+ PrometheusWriter writer(&output);
+ ASSERT_OK(gauge->WriteAsPrometheus(&writer, prefix));
+
+ const string expected_output = "# HELP test_func_gauge Test Gauge 2\n"
+ "# TYPE test_func_gauge gauge\n"
+ "test_func_gauge{unit_type=\"bytes\"} 1000\n";
+
+ ASSERT_EQ(expected_output, output.str());
+}
+
METRIC_DEFINE_gauge_uint64(test_entity, counter_as_gauge, "Gauge exposed as
Counter",
MetricUnit::kBytes, "Gauge exposed as Counter",
kudu::MetricLevel::kInfo,
@@ -515,6 +617,28 @@ TEST_F(MetricsTest, SimpleHistogramMergeTest) {
ASSERT_EQ(18, hist_for_merge->histogram()->TotalSum());
}
+TEST_F(MetricsTest, HistogramPrometheusTest) {
+ scoped_refptr<Histogram> hist = METRIC_test_hist.Instantiate(entity_);
+
+ std::ostringstream output;
+ PrometheusWriter writer(&output);
+ string prefix;
+ ASSERT_OK(hist->WriteAsPrometheus(&writer, prefix));
+
+ const string expected_output = "# HELP test_hist foo\n"
+ "# TYPE test_hist histogram\n"
+ "test_hist_bucket{unit_type=\"milliseconds\",
le=\"0.75\"} 0\n"
+ "test_hist_bucket{unit_type=\"milliseconds\",
le=\"0.95\"} 0\n"
+ "test_hist_bucket{unit_type=\"milliseconds\",
le=\"0.99\"} 0\n"
+ "test_hist_bucket{unit_type=\"milliseconds\",
le=\"0.999\"} 0\n"
+ "test_hist_bucket{unit_type=\"milliseconds\",
le=\"0.9999\"} 0\n"
+ "test_hist_bucket{unit_type=\"milliseconds\",
le=\"+Inf\"} 0\n"
+ "test_hist_sum{unit_type=\"milliseconds\"}
0\n"
+ "test_hist_count{unit_type=\"milliseconds\"}
0\n";
+
+ ASSERT_EQ(expected_output, output.str());
+}
+
TEST_F(MetricsTest, JsonPrintTest) {
scoped_refptr<Counter> test_counter =
METRIC_test_counter.Instantiate(entity_);
test_counter->Increment();
diff --git a/src/kudu/util/metrics.cc b/src/kudu/util/metrics.cc
index 663ffe604..d60cde079 100644
--- a/src/kudu/util/metrics.cc
+++ b/src/kudu/util/metrics.cc
@@ -48,6 +48,7 @@ using std::string;
using std::unordered_set;
using std::vector;
using strings::Substitute;
+using strings::SubstituteAndAppend;
template<typename Collection>
void WriteMetricsToJson(JsonWriter* writer,
@@ -68,6 +69,16 @@ void WriteMetricsToJson(JsonWriter* writer,
writer->EndArray();
}
+template<typename Collection>
+void WriteMetricsToPrometheus(PrometheusWriter* writer,
+ const Collection& metrics,
+ const std::string& prefix) {
+ for (const auto& [name, val] : metrics) {
+ WARN_NOT_OK(val->WriteAsPrometheus(writer, prefix),
+ Substitute("Failed to write $0 as Prometheus", name));
+ }
+}
+
void WriteToJson(JsonWriter* writer,
const MergedEntityMetrics &merged_entity_metrics,
const MetricJsonOptions &opts) {
@@ -392,6 +403,40 @@ Status MetricEntity::WriteAsJson(JsonWriter* writer, const
MetricJsonOptions& op
return Status::OK();
}
+Status MetricEntity::WriteAsPrometheus(PrometheusWriter* writer) const {
+ MetricMap metrics;
+ AttributeMap attrs;
+ MetricFilters filters;
+ filters.entity_level = "debug";
+ const string master_prefix = "kudu_master_";
+ const string tserver_prefix = "kudu_tserver_";
+ const string master_server = "kudu.master";
+ const string tablet_server = "kudu.tabletserver";
+ // Empty filters results in getting all the metrics for this MetricEntity.
+ const auto s = GetMetricsAndAttrs(filters, &metrics, &attrs);
+ if (s.IsNotFound()) {
+ // Status::NotFound is returned when this entity has been filtered, treat
it
+ // as OK, and skip printing it.
+ return Status::OK();
+ }
+ RETURN_NOT_OK(s);
+ // Only emit server level metrics
+ if (strcmp(prototype_->name(), "server") == 0) {
+ if (id_ == master_server) {
+ // attach kudu_master_ as prefix to metrics
+ WriteMetricsToPrometheus(writer, metrics, master_prefix);
+ return Status::OK();
+ }
+ if (id_ == tablet_server) {
+ // attach kudu_tserver_ as prefix to metrics
+ WriteMetricsToPrometheus(writer, metrics, tserver_prefix);
+ return Status::OK();
+ }
+ }
+
+ return Status::NotFound("Entity is not relevant to Prometheus");
+}
+
Status MetricEntity::CollectTo(MergedEntityMetrics* collections,
const MetricFilters& filters,
const MetricMergeRules& merge_rules) const {
@@ -540,6 +585,22 @@ Status MetricRegistry::WriteAsJson(JsonWriter* writer,
const MetricJsonOptions&
return Status::OK();
}
+Status MetricRegistry::WriteAsPrometheus(PrometheusWriter* writer) const {
+ EntityMap entities;
+ {
+ std::lock_guard<simple_spinlock> l(lock_);
+ entities = entities_;
+ }
+ for (const auto& e : entities) {
+ WARN_NOT_OK(e.second->WriteAsPrometheus(writer),
+ Substitute("Failed to write entity $0 as Prometheus",
e.second->id()));
+ }
+
+ entities.clear(); // necessary to deref metrics we just dumped before doing
retirement scan.
+ const_cast<MetricRegistry*>(this)->RetireOldMetrics();
+ return Status::OK();
+}
+
void MetricRegistry::RetireOldMetrics() {
std::lock_guard<simple_spinlock> l(lock_);
for (auto it = entities_.begin(); it != entities_.end();) {
@@ -674,6 +735,12 @@ void MetricPrototype::WriteFields(JsonWriter* writer,
}
}
+void MetricPrototype::WriteFields(PrometheusWriter* writer, const string
&prefix) const {
+ writer->WriteEntry(Substitute("# HELP $0$1 $2\n# TYPE $3$4 $5\n",
+ prefix, name(), description(),
+ prefix, name(), MetricType::Name(type())));
+}
+
//
// FunctionGaugeDetacher
//
@@ -751,6 +818,14 @@ Status Gauge::WriteAsJson(JsonWriter* writer,
return Status::OK();
}
+Status Gauge::WriteAsPrometheus(PrometheusWriter* writer, const std::string&
prefix) const {
+ prototype_->WriteFields(writer, prefix);
+
+ WriteValue(writer, prefix);
+
+ return Status::OK();
+}
+
//
// StringGauge
//
@@ -822,6 +897,24 @@ void StringGauge::WriteValue(JsonWriter* writer) const {
writer->String(value());
}
+// A string gauge's value can be anything, but Prometheus does not support
+// non-numeric values for gauges with exception of {+,-}Inf and NaN
+// (see https://prometheus.io/docs/instrumenting/exposition_formats/).
+// DCHECK() is added to make sure this method is not called from anywhere,
+// but overriding it is necessary since Gauge::WriteValue() is a pure virtual
one.
+// An alternative could be defining a empty implementation for
Gauge::WriteValue()
+// virtual method and not adding this empty override here.
+void StringGauge::WriteValue(PrometheusWriter* writer, const std::string&
prefix) const {
+ DCHECK(false);
+}
+
+Status StringGauge::WriteAsPrometheus(PrometheusWriter* /*writer*/,
+ const std::string& /*prefix*/) const {
+ // Prometheus doesn't support string gauges.
+ // This function ensures that output written to Prometheus is empty.
+ return Status::OK();
+}
+
//
// MeanGauge
//
@@ -883,6 +976,21 @@ void MeanGauge::WriteValue(JsonWriter* writer) const {
writer->Double(total_count());
}
+void MeanGauge::WriteValue(PrometheusWriter* writer, const std::string&
prefix) const {
+ auto output = Substitute("$0$1{unit_type=\"$2\"} $3\n", prefix,
prototype_->name(),
+ MetricUnit::Name(prototype_->unit()), value());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\"} $4\n",
+ prefix, prototype_->name(), "_count",
+ MetricUnit::Name(prototype_->unit()), total_count());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\"} $4\n",
+ prefix, prototype_->name(), "_sum",
+ MetricUnit::Name(prototype_->unit()), total_sum());
+
+ writer->WriteEntry(output);
+}
+
//
// Counter
//
@@ -921,6 +1029,15 @@ Status Counter::WriteAsJson(JsonWriter* writer,
return Status::OK();
}
+Status Counter::WriteAsPrometheus(PrometheusWriter* writer, const std::string&
prefix) const {
+ prototype_->WriteFields(writer, prefix);
+
+ writer->WriteEntry(Substitute("$0$1{unit_type=\"$2\"} $3\n", prefix,
prototype_->name(),
+ MetricUnit::Name(prototype_->unit()),
value()));
+
+ return Status::OK();
+}
+
/////////////////////////////////////////////////
// HistogramPrototype
/////////////////////////////////////////////////
@@ -977,6 +1094,61 @@ Status Histogram::WriteAsJson(JsonWriter* writer,
return Status::OK();
}
+Status Histogram::WriteAsPrometheus(PrometheusWriter* writer, const
std::string& prefix) const {
+ prototype_->WriteFields(writer, prefix);
+ std::string output = "";
+ MetricJsonOptions opts;
+ // Snapshot is taken to preserve the consistency across metrics and
+ // requirements given by Prometheus. The value for the _bucket in +Inf
+ // quantile needs to be equal to the total _count
+ HistogramSnapshotPB snapshot;
+ RETURN_NOT_OK(GetHistogramSnapshotPB(&snapshot, opts));
+
+ output = Substitute("$0$1$2{unit_type=\"$3\", le=\"0.75\"} $4\n",
+ prefix, prototype_->name(), "_bucket",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.percentile_75());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\", le=\"0.95\"} $4\n",
+ prefix, prototype_->name(), "_bucket",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.percentile_95());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\", le=\"0.99\"} $4\n",
+ prefix, prototype_->name(), "_bucket",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.percentile_99());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\", le=\"0.999\"} $4\n",
+ prefix, prototype_->name(), "_bucket",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.percentile_99_9());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\", le=\"0.9999\"} $4\n",
+ prefix, prototype_->name(), "_bucket",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.percentile_99_99());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\", le=\"+Inf\"} $4\n",
+ prefix, prototype_->name(), "_bucket",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.total_count());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\"} $4\n",
+ prefix, prototype_->name(), "_sum",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.total_sum());
+
+ SubstituteAndAppend(&output, "$0$1$2{unit_type=\"$3\"} $4\n",
+ prefix, prototype_->name(), "_count",
+ MetricUnit::Name(prototype_->unit()),
+ snapshot.total_count());
+
+ writer->WriteEntry(output);
+
+ return Status::OK();
+}
+
Status Histogram::GetHistogramSnapshotPB(HistogramSnapshotPB* snapshot_pb,
const MetricJsonOptions& opts) const {
snapshot_pb->set_name(prototype_->name());
diff --git a/src/kudu/util/metrics.h b/src/kudu/util/metrics.h
index db8bccbfe..28e49b827 100644
--- a/src/kudu/util/metrics.h
+++ b/src/kudu/util/metrics.h
@@ -249,6 +249,7 @@
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/port.h"
#include "kudu/gutil/ref_counted.h"
+#include "kudu/gutil/strings/substitute.h"
#include "kudu/util/atomic.h"
#include "kudu/util/hdr_histogram.h"
#include "kudu/util/jsonwriter.h" // IWYU pragma: keep
@@ -256,6 +257,7 @@
#include "kudu/util/monotime.h"
#include "kudu/util/status.h"
#include "kudu/util/striped64.h"
+#include "kudu/util/prometheus_writer.h"
// Define a new entity type.
//
@@ -581,6 +583,8 @@ class MetricPrototype {
void WriteFields(JsonWriter* writer,
const MetricJsonOptions& opts) const;
+ void WriteFields(PrometheusWriter* writer, const std::string& prefix) const;
+
protected:
explicit MetricPrototype(CtorArgs args);
virtual ~MetricPrototype() {
@@ -665,6 +669,8 @@ class MetricEntity : public
RefCountedThreadSafe<MetricEntity> {
// See MetricRegistry::WriteAsJson()
Status WriteAsJson(JsonWriter* writer, const MetricJsonOptions& opts) const;
+ Status WriteAsPrometheus(PrometheusWriter* writer) const;
+
// Collect metrics of this entity to 'collections'. Metrics will be filtered
by 'filters',
// and will be merged under the rule of 'merge_rules'.
Status CollectTo(MergedEntityMetrics* collections,
@@ -756,6 +762,8 @@ class Metric : public RefCountedThreadSafe<Metric> {
// All metrics must be able to render themselves as JSON.
virtual Status WriteAsJson(JsonWriter* writer,
const MetricJsonOptions& opts) const = 0;
+ // All metrics must be able to render themselves as Prometheus.
+ virtual Status WriteAsPrometheus(PrometheusWriter* writer, const
std::string& prefix) const = 0;
const MetricPrototype* prototype() const { return prototype_; }
@@ -877,6 +885,8 @@ class MetricRegistry {
// output of this function.
Status WriteAsJson(JsonWriter* writer, const MetricJsonOptions& opts) const;
+ // Writes metrics in this registry to given Prometheus 'writer'.
+ Status WriteAsPrometheus(PrometheusWriter* writer) const;
// For each registered entity, retires orphaned metrics. If an entity has no
more
// metrics and there are no external references, entities are removed as
well.
//
@@ -1015,12 +1025,13 @@ class Gauge : public Metric {
explicit Gauge(const MetricPrototype* prototype)
: Metric(prototype) {
}
- virtual ~Gauge() {}
- virtual Status WriteAsJson(JsonWriter* w,
- const MetricJsonOptions& opts) const OVERRIDE;
+ ~Gauge() override {}
+ Status WriteAsJson(JsonWriter* w, const MetricJsonOptions& opts) const
OVERRIDE;
+ Status WriteAsPrometheus(PrometheusWriter* w, const std::string& prefix)
const OVERRIDE;
protected:
virtual void WriteValue(JsonWriter* writer) const = 0;
+ virtual void WriteValue(PrometheusWriter* writer, const std::string& prefix)
const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(Gauge);
};
@@ -1042,7 +1053,10 @@ class StringGauge : public Gauge {
protected:
FRIEND_TEST(MetricsTest, SimpleStringGaugeForMergeTest);
- virtual void WriteValue(JsonWriter* writer) const OVERRIDE;
+ FRIEND_TEST(MetricsTest, StringGaugePrometheusTest);
+ Status WriteAsPrometheus(PrometheusWriter* w, const std::string& prefix)
const OVERRIDE;
+ void WriteValue(JsonWriter* writer) const OVERRIDE;
+ void WriteValue(PrometheusWriter* writer, const std::string& prefix) const
OVERRIDE;
void FillUniqueValuesUnlocked();
std::unordered_set<std::string> unique_values();
private:
@@ -1071,7 +1085,8 @@ class MeanGauge : public Gauge {
void MergeFrom(const scoped_refptr<Metric>& other) override;
protected:
- virtual void WriteValue(JsonWriter* writer) const override;
+ void WriteValue(JsonWriter* writer) const override;
+ void WriteValue(PrometheusWriter* writer, const std::string& prefix) const
override;
private:
double total_sum_;
double total_count_;
@@ -1144,9 +1159,29 @@ class AtomicGauge : public Gauge {
}
}
protected:
- virtual void WriteValue(JsonWriter* writer) const OVERRIDE {
+ void WriteValue(JsonWriter* writer) const OVERRIDE {
writer->Value(value());
}
+
+ void WriteValue(PrometheusWriter* writer,const std::string& prefix) const
OVERRIDE {
+ std::string output = "";
+
+ // If Boolean Gauge, convert false/true to 0/1 for Prometheus
+ if constexpr (std::is_same_v<T, bool> ) {
+ int check = 0;
+ if (value() == true) {
+ check = 1;
+ } else {
+ check = 0;
+ }
+ output = strings::Substitute("$0$1{unit_type=\"$2\"} $3\n", prefix,
prototype_->name(),
+ MetricUnit::Name(prototype_->unit()),
check);
+ } else {
+ output = strings::Substitute("$0$1{unit_type=\"$2\"} $3\n", prefix,
prototype_->name(),
+ MetricUnit::Name(prototype_->unit()),
value());
+ }
+ writer->WriteEntry(output);
+ }
private:
AtomicInt<int64_t> value_;
MergeType type_;
@@ -1235,10 +1270,28 @@ class FunctionGauge : public Gauge {
return function_();
}
- virtual void WriteValue(JsonWriter* writer) const OVERRIDE {
+ void WriteValue(JsonWriter* writer) const OVERRIDE {
writer->Value(value());
}
+ void WriteValue(PrometheusWriter* writer, const std::string& prefix) const
OVERRIDE {
+ std::string output = "";
+ // If Boolean Gauge, convert false/true to 0/1 for Prometheus
+ if constexpr (std::is_same_v<T, bool> ) {
+ int check = 0;
+ if (value() == true) {
+ check = 1;
+ } else {
+ check = 0;
+ }
+ output = strings::Substitute("$0$1{unit_type=\"$2\"} $3\n", prefix,
prototype_->name(),
+ MetricUnit::Name(prototype_->unit()),
check);
+ } else {
+ output = strings::Substitute("$0$1{unit_type=\"$2\"} $3\n", prefix,
prototype_->name(),
+ MetricUnit::Name(prototype_->unit()),
value());
+ }
+ writer->WriteEntry(output);
+ }
// Reset this FunctionGauge to return a specific value.
// This should be used during destruction. If you want a settable
// Gauge, use a normal Gauge instead of a FunctionGauge.
@@ -1357,10 +1410,11 @@ class Counter : public Metric {
int64_t value() const;
void Increment();
void IncrementBy(int64_t amount);
- virtual Status WriteAsJson(JsonWriter* w,
- const MetricJsonOptions& opts) const OVERRIDE;
+ Status WriteAsJson(JsonWriter* w, const MetricJsonOptions& opts) const
OVERRIDE;
- virtual bool IsUntouched() const override {
+ Status WriteAsPrometheus(PrometheusWriter* w, const std::string& prefix)
const OVERRIDE;
+
+ bool IsUntouched() const override {
return value() == 0;
}
@@ -1385,6 +1439,7 @@ class Counter : public Metric {
FRIEND_TEST(MetricsTest, SimpleCounterTest);
FRIEND_TEST(MetricsTest, SimpleCounterMergeTest);
FRIEND_TEST(MultiThreadedMetricsTest, CounterIncrementTest);
+ FRIEND_TEST(MetricsTest, CounterPrometheusTest);
friend class MetricEntity;
explicit Counter(const CounterPrototype* proto);
@@ -1431,8 +1486,9 @@ class Histogram : public Metric {
// or IncrementBy()).
uint64_t TotalCount() const;
- virtual Status WriteAsJson(JsonWriter* w,
- const MetricJsonOptions& opts) const OVERRIDE;
+ Status WriteAsJson(JsonWriter* w, const MetricJsonOptions& opts) const
OVERRIDE;
+
+ Status WriteAsPrometheus(PrometheusWriter* w, const std::string& prefix)
const OVERRIDE;
// Returns a snapshot of this histogram including the bucketed values and
counts.
Status GetHistogramSnapshotPB(HistogramSnapshotPB* snapshot_pb,
diff --git a/src/kudu/server/default_path_handlers.h
b/src/kudu/util/prometheus_writer.cc
similarity index 51%
copy from src/kudu/server/default_path_handlers.h
copy to src/kudu/util/prometheus_writer.cc
index 1a5891546..d68f88b9e 100644
--- a/src/kudu/server/default_path_handlers.h
+++ b/src/kudu/util/prometheus_writer.cc
@@ -15,25 +15,10 @@
// specific language governing permissions and limitations
// under the License.
-#ifndef KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
-#define KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
+#include "kudu/util/prometheus_writer.h"
-namespace kudu {
+#include <sstream>
-class MetricRegistry;
-class Webserver;
-
-// Adds a set of default path handlers to the webserver to display
-// logs and configuration flags before the server is initialized.
-void AddPreInitializedDefaultPathHandlers(Webserver* webserver);
-
-// Adds a set of default path handlers to the webserver to display
-// stacks and support pprof profiling after the server is initialized.
-void AddPostInitializedDefaultPathHandlers(Webserver* webserver);
-
-// Adds an endpoint to get metrics in JSON format.
-void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry*
const metrics);
-
-} // namespace kudu
-
-#endif // KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
+void PrometheusWriter::WriteEntry(const std::string& data) {
+ *output_ << data;
+}
diff --git a/src/kudu/server/default_path_handlers.h
b/src/kudu/util/prometheus_writer.h
similarity index 51%
copy from src/kudu/server/default_path_handlers.h
copy to src/kudu/util/prometheus_writer.h
index 1a5891546..528438f6a 100644
--- a/src/kudu/server/default_path_handlers.h
+++ b/src/kudu/util/prometheus_writer.h
@@ -14,26 +14,18 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
+#pragma once
-#ifndef KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
-#define KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
+#include <iosfwd>
+#include <string>
-namespace kudu {
+class PrometheusWriter {
-class MetricRegistry;
-class Webserver;
+ public:
+ explicit PrometheusWriter(std::ostringstream* output): output_(output) {}
-// Adds a set of default path handlers to the webserver to display
-// logs and configuration flags before the server is initialized.
-void AddPreInitializedDefaultPathHandlers(Webserver* webserver);
+ void WriteEntry(const std::string& data);
-// Adds a set of default path handlers to the webserver to display
-// stacks and support pprof profiling after the server is initialized.
-void AddPostInitializedDefaultPathHandlers(Webserver* webserver);
-
-// Adds an endpoint to get metrics in JSON format.
-void RegisterMetricsJsonHandler(Webserver* webserver, const MetricRegistry*
const metrics);
-
-} // namespace kudu
-
-#endif // KUDU_SERVER_DEFAULT_PATH_HANDLERS_H
+ private:
+ std::ostringstream* output_;
+};