Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[native] Expose REST API to fetch worker stats in Prometheus format #22360

Merged
merged 1 commit into from
Jul 3, 2024

Conversation

karteekmurthys
Copy link
Contributor

@karteekmurthys karteekmurthys commented Mar 28, 2024

Description

  1. Add Prometheus Reporter using the prometheus-cpp library. The library has interfaces to create all metric types (COUNTER, GAUGE, Histograms and Summaries) defined in the BaseStatsReporter. The library provides a Registry which can be used to hold the references toof the counters and a serializer interface that can convert the metrics into Prometheus Data Model.
  2. Add a CMake flag PRESTO_ENABLE_PROMETHEUS_REPORTER to enable Prometheus Reporter.
  3. Add REST API /v1/info/metrics to fetch the metrics from Prometheus Reporter in prometheus format. This endpoint is only enabled if the Prometheus Reporter is enabled.

Related RFC.

@karteekmurthys karteekmurthys self-assigned this Mar 28, 2024
@karteekmurthys karteekmurthys requested a review from a team as a code owner March 28, 2024 20:48
@karteekmurthys karteekmurthys force-pushed the prestissimo-metrics branch 3 times, most recently from f218fc1 to 6470454 Compare March 29, 2024 18:13
Copy link

github-actions bot commented Mar 29, 2024

Codenotify: Notifying subscribers in CODENOTIFY files for diff bccb13c...c73c5aa.

No notifications.

#include <gtest/gtest.h>

