// Copyright 2024 The gRPC Authors. // // 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 GRPC_SRC_CORE_TELEMETRY_METRICS_H #define GRPC_SRC_CORE_TELEMETRY_METRICS_H #include #include #include #include #include "absl/functional/any_invocable.h" #include "absl/functional/function_ref.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" #include #include #include #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/gprpp/no_destruct.h" #include "src/core/lib/gprpp/sync.h" #include "src/core/lib/gprpp/time.h" #include "src/core/lib/slice/slice.h" #include "src/core/telemetry/call_tracer.h" namespace grpc_core { constexpr absl::string_view kMetricLabelTarget = "grpc.target"; // A global registry of instruments(metrics). This API is designed to be used // to register instruments (Counter, Histogram, and Gauge) as part of program // startup, before the execution of the main function (during dynamic // initialization time). Using this API after the main function begins may // result into missing instruments. This API is thread-unsafe. // // The registration of instruments is done through the templated // RegistrationBuilder API and gets back a handle with an opaque type. At // runtime, the handle should be used with the StatsPluginGroup API to record // metrics for the instruments. // // At dynamic initialization time: // const auto kMetricHandle = // GlobalInstrumentsRegistry::RegisterUInt64Counter( // "name", // "description", // "unit", /*enable_by_default=*/false) // .Labels(kLabel1, kLabel2, kLabel3) // .OptionalLabels(kOptionalLabel1, kOptionalLabel2) // .Build(); // // At runtime time: // stats_plugin_group.AddCounter(kMetricHandle, 1, // {"label_value_1", "label_value_2", "label_value_3"}, // {"optional_label_value_1", "optional_label_value_2"}); // class GlobalInstrumentsRegistry { public: enum class ValueType { kUndefined, kInt64, kUInt64, kDouble, }; enum class InstrumentType { kUndefined, kCounter, kHistogram, kCallbackGauge, }; using InstrumentID = uint32_t; struct GlobalInstrumentDescriptor { ValueType value_type; InstrumentType instrument_type; InstrumentID index; bool enable_by_default; absl::string_view name; absl::string_view description; absl::string_view unit; std::vector label_keys; std::vector optional_label_keys; }; struct GlobalInstrumentHandle { // This is the index for the corresponding registered instrument that // StatsPlugins can use to uniquely identify an instrument in the current // process. Though this is not guaranteed to be stable between different // runs or between different versions. InstrumentID index; }; template struct TypedGlobalInstrumentHandle : public GlobalInstrumentHandle {}; template class RegistrationBuilder { public: template RegistrationBuilder Labels(Args&&... args) { return RegistrationBuilder( name_, description_, unit_, enable_by_default_, std::array({args...}), optional_label_keys_); } template RegistrationBuilder OptionalLabels( Args&&... args) { return RegistrationBuilder( name_, description_, unit_, enable_by_default_, label_keys_, std::array({args...})); } TypedGlobalInstrumentHandle Build() { TypedGlobalInstrumentHandle handle; handle.index = RegisterInstrument(V, I, name_, description_, unit_, enable_by_default_, label_keys_, optional_label_keys_); return handle; } private: friend class GlobalInstrumentsRegistry; RegistrationBuilder(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) : name_(name), description_(description), unit_(unit), enable_by_default_(enable_by_default) {} RegistrationBuilder(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default, std::array label_keys, std::array optional_label_keys) : name_(name), description_(description), unit_(unit), enable_by_default_(enable_by_default), label_keys_(std::move(label_keys)), optional_label_keys_(std::move(optional_label_keys)) {} absl::string_view name_; absl::string_view description_; absl::string_view unit_; bool enable_by_default_; std::array label_keys_; std::array optional_label_keys_; }; // Creates instrument in the GlobalInstrumentsRegistry. static RegistrationBuilder RegisterUInt64Counter(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) { return RegistrationBuilder(name, description, unit, enable_by_default); } static RegistrationBuilder RegisterDoubleCounter(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) { return RegistrationBuilder(name, description, unit, enable_by_default); } static RegistrationBuilder RegisterUInt64Histogram(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) { return RegistrationBuilder(name, description, unit, enable_by_default); } static RegistrationBuilder RegisterDoubleHistogram(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) { return RegistrationBuilder(name, description, unit, enable_by_default); } static RegistrationBuilder RegisterCallbackInt64Gauge(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) { return RegistrationBuilder( name, description, unit, enable_by_default); } static RegistrationBuilder RegisterCallbackDoubleGauge(absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default) { return RegistrationBuilder( name, description, unit, enable_by_default); } static void ForEach( absl::FunctionRef f); static const GlobalInstrumentDescriptor& GetInstrumentDescriptor( GlobalInstrumentHandle handle); static absl::optional FindInstrumentByName(absl::string_view name); private: friend class GlobalInstrumentsRegistryTestPeer; GlobalInstrumentsRegistry() = delete; static std::vector& GetInstrumentList(); static InstrumentID RegisterInstrument( ValueType value_type, InstrumentType instrument_type, absl::string_view name, absl::string_view description, absl::string_view unit, bool enable_by_default, absl::Span label_keys, absl::Span optional_label_keys); }; // An interface for implementing callback-style metrics. // To be implemented by stats plugins. class CallbackMetricReporter { public: virtual ~CallbackMetricReporter() = default; template void Report( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle< GlobalInstrumentsRegistry::ValueType::kInt64, GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge, M, N> handle, int64_t value, std::array label_values, std::array optional_values) { ReportInt64(handle, value, label_values, optional_values); } template void Report( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle< GlobalInstrumentsRegistry::ValueType::kDouble, GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge, M, N> handle, double value, std::array label_values, std::array optional_values) { ReportDouble(handle, value, label_values, optional_values); } private: virtual void ReportInt64( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle, int64_t value, absl::Span label_values, absl::Span optional_values) = 0; virtual void ReportDouble( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle, double value, absl::Span label_values, absl::Span optional_values) = 0; }; class RegisteredMetricCallback; // The StatsPlugin interface. class StatsPlugin { public: // A general-purpose way for stats plugin to store per-channel or per-server // state. class ScopeConfig { public: virtual ~ScopeConfig() = default; }; virtual ~StatsPlugin() = default; // Whether this stats plugin is enabled for the channel specified by \a scope. // Returns true and a channel-specific ScopeConfig which may then be used to // configure the ClientCallTracer in GetClientCallTracer(). virtual std::pair> IsEnabledForChannel( const experimental::StatsPluginChannelScope& scope) const = 0; // Whether this stats plugin is enabled for the server specified by \a args. // Returns true and a server-specific ScopeConfig which may then be used to // configure the ServerCallTracer in GetServerCallTracer(). virtual std::pair> IsEnabledForServer( const ChannelArgs& args) const = 0; // Gets a scope config for the client channel specified by \a scope. Note that // the stats plugin should have been enabled for the channel. virtual std::shared_ptr GetChannelScopeConfig( const experimental::StatsPluginChannelScope& scope) const = 0; // Gets a scope config for the server specified by \a args. Note that the // stats plugin should have been enabled for the server. virtual std::shared_ptr GetServerScopeConfig( const ChannelArgs& args) const = 0; // Adds \a value to the uint64 counter specified by \a handle. \a label_values // and \a optional_label_values specify attributes that are associated with // this measurement and must match with their corresponding keys in // GlobalInstrumentsRegistry::RegisterUInt64Counter(). virtual void AddCounter( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle, uint64_t value, absl::Span label_values, absl::Span optional_label_values) = 0; // Adds \a value to the double counter specified by \a handle. \a label_values // and \a optional_label_values specify attributes that are associated with // this measurement and must match with their corresponding keys in // GlobalInstrumentsRegistry::RegisterDoubleCounter(). virtual void AddCounter( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle, double value, absl::Span label_values, absl::Span optional_label_values) = 0; // Records a uint64 \a value to the histogram specified by \a handle. \a // label_values and \a optional_label_values specify attributes that are // associated with this measurement and must match with their corresponding // keys in GlobalInstrumentsRegistry::RegisterUInt64Histogram(). virtual void RecordHistogram( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle, uint64_t value, absl::Span label_values, absl::Span optional_label_values) = 0; // Records a double \a value to the histogram specified by \a handle. \a // label_values and \a optional_label_values specify attributes that are // associated with this measurement and must match with their corresponding // keys in GlobalInstrumentsRegistry::RegisterDoubleHistogram(). virtual void RecordHistogram( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle, double value, absl::Span label_values, absl::Span optional_label_values) = 0; // Adds a callback to be invoked when the stats plugin wants to // populate the corresponding metrics (see callback->metrics() for list). virtual void AddCallback(RegisteredMetricCallback* callback) = 0; // Removes a callback previously added via AddCallback(). The stats // plugin may not use the callback after this method returns. virtual void RemoveCallback(RegisteredMetricCallback* callback) = 0; // Returns true if instrument \a handle is enabled. virtual bool IsInstrumentEnabled( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle) const = 0; // Gets a ClientCallTracer associated with this stats plugin which can be used // in a call. virtual ClientCallTracer* GetClientCallTracer( const Slice& path, bool registered_method, std::shared_ptr scope_config) = 0; // Gets a ServerCallTracer associated with this stats plugin which can be used // in a call. virtual ServerCallTracer* GetServerCallTracer( std::shared_ptr scope_config) = 0; // TODO(yijiem): This is an optimization for the StatsPlugin to create its own // representation of the label_values and use it multiple times. We would // change AddCounter and RecordHistogram to take RefCountedPtr // and also change the StatsPluginsGroup to support this. // Use the StatsPlugin to get a representation of label values that can be // saved for multiple uses later. // virtual RefCountedPtr MakeLabelValueSet( // absl::Span label_values) = 0; }; // A global registry of stats plugins. It has shared ownership to the registered // stats plugins. This API is supposed to be used during runtime after the main // function begins. This API is thread-safe. class GlobalStatsPluginRegistry { public: // A stats plugin group object is how the code in gRPC normally interacts with // stats plugins. They got a stats plugin group which contains all the stats // plugins for a specific scope and all operations on the stats plugin group // will be applied to all the stats plugins within the group. class StatsPluginGroup { public: // Adds a stats plugin and a scope config (per-channel or per-server) to the // group. void AddStatsPlugin(std::shared_ptr plugin, std::shared_ptr config) { PluginState plugin_state; plugin_state.plugin = std::move(plugin); plugin_state.scope_config = std::move(config); plugins_state_.push_back(std::move(plugin_state)); } // Adds a counter in all stats plugins within the group. See the StatsPlugin // interface for more documentation and valid types. template void AddCounter( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle< GlobalInstrumentsRegistry::ValueType::kUInt64, GlobalInstrumentsRegistry::InstrumentType::kCounter, M, N> handle, uint64_t value, std::array label_values, std::array optional_values) { for (auto& state : plugins_state_) { state.plugin->AddCounter(handle, value, label_values, optional_values); } } template void AddCounter( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle< GlobalInstrumentsRegistry::ValueType::kDouble, GlobalInstrumentsRegistry::InstrumentType::kCounter, M, N> handle, double value, std::array label_values, std::array optional_values) { for (auto& state : plugins_state_) { state.plugin->AddCounter(handle, value, label_values, optional_values); } } // Records a value to a histogram in all stats plugins within the group. See // the StatsPlugin interface for more documentation and valid types. template void RecordHistogram( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle< GlobalInstrumentsRegistry::ValueType::kUInt64, GlobalInstrumentsRegistry::InstrumentType::kHistogram, M, N> handle, uint64_t value, std::array label_values, std::array optional_values) { for (auto& state : plugins_state_) { state.plugin->RecordHistogram(handle, value, label_values, optional_values); } } template void RecordHistogram( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle< GlobalInstrumentsRegistry::ValueType::kDouble, GlobalInstrumentsRegistry::InstrumentType::kHistogram, M, N> handle, double value, std::array label_values, std::array optional_values) { for (auto& state : plugins_state_) { state.plugin->RecordHistogram(handle, value, label_values, optional_values); } } // Returns true if any of the stats plugins in the group have enabled \a // handle. bool IsInstrumentEnabled( GlobalInstrumentsRegistry::GlobalInstrumentHandle handle) const { for (auto& state : plugins_state_) { if (state.plugin->IsInstrumentEnabled(handle)) { return true; } } return false; } // Registers a callback to be used to populate callback metrics. // The callback will update the specified metrics. The callback // will be invoked no more often than min_interval. Multiple callbacks may // be registered for the same metrics, as long as no two callbacks report // data for the same set of labels in which case the behavior is undefined. // // The returned object is a handle that allows the caller to control // the lifetime of the callback; when the returned object is // destroyed, the callback is de-registered. The returned object // must not outlive the StatsPluginGroup object that created it. template GRPC_MUST_USE_RESULT std::unique_ptr RegisterCallback(absl::AnyInvocable callback, Duration min_interval, Args... args) { AssertIsCallbackGaugeHandle(args...); return std::make_unique( *this, std::move(callback), std::vector{ args...}, min_interval); } // Adds all available client call tracers associated with the stats plugins // within the group to \a call_context. void AddClientCallTracers(const Slice& path, bool registered_method, Arena* arena); // Adds all available server call tracers associated with the stats plugins // within the group to \a call_context. void AddServerCallTracers(Arena* arena); private: friend class RegisteredMetricCallback; struct PluginState { std::shared_ptr scope_config; std::shared_ptr plugin; }; // C++17 has fold expression that may simplify this. template static constexpr void AssertIsCallbackGaugeHandle( GlobalInstrumentsRegistry::TypedGlobalInstrumentHandle) { static_assert(V == GlobalInstrumentsRegistry::ValueType::kInt64 || V == GlobalInstrumentsRegistry::ValueType::kDouble, "ValueType must be kInt64 or kDouble"); static_assert( I == GlobalInstrumentsRegistry::InstrumentType::kCallbackGauge, "InstrumentType must be kCallbackGauge"); } template static constexpr void AssertIsCallbackGaugeHandle(T t, Args&&... args) { AssertIsCallbackGaugeHandle(t); AssertIsCallbackGaugeHandle(args...); } std::vector plugins_state_; }; // Registers a stats plugin with the global stats plugin registry. static void RegisterStatsPlugin(std::shared_ptr plugin); // The following functions can be invoked to get a StatsPluginGroup for // a specified scope. static StatsPluginGroup GetStatsPluginsForChannel( const experimental::StatsPluginChannelScope& scope); static StatsPluginGroup GetStatsPluginsForServer(const ChannelArgs& args); private: friend class GlobalStatsPluginRegistryTestPeer; GlobalStatsPluginRegistry() = default; static NoDestruct mutex_; static NoDestruct>> plugins_ ABSL_GUARDED_BY(mutex_); }; // A metric callback that is registered with a stats plugin group. class RegisteredMetricCallback { public: RegisteredMetricCallback( GlobalStatsPluginRegistry::StatsPluginGroup& stats_plugin_group, absl::AnyInvocable callback, std::vector metrics, Duration min_interval); ~RegisteredMetricCallback(); // Invokes the callback. The callback will report metric data via reporter. void Run(CallbackMetricReporter& reporter) { callback_(reporter); } // Returns the set of metrics that this callback will modify. const std::vector& metrics() const { return metrics_; } // Returns the minimum interval at which a stats plugin may invoke the // callback. Duration min_interval() const { return min_interval_; } private: GlobalStatsPluginRegistry::StatsPluginGroup& stats_plugin_group_; absl::AnyInvocable callback_; std::vector metrics_; Duration min_interval_; }; } // namespace grpc_core #endif // GRPC_SRC_CORE_TELEMETRY_METRICS_H