[Metrics] gRPC Non-Per-Call Metrics framework implementation (#35871)

<!--

If you know who should review your pull request, please assign it to that
person, otherwise the pull request would get assigned randomly.

If your pull request is for a specific language, please add the appropriate
lang label.

-->

Closes #35871

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/35871 from yijiem:grpc-metrics 86ebe484ae
PiperOrigin-RevId: 609533796
pull/35980/head
Yijie Ma 11 months ago committed by Copybara-Service
parent 5838f6840a
commit fb4c043803
  1. 44
      CMakeLists.txt
  2. 13
      build_autogenerated.yaml
  3. 22
      src/core/BUILD
  4. 188
      src/core/lib/channel/metrics.cc
  5. 231
      src/core/lib/channel/metrics.h
  6. 15
      test/core/channel/BUILD
  7. 528
      test/core/channel/metrics_test.cc
  8. 24
      tools/run_tests/generated/tests.json

44
CMakeLists.txt generated

@ -1177,6 +1177,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx message_compress_test)
add_dependencies(buildtests_cxx message_size_service_config_test)
add_dependencies(buildtests_cxx metadata_map_test)
add_dependencies(buildtests_cxx metrics_test)
add_dependencies(buildtests_cxx minimal_stack_is_minimal_test)
add_dependencies(buildtests_cxx miscompile_with_no_unique_address_test)
add_dependencies(buildtests_cxx mock_stream_test)
@ -19602,6 +19603,49 @@ target_link_libraries(metadata_map_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(metrics_test
src/core/lib/channel/metrics.cc
test/core/channel/metrics_test.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(metrics_test
PRIVATE
"GPR_DLL_IMPORTS"
"GRPC_DLL_IMPORTS"
)
endif()
endif()
target_compile_features(metrics_test PUBLIC cxx_std_14)
target_include_directories(metrics_test
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
${_gRPC_RE2_INCLUDE_DIR}
${_gRPC_SSL_INCLUDE_DIR}
${_gRPC_UPB_GENERATED_DIR}
${_gRPC_UPB_GRPC_GENERATED_DIR}
${_gRPC_UPB_INCLUDE_DIR}
${_gRPC_XXHASH_INCLUDE_DIR}
${_gRPC_ZLIB_INCLUDE_DIR}
third_party/googletest/googletest/include
third_party/googletest/googletest
third_party/googletest/googlemock/include
third_party/googletest/googlemock
${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(metrics_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
grpc_test_util
)
endif()
if(gRPC_BUILD_TESTS)

@ -12797,6 +12797,19 @@ targets:
deps:
- gtest
- grpc_test_util
- name: metrics_test
gtest: true
build: test
language: c++
headers:
- src/core/lib/channel/metrics.h
src:
- src/core/lib/channel/metrics.cc
- test/core/channel/metrics_test.cc
deps:
- gtest
- grpc_test_util
uses_polling: false
- name: minimal_stack_is_minimal_test
gtest: true
build: test

@ -7437,6 +7437,28 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "metrics",
srcs = [
"lib/channel/metrics.cc",
],
hdrs = [
"lib/channel/metrics.h",
],
external_deps = [
"absl/container:flat_hash_map",
"absl/functional:function_ref",
"absl/strings",
"absl/types:span",
],
language = "c++",
deps = [
"channel_args",
"no_destruct",
"//:gpr",
],
)
### UPB Targets
grpc_upb_proto_library(

@ -0,0 +1,188 @@
// 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.
#include <grpc/support/port_platform.h>
#include "src/core/lib/channel/metrics.h"
#include "absl/container/flat_hash_map.h"
#include "src/core/lib/gprpp/crash.h"
namespace grpc_core {
namespace {
// Uses the Construct-on-First-Use idiom to avoid the static initialization
// order fiasco.
absl::flat_hash_map<absl::string_view,
GlobalInstrumentsRegistry::GlobalInstrumentDescriptor>&
GetInstrumentList() {
static NoDestruct<absl::flat_hash_map<
absl::string_view, GlobalInstrumentsRegistry::GlobalInstrumentDescriptor>>
instruments;
return *instruments;
}
} // namespace
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle
GlobalInstrumentsRegistry::RegisterUInt64Counter(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default) {
auto& instruments = GetInstrumentList();
if (instruments.find(name) != instruments.end()) {
Crash(absl::StrFormat("Metric name %s has already been registered.", name));
}
uint32_t index = instruments.size();
GPR_ASSERT(index < std::numeric_limits<uint32_t>::max());
GlobalInstrumentDescriptor descriptor;
descriptor.value_type = ValueType::kUInt64;
descriptor.instrument_type = InstrumentType::kCounter;
descriptor.index = index;
descriptor.enable_by_default = enable_by_default;
descriptor.name = name;
descriptor.description = description;
descriptor.unit = unit;
descriptor.label_keys = {label_keys.begin(), label_keys.end()};
descriptor.optional_label_keys = {optional_label_keys.begin(),
optional_label_keys.end()};
instruments.emplace(name, std::move(descriptor));
GlobalUInt64CounterHandle handle;
handle.index = index;
return handle;
}
GlobalInstrumentsRegistry::GlobalDoubleCounterHandle
GlobalInstrumentsRegistry::RegisterDoubleCounter(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default) {
auto& instruments = GetInstrumentList();
if (instruments.find(name) != instruments.end()) {
Crash(absl::StrFormat("Metric name %s has already been registered.", name));
}
uint32_t index = instruments.size();
GPR_ASSERT(index < std::numeric_limits<uint32_t>::max());
GlobalInstrumentDescriptor descriptor;
descriptor.value_type = ValueType::kDouble;
descriptor.instrument_type = InstrumentType::kCounter;
descriptor.index = index;
descriptor.enable_by_default = enable_by_default;
descriptor.name = name;
descriptor.description = description;
descriptor.unit = unit;
descriptor.label_keys = {label_keys.begin(), label_keys.end()};
descriptor.optional_label_keys = {optional_label_keys.begin(),
optional_label_keys.end()};
instruments.emplace(name, std::move(descriptor));
GlobalDoubleCounterHandle handle;
handle.index = index;
return handle;
}
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle
GlobalInstrumentsRegistry::RegisterUInt64Histogram(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default) {
auto& instruments = GetInstrumentList();
if (instruments.find(name) != instruments.end()) {
Crash(absl::StrFormat("Metric name %s has already been registered.", name));
}
uint32_t index = instruments.size();
GPR_ASSERT(index < std::numeric_limits<uint32_t>::max());
GlobalInstrumentDescriptor descriptor;
descriptor.value_type = ValueType::kUInt64;
descriptor.instrument_type = InstrumentType::kHistogram;
descriptor.index = index;
descriptor.enable_by_default = enable_by_default;
descriptor.name = name;
descriptor.description = description;
descriptor.unit = unit;
descriptor.label_keys = {label_keys.begin(), label_keys.end()};
descriptor.optional_label_keys = {optional_label_keys.begin(),
optional_label_keys.end()};
instruments.emplace(name, std::move(descriptor));
GlobalUInt64HistogramHandle handle;
handle.index = index;
return handle;
}
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle
GlobalInstrumentsRegistry::RegisterDoubleHistogram(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default) {
auto& instruments = GetInstrumentList();
if (instruments.find(name) != instruments.end()) {
Crash(absl::StrFormat("Metric name %s has already been registered.", name));
}
uint32_t index = instruments.size();
GPR_ASSERT(index < std::numeric_limits<uint32_t>::max());
GlobalInstrumentDescriptor descriptor;
descriptor.value_type = ValueType::kDouble;
descriptor.instrument_type = InstrumentType::kHistogram;
descriptor.index = index;
descriptor.enable_by_default = enable_by_default;
descriptor.name = name;
descriptor.description = description;
descriptor.unit = unit;
descriptor.label_keys = {label_keys.begin(), label_keys.end()};
descriptor.optional_label_keys = {optional_label_keys.begin(),
optional_label_keys.end()};
instruments.emplace(name, std::move(descriptor));
GlobalDoubleHistogramHandle handle;
handle.index = index;
return handle;
}
void GlobalInstrumentsRegistry::ForEach(
absl::FunctionRef<void(const GlobalInstrumentDescriptor&)> f) {
for (const auto& instrument : GetInstrumentList()) {
f(instrument.second);
}
}
void GlobalInstrumentsRegistry::TestOnlyResetGlobalInstrumentsRegistry() {
auto& instruments = GetInstrumentList();
instruments.clear();
}
NoDestruct<Mutex> GlobalStatsPluginRegistry::mutex_;
NoDestruct<std::vector<std::shared_ptr<StatsPlugin>>>
GlobalStatsPluginRegistry::plugins_;
void GlobalStatsPluginRegistry::RegisterStatsPlugin(
std::shared_ptr<StatsPlugin> plugin) {
MutexLock lock(&*mutex_);
plugins_->push_back(std::move(plugin));
}
GlobalStatsPluginRegistry::StatsPluginGroup
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
const StatsPlugin::ChannelScope& scope) {
MutexLock lock(&*mutex_);
StatsPluginGroup group;
for (const auto& plugin : *plugins_) {
if (plugin->IsEnabledForChannel(scope)) {
group.push_back(plugin);
}
}
return group;
}
} // namespace grpc_core

@ -0,0 +1,231 @@
// 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_LIB_CHANNEL_METRICS_H
#define GRPC_SRC_CORE_LIB_CHANNEL_METRICS_H
#include <grpc/support/port_platform.h>
#include <cstdint>
#include <memory>
#include <vector>
#include "absl/functional/function_ref.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include <grpc/support/log.h>
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gprpp/no_destruct.h"
#include "src/core/lib/gprpp/sync.h"
namespace grpc_core {
// A global registry of instruments(metrics). This API is designed to be used to
// register instruments (Counter and Histogram) 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.
class GlobalInstrumentsRegistry {
public:
enum class ValueType {
kUndefined,
kUInt64,
kDouble,
};
enum class InstrumentType {
kUndefined,
kCounter,
kHistogram,
};
struct GlobalInstrumentDescriptor {
ValueType value_type;
InstrumentType instrument_type;
uint32_t index;
bool enable_by_default;
absl::string_view name;
absl::string_view description;
absl::string_view unit;
std::vector<absl::string_view> label_keys;
std::vector<absl::string_view> optional_label_keys;
};
struct GlobalHandle {
// 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.
uint32_t index;
};
struct GlobalUInt64CounterHandle : public GlobalHandle {};
struct GlobalDoubleCounterHandle : public GlobalHandle {};
struct GlobalUInt64HistogramHandle : public GlobalHandle {};
struct GlobalDoubleHistogramHandle : public GlobalHandle {};
// Creates instrument in the GlobalInstrumentsRegistry.
static GlobalUInt64CounterHandle RegisterUInt64Counter(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalDoubleCounterHandle RegisterDoubleCounter(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalUInt64HistogramHandle RegisterUInt64Histogram(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static GlobalDoubleHistogramHandle RegisterDoubleHistogram(
absl::string_view name, absl::string_view description,
absl::string_view unit, absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> optional_label_keys,
bool enable_by_default);
static void ForEach(
absl::FunctionRef<void(const GlobalInstrumentDescriptor&)> f);
static void TestOnlyResetGlobalInstrumentsRegistry();
GlobalInstrumentsRegistry() = delete;
};
// The StatsPlugin interface.
class StatsPlugin {
public:
class ChannelScope {
public:
ChannelScope(absl::string_view target, absl::string_view authority)
: target_(target), authority_(authority) {}
absl::string_view target() const { return target_; }
absl::string_view authority() const { return authority_; }
private:
absl::string_view target_;
absl::string_view authority_;
};
virtual ~StatsPlugin() = default;
virtual bool IsEnabledForChannel(const ChannelScope& scope) const = 0;
virtual bool IsEnabledForServer(const ChannelArgs& args) const = 0;
virtual void AddCounter(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
virtual void AddCounter(
GlobalInstrumentsRegistry::GlobalDoubleCounterHandle handle, double value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
virtual void RecordHistogram(
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
virtual void RecordHistogram(
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
double value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) = 0;
// TODO(yijiem): Details pending.
// std::unique_ptr<AsyncInstrument> GetObservableGauge(
// absl::string_view name, absl::string_view description,
// absl::string_view unit);
// AsyncInstrument* GetObservableCounter(
// absl::string_view name, absl::string_view description,
// absl::string_view unit);
// 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<LabelValueSet>
// 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<LabelValueSet> MakeLabelValueSet(
// absl::Span<absl::string_view> label_values) = 0;
};
// A global registry of StatsPlugins. It has shared ownership to the registered
// StatsPlugins. This API is supposed to be used during runtime after the main
// function begins. This API is thread-safe.
class GlobalStatsPluginRegistry {
public:
class StatsPluginGroup {
public:
void push_back(std::shared_ptr<StatsPlugin> plugin) {
plugins_.push_back(std::move(plugin));
}
void AddCounter(GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
uint64_t value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
for (auto& plugin : plugins_) {
plugin->AddCounter(handle, value, label_values, optional_values);
}
}
void AddCounter(GlobalInstrumentsRegistry::GlobalDoubleCounterHandle handle,
double value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
for (auto& plugin : plugins_) {
plugin->AddCounter(handle, value, label_values, optional_values);
}
}
void RecordHistogram(
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
for (auto& plugin : plugins_) {
plugin->RecordHistogram(handle, value, label_values, optional_values);
}
}
void RecordHistogram(
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
double value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
for (auto& plugin : plugins_) {
plugin->RecordHistogram(handle, value, label_values, optional_values);
}
}
private:
std::vector<std::shared_ptr<StatsPlugin>> plugins_;
};
static void RegisterStatsPlugin(std::shared_ptr<StatsPlugin> plugin);
// The following two functions can be invoked to get a StatsPluginGroup for
// a specified scope.
static StatsPluginGroup GetStatsPluginsForChannel(
const StatsPlugin::ChannelScope& scope);
// TODO(yijiem): Implement this.
// StatsPluginsGroup GetStatsPluginsForServer(ChannelArgs& args);
static void TestOnlyResetGlobalStatsPluginRegistry() {
MutexLock lock(&*mutex_);
plugins_->clear();
}
private:
GlobalStatsPluginRegistry() = default;
static NoDestruct<Mutex> mutex_;
static NoDestruct<std::vector<std::shared_ptr<StatsPlugin>>> plugins_
ABSL_GUARDED_BY(mutex_);
};
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_CHANNEL_METRICS_H

@ -192,3 +192,18 @@ grpc_cc_test(
"//test/core/util:grpc_test_util",
],
)
grpc_cc_test(
name = "metrics_test",
srcs = ["metrics_test.cc"],
external_deps = [
"gtest",
],
language = "C++",
uses_event_engine = False,
uses_polling = False,
deps = [
"//src/core:metrics",
"//test/core/util:grpc_test_util",
],
)

@ -0,0 +1,528 @@
// 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.
#include "src/core/lib/channel/metrics.h"
#include <memory>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "test/core/util/test_config.h"
namespace grpc_core {
namespace {
void AddKeyValuePairs(absl::Span<const absl::string_view> keys,
absl::Span<const absl::string_view> values,
std::vector<std::string>* key_value_pairs) {
GPR_ASSERT(keys.size() == values.size());
for (size_t i = 0; i < keys.size(); ++i) {
key_value_pairs->push_back(absl::StrCat(keys[i], "=", values[i]));
}
}
std::string MakeLabelString(
absl::Span<const absl::string_view> label_keys,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_label_keys,
absl::Span<const absl::string_view> optional_values) {
std::vector<std::string> key_value_pairs;
AddKeyValuePairs(label_keys, label_values, &key_value_pairs);
AddKeyValuePairs(optional_label_keys, optional_values, &key_value_pairs);
return absl::StrJoin(key_value_pairs, ",");
}
// TODO(yijiem): Move this to test/core/util/fake_stats_plugin.h
class FakeStatsPlugin : public StatsPlugin {
public:
bool IsEnabledForChannel(
const StatsPlugin::ChannelScope& scope) const override {
return channel_filter_(scope);
}
bool IsEnabledForServer(const ChannelArgs& /*args*/) const override {
return false;
}
void AddCounter(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
// The problem with this approach is that we initialize uint64_counters_ in
// BuildAndRegister by querying the GlobalInstrumentsRegistry at the time.
// If the GlobalInstrumentsRegistry has changed since then (which we
// currently don't allow), we might not have seen that descriptor nor have
// we created an instrument for it. We probably could copy the existing
// instruments at build time and for the handle that we haven't seen we will
// just ignore it here. This would also prevent us from having to lock the
// GlobalInstrumentsRegistry everytime a metric is recorded. But this is not
// a concern for now.
auto iter = uint64_counters_.find(handle.index);
if (iter == uint64_counters_.end()) {
return;
}
iter->second.Add(value, label_values, optional_values);
}
void AddCounter(
GlobalInstrumentsRegistry::GlobalDoubleCounterHandle handle, double value,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
auto iter = double_counters_.find(handle.index);
if (iter == double_counters_.end()) {
return;
}
iter->second.Add(value, label_values, optional_values);
}
void RecordHistogram(
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle handle,
uint64_t value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
auto iter = uint64_histograms_.find(handle.index);
if (iter == uint64_histograms_.end()) {
return;
}
iter->second.Record(value, label_values, optional_values);
}
void RecordHistogram(
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
double value, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) override {
auto iter = double_histograms_.find(handle.index);
if (iter == double_histograms_.end()) {
return;
}
iter->second.Record(value, label_values, optional_values);
}
absl::optional<uint64_t> GetCounterValue(
GlobalInstrumentsRegistry::GlobalUInt64CounterHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = uint64_counters_.find(handle.index);
if (iter == uint64_counters_.end()) {
return absl::nullopt;
}
return iter->second.GetValue(label_values, optional_values);
}
absl::optional<double> GetCounterValue(
GlobalInstrumentsRegistry::GlobalDoubleCounterHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = double_counters_.find(handle.index);
if (iter == double_counters_.end()) {
return absl::nullopt;
}
return iter->second.GetValue(label_values, optional_values);
}
absl::optional<std::vector<uint64_t>> GetHistogramValue(
GlobalInstrumentsRegistry::GlobalUInt64HistogramHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = uint64_histograms_.find(handle.index);
if (iter == uint64_histograms_.end()) {
return absl::nullopt;
}
return iter->second.GetValues(label_values, optional_values);
}
absl::optional<std::vector<double>> GetHistogramValue(
GlobalInstrumentsRegistry::GlobalDoubleHistogramHandle handle,
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = double_histograms_.find(handle.index);
if (iter == double_histograms_.end()) {
return absl::nullopt;
}
return iter->second.GetValues(label_values, optional_values);
}
private:
friend class FakeStatsPluginBuilder;
explicit FakeStatsPlugin(
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter)
: channel_filter_(std::move(channel_filter)) {
GlobalInstrumentsRegistry::ForEach(
[this](const GlobalInstrumentsRegistry::GlobalInstrumentDescriptor&
descriptor) {
if (!descriptor.enable_by_default) {
return;
}
if (descriptor.instrument_type ==
GlobalInstrumentsRegistry::InstrumentType::kCounter) {
if (descriptor.value_type ==
GlobalInstrumentsRegistry::ValueType::kUInt64) {
uint64_counters_.emplace(descriptor.index, descriptor);
} else {
double_counters_.emplace(descriptor.index, descriptor);
}
} else {
EXPECT_EQ(descriptor.instrument_type,
GlobalInstrumentsRegistry::InstrumentType::kHistogram);
if (descriptor.value_type ==
GlobalInstrumentsRegistry::ValueType::kUInt64) {
uint64_histograms_.emplace(descriptor.index, descriptor);
} else {
double_histograms_.emplace(descriptor.index, descriptor);
}
}
});
}
template <class T>
class Counter {
public:
explicit Counter(GlobalInstrumentsRegistry::GlobalInstrumentDescriptor u)
: name_(u.name),
description_(u.description),
unit_(u.unit),
label_keys_(std::move(u.label_keys)),
optional_label_keys_(std::move(u.optional_label_keys)) {}
void Add(T t, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter != storage_.end()) {
iter->second += t;
} else {
storage_[MakeLabelString(label_keys_, label_values,
optional_label_keys_, optional_values)] = t;
}
}
absl::optional<T> GetValue(
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter == storage_.end()) {
return absl::nullopt;
}
return iter->second;
}
private:
absl::string_view name_;
absl::string_view description_;
absl::string_view unit_;
std::vector<absl::string_view> label_keys_;
std::vector<absl::string_view> optional_label_keys_;
// Aggregation of the same key attributes.
absl::flat_hash_map<std::string, T> storage_;
};
template <class T>
class Histogram {
public:
explicit Histogram(GlobalInstrumentsRegistry::GlobalInstrumentDescriptor u)
: name_(u.name),
description_(u.description),
unit_(u.unit),
label_keys_(std::move(u.label_keys)),
optional_label_keys_(std::move(u.optional_label_keys)) {}
void Record(T t, absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
std::string key = MakeLabelString(label_keys_, label_values,
optional_label_keys_, optional_values);
auto iter = storage_.find(key);
if (iter == storage_.end()) {
storage_.emplace(key, std::initializer_list<T>{t});
} else {
iter->second.push_back(t);
}
}
absl::optional<std::vector<T>> GetValues(
absl::Span<const absl::string_view> label_values,
absl::Span<const absl::string_view> optional_values) {
auto iter = storage_.find(MakeLabelString(
label_keys_, label_values, optional_label_keys_, optional_values));
if (iter == storage_.end()) {
return absl::nullopt;
}
return iter->second;
}
private:
absl::string_view name_;
absl::string_view description_;
absl::string_view unit_;
std::vector<absl::string_view> label_keys_;
std::vector<absl::string_view> optional_label_keys_;
absl::flat_hash_map<std::string, std::vector<T>> storage_;
};
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter_;
// Instruments.
absl::flat_hash_map<uint32_t, Counter<uint64_t>> uint64_counters_;
absl::flat_hash_map<uint32_t, Counter<double>> double_counters_;
absl::flat_hash_map<uint32_t, Histogram<uint64_t>> uint64_histograms_;
absl::flat_hash_map<uint32_t, Histogram<double>> double_histograms_;
};
// TODO(yijiem): Move this to test/core/util/fake_stats_plugin.h
class FakeStatsPluginBuilder {
public:
FakeStatsPluginBuilder& SetChannelFilter(
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter) {
channel_filter_ = std::move(channel_filter);
return *this;
}
std::shared_ptr<FakeStatsPlugin> BuildAndRegister() {
auto f = std::shared_ptr<FakeStatsPlugin>(
new FakeStatsPlugin(std::move(channel_filter_)));
GlobalStatsPluginRegistry::RegisterStatsPlugin(f);
return f;
}
private:
absl::AnyInvocable<bool(const StatsPlugin::ChannelScope& /*scope*/) const>
channel_filter_;
};
std::shared_ptr<FakeStatsPlugin> MakeStatsPluginForTarget(
absl::string_view target_suffix) {
return FakeStatsPluginBuilder()
.SetChannelFilter(
[target_suffix](const StatsPlugin::ChannelScope& scope) {
return absl::EndsWith(scope.target(), target_suffix);
})
.BuildAndRegister();
}
class MetricsTest : public testing::Test {
public:
void TearDown() override {
GlobalInstrumentsRegistry::TestOnlyResetGlobalInstrumentsRegistry();
GlobalStatsPluginRegistry::TestOnlyResetGlobalStatsPluginRegistry();
}
};
TEST_F(MetricsTest, UInt64Counter) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto uint64_counter_handle = GlobalInstrumentsRegistry::RegisterUInt64Counter(
"uint64_counter", "A simple uint64 counter.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.AddCounter(uint64_counter_handle, 1, kLabelValues, kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.AddCounter(uint64_counter_handle, 2, kLabelValues, kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.AddCounter(uint64_counter_handle, 3, kLabelValues, kOptionalLabelValues);
EXPECT_THAT(plugin1->GetCounterValue(uint64_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(1));
EXPECT_THAT(plugin2->GetCounterValue(uint64_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(3));
EXPECT_THAT(plugin3->GetCounterValue(uint64_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(6));
}
TEST_F(MetricsTest, DoubleCounter) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto double_counter_handle = GlobalInstrumentsRegistry::RegisterDoubleCounter(
"double_counter", "A simple double counter.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.AddCounter(double_counter_handle, 1.23, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.AddCounter(double_counter_handle, 2.34, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.AddCounter(double_counter_handle, 3.45, kLabelValues,
kOptionalLabelValues);
EXPECT_THAT(plugin1->GetCounterValue(double_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(1.23));
EXPECT_THAT(plugin2->GetCounterValue(double_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(3.57));
EXPECT_THAT(plugin3->GetCounterValue(double_counter_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(7.02));
}
TEST_F(MetricsTest, UInt64Histogram) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto uint64_histogram_handle =
GlobalInstrumentsRegistry::RegisterUInt64Histogram(
"uint64_histogram", "A simple uint64 histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.RecordHistogram(uint64_histogram_handle, 1, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.RecordHistogram(uint64_histogram_handle, 2, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.RecordHistogram(uint64_histogram_handle, 3, kLabelValues,
kOptionalLabelValues);
EXPECT_THAT(plugin1->GetHistogramValue(uint64_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1)));
EXPECT_THAT(plugin2->GetHistogramValue(uint64_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1, 2)));
EXPECT_THAT(plugin3->GetHistogramValue(uint64_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1, 2, 3)));
}
TEST_F(MetricsTest, DoubleHistogram) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto double_histogram_handle =
GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
constexpr absl::string_view kDomain2To4 = "domain2.domain3.domain4";
constexpr absl::string_view kDomain3To4 = "domain3.domain4";
auto plugin1 = MakeStatsPluginForTarget(kDomain1To4);
auto plugin2 = MakeStatsPluginForTarget(kDomain2To4);
auto plugin3 = MakeStatsPluginForTarget(kDomain3To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.RecordHistogram(double_histogram_handle, 1.23, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain2To4, ""))
.RecordHistogram(double_histogram_handle, 2.34, kLabelValues,
kOptionalLabelValues);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain3To4, ""))
.RecordHistogram(double_histogram_handle, 3.45, kLabelValues,
kOptionalLabelValues);
EXPECT_THAT(plugin1->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1.23)));
EXPECT_THAT(plugin2->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1.23, 2.34)));
EXPECT_THAT(
plugin3->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
::testing::Optional(::testing::UnorderedElementsAre(1.23, 2.34, 3.45)));
}
TEST_F(MetricsTest, DisableByDefaultMetricIsNotRecordedByFakeStatsPlugin) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
auto double_histogram_handle =
GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, /*enable_by_default=*/false);
constexpr absl::string_view kLabelValues[] = {"label_value_1",
"label_value_2"};
constexpr absl::string_view kOptionalLabelValues[] = {
"optional_label_value_1", "optional_label_value_2"};
constexpr absl::string_view kDomain1To4 = "domain1.domain2.domain3.domain4";
auto plugin = MakeStatsPluginForTarget(kDomain1To4);
GlobalStatsPluginRegistry::GetStatsPluginsForChannel(
StatsPlugin::ChannelScope(kDomain1To4, ""))
.RecordHistogram(double_histogram_handle, 1.23, kLabelValues,
kOptionalLabelValues);
EXPECT_EQ(plugin->GetHistogramValue(double_histogram_handle, kLabelValues,
kOptionalLabelValues),
absl::nullopt);
}
using MetricsDeathTest = MetricsTest;
TEST_F(MetricsDeathTest, RegisterTheSameMetricNameWouldCrash) {
const absl::string_view kLabelKeys[] = {"label_key_1", "label_key_2"};
const absl::string_view kOptionalLabelKeys[] = {"optional_label_key_1",
"optional_label_key_2"};
(void)GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit", kLabelKeys,
kOptionalLabelKeys, true);
EXPECT_DEATH(GlobalInstrumentsRegistry::RegisterDoubleHistogram(
"double_histogram", "A simple double histogram.", "unit",
kLabelKeys, kOptionalLabelKeys, true),
"Metric name double_histogram has already been registered.");
}
} // namespace
} // namespace grpc_core
int main(int argc, char** argv) {
grpc::testing::TestEnvironment env(&argc, argv);
::testing::InitGoogleTest(&argc, argv);
int ret = RUN_ALL_TESTS();
return ret;
}

@ -5979,6 +5979,30 @@
],
"uses_polling": true
},
{
"args": [],
"benchmark": false,
"ci_platforms": [
"linux",
"mac",
"posix",
"windows"
],
"cpu_cost": 1.0,
"exclude_configs": [],
"exclude_iomgrs": [],
"flaky": false,
"gtest": true,
"language": "c++",
"name": "metrics_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save