Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Version history
* router: added ability to configure a :ref:`retry policy <envoy_api_msg_route.RetryPolicy>` at the
virtual host level.
* stats: added support for histograms in prometheus
* stats: added usedonly flag to prometheus stats to only output metrics which have been
updated at least once.
* tap: added new alpha :ref:`HTTP tap filter <config_http_filters_tap>`.
* tls: enabled TLS 1.3 on the server-side (non-FIPS builds).
* router: added per-route configuration of :ref:`internal redirects <envoy_api_field_route.RouteAction.internal_redirect_action>`.
Expand Down
4 changes: 4 additions & 0 deletions docs/root/operations/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ explanation of the output.
Outputs /stats in `Prometheus <https://prometheus.io/docs/instrumenting/exposition_formats/>`_
v0.0.4 format. This can be used to integrate with a Prometheus server.

You can optionally pass the `usedonly` URL query argument to only get statistics that
Envoy has updated (counters incremented at least once, gauges changed at least once,
and histograms added to at least once)

.. _operations_admin_interface_runtime:

.. http:get:: /runtime
Expand Down
21 changes: 18 additions & 3 deletions source/server/http/admin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -669,10 +669,12 @@ Http::Code AdminImpl::handlerStats(absl::string_view url, Http::HeaderMap& respo
return rc;
}

Http::Code AdminImpl::handlerPrometheusStats(absl::string_view, Http::HeaderMap&,
Http::Code AdminImpl::handlerPrometheusStats(absl::string_view path_and_query, Http::HeaderMap&,
Buffer::Instance& response, AdminStream&) {
const Http::Utility::QueryParams params = Http::Utility::parseQueryString(path_and_query);
const bool used_only = params.find("usedonly") != params.end();
PrometheusStatsFormatter::statsAsPrometheus(server_.stats().counters(), server_.stats().gauges(),
server_.stats().histograms(), response);
server_.stats().histograms(), response, used_only);
return Http::Code::OK;
}

Expand Down Expand Up @@ -705,9 +707,14 @@ std::string PrometheusStatsFormatter::metricName(const std::string& extractedNam
uint64_t PrometheusStatsFormatter::statsAsPrometheus(
const std::vector<Stats::CounterSharedPtr>& counters,
const std::vector<Stats::GaugeSharedPtr>& gauges,
const std::vector<Stats::ParentHistogramSharedPtr>& histograms, Buffer::Instance& response) {
const std::vector<Stats::ParentHistogramSharedPtr>& histograms, Buffer::Instance& response,
const bool used_only) {
std::unordered_set<std::string> metric_type_tracker;
for (const auto& counter : counters) {
if (!shouldShowMetric(counter, used_only)) {
continue;
}

const std::string tags = formattedTags(counter->tags());
const std::string metric_name = metricName(counter->tagExtractedName());
if (metric_type_tracker.find(metric_name) == metric_type_tracker.end()) {
Expand All @@ -718,6 +725,10 @@ uint64_t PrometheusStatsFormatter::statsAsPrometheus(
}

for (const auto& gauge : gauges) {
if (!shouldShowMetric(gauge, used_only)) {
continue;
}

const std::string tags = formattedTags(gauge->tags());
const std::string metric_name = metricName(gauge->tagExtractedName());
if (metric_type_tracker.find(metric_name) == metric_type_tracker.end()) {
Expand All @@ -728,6 +739,10 @@ uint64_t PrometheusStatsFormatter::statsAsPrometheus(
}

for (const auto& histogram : histograms) {
if (!shouldShowMetric(histogram, used_only)) {
continue;
}

const std::string tags = formattedTags(histogram->tags());
const std::string hist_tags = histogram->tags().empty() ? EMPTY_STRING : (tags + ",");

Expand Down
10 changes: 9 additions & 1 deletion source/server/http/admin.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ class PrometheusStatsFormatter {
static uint64_t statsAsPrometheus(const std::vector<Stats::CounterSharedPtr>& counters,
const std::vector<Stats::GaugeSharedPtr>& gauges,
const std::vector<Stats::ParentHistogramSharedPtr>& histograms,
Buffer::Instance& response);
Buffer::Instance& response, const bool used_only);
/**
* Format the given tags, returning a string as a comma-separated list
* of <tag_name>="<tag_value>" pairs.
Expand All @@ -392,6 +392,14 @@ class PrometheusStatsFormatter {
* Take a string and sanitize it according to Prometheus conventions.
*/
static std::string sanitizeName(const std::string& name);

/*
* Determine whether a metric has never been emitted and choose to
* not show it if we only wanted used metrics.
*/
static bool shouldShowMetric(const std::shared_ptr<Stats::Metric>& metric, const bool used_only) {
return !used_only || metric->used();
}
};

} // namespace Server
Expand Down
94 changes: 90 additions & 4 deletions test/server/http/admin_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ TEST_F(PrometheusStatsFormatterTest, MetricNameCollison) {

Buffer::OwnedImpl response;
auto size =
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response);
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response, false);
EXPECT_EQ(2UL, size);
}

Expand All @@ -1311,7 +1311,7 @@ TEST_F(PrometheusStatsFormatterTest, UniqueMetricName) {

Buffer::OwnedImpl response;
auto size =
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response);
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response, false);
EXPECT_EQ(4UL, size);
}

Expand All @@ -1330,7 +1330,7 @@ TEST_F(PrometheusStatsFormatterTest, HistogramWithNoValuesAndNoTags) {

Buffer::OwnedImpl response;
auto size =
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response);
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response, false);
EXPECT_EQ(1UL, size);

