diff --git a/tpu_raiden/telemetry/BUILD b/tpu_raiden/telemetry/BUILD new file mode 100644 index 00000000..789f4959 --- /dev/null +++ b/tpu_raiden/telemetry/BUILD @@ -0,0 +1,56 @@ +# Copyright 2026 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Copyright 2026 Google LLC + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +cc_library( + name = "metrics_api", + srcs = ["metrics_api.cc"], + hdrs = ["metrics_api.h"], + copts = [ + "-fno-strict-aliasing", + "-fexceptions", + ], + # -use_header_modules required due to -fexceptions mismatch with precompiled header modules. + features = ["-use_header_modules"], + deps = [ + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/log", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", + ], +) + +cc_test( + name = "metrics_api_test", + srcs = ["metrics_api_test.cc"], + copts = [ + "-fno-strict-aliasing", + "-fexceptions", + ], + # -use_header_modules required due to -fexceptions mismatch with precompiled header modules. + features = ["-use_header_modules"], + deps = [ + ":metrics_api", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/tpu_raiden/telemetry/metrics_api.cc b/tpu_raiden/telemetry/metrics_api.cc new file mode 100644 index 00000000..eebaf962 --- /dev/null +++ b/tpu_raiden/telemetry/metrics_api.cc @@ -0,0 +1,151 @@ +// Copyright 2026 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tpu_raiden/telemetry/metrics_api.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/log/log.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" + +namespace tpu_raiden::telemetry { + +RaidenMetricStore& RaidenMetricStore::GetGlobalMetricStore() { + static RaidenMetricStore* global_store = new RaidenMetricStore(); + return *global_store; +} + +void RaidenMetricStore::AddBackend( + std::shared_ptr backend) noexcept { + if (!backend) return; + absl::MutexLock lock(mutex_); + for (const auto& b : backends_) { + if (b == backend) return; + } + backends_.push_back(std::move(backend)); + has_backends_.store(true, std::memory_order_release); +} + +void RaidenMetricStore::ClearBackends() noexcept { + absl::MutexLock lock(mutex_); + backends_.clear(); + has_backends_.store(false, std::memory_order_release); +} + +bool RaidenMetricStore::HasBackends() const noexcept { + return has_backends_.load(std::memory_order_relaxed); +} + +void RaidenMetricStore::IncrementCounter(absl::string_view name, uint64_t val, + LabelSpan labels) noexcept { + if (!has_backends_.load(std::memory_order_relaxed)) return; + absl::MutexLock lock(mutex_); + if (backends_.empty()) return; + for (const auto& backend : backends_) { + try { + backend->IncrementCounter(name, val, labels); + } catch (const std::exception& e) { + LOG_EVERY_N_SEC(ERROR, 10) << "RaidenMetricStore::IncrementCounter " + "swallowed exception for metric '" + << name << "': " << e.what(); + } catch (...) { + LOG_EVERY_N_SEC(ERROR, 10) << "RaidenMetricStore::IncrementCounter " + "swallowed unknown exception for metric '" + << name << "'"; + } + } +} + +void RaidenMetricStore::SetGauge(absl::string_view name, int64_t val, + LabelSpan labels) noexcept { + if (!has_backends_.load(std::memory_order_relaxed)) return; + absl::MutexLock lock(mutex_); + if (backends_.empty()) return; + for (const auto& backend : backends_) { + try { + backend->SetGauge(name, val, labels); + } catch (const std::exception& e) { + LOG_EVERY_N_SEC(ERROR, 10) + << "RaidenMetricStore::SetGauge swallowed exception for metric '" + << name << "': " << e.what(); + } catch (...) { + LOG_EVERY_N_SEC(ERROR, 10) << "RaidenMetricStore::SetGauge swallowed " + "unknown exception for metric '" + << name << "'"; + } + } +} + +void RaidenMetricStore::ObserveHistogram(absl::string_view name, double val, + LabelSpan labels) noexcept { + if (!has_backends_.load(std::memory_order_relaxed)) return; + absl::MutexLock lock(mutex_); + if (backends_.empty()) return; + for (const auto& backend : backends_) { + try { + backend->ObserveHistogram(name, val, labels); + } catch (const std::exception& e) { + LOG_EVERY_N_SEC(ERROR, 10) << "RaidenMetricStore::ObserveHistogram " + "swallowed exception for metric '" + << name << "': " << e.what(); + } catch (...) { + LOG_EVERY_N_SEC(ERROR, 10) << "RaidenMetricStore::ObserveHistogram " + "swallowed unknown exception for metric '" + << name << "'"; + } + } +} + +std::string RaidenMetricStore::GetTextSnapshot() { + if (!has_backends_.load(std::memory_order_relaxed)) return ""; + absl::MutexLock lock(mutex_); + std::string result; + for (const auto& backend : backends_) { + try { + absl::StrAppend(&result, backend->GetTextSnapshot()); + } catch (const std::exception& e) { + LOG_EVERY_N_SEC(ERROR, 10) + << "RaidenMetricStore::GetTextSnapshot swallowed exception: " + << e.what(); + } catch (...) { + LOG_EVERY_N_SEC(ERROR, 10) + << "RaidenMetricStore::GetTextSnapshot swallowed unknown exception"; + } + } + return result; +} + +} // namespace tpu_raiden::telemetry diff --git a/tpu_raiden/telemetry/metrics_api.h b/tpu_raiden/telemetry/metrics_api.h new file mode 100644 index 00000000..82b42840 --- /dev/null +++ b/tpu_raiden/telemetry/metrics_api.h @@ -0,0 +1,126 @@ +// Copyright 2026 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_TPU_RAIDEN_TPU_RAIDEN_TELEMETRY_METRICS_API_H_ +#define THIRD_PARTY_TPU_RAIDEN_TPU_RAIDEN_TELEMETRY_METRICS_API_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/base/optimization.h" +#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 { + +namespace metrics { +inline constexpr absl::string_view kMetricSentBytesTotal = + "tpu_raiden_sent_bytes_total"; +inline constexpr absl::string_view kMetricReceivedBytesTotal = + "tpu_raiden_received_bytes_total"; +inline constexpr absl::string_view kMetricTransferDurationSeconds = + "tpu_raiden_transfer_duration_seconds"; +inline constexpr absl::string_view kMetricStageLatencySeconds = + "tpu_raiden_stage_latency_seconds"; +inline constexpr absl::string_view kMetricActiveTransfers = + "tpu_raiden_active_transfers"; +inline constexpr absl::string_view kMetricBufferOccupancyBytes = + "tpu_raiden_buffer_occupancy_bytes"; +inline constexpr absl::string_view kMetricTransferFailuresTotal = + "tpu_raiden_transfer_failures_total"; +} // namespace metrics + +// Allocation-free label view span type definition +using LabelSpan = + absl::Span>; + +// Abstract Dual-Backend Interface +class MetricsBackend { + public: + virtual ~MetricsBackend() = default; + + virtual void IncrementCounter(absl::string_view name, uint64_t val = 1, + LabelSpan labels = {}) = 0; + + virtual void SetGauge(absl::string_view name, int64_t val, + LabelSpan labels = {}) = 0; + + virtual void ObserveHistogram(absl::string_view name, double val, + LabelSpan labels = {}) = 0; + + virtual std::string GetTextSnapshot() = 0; +}; + +// Central Telemetry Facade with Multi-Backend Support & Fast-Path Exit +class RaidenMetricStore { + public: + static RaidenMetricStore& GetGlobalMetricStore(); + + RaidenMetricStore() = default; + ~RaidenMetricStore() = default; + + RaidenMetricStore(const RaidenMetricStore&) = delete; + RaidenMetricStore& operator=(const RaidenMetricStore&) = delete; + + // Register one or more backends + void AddBackend(std::shared_ptr backend) noexcept; + + // Clear backends to disable telemetry instantly + void ClearBackends() noexcept; + + // Returns whether any backends are registered + bool HasBackends() const noexcept; + + // Telemetry API methods + void IncrementCounter(absl::string_view name, uint64_t val = 1, + LabelSpan labels = {}) noexcept; + + void SetGauge(absl::string_view name, int64_t val, + LabelSpan labels = {}) noexcept; + + void ObserveHistogram(absl::string_view name, double val, + LabelSpan labels = {}) noexcept; + + std::string GetTextSnapshot(); + + private: + mutable absl::Mutex mutex_; + std::vector> backends_ + ABSL_GUARDED_BY(mutex_); + std::atomic has_backends_{false}; +}; + +} // namespace tpu_raiden::telemetry + +#endif // THIRD_PARTY_TPU_RAIDEN_TPU_RAIDEN_TELEMETRY_METRICS_API_H_ diff --git a/tpu_raiden/telemetry/metrics_api_test.cc b/tpu_raiden/telemetry/metrics_api_test.cc new file mode 100644 index 00000000..9bd69407 --- /dev/null +++ b/tpu_raiden/telemetry/metrics_api_test.cc @@ -0,0 +1,161 @@ +// Copyright 2026 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tpu_raiden/telemetry/metrics_api.h" + +#include +#include +#include +#include +#include + +#include +#include "absl/strings/string_view.h" + +namespace tpu_raiden::telemetry { +namespace { + +class MockBackend : public MetricsBackend { + public: + struct Record { + std::string type; + std::string name; + double value; + }; + + void IncrementCounter(absl::string_view name, uint64_t val = 1, + LabelSpan labels = {}) override { + records.push_back({"counter", std::string(name), static_cast(val)}); + } + + void SetGauge(absl::string_view name, int64_t val, + LabelSpan labels = {}) override { + records.push_back({"gauge", std::string(name), static_cast(val)}); + } + + void ObserveHistogram(absl::string_view name, double val, + LabelSpan labels = {}) override { + records.push_back({"histogram", std::string(name), val}); + } + + std::string GetTextSnapshot() override { return "# HELP mock\n"; } + + std::vector records; +}; + +class ThrowingBackend : public MetricsBackend { + public: + void IncrementCounter(absl::string_view name, uint64_t val = 1, + LabelSpan labels = {}) override { + throw std::runtime_error("Simulated counter backend failure"); + } + + void SetGauge(absl::string_view name, int64_t val, + LabelSpan labels = {}) override { + throw std::runtime_error("Simulated gauge backend failure"); + } + + void ObserveHistogram(absl::string_view name, double val, + LabelSpan labels = {}) override { + throw std::runtime_error("Simulated histogram backend failure"); + } + + std::string GetTextSnapshot() override { + throw std::runtime_error("Simulated text snapshot failure"); + } +}; + +class MetricsApiTest : public ::testing::Test { + protected: + RaidenMetricStore store_; +}; + +TEST_F(MetricsApiTest, GlobalMetricStoreSingleton) { + auto& global1 = RaidenMetricStore::GetGlobalMetricStore(); + auto& global2 = RaidenMetricStore::GetGlobalMetricStore(); + EXPECT_EQ(&global1, &global2); +} + +TEST_F(MetricsApiTest, FastPathExitWhenNoBackends) { + EXPECT_FALSE(store_.HasBackends()); + + store_.IncrementCounter(metrics::kMetricSentBytesTotal, 1024); + store_.SetGauge(metrics::kMetricActiveTransfers, 5); + store_.ObserveHistogram(metrics::kMetricTransferDurationSeconds, 0.0125); +} + +TEST_F(MetricsApiTest, DispatchesToRegisteredBackend) { + auto mock_backend = std::make_shared(); + + store_.AddBackend(mock_backend); + EXPECT_TRUE(store_.HasBackends()); + + store_.IncrementCounter(metrics::kMetricSentBytesTotal, 2048); + ASSERT_EQ(mock_backend->records.size(), 1); + EXPECT_EQ(mock_backend->records[0].name, metrics::kMetricSentBytesTotal); + EXPECT_DOUBLE_EQ(mock_backend->records[0].value, 2048.0); + + store_.SetGauge(metrics::kMetricActiveTransfers, 3); + ASSERT_EQ(mock_backend->records.size(), 2); + EXPECT_EQ(mock_backend->records[1].name, metrics::kMetricActiveTransfers); + EXPECT_DOUBLE_EQ(mock_backend->records[1].value, 3.0); + + store_.ObserveHistogram(metrics::kMetricTransferDurationSeconds, 0.005); + ASSERT_EQ(mock_backend->records.size(), 3); + EXPECT_EQ(mock_backend->records[2].name, + metrics::kMetricTransferDurationSeconds); + EXPECT_DOUBLE_EQ(mock_backend->records[2].value, 0.005); +} + +TEST_F(MetricsApiTest, ClearBackendsResetsFastPath) { + auto mock_backend = std::make_shared(); + + store_.AddBackend(mock_backend); + EXPECT_TRUE(store_.HasBackends()); + + store_.ClearBackends(); + EXPECT_FALSE(store_.HasBackends()); + + store_.IncrementCounter(metrics::kMetricReceivedBytesTotal, 1); + EXPECT_EQ(mock_backend->records.size(), 0); +} + +TEST_F(MetricsApiTest, SwallowsBackendExceptions) { + auto throwing_backend = std::make_shared(); + store_.AddBackend(throwing_backend); + + EXPECT_NO_THROW( + store_.IncrementCounter(metrics::kMetricSentBytesTotal, 1024)); + EXPECT_NO_THROW(store_.SetGauge(metrics::kMetricActiveTransfers, 5)); + EXPECT_NO_THROW( + store_.ObserveHistogram(metrics::kMetricTransferDurationSeconds, 0.05)); + EXPECT_NO_THROW(store_.GetTextSnapshot()); +} + +} // namespace +} // namespace tpu_raiden::telemetry