diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 031346c8e8dd0..f40ca143c43a0 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -29,6 +29,7 @@ Version history * redis: migrate hash function for host selection to `MurmurHash2 `_ from std::hash. MurmurHash2 is compatible with std::hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled on Linux and not macOS. * router: added ability to configure a :ref:`retry policy ` at the virtual host level. +* stats: added support for histograms in prometheus * tap: added new alpha :ref:`HTTP tap filter `. * tls: enabled TLS 1.3 on the server-side (non-FIPS builds). * router: added per-route configuration of :ref:`internal redirects `. diff --git a/docs/root/operations/admin.rst b/docs/root/operations/admin.rst index 99ecfd8fce449..502312fefbaac 100644 --- a/docs/root/operations/admin.rst +++ b/docs/root/operations/admin.rst @@ -310,8 +310,7 @@ explanation of the output. .. http:get:: /stats/prometheus Outputs /stats in `Prometheus `_ - v0.0.4 format. This can be used to integrate with a Prometheus server. Currently, only counters and - gauges are output. Histograms will be output in a future update. + v0.0.4 format. This can be used to integrate with a Prometheus server. .. _operations_admin_interface_runtime: diff --git a/include/envoy/stats/histogram.h b/include/envoy/stats/histogram.h index 59ba29666fb0a..ffd76f274b248 100644 --- a/include/envoy/stats/histogram.h +++ b/include/envoy/stats/histogram.h @@ -18,9 +18,14 @@ class HistogramStatistics { virtual ~HistogramStatistics() {} /** - * Returns summary representation of the histogram. + * Returns quantile summary representation of the histogram. */ - virtual std::string summary() const PURE; + virtual std::string quantileSummary() const PURE; + + /** + * Returns bucket summary representation of the histogram. + */ + virtual std::string bucketSummary() const PURE; /** * Returns supported quantiles. @@ -31,6 +36,32 @@ class HistogramStatistics { * Returns computed quantile values during the period. */ virtual const std::vector& computedQuantiles() const PURE; + + /** + * Returns supported buckets. Each value is the upper bound of the bucket + * with 0 as the implicit lower bound. For timers, these bucket thresholds + * are in milliseconds but the thresholds are applicable to all types of data. + */ + virtual const std::vector& supportedBuckets() const PURE; + + /** + * Returns computed bucket values during the period. The vector contains an appoximation + * of samples below each quantile bucket defined in supportedBuckets(). This vector is + * guaranteed to be the same length as supportedBuckets(). + */ + virtual const std::vector& computedBuckets() const PURE; + + /** + * Returns number of values during the period. This number may be an approximation + * of the number of samples in the histogram, it is not guaranteed that this will be + * 100% the number of samples observed. + */ + virtual double sampleCount() const PURE; + + /** + * Returns sum of all values during the period. + */ + virtual double sampleSum() const PURE; }; /** @@ -76,9 +107,14 @@ class ParentHistogram : public virtual Histogram { virtual const HistogramStatistics& cumulativeStatistics() const PURE; /** - * Returns the summary representation. + * Returns the quantile summary representation. + */ + virtual const std::string quantileSummary() const PURE; + + /** + * Returns the bucket summary representation. */ - virtual const std::string summary() const PURE; + virtual const std::string bucketSummary() const PURE; }; typedef std::shared_ptr ParentHistogramSharedPtr; diff --git a/source/common/stats/histogram_impl.cc b/source/common/stats/histogram_impl.cc index 83fe9086a8ca5..a22ac822c4a6c 100644 --- a/source/common/stats/histogram_impl.cc +++ b/source/common/stats/histogram_impl.cc @@ -14,6 +14,15 @@ HistogramStatisticsImpl::HistogramStatisticsImpl(const histogram_t* histogram_pt : computed_quantiles_(supportedQuantiles().size(), 0.0) { hist_approx_quantile(histogram_ptr, supportedQuantiles().data(), supportedQuantiles().size(), computed_quantiles_.data()); + + sample_count_ = hist_sample_count(histogram_ptr); + sample_sum_ = hist_approx_sum(histogram_ptr); + + const std::vector& supported_buckets = supportedBuckets(); + computed_buckets_.reserve(supported_buckets.size()); + for (const auto bucket : supported_buckets) { + computed_buckets_.emplace_back(hist_approx_count_below(histogram_ptr, bucket)); + } } const std::vector& HistogramStatisticsImpl::supportedQuantiles() const { @@ -22,17 +31,33 @@ const std::vector& HistogramStatisticsImpl::supportedQuantiles() const { return supported_quantiles; } -std::string HistogramStatisticsImpl::summary() const { +const std::vector& HistogramStatisticsImpl::supportedBuckets() const { + static const std::vector supported_buckets = { + 0.5, 1, 5, 10, 25, 50, 100, 250, 500, 1000, + 2500, 5000, 10000, 30000, 60000, 300000, 600000, 1800000, 3600000}; + return supported_buckets; +} + +std::string HistogramStatisticsImpl::quantileSummary() const { std::vector summary; - const std::vector& supported_quantiles_ref = supportedQuantiles(); - summary.reserve(supported_quantiles_ref.size()); - for (size_t i = 0; i < supported_quantiles_ref.size(); ++i) { - summary.push_back( - fmt::format("P{}: {}", 100 * supported_quantiles_ref[i], computed_quantiles_[i])); + const std::vector& supported_quantiles = supportedQuantiles(); + summary.reserve(supported_quantiles.size()); + for (size_t i = 0; i < supported_quantiles.size(); ++i) { + summary.push_back(fmt::format("P{}: {}", 100 * supported_quantiles[i], computed_quantiles_[i])); } return absl::StrJoin(summary, ", "); } +std::string HistogramStatisticsImpl::bucketSummary() const { + std::vector bucket_summary; + const std::vector& supported_buckets = supportedBuckets(); + bucket_summary.reserve(supported_buckets.size()); + for (size_t i = 0; i < supported_buckets.size(); ++i) { + bucket_summary.push_back(fmt::format("B{}: {}", supported_buckets[i], computed_buckets_[i])); + } + return absl::StrJoin(bucket_summary, ", "); +} + /** * Clears the old computed values and refreshes it with values computed from passed histogram. */ @@ -41,6 +66,17 @@ void HistogramStatisticsImpl::refresh(const histogram_t* new_histogram_ptr) { ASSERT(supportedQuantiles().size() == computed_quantiles_.size()); hist_approx_quantile(new_histogram_ptr, supportedQuantiles().data(), supportedQuantiles().size(), computed_quantiles_.data()); + + sample_count_ = hist_sample_count(new_histogram_ptr); + sample_sum_ = hist_approx_sum(new_histogram_ptr); + + ASSERT(supportedBuckets().size() == computed_buckets_.size()); + computed_buckets_.clear(); + const std::vector& supported_buckets = supportedBuckets(); + computed_buckets_.reserve(supported_buckets.size()); + for (const auto bucket : supported_buckets) { + computed_buckets_.emplace_back(hist_approx_count_below(new_histogram_ptr, bucket)); + } } } // namespace Stats diff --git a/source/common/stats/histogram_impl.h b/source/common/stats/histogram_impl.h index fc6b39944ba33..ef11744ac7163 100644 --- a/source/common/stats/histogram_impl.h +++ b/source/common/stats/histogram_impl.h @@ -31,12 +31,20 @@ class HistogramStatisticsImpl : public HistogramStatistics, NonCopyable { void refresh(const histogram_t* new_histogram_ptr); // HistogramStatistics - std::string summary() const override; + std::string quantileSummary() const override; + std::string bucketSummary() const override; const std::vector& supportedQuantiles() const override; const std::vector& computedQuantiles() const override { return computed_quantiles_; } + const std::vector& supportedBuckets() const override; + const std::vector& computedBuckets() const override { return computed_buckets_; } + double sampleCount() const override { return sample_count_; } + double sampleSum() const override { return sample_sum_; } private: std::vector computed_quantiles_; + std::vector computed_buckets_; + double sample_count_; + double sample_sum_; }; /** diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 684102a25c8ba..2c72dbf94b983 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -503,7 +503,7 @@ void ParentHistogramImpl::merge() { } } -const std::string ParentHistogramImpl::summary() const { +const std::string ParentHistogramImpl::quantileSummary() const { if (used()) { std::vector summary; const std::vector& supported_quantiles_ref = interval_statistics_.supportedQuantiles(); @@ -519,6 +519,22 @@ const std::string ParentHistogramImpl::summary() const { } } +const std::string ParentHistogramImpl::bucketSummary() const { + if (used()) { + std::vector bucket_summary; + const std::vector& supported_buckets = interval_statistics_.supportedBuckets(); + bucket_summary.reserve(supported_buckets.size()); + for (size_t i = 0; i < supported_buckets.size(); ++i) { + bucket_summary.push_back(fmt::format("B{}({},{})", supported_buckets[i], + interval_statistics_.computedBuckets()[i], + cumulative_statistics_.computedBuckets()[i])); + } + return absl::StrJoin(bucket_summary, " "); + } else { + return std::string("No recorded values"); + } +} + void ParentHistogramImpl::addTlsHistogram(const TlsHistogramSharedPtr& hist_ptr) { Thread::LockGuard lock(merge_lock_); tls_histograms_.emplace_back(hist_ptr); diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 35e5356adca39..dea59e20ea8b4 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -90,7 +90,8 @@ class ParentHistogramImpl : public ParentHistogram, public MetricImpl { const HistogramStatistics& cumulativeStatistics() const override { return cumulative_statistics_; } - const std::string summary() const override; + const std::string quantileSummary() const override; + const std::string bucketSummary() const override; // Stats::Metric std::string name() const override { return name_; } diff --git a/source/server/http/admin.cc b/source/server/http/admin.cc index 19d1e52cdb1da..1df56ddcf8614 100644 --- a/source/server/http/admin.cc +++ b/source/server/http/admin.cc @@ -30,6 +30,7 @@ #include "common/access_log/access_log_impl.h" #include "common/buffer/buffer_impl.h" #include "common/common/assert.h" +#include "common/common/empty_string.h" #include "common/common/enum_to_int.h" #include "common/common/fmt.h" #include "common/common/mutex_tracer_impl.h" @@ -658,7 +659,7 @@ Http::Code AdminImpl::handlerStats(absl::string_view url, Http::HeaderMap& respo std::multimap all_histograms; for (const Stats::ParentHistogramSharedPtr& histogram : server_.stats().histograms()) { if (shouldShowMetric(histogram, used_only, regex)) { - all_histograms.emplace(histogram->name(), histogram->summary()); + all_histograms.emplace(histogram->name(), histogram->quantileSummary()); } } for (auto histogram : all_histograms) { @@ -671,7 +672,7 @@ Http::Code AdminImpl::handlerStats(absl::string_view url, Http::HeaderMap& respo Http::Code AdminImpl::handlerPrometheusStats(absl::string_view, Http::HeaderMap&, Buffer::Instance& response, AdminStream&) { PrometheusStatsFormatter::statsAsPrometheus(server_.stats().counters(), server_.stats().gauges(), - response); + server_.stats().histograms(), response); return Http::Code::OK; } @@ -701,11 +702,10 @@ std::string PrometheusStatsFormatter::metricName(const std::string& extractedNam return sanitizeName(fmt::format("envoy_{0}", extractedName)); } -// TODO(ramaraochavali): Add summary histogram output for Prometheus. -uint64_t -PrometheusStatsFormatter::statsAsPrometheus(const std::vector& counters, - const std::vector& gauges, - Buffer::Instance& response) { +uint64_t PrometheusStatsFormatter::statsAsPrometheus( + const std::vector& counters, + const std::vector& gauges, + const std::vector& histograms, Buffer::Instance& response) { std::unordered_set metric_type_tracker; for (const auto& counter : counters) { const std::string tags = formattedTags(counter->tags()); @@ -726,6 +726,38 @@ PrometheusStatsFormatter::statsAsPrometheus(const std::vectorvalue())); } + + for (const auto& histogram : histograms) { + const std::string tags = formattedTags(histogram->tags()); + const std::string hist_tags = histogram->tags().empty() ? EMPTY_STRING : (tags + ","); + + const std::string metric_name = metricName(histogram->tagExtractedName()); + if (metric_type_tracker.find(metric_name) == metric_type_tracker.end()) { + metric_type_tracker.insert(metric_name); + response.add(fmt::format("# TYPE {0} histogram\n", metric_name)); + } + + const Stats::HistogramStatistics& stats = histogram->cumulativeStatistics(); + const std::vector& supported_buckets = stats.supportedBuckets(); + const std::vector& computed_buckets = stats.computedBuckets(); + for (size_t i = 0; i < supported_buckets.size(); ++i) { + double bucket = supported_buckets[i]; + uint64_t value = computed_buckets[i]; + // We want to print the bucket in a fixed point (non-scientific) format. The fmt library + // doesn't have a specific modifier to format as a fixed-point value only so we use the + // 'g' operator which prints the number in general fixed point format or scientific format + // with precision 50 to round the number up to 32 significant digits in fixed point format + // which should cover pretty much all cases + response.add(fmt::format("{0}_bucket{{{1}le=\"{2:.32g}\"}} {3}\n", metric_name, hist_tags, + bucket, value)); + } + + response.add(fmt::format("{0}_bucket{{{1}le=\"+Inf\"}} {2}\n", metric_name, hist_tags, + stats.sampleCount())); + response.add(fmt::format("{0}_sum{{{1}}} {2}\n", metric_name, tags, stats.sampleSum())); + response.add(fmt::format("{0}_count{{{1}}} {2}\n", metric_name, tags, stats.sampleCount())); + } + return metric_type_tracker.size(); } diff --git a/source/server/http/admin.h b/source/server/http/admin.h index 099bb46f11358..d9205dba7e0da 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -375,6 +375,7 @@ class PrometheusStatsFormatter { */ static uint64_t statsAsPrometheus(const std::vector& counters, const std::vector& gauges, + const std::vector& histograms, Buffer::Instance& response); /** * Format the given tags, returning a string as a comma-separated list diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index fb5b798e9db6a..7d69bf0abae53 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -129,13 +129,21 @@ class HistogramTest : public testing::Test { NameHistogramMap name_histogram_map = makeHistogramMap(histogram_list); const ParentHistogramSharedPtr& h1 = name_histogram_map["h1"]; - EXPECT_EQ(h1->cumulativeStatistics().summary(), h1_cumulative_statistics.summary()); - EXPECT_EQ(h1->intervalStatistics().summary(), h1_interval_statistics.summary()); + EXPECT_EQ(h1->cumulativeStatistics().quantileSummary(), + h1_cumulative_statistics.quantileSummary()); + EXPECT_EQ(h1->intervalStatistics().quantileSummary(), h1_interval_statistics.quantileSummary()); + EXPECT_EQ(h1->cumulativeStatistics().bucketSummary(), h1_cumulative_statistics.bucketSummary()); + EXPECT_EQ(h1->intervalStatistics().bucketSummary(), h1_interval_statistics.bucketSummary()); if (histogram_list.size() > 1) { const ParentHistogramSharedPtr& h2 = name_histogram_map["h2"]; - EXPECT_EQ(h2->cumulativeStatistics().summary(), h2_cumulative_statistics.summary()); - EXPECT_EQ(h2->intervalStatistics().summary(), h2_interval_statistics.summary()); + EXPECT_EQ(h2->cumulativeStatistics().quantileSummary(), + h2_cumulative_statistics.quantileSummary()); + EXPECT_EQ(h2->intervalStatistics().quantileSummary(), + h2_interval_statistics.quantileSummary()); + EXPECT_EQ(h2->cumulativeStatistics().bucketSummary(), + h2_cumulative_statistics.bucketSummary()); + EXPECT_EQ(h2->intervalStatistics().bucketSummary(), h2_interval_statistics.bucketSummary()); } h1_interval_values_.clear(); @@ -850,8 +858,18 @@ TEST_F(HistogramTest, BasicHistogramSummaryValidate) { const std::string h1_expected_summary = "P0: 1, P25: 1.025, P50: 1.05, P75: 1.075, P90: 1.09, P95: 1.095, " "P99: 1.099, P99.5: 1.0995, P99.9: 1.0999, P100: 1.1"; - const std::string h2_expected_summary = "P0: 0, P25: 25, P50: 50, P75: 75, P90: 90, P95: 95, " - "P99: 99, P99.5: 99.5, P99.9: 99.9, P100: 100"; + const std::string h2_expected_summary = + "P0: 0, P25: 25, P50: 50, P75: 75, P90: 90, P95: 95, P99: 99, " + "P99.5: 99.5, P99.9: 99.9, P100: 100"; + + const std::string h1_expected_buckets = + "B0.5: 0, B1: 0, B5: 1, B10: 1, B25: 1, B50: 1, B100: 1, B250: 1, " + "B500: 1, B1000: 1, B2500: 1, B5000: 1, B10000: 1, B30000: 1, B60000: 1, " + "B300000: 1, B600000: 1, B1.8e+06: 1, B3.6e+06: 1"; + const std::string h2_expected_buckets = + "B0.5: 1, B1: 1, B5: 5, B10: 10, B25: 25, B50: 50, B100: 100, B250: 100, " + "B500: 100, B1000: 100, B2500: 100, B5000: 100, B10000: 100, B30000: 100, " + "B60000: 100, B300000: 100, B600000: 100, B1.8e+06: 100, B3.6e+06: 100"; for (size_t i = 0; i < 100; ++i) { expectCallAndAccumulate(h2, i); @@ -860,8 +878,12 @@ TEST_F(HistogramTest, BasicHistogramSummaryValidate) { EXPECT_EQ(2, validateMerge()); NameHistogramMap name_histogram_map = makeHistogramMap(store_->histograms()); - EXPECT_EQ(h1_expected_summary, name_histogram_map["h1"]->cumulativeStatistics().summary()); - EXPECT_EQ(h2_expected_summary, name_histogram_map["h2"]->cumulativeStatistics().summary()); + EXPECT_EQ(h1_expected_summary, + name_histogram_map["h1"]->cumulativeStatistics().quantileSummary()); + EXPECT_EQ(h2_expected_summary, + name_histogram_map["h2"]->cumulativeStatistics().quantileSummary()); + EXPECT_EQ(h1_expected_buckets, name_histogram_map["h1"]->cumulativeStatistics().bucketSummary()); + EXPECT_EQ(h2_expected_buckets, name_histogram_map["h2"]->cumulativeStatistics().bucketSummary()); } // Validates the summary after known value merge in to same histogram. @@ -880,9 +902,15 @@ TEST_F(HistogramTest, BasicHistogramMergeSummary) { const std::string expected_summary = "P0: 0, P25: 25, P50: 50, P75: 75, P90: 90, P95: 95, P99: " "99, P99.5: 99.5, P99.9: 99.9, P100: 100"; + const std::string expected_bucket_summary = + "B0.5: 1, B1: 1, B5: 5, B10: 10, B25: 25, B50: 50, B100: 100, B250: 100, " + "B500: 100, B1000: 100, B2500: 100, B5000: 100, B10000: 100, B30000: 100, " + "B60000: 100, B300000: 100, B600000: 100, B1.8e+06: 100, B3.6e+06: 100"; NameHistogramMap name_histogram_map = makeHistogramMap(store_->histograms()); - EXPECT_EQ(expected_summary, name_histogram_map["h1"]->cumulativeStatistics().summary()); + EXPECT_EQ(expected_summary, name_histogram_map["h1"]->cumulativeStatistics().quantileSummary()); + EXPECT_EQ(expected_bucket_summary, + name_histogram_map["h1"]->cumulativeStatistics().bucketSummary()); } TEST_F(HistogramTest, BasicHistogramUsed) { diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index d70e9c6a37d55..c079e785be16b 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -104,7 +104,8 @@ class MockParentHistogram : public ParentHistogram { std::string name() const override { return name_; }; const char* nameCStr() const override { return name_.c_str(); }; void merge() override {} - const std::string summary() const override { return ""; }; + const std::string quantileSummary() const override { return ""; }; + const std::string bucketSummary() const override { return ""; }; MOCK_CONST_METHOD0(used, bool()); MOCK_CONST_METHOD0(tagExtractedName, const std::string&()); diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index f7e6794babe0c..e7ca80fbdf6d1 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -1201,6 +1201,24 @@ TEST_P(AdminInstanceTest, PostRequest) { HasSubstr("text/plain")); } +class HistogramWrapper { +public: + HistogramWrapper() : histogram_(hist_alloc()) {} + + ~HistogramWrapper() { hist_free(histogram_); } + + const histogram_t* getHistogram() { return histogram_; } + + void setHistogramValues(const std::vector& values) { + for (uint64_t value : values) { + hist_insert_intscale(histogram_, value, 0, 1); + } + } + +private: + histogram_t* histogram_; +}; + class PrometheusStatsFormatterTest : public testing::Test { protected: PrometheusStatsFormatterTest() /*: alloc_(stats_options_)*/ {} @@ -1214,10 +1232,15 @@ class PrometheusStatsFormatterTest : public testing::Test { gauges_.push_back(alloc_.makeGauge(name, std::move(tname), std::move(cluster_tags))); } + void addHistogram(const Stats::ParentHistogramSharedPtr histogram) { + histograms_.push_back(histogram); + } + Stats::StatsOptionsImpl stats_options_; Stats::HeapStatDataAllocator alloc_; std::vector counters_; std::vector gauges_; + std::vector histograms_; }; TEST_F(PrometheusStatsFormatterTest, MetricName) { @@ -1267,7 +1290,9 @@ TEST_F(PrometheusStatsFormatterTest, MetricNameCollison) { {{"another_tag_name_4", "another_tag_4-value"}}); Buffer::OwnedImpl response; - EXPECT_EQ(2UL, PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, response)); + auto size = + PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response); + EXPECT_EQ(2UL, size); } TEST_F(PrometheusStatsFormatterTest, UniqueMetricName) { @@ -1285,7 +1310,115 @@ TEST_F(PrometheusStatsFormatterTest, UniqueMetricName) { {{"another_tag_name_4", "another_tag_4-value"}}); Buffer::OwnedImpl response; - EXPECT_EQ(4UL, PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, response)); + auto size = + PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response); + EXPECT_EQ(4UL, size); +} + +TEST_F(PrometheusStatsFormatterTest, HistogramWithNoValuesAndNoTags) { + HistogramWrapper h1_cumulative; + h1_cumulative.setHistogramValues(std::vector(0)); + Stats::HistogramStatisticsImpl h1_cumulative_statistics(h1_cumulative.getHistogram()); + + auto histogram = std::make_shared>(); + histogram->name_ = "histogram1"; + histogram->used_ = true; + ON_CALL(*histogram, cumulativeStatistics()) + .WillByDefault(testing::ReturnRef(h1_cumulative_statistics)); + + addHistogram(histogram); + + Buffer::OwnedImpl response; + auto size = + PrometheusStatsFormatter::statsAsPrometheus(counters_, gauges_, histograms_, response); + EXPECT_EQ(1UL, size); + + const std::string expected_output = R"EOF(# TYPE envoy_histogram1 histogram +envoy_histogram1_bucket{le="0.5"} 0 +envoy_histogram1_bucket{le="1"} 0 +envoy_histogram1_bucket{le="5"} 0 +envoy_histogram1_bucket{le="10"} 0 +envoy_histogram1_bucket{le="25"} 0 +envoy_histogram1_bucket{le="50"} 0 +envoy_histogram1_bucket{le="100"} 0 +envoy_histogram1_bucket{le="250"} 0 +envoy_histogram1_bucket{le="500"} 0 +envoy_histogram1_bucket{le="1000"} 0 +envoy_histogram1_bucket{le="2500"} 0 +envoy_histogram1_bucket{le="5000"} 0 +envoy_histogram1_bucket{le="10000"} 0 +envoy_histogram1_bucket{le="30000"} 0 +envoy_histogram1_bucket{le="60000"} 0 +envoy_histogram1_bucket{le="300000"} 0 +envoy_histogram1_bucket{le="600000"} 0 +envoy_histogram1_bucket{le="1800000"} 0 +envoy_histogram1_bucket{le="3600000"} 0 +envoy_histogram1_bucket{le="+Inf"} 0 +envoy_histogram1_sum{} 0 +envoy_histogram1_count{} 0 +)EOF"; + + EXPECT_EQ(expected_output, response.toString()); +} + +TEST_F(PrometheusStatsFormatterTest, OutputWithAllMetricTypes) { + 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 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>(); + 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); + EXPECT_EQ(5UL, size); + + const std::string expected_output = R"EOF(# TYPE envoy_cluster_test_1_upstream_cx_total counter +envoy_cluster_test_1_upstream_cx_total{a_tag_name="a.tag-value"} 0 +# TYPE envoy_cluster_test_2_upstream_cx_total counter +envoy_cluster_test_2_upstream_cx_total{another_tag_name="another_tag-value"} 0 +# TYPE envoy_cluster_test_3_upstream_cx_total gauge +envoy_cluster_test_3_upstream_cx_total{another_tag_name_3="another_tag_3-value"} 0 +# TYPE envoy_cluster_test_4_upstream_cx_total gauge +envoy_cluster_test_4_upstream_cx_total{another_tag_name_4="another_tag_4-value"} 0 +# 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()); } } // namespace Server