const std::string expected_output = R"EOF(# TYPE envoy_histogram1 histogram
Expand Down Expand Up @@ -1382,7 +1382,7 @@ TEST_F(PrometheusStatsFormatterTest, OutputWithAllMetricTypes) {

Buffer::OwnedImpl response;
auto size =
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response);
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response, false);
EXPECT_EQ(5UL, size);

const std::string expected_output = R"EOF(# TYPE envoy_cluster_test_1_upstream_cx_total counter
Expand Down Expand Up @@ -1421,5 +1421,91 @@ envoy_cluster_test_1_upstream_rq_time_count{key1="value1",key2="value2"} 7
EXPECT_EQ(expected_output, response.toString());
}

TEST_F(PrometheusStatsFormatterTest, OutputWithUsedOnly) {
addCounter("cluster.test_1.upstream_cx_total", {{"a.tag-name", "a.tag-value"}});
addCounter("cluster.test_2.upstream_cx_total", {{"another_tag_name", "another_tag-value"}});
addGauge("cluster.test_3.upstream_cx_total", {{"another_tag_name_3", "another_tag_3-value"}});
addGauge("cluster.test_4.upstream_cx_total", {{"another_tag_name_4", "another_tag_4-value"}});

const std::vector<uint64_t> h1_values = {50, 20, 30, 70, 100, 5000, 200};
HistogramWrapper h1_cumulative;
h1_cumulative.setHistogramValues(h1_values);
Stats::HistogramStatisticsImpl h1_cumulative_statistics(h1_cumulative.getHistogram());

auto histogram1 = std::make_shared<NiceMock<Stats::MockParentHistogram>>();
histogram1->name_ = "cluster.test_1.upstream_rq_time";
histogram1->used_ = true;
histogram1->tags_ = {Stats::Tag{"key1", "value1"}, Stats::Tag{"key2", "value2"}};
addHistogram(histogram1);
EXPECT_CALL(*histogram1, cumulativeStatistics())
.WillOnce(testing::ReturnRef(h1_cumulative_statistics));

Buffer::OwnedImpl response;
auto size =
PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response, true);
EXPECT_EQ(1UL, size);

const std::string expected_output = R"EOF(# TYPE envoy_cluster_test_1_upstream_rq_time histogram
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="0.5"} 0
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="1"} 0
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="5"} 0
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="10"} 0
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="25"} 1
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="50"} 2
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="100"} 4
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="250"} 6
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="500"} 6
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="1000"} 6
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="2500"} 6
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="5000"} 6
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="10000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="30000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="60000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="300000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="600000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="1800000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="3600000"} 7
envoy_cluster_test_1_upstream_rq_time_bucket{key1="value1",key2="value2",le="+Inf"} 7
envoy_cluster_test_1_upstream_rq_time_sum{key1="value1",key2="value2"} 5532
envoy_cluster_test_1_upstream_rq_time_count{key1="value1",key2="value2"} 7
)EOF";

EXPECT_EQ(expected_output, response.toString());
}

TEST_F(PrometheusStatsFormatterTest, OutputWithUsedOnlyHistogram) {
const std::vector<uint64_t> h1_values = {};
HistogramWrapper h1_cumulative;
h1_cumulative.setHistogramValues(h1_values);
Stats::HistogramStatisticsImpl h1_cumulative_statistics(h1_cumulative.getHistogram());

auto histogram1 = std::make_shared<NiceMock<Stats::MockParentHistogram>>();
histogram1->name_ = "cluster.test_1.upstream_rq_time";
histogram1->used_ = false;
histogram1->tags_ = {Stats::Tag{"key1", "value1"}, Stats::Tag{"key2", "value2"}};
addHistogram(histogram1);

{
const bool used_only = true;
EXPECT_CALL(*histogram1, cumulativeStatistics()).Times(0);

Buffer::OwnedImpl response;
auto size = PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_,
response, used_only);
EXPECT_EQ(0UL, size);
}

{
const bool used_only = false;
EXPECT_CALL(*histogram1, cumulativeStatistics())
.WillOnce(testing::ReturnRef(h1_cumulative_statistics));

Buffer::OwnedImpl response;
auto size = PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_,
response, used_only);
EXPECT_EQ(1UL, size);
}
}

} // namespace Server
} // namespace Envoy