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
18 changes: 15 additions & 3 deletions tpu_sync/telemetry/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ cc_library(
name = "metrics_backend",
hdrs = ["metrics_backend.h"],
deps = [
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/types:span",
],
)
Expand All @@ -46,6 +44,12 @@ cc_library(
],
)

cc_library(
name = "test_util",
testonly = True,
hdrs = ["test_util.h"],
)

cc_library(
name = "mock_metrics_backend",
testonly = True,
Expand All @@ -65,6 +69,7 @@ cc_test(
":metrics_api",
":metrics_backend",
":mock_metrics_backend",
":test_util",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:status_matchers",
"@com_google_absl//absl/strings",
Expand All @@ -76,11 +81,17 @@ cc_library(
name = "metrics_3p_prometheus_exporter",
srcs = ["prometheus_exporter.cc"],
hdrs = ["prometheus_exporter.h"],
# -fexceptions is required because prometheus-cpp Exposer throws C++
# exceptions (std::runtime_error) on socket binding and initialization failure.
copts = ["-fexceptions"],
# Disables Clang header modules due to incompatibility with prometheus-cpp / CivetWeb headers.
features = ["-use_header_modules"],
deps = [
":metrics_backend",
"@com_github_jupp0r_prometheus_cpp//core",
"@com_google_absl//absl/base:no_destructor",
"@com_github_jupp0r_prometheus_cpp//pull",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/log",
"@com_google_absl//absl/strings",
],
)
Expand All @@ -91,6 +102,7 @@ cc_test(
deps = [
":metrics_3p_prometheus_exporter",
":metrics_backend",
":test_util",
"@com_github_jupp0r_prometheus_cpp//core",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
Expand Down
36 changes: 35 additions & 1 deletion tpu_sync/telemetry/metrics_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/ascii.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
Expand All @@ -39,6 +40,35 @@

namespace tpu_raiden::telemetry {

namespace {

int ResolveExporterPort() {
const char* env_port = std::getenv(kPrometheusPortEnvVar);
if (env_port != nullptr && *env_port != '\0') {
int parsed_port = 0;
if (absl::SimpleAtoi(env_port, &parsed_port) && parsed_port >= kMinPort &&
parsed_port <= kMaxPort) {
return parsed_port;
}
LOG(WARNING) << "Invalid port specified in " << kPrometheusPortEnvVar
<< ": '" << env_port << "'. Expected integer in range ["
<< kMinPort << ", " << kMaxPort
<< "]. Falling back to default port (" << kDefaultExporterPort
<< ").";
}
return kDefaultExporterPort;
}

std::string ResolveExporterHost() {
const char* env_host = std::getenv(kPrometheusHostEnvVar);
if (env_host != nullptr && *env_host != '\0') {
return std::string(env_host);
}
return std::string(kDefaultExporterHost);
}

} // namespace

RaidenMetricStore& RaidenMetricStore::GetGlobalMetricStore() {
static absl::NoDestructor<RaidenMetricStore> global_store;
static const bool initialized = [&] {
Expand Down Expand Up @@ -80,7 +110,11 @@ absl::Status RaidenMetricStore::InitializeFromBackendNames(
continue;
}
if (name == kPrometheus) {
new_backends.push_back(std::make_unique<PrometheusExporter>());
new_backends.push_back(
std::make_unique<PrometheusExporter>(ExporterOptions{
.bind_address = ResolveExporterHost(),
.port = ResolveExporterPort(),
}));
} else if (name == kBuffered) {
new_backends.push_back(std::make_unique<BufferedMetricsExporter>());
} else {
Expand Down
2 changes: 2 additions & 0 deletions tpu_sync/telemetry/metrics_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ namespace tpu_raiden::telemetry {
// Environment Variables START.
inline constexpr char kTelemetryBackendsEnvVar[] =
"TPU_RAIDEN_TELEMETRY_BACKENDS";
inline constexpr char kPrometheusPortEnvVar[] = "TPU_RAIDEN_PROMETHEUS_PORT";
inline constexpr char kPrometheusHostEnvVar[] = "TPU_RAIDEN_PROMETHEUS_HOST";
// Environment Variables END.

// Backend names START.
Expand Down
33 changes: 33 additions & 0 deletions tpu_sync/telemetry/metrics_api_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
#include <gtest/gtest.h>
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tpu_sync/telemetry/metrics_backend.h"
#include "tpu_sync/telemetry/test_util.h"
#include "tpu_sync/telemetry/mock_metrics_backend.h"

namespace tpu_raiden::telemetry {
Expand Down Expand Up @@ -659,5 +661,36 @@ TEST_F(MetricsApiTest, InitializeFromEnvironmentNoOpIfAlreadyInitialized) {
EXPECT_TRUE(store_.HasBackends());
}

TEST_F(MetricsApiTest, InitializeWithPrometheusPortEnvironmentVariable) {
int port = PickUnusedPort();
std::string port_str = absl::StrCat(port);
ScopedEnvironmentVariable port_env(kPrometheusPortEnvVar, port_str.c_str());
EXPECT_THAT(store_.InitializeFromBackendNames({"prometheus"}), IsOk());
EXPECT_TRUE(store_.HasBackends());
}

TEST_F(MetricsApiTest, InitializeWithPrometheusHostEnvironmentVariable) {
int port = PickUnusedPort();
std::string port_str = absl::StrCat(port);
ScopedEnvironmentVariable port_env(kPrometheusPortEnvVar, port_str.c_str());
ScopedEnvironmentVariable host_env(kPrometheusHostEnvVar, "127.0.0.1");
EXPECT_THAT(store_.InitializeFromBackendNames({"prometheus"}), IsOk());
EXPECT_TRUE(store_.HasBackends());
}

TEST_F(MetricsApiTest, InitializeWithInvalidPrometheusPortFallsBackGracefully) {
ScopedEnvironmentVariable invalid_port_env(kPrometheusPortEnvVar,
"invalid_port");
EXPECT_THAT(store_.InitializeFromBackendNames({"prometheus"}), IsOk());
EXPECT_TRUE(store_.HasBackends());
}

TEST_F(MetricsApiTest,
InitializeWithOutOfRangePrometheusPortFallsBackGracefully) {
ScopedEnvironmentVariable out_of_range_env(kPrometheusPortEnvVar, "99999");
EXPECT_THAT(store_.InitializeFromBackendNames({"prometheus"}), IsOk());
EXPECT_TRUE(store_.HasBackends());
}

} // namespace
} // namespace tpu_raiden::telemetry
19 changes: 16 additions & 3 deletions tpu_sync/telemetry/metrics_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,10 @@

#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"

namespace tpu_raiden::telemetry {
Expand All @@ -39,6 +36,22 @@ inline constexpr double kDefaultHistogramBuckets[] = {
25.0, 50.0, 100.0, 250.0, 500.0, 750.0, 1000.0,
2500.0, 5000.0, 7500.0, 10000.0, 25000.0, 50000.0};

inline constexpr int kDefaultExporterPort = 0;
inline constexpr absl::string_view kDefaultExporterHost = "0.0.0.0";
inline constexpr int kMinPort = 1;
inline constexpr int kMaxPort = 65535;

// Configuration options for metric exporters.
struct ExporterOptions {
// Bind address for HTTP metric exporter. Defaults to "0.0.0.0".
std::string bind_address{kDefaultExporterHost};
// TCP port for HTTP metric exporter. If <= 0, HTTP serving is disabled.
int port = kDefaultExporterPort;
// Non-owning view of histogram bucket boundaries. Defaults to
// kDefaultHistogramBuckets and is copied by the exporter during construction.
absl::Span<const double> custom_buckets = kDefaultHistogramBuckets;
};

// Structure defining centralized metadata for a Raiden metric.
struct MetricMetadata {
absl::string_view name;
Expand Down
41 changes: 38 additions & 3 deletions tpu_sync/telemetry/prometheus_exporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@
#include "tpu_sync/telemetry/prometheus_exporter.h"

#include <cstdint>
#include <exception>
#include <map> // NOLINT: Required by prometheus-cpp client API.
#include <memory>
#include <string>
#include <utility>

#include "prometheus/counter.h"
#include "prometheus/exposer.h"
#include "prometheus/family.h"
#include "prometheus/gauge.h"
#include "prometheus/histogram.h"
#include "prometheus/registry.h"
#include "prometheus/text_serializer.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/log.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tpu_sync/telemetry/metrics_backend.h"
Expand All @@ -48,6 +52,13 @@ std::map<std::string, std::string> ConvertLabels(LabelSpan labels) {
return result;
}

std::string JoinHostPort(absl::string_view host, int port) {
if (absl::StrContains(host, ':') && !absl::StartsWith(host, "[")) {
return absl::StrCat("[", host, "]:", port);
}
return absl::StrCat(host, ":", port);
}

} // namespace

void PrometheusExporter::RegisterKnownFamilies() {
Expand Down Expand Up @@ -87,13 +98,37 @@ void PrometheusExporter::RegisterKnownFamilies() {
}
}

PrometheusExporter::PrometheusExporter(
const prometheus::Histogram::BucketBoundaries& custom_buckets)
PrometheusExporter::PrometheusExporter(const ExporterOptions& options)
: registry_(std::make_shared<prometheus::Registry>()),
default_buckets_(custom_buckets) {
default_buckets_(options.custom_buckets.begin(),
options.custom_buckets.end()),
options_(options) {
RegisterKnownFamilies();

if (options_.port >= kMinPort && options_.port <= kMaxPort) {
std::string endpoint = JoinHostPort(options_.bind_address, options_.port);
// prometheus-cpp Exposer throws std::runtime_error on socket binding or
// initialization failure. Catching here prevents abnormal termination and
// allows graceful degradation with IsServerRunning() reporting false.
try {
exposer_ = std::make_unique<prometheus::Exposer>(endpoint);
exposer_->RegisterCollectable(registry_);
} catch (const std::exception& e) {
LOG(ERROR) << "Failed to start Prometheus HTTP exporter on " << endpoint
<< ": " << e.what();
exposer_.reset();
}
} else if (options_.port != 0) {
LOG(WARNING) << "Invalid port configured for Prometheus HTTP exporter: "
<< options_.port << ". Expected port in range [" << kMinPort
<< ", " << kMaxPort << "].";
}
}

PrometheusExporter::~PrometheusExporter() = default;

bool PrometheusExporter::IsServerRunning() const { return exposer_ != nullptr; }

prometheus::Family<prometheus::Counter>* PrometheusExporter::GetCounterFamily(
absl::string_view name) const {
auto it = counter_families_.find(name);
Expand Down
37 changes: 22 additions & 15 deletions tpu_sync/telemetry/prometheus_exporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,29 @@
#define THIRD_PARTY_TPU_RAIDEN_TPU_SYNC_TELEMETRY_PROMETHEUS_EXPORTER_H_

#include <cstdint>
#include <iterator>
#include <memory>
#include <string>
#include <vector>

#include "prometheus/counter.h"
#include "prometheus/family.h"
#include "prometheus/gauge.h"
#include "prometheus/histogram.h"
#include "prometheus/registry.h"
#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "tpu_sync/telemetry/metrics_backend.h"

namespace tpu_raiden::telemetry {
// Forward declaration of prometheus::Exposer.
// Note: "prometheus/exposer.h" transitively includes CivetWeb ("civetweb.h"),
// which requires -fexceptions and -use_header_modules. Forward-declaring
// Exposer here prevents forcing those compiler constraints onto any library
// that includes "prometheus_exporter.h".
namespace prometheus {
class Exposer;
} // namespace prometheus

inline const prometheus::Histogram::BucketBoundaries&
DefaultHistogramBuckets() {
static const absl::NoDestructor<prometheus::Histogram::BucketBoundaries>
kBuckets(prometheus::Histogram::BucketBoundaries(
std::begin(kDefaultHistogramBuckets),
std::end(kDefaultHistogramBuckets)));
return *kBuckets;
}
namespace tpu_raiden::telemetry {

// Custom MetricsBackend that formats and exports TPU Raiden metrics to
// prometheus-cpp.
Expand All @@ -53,10 +52,10 @@ DefaultHistogramBuckets() {
// metric families.
class PrometheusExporter : public MetricsBackend {
public:
explicit PrometheusExporter(const prometheus::Histogram::BucketBoundaries&
custom_buckets = DefaultHistogramBuckets());
explicit PrometheusExporter(
const ExporterOptions& options = ExporterOptions{});

~PrometheusExporter() override = default;
~PrometheusExporter() override;

PrometheusExporter(const PrometheusExporter&) = delete;
PrometheusExporter& operator=(const PrometheusExporter&) = delete;
Expand All @@ -74,6 +73,11 @@ class PrometheusExporter : public MetricsBackend {

std::string GetTextSnapshot() const override;

bool IsServerRunning() const;
// Returns the bound HTTP port if the server is running, or 0 if disabled or
// failed to bind. Useful for tests and runtime port inspection.
int GetBoundPort() const { return IsServerRunning() ? options_.port : 0; }

const std::shared_ptr<prometheus::Registry>& GetRegistry() const {
return registry_;
}
Expand All @@ -88,7 +92,10 @@ class PrometheusExporter : public MetricsBackend {
prometheus::Family<prometheus::Histogram>* GetHistogramFamily(
absl::string_view name) const;
std::shared_ptr<prometheus::Registry> registry_;
prometheus::Histogram::BucketBoundaries default_buckets_;
std::vector<double> default_buckets_;
ExporterOptions options_;

std::unique_ptr<prometheus::Exposer> exposer_;

absl::flat_hash_map<absl::string_view,
prometheus::Family<prometheus::Counter>*>
Expand Down
Loading
Loading