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
79 changes: 46 additions & 33 deletions source/server/admin/stats_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ Http::Code StatsHandler::handlerStats(absl::string_view url,
server_.flushStats();
}

Http::Code rc = Http::Code::OK;
const Http::Utility::QueryParams params = Http::Utility::parseAndDecodeQueryString(url);

const bool used_only = params.find("usedonly") != params.end();
Expand Down Expand Up @@ -104,38 +103,27 @@ Http::Code StatsHandler::handlerStats(absl::string_view url,
}
}

if (const auto format_value = Utility::formatParam(params)) {
if (format_value.value() == "json") {
response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Json);
response.add(
statsAsJson(all_stats, text_readouts, server_.stats().histograms(), used_only, regex));
} else if (format_value.value() == "prometheus") {
return handlerPrometheusStats(url, response_headers, response, admin_stream);
} else {
response.add("usage: /stats?format=json or /stats?format=prometheus \n");
response.add("\n");
rc = Http::Code::NotFound;
}
} else { // Display plain stats if format query param is not there.
for (const auto& text_readout : text_readouts) {
response.add(fmt::format("{}: \"{}\"\n", text_readout.first,
Html::Utility::sanitize(text_readout.second)));
}
for (const auto& stat : all_stats) {
response.add(fmt::format("{}: {}\n", stat.first, stat.second));
}
std::map<std::string, std::string> all_histograms;
for (const Stats::ParentHistogramSharedPtr& histogram : server_.stats().histograms()) {
if (shouldShowMetric(*histogram, used_only, regex)) {
auto insert = all_histograms.emplace(histogram->name(), histogram->quantileSummary());
ASSERT(insert.second); // No duplicates expected.
}
}
for (const auto& histogram : all_histograms) {
response.add(fmt::format("{}: {}\n", histogram.first, histogram.second));
}
absl::optional<std::string> format_value = Utility::formatParam(params);
if (!format_value.has_value()) {
// Display plain stats if format query param is not there.
statsAsText(all_stats, text_readouts, server_.stats().histograms(), used_only, regex, response);
return Http::Code::OK;
}

if (format_value.value() == "json") {
response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Json);
response.add(
statsAsJson(all_stats, text_readouts, server_.stats().histograms(), used_only, regex));
return Http::Code::OK;
}

if (format_value.value() == "prometheus") {
return handlerPrometheusStats(url, response_headers, response, admin_stream);
}
return rc;

response.add("usage: /stats?format=json or /stats?format=prometheus \n");
response.add("\n");
return Http::Code::NotFound;
}

Http::Code StatsHandler::handlerPrometheusStats(absl::string_view path_and_query,
Expand Down Expand Up @@ -174,11 +162,36 @@ Http::Code StatsHandler::handlerContention(absl::string_view,
return Http::Code::OK;
}

void StatsHandler::statsAsText(const std::map<std::string, uint64_t>& all_stats,
const std::map<std::string, std::string>& text_readouts,
const std::vector<Stats::ParentHistogramSharedPtr>& histograms,
bool used_only, const absl::optional<std::regex>& regex,
Buffer::Instance& response) {
// Display plain stats if format query param is not there.
for (const auto& text_readout : text_readouts) {
response.add(fmt::format("{}: \"{}\"\n", text_readout.first,
Html::Utility::sanitize(text_readout.second)));
}
for (const auto& stat : all_stats) {
response.add(fmt::format("{}: {}\n", stat.first, stat.second));
}
std::map<std::string, std::string> all_histograms;
for (const Stats::ParentHistogramSharedPtr& histogram : histograms) {
if (shouldShowMetric(*histogram, used_only, regex)) {
auto insert = all_histograms.emplace(histogram->name(), histogram->quantileSummary());
ASSERT(insert.second); // No duplicates expected.
}
}
for (const auto& histogram : all_histograms) {
response.add(fmt::format("{}: {}\n", histogram.first, histogram.second));
}
}

std::string
StatsHandler::statsAsJson(const std::map<std::string, uint64_t>& all_stats,
const std::map<std::string, std::string>& text_readouts,
const std::vector<Stats::ParentHistogramSharedPtr>& all_histograms,
const bool used_only, const absl::optional<std::regex> regex,
const bool used_only, const absl::optional<std::regex>& regex,
const bool pretty_print) {

ProtobufWkt::Struct document;
Expand Down
9 changes: 7 additions & 2 deletions source/server/admin/stats_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,14 @@ class StatsHandler : public HandlerContextBase {
static std::string statsAsJson(const std::map<std::string, uint64_t>& all_stats,
const std::map<std::string, std::string>& text_readouts,
const std::vector<Stats::ParentHistogramSharedPtr>& all_histograms,
bool used_only,
const absl::optional<std::regex> regex = absl::nullopt,
bool used_only, const absl::optional<std::regex>& regex,
bool pretty_print = false);

void statsAsText(const std::map<std::string, uint64_t>& all_stats,
const std::map<std::string, std::string>& text_readouts,
const std::vector<Stats::ParentHistogramSharedPtr>& all_histograms,
bool used_only, const absl::optional<std::regex>& regex,
Buffer::Instance& response);
};

} // namespace Server
Expand Down
1 change: 1 addition & 0 deletions test/server/admin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ envoy_cc_test(
":admin_instance_lib",
"//source/common/stats:thread_local_store_lib",
"//source/server/admin:stats_handler_lib",
"//test/mocks/server:admin_stream_mocks",
"//test/test_common:logging_lib",
"//test/test_common:utility_lib",
],
Expand Down
184 changes: 184 additions & 0 deletions test/server/admin/stats_handler_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "source/common/stats/thread_local_store.h"
#include "source/server/admin/stats_handler.h"

#include "test/mocks/server/admin_stream.h"
#include "test/mocks/server/instance.h"
#include "test/server/admin/admin_instance.h"
#include "test/test_common/logging.h"
#include "test/test_common/utility.h"
Expand Down Expand Up @@ -50,6 +52,188 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, AdminStatsTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);

TEST_P(AdminStatsTest, HandlerStatsInvalidFormat) {
const std::string url = "/stats?format=blergh";
Http::TestResponseHeaderMapImpl response_headers;
Buffer::OwnedImpl data;
MockAdminStream admin_stream;
Configuration::MockStatsConfig stats_config;
EXPECT_CALL(stats_config, flushOnAdmin()).WillRepeatedly(testing::Return(false));
MockInstance instance;
EXPECT_CALL(instance, stats()).WillRepeatedly(testing::ReturnRef(*store_));
EXPECT_CALL(instance, statsConfig()).WillRepeatedly(testing::ReturnRef(stats_config));
StatsHandler handler(instance);
Http::Code code = handler.handlerStats(url, response_headers, data, admin_stream);
EXPECT_EQ(Http::Code::NotFound, code);
EXPECT_EQ("usage: /stats?format=json or /stats?format=prometheus \n\n", data.toString());
}

TEST_P(AdminStatsTest, HandlerStatsPlainText) {
const std::string url = "/stats";
Http::TestResponseHeaderMapImpl response_headers;
Buffer::OwnedImpl data;
MockAdminStream admin_stream;
Configuration::MockStatsConfig stats_config;
EXPECT_CALL(stats_config, flushOnAdmin()).WillRepeatedly(testing::Return(false));
MockInstance instance;
store_->initializeThreading(main_thread_dispatcher_, tls_);
EXPECT_CALL(instance, stats()).WillRepeatedly(testing::ReturnRef(*store_));
EXPECT_CALL(instance, statsConfig()).WillRepeatedly(testing::ReturnRef(stats_config));
StatsHandler handler(instance);

Stats::Counter& c1 = store_->counterFromString("c1");
Stats::Counter& c2 = store_->counterFromString("c2");

c1.add(10);
c2.add(20);

Stats::TextReadout& t = store_->textReadoutFromString("t");
t.set("hello world");

Stats::Histogram& h1 = store_->histogramFromString("h1", Stats::Histogram::Unit::Unspecified);
Stats::Histogram& h2 = store_->histogramFromString("h2", Stats::Histogram::Unit::Unspecified);

EXPECT_CALL(sink_, onHistogramComplete(Ref(h1), 200));
h1.recordValue(200);

EXPECT_CALL(sink_, onHistogramComplete(Ref(h2), 100));
h2.recordValue(100);

store_->mergeHistograms([]() -> void {});

Http::Code code = handler.handlerStats(url, response_headers, data, admin_stream);
EXPECT_EQ(Http::Code::OK, code);
EXPECT_EQ("t: \"hello world\"\n"
"c1: 10\n"
"c2: 20\n"
"h1: P0(200.0,200.0) P25(202.5,202.5) P50(205.0,205.0) P75(207.5,207.5) "
"P90(209.0,209.0) P95(209.5,209.5) P99(209.9,209.9) P99.5(209.95,209.95) "
"P99.9(209.99,209.99) P100(210.0,210.0)\n"
"h2: P0(100.0,100.0) P25(102.5,102.5) P50(105.0,105.0) P75(107.5,107.5) "
"P90(109.0,109.0) P95(109.5,109.5) P99(109.9,109.9) P99.5(109.95,109.95) "
"P99.9(109.99,109.99) P100(110.0,110.0)\n",
data.toString());

shutdownThreading();
}

TEST_P(AdminStatsTest, HandlerStatsJson) {
const std::string url = "/stats?format=json";
Http::TestResponseHeaderMapImpl response_headers;
Buffer::OwnedImpl data;
MockAdminStream admin_stream;
Configuration::MockStatsConfig stats_config;
EXPECT_CALL(stats_config, flushOnAdmin()).WillRepeatedly(testing::Return(false));
MockInstance instance;
store_->initializeThreading(main_thread_dispatcher_, tls_);
EXPECT_CALL(instance, stats()).WillRepeatedly(testing::ReturnRef(*store_));
EXPECT_CALL(instance, statsConfig()).WillRepeatedly(testing::ReturnRef(stats_config));
StatsHandler handler(instance);

Stats::Counter& c1 = store_->counterFromString("c1");
Stats::Counter& c2 = store_->counterFromString("c2");

c1.add(10);
c2.add(20);

Stats::TextReadout& t = store_->textReadoutFromString("t");
t.set("hello world");

Stats::Histogram& h = store_->histogramFromString("h", Stats::Histogram::Unit::Unspecified);

EXPECT_CALL(sink_, onHistogramComplete(Ref(h), 200));
h.recordValue(200);

store_->mergeHistograms([]() -> void {});

Http::Code code = handler.handlerStats(url, response_headers, data, admin_stream);
EXPECT_EQ(Http::Code::OK, code);

const std::string expected_json_old = R"EOF({
"stats": [
{
"name":"t",
"value":"hello world"
},
{
"name":"c1",
"value":10,
},
{
"name":"c2",
"value":20
},
{
"histograms": {
"supported_quantiles": [
0.0,
25.0,
50.0,
75.0,
90.0,
95.0,
99.0,
99.5,
99.9,
100.0
],
"computed_quantiles": [
{
"name":"h",
"values": [
{
"cumulative":200,
"interval":200
},
{
"cumulative":202.5,
"interval":202.5
},
{
"cumulative":205,
"interval":205
},
{
"cumulative":207.5,
"interval":207.5
},
{
"cumulative":209,
"interval":209
},
{
"cumulative":209.5,
"interval":209.5
},
{
"cumulative":209.9,
"interval":209.9
},
{
"cumulative":209.95,
"interval":209.95
},
{
"cumulative":209.99,
"interval":209.99
},
{
"cumulative":210,
"interval":210
}
]
},
]
}
}
]
})EOF";

EXPECT_THAT(expected_json_old, JsonStringEq(data.toString()));

shutdownThreading();
}

TEST_P(AdminStatsTest, StatsAsJson) {
InSequence s;
store_->initializeThreading(main_thread_dispatcher_, tls_);
Expand Down