namespace facebook::presto::prometheus {
class PrometheusReporterTest : public testing::Test {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will not run in the CI since that flag is off by default, can we do anything else to make it run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may have to create a new CI job that builds code with prometheus enabled and run this test. Or, always build the code in existing CI jobs with prometheus enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just add a comment stating that this test is NoOp in CI for now

// the ::prometheus::Registry allows us to Add new metric object each time
// which overwrites the existing metric object in its internal mapping.
mutable CounterMap countersMap_;
mutable GaugeMap gaugeMap_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better to use folly::ConcurrentHashMaps directly instead of having a custom mutex? That way concurrency will be better since all maps do not need to share a single mutex

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to using Synchronous<> decorator.

mutable std::unordered_map<std::string, ::facebook::velox::StatType>
registeredMetrics_;
// @TODO: may be add StatType::HISTOGRAM to avoid this member.
mutable std::unordered_set<std::string> registeredHistogramMetrics_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this set? Can we just use histogramMap_ instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that we don't need it. I wanted to isolate the registered metrics Map which is modified once at startup from the actual metrics map (which can be modified concurrently).

// If percentiles are provided, create a Summary type metric and register.
if (pcts.size() > 0) {
auto& summaryFamily = ::prometheus::BuildSummary()
.Name(keyStr.append(kSummarySuffix))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wont the metric name changed if we add a ksummarySuffix? Why is this needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or is it so that maybe we don't need the histogram if pcts are present and we can just register summary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you check prometheus, Histograms and Summaries are 2 different metric types. If the incoming metric has quantiles (pcts) specified, then we use summary type as well.
https://prometheus.io/docs/concepts/metric_types/#histogram

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://prometheus.io/docs/practices/histograms/#:~:text=The%20essential%20difference%20between%20summaries,the%20server%20side%20using%20the
The essential difference between summaries and histograms is that summaries calculate streaming φ-quantiles on the client side and expose them directly, while histograms expose bucketed observation counts and the calculation of quantiles from the buckets of a histogram happens on the server side using the [histogram_quantile() function](https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile).
So maybe we do not need to register histogram if quantiles are present?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this^ just fyi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revisit this. Not sure at this point if both give different viewpoints or Summary is sufficient over histograms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool thats fine, just check the kSummarySuffix i think the code here should be keyStr + "_Suffix" by value and we should not change the reference here

case facebook::velox::StatType::SUM:
case facebook::velox::StatType::AVG:
case facebook::velox::StatType::RATE: {
auto& gaugeFamily =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely sure about the code style but if we don't write std::string as ::std::string then why should we write ::prometheus?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a required thing to resolve global namespace. Compiler complains if we remove this. I don't have an exact reason why we need it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karteekmurthys : Can you add "using namespace prometheus" statement to avoid this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
// Prometheus format requires '.' to be replaced.
std::string keyStr = std::string(key);
std::replace(keyStr.begin(), keyStr.end(), '.', '_');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the replacement of '.' being used in addMetricValue function do we need this there?

// If percentiles are provided, create a Summary type metric and register.
if (pcts.size() > 0) {
auto& summaryFamily = ::prometheus::BuildSummary()
.Name(keyStr.append(kSummarySuffix))
Copy link
Member

@jaystarshot jaystarshot Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have keyStr.append(kSummarySuffix) which means that the reference changed, then you have summaryMap_.emplace(std::string(key), summaryMetric);. So i think you are storing the key+suffix in the summary map

.Register(*registry_);
::prometheus::Summary::Quantiles quantiles;
for (auto pct : pcts) {
quantiles.push_back(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can just use quantiles.emplace_back(pct / (double)100, 0);

Copy link
Member

@jaystarshot jaystarshot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments + I think we need to setup a CI job for this (maybe make unittest with flag on just for this unit test temoporariliy)

Copy link
Contributor

@aditi-pandit aditi-pandit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @karteekmurthys. Only few minor comments.

// Initialize singleton for the reporter
folly::Singleton<facebook::velox::BaseStatsReporter> reporter([]() {
auto nodeConfig = facebook::presto::NodeConfig::instance();
std::string cluster = nodeConfig->nodeEnvironment();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const for cluster, hostName and worker variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -1093,6 +1109,10 @@ void PrestoServer::populateMemAndCPUInfo() {

// Fill global pool fields.
poolInfo.maxBytes = nodeMemoryGb * 1024 * 1024 * 1024;
// TODO(sperhsin): If 'current bytes' is the same as we get by summing up all
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit spelling spershin

@@ -1146,6 +1165,20 @@ void PrestoServer::reportServerInfo(proxygen::ResponseHandler* downstream) {
http::sendOkResponse(downstream, json(serverInfo));
}

void PrestoServer::reportWorkerMetrics(proxygen::ResponseHandler* downstream) {
auto nodeConfig = facebook::presto::NodeConfig::instance();
std::string cluster = nodeConfig->nodeEnvironment();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use const

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

char* hostName = std::getenv("HOSTNAME");
std::string worker = !hostName ? "" : hostName;
#ifdef PRESTO_ENABLE_PROMETHEUS_REPORTER
auto reporter = std::dynamic_pointer_cast<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe all variables above should be within #ifdef. Else it can be empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -328,6 +336,14 @@ void PrestoServer::run() {
proxygen::ResponseHandler* downstream) {
server->reportServerInfo(downstream);
});
httpServer_->registerGet(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this will always be registered irrespective of whether you are using Prometheus. Is that desired ? We could avoid it in the Meta environment where they are not using Prometheus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now it is registered and if the Prometheus is not enabled it is a no-op.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is odd to make it a no-op. It is better to return an invalid HTTP request error than an empty result. The later would be confusing.
Let's move the above if(systemConfig->enableRuntimeMetricsCollection()) check here and include this.

auto bound = min + bucketWidth;
std::string keyStr = std::string(key);
std::replace(keyStr.begin(), keyStr.end(), '.', '_');
auto& histogramFamily =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit : Add newline before

}

void PrometheusReporter::registerHistogramMetricExportType(
folly::StringPiece key,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not const& ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defined in interface.

"test_key4{" + labelsSerialized + "} 0"};

auto verifySerializedResult = [&](std::string& fullSerializedResult,
std::vector<std::string>& expected) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const &

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"# TYPE test_key4 gauge",
"test_key4{" + labelsSerialized + "} 0"};

auto verifySerializedResult = [&](std::string& fullSerializedResult,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel you should send a const& here and make a copy in the verify routine. It makes it more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"# TYPE test_key4 gauge",
"test_key4{" + labelsSerialized + "} 0"};

auto verifySerializedResult = [&](std::string& fullSerializedResult,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can even make this a separate function instead of a lambda

@karteekmurthys karteekmurthys force-pushed the prestissimo-metrics branch 4 times, most recently from 22c1820 to 12302f1 Compare April 27, 2024 05:54
Copy link
Contributor

@czentgr czentgr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments related to build integration.

Copy link
Collaborator

@majetideepak majetideepak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karteekmurthys some high-level comments.

presto-native-execution/presto_cpp/main/PrestoMain.cpp Outdated Show resolved Hide resolved
@@ -328,6 +336,14 @@ void PrestoServer::run() {
proxygen::ResponseHandler* downstream) {
server->reportServerInfo(downstream);
});
httpServer_->registerGet(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is odd to make it a no-op. It is better to return an invalid HTTP request error than an empty result. The later would be confusing.
Let's move the above if(systemConfig->enableRuntimeMetricsCollection()) check here and include this.

presto-native-execution/presto_cpp/main/PrestoServer.cpp Outdated Show resolved Hide resolved
presto-native-execution/presto_cpp/main/PrestoServer.cpp Outdated Show resolved Hide resolved
presto-native-execution/presto_cpp/main/PrestoServer.cpp Outdated Show resolved Hide resolved
@majetideepak
Copy link
Collaborator

@tanjialiang, @amitkdutta Can you make another pass? All your feedback has been addressed.

@majetideepak
Copy link
Collaborator

@amitkdutta, please import this PR and let us know if you see any other issues inside Meta. Thanks!

@facebook-github-bot
Copy link
Collaborator

@amitkdutta has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

Copy link
Contributor

@amitkdutta amitkdutta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me. The only issue is renaming the virtual function from enableRuntimeMetricReporting to enableWorkerStatsReporting, which we can fix inside Meta.

Thanks @karteekmurthys for all the iterations.

@majetideepak majetideepak changed the title [Native] Expose REST API to fetch runtime metrics in prometheus format [Native] Expose REST API to fetch worker stats in prometheus format Jun 29, 2024
@majetideepak
Copy link
Collaborator

majetideepak commented Jun 29, 2024

@karteekmurthys Let's fix the commit name to match the new naming
[Native] Expose REST API to fetch worker stats in Prometheus format
Let's also add the following description to the commit.

Add Prometheus Reporter using the prometheus-cpp library.
Add a CMake flag PRESTO_ENABLE_PROMETHEUS_REPORTER to enable Prometheus Reporter.
Add REST API '/v1/info/metrics' to fetch worker metrics from Prometheus Reporter in prometheus format.
This endpoint is only enabled if the Prometheus Reporter is enabled.

@majetideepak majetideepak changed the title [Native] Expose REST API to fetch worker stats in prometheus format [Native] Expose REST API to fetch worker stats in Prometheus format Jun 29, 2024
@karteekmurthys
Copy link
Contributor Author

@amitkdutta @tanjialiang Thanks for the review.

@majetideepak
Copy link
Collaborator

@tdcmeehan can you please approve this PR? The circleci change requires a committer approval. Thanks!

@lingbin
Copy link
Contributor

lingbin commented Jul 1, 2024

nit: Should use "[native]"(lowercase) as a tag instead of "[Native]".

@tdcmeehan
Copy link
Contributor

Should we add documentation on how to enable reporting?

@karteekmurthys
Copy link
Contributor Author

Should we add documentation on how to enable reporting?

I have opened another PR for that https://github.com/prestodb/presto/pull/23107/files.
We can iterate over documentation there.

Copy link
Contributor

@lingbin lingbin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking forward to this feature.

size_t value) const {
auto metricIterator = registeredMetricsMap_.find(key);
if (metricIterator == registeredMetricsMap_.end()) {
VLOG(1) << "addMetricValue for unregistered metric " << key;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addMetricValue -> addHistogramMetricValue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@karteekmurthys karteekmurthys changed the title [Native] Expose REST API to fetch worker stats in Prometheus format [native] Expose REST API to fetch worker stats in Prometheus format Jul 1, 2024
Add Prometheus Reporter using the prometheus-cpp library.
Add a CMake flag PRESTO_ENABLE_PROMETHEUS_REPORTER to enable Prometheus Reporter.
Add REST API '/v1/info/metrics' to fetch worker metrics from Prometheus Reporter in prometheus format.
This endpoint is only enabled if the Prometheus Reporter is enabled.

Co-authored-by:jaystarshot <jay.narale@uber.com>
@majetideepak majetideepak merged commit fd57488 into prestodb:master Jul 3, 2024
59 checks passed
@tdcmeehan tdcmeehan mentioned this pull request Aug 23, 2024
34 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants