SERVER-123238 Add unit tests for serverStatus command with otel metrics (#51209)
GitOrigin-RevId: c2ee07f640f77a92b33a3cb41e9d3cddd68ea56d
This commit is contained in:
parent
811c56cf0d
commit
2b0757a642
@ -1139,10 +1139,12 @@ mongo_cc_unit_test(
|
||||
"//src/mongo/db:fle_mocks",
|
||||
"//src/mongo/db:multitenancy",
|
||||
"//src/mongo/db:query_exec",
|
||||
"//src/mongo/db:read_write_concern_defaults_mock",
|
||||
"//src/mongo/db:service_context_d_test_fixture",
|
||||
"//src/mongo/db/auth:authmocks",
|
||||
"//src/mongo/db/collection_crud",
|
||||
"//src/mongo/db/commands/server_status:server_status_core",
|
||||
"//src/mongo/db/commands/server_status:server_status_metric",
|
||||
"//src/mongo/db/index_builds:index_builds_coordinator",
|
||||
"//src/mongo/db/memory_tracking",
|
||||
"//src/mongo/db/op_observer",
|
||||
|
||||
@ -331,8 +331,60 @@ void MetricTree::_add(StringData path, std::unique_ptr<ServerStatusMetric> metri
|
||||
}
|
||||
}
|
||||
|
||||
void MetricTree::clearForTests() {
|
||||
_children.clear();
|
||||
void MetricTree::removeForTests(StringData path) {
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
if (path.starts_with('.')) {
|
||||
path.remove_prefix(1);
|
||||
if (!path.empty()) {
|
||||
_removeForTests(path);
|
||||
}
|
||||
} else {
|
||||
_removeForTests(fmt::format("metrics.{}", path));
|
||||
}
|
||||
}
|
||||
|
||||
void MetricTree::_removeForTests(StringData path) {
|
||||
// Walk the path, recording (parent, key) pairs so we can prune empty subtrees afterward.
|
||||
struct Node {
|
||||
MetricTree* parent;
|
||||
std::string key;
|
||||
};
|
||||
std::vector<Node> stack;
|
||||
StringData tail = path;
|
||||
MetricTree* subTree = this;
|
||||
|
||||
while (true) {
|
||||
auto dot = tail.find('.');
|
||||
|
||||
if (dot == std::string::npos) {
|
||||
subTree->_children.erase(std::string{tail});
|
||||
break;
|
||||
}
|
||||
|
||||
StringData part = tail.substr(0, dot);
|
||||
tail = tail.substr(dot + 1);
|
||||
auto iter = subTree->_children.find(part);
|
||||
|
||||
if (iter == subTree->_children.end() || !iter->second.isSubtree()) {
|
||||
return;
|
||||
}
|
||||
stack.push_back({subTree, std::string{part}});
|
||||
subTree = iter->second.getSubtree().get();
|
||||
}
|
||||
|
||||
// Prune empty parent subtrees bottom-up.
|
||||
for (auto it = stack.rbegin(); it != stack.rend(); ++it) {
|
||||
auto childIter = it->parent->_children.find(it->key);
|
||||
if (childIter == it->parent->_children.end() || !childIter->second.isSubtree()) {
|
||||
break;
|
||||
}
|
||||
if (!childIter->second.getSubtree()->_children.empty()) {
|
||||
break;
|
||||
}
|
||||
it->parent->_children.erase(childIter);
|
||||
}
|
||||
}
|
||||
|
||||
void appendMergedTrees(std::vector<const MetricTree*> trees,
|
||||
@ -360,11 +412,5 @@ MetricTreeSet& globalMetricTreeSet() {
|
||||
return *obj;
|
||||
}
|
||||
|
||||
void clearGlobalMetricTreeSetForTests() {
|
||||
for (const auto role :
|
||||
{ClusterRole::None, ClusterRole::ShardServer, ClusterRole::RouterServer}) {
|
||||
globalMetricTreeSet()[role].clearForTests();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mongo
|
||||
|
||||
@ -194,11 +194,26 @@ public:
|
||||
return _children;
|
||||
}
|
||||
|
||||
void clearForTests();
|
||||
/**
|
||||
* Removes the metric at `path` from the tree, then prunes any intermediate subtrees that
|
||||
* become empty as a result. The path follows the same leading-dot convention as `add`: a
|
||||
* leading '.' means the path is absolute (relative to the root of the tree), while a path
|
||||
* without a leading '.' is implicitly rooted under "metrics.". Does nothing if `path` is
|
||||
* empty or does not exist in the tree. Intended for use in tests only.
|
||||
*/
|
||||
void removeForTests(StringData path);
|
||||
|
||||
private:
|
||||
void _add(StringData path, std::unique_ptr<ServerStatusMetric> metric);
|
||||
|
||||
/**
|
||||
* The helper for `removeForTests`. Removes the node at `path` (a dot-separated absolute path
|
||||
* with no leading dot) and bottom-up prunes any intermediate subtrees that become empty after
|
||||
* after the removal. Silently returns without modifying the tree when any component of
|
||||
* `path` is missing or when an intermediate component is a leaf metric rather than a subtree.
|
||||
*/
|
||||
void _removeForTests(StringData path);
|
||||
|
||||
ChildMap _children;
|
||||
};
|
||||
|
||||
@ -218,14 +233,6 @@ private:
|
||||
|
||||
MetricTreeSet& globalMetricTreeSet();
|
||||
|
||||
/**
|
||||
* Used in unit tests only. Removes all metrics from globalMetricTreeSet() for every ClusterRole.
|
||||
*
|
||||
* MetricsService may register OtelMetricServerStatusAdapter entries that hold raw Metric* pointers.
|
||||
* After a test destroys its MetricsService, those pointers are no longer be valid so they must be
|
||||
* removed from the tree set before a subsequent test runs.
|
||||
*/
|
||||
void clearGlobalMetricTreeSetForTests();
|
||||
|
||||
/**
|
||||
* Write a merger of the `trees` to `b`, under field `name`. `excludePaths` is a
|
||||
|
||||
@ -29,29 +29,252 @@
|
||||
|
||||
#include "mongo/bson/bsonobj.h"
|
||||
#include "mongo/db/commands/db_command_test_fixture.h"
|
||||
#include "mongo/db/commands/server_status/server_status_metric.h"
|
||||
#include "mongo/db/database_name.h"
|
||||
#include "mongo/db/read_write_concern_defaults_cache_lookup_mock.h"
|
||||
#include "mongo/db/service_context_test_fixture.h"
|
||||
#include "mongo/db/service_entry_point_shard_role.h"
|
||||
#include "mongo/db/topology/cluster_role.h"
|
||||
#include "mongo/otel/metrics/metric_names.h"
|
||||
#include "mongo/otel/metrics/metrics_service.h"
|
||||
#include "mongo/otel/metrics/metrics_test_util.h"
|
||||
#include "mongo/s/service_entry_point_router_role.h"
|
||||
#include "mongo/unittest/unittest.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace mongo {
|
||||
namespace {
|
||||
class ServerStatusServersTest : public DBCommandTestFixture {};
|
||||
|
||||
TEST_F(ServerStatusServersTest, IncludesOtelMetrics) {
|
||||
otel::metrics::OtelMetricsCapturer capturer;
|
||||
class ServerStatusServersTest : public DBCommandTestFixture {
|
||||
public:
|
||||
void setUp() override {
|
||||
DBCommandTestFixture::setUp();
|
||||
}
|
||||
|
||||
void tearDown() override {
|
||||
otel::metrics::MetricsService::instance().clearForTests();
|
||||
DBCommandTestFixture::tearDown();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ServerStatusServersTest, IncludeUnderMetricsSection) {
|
||||
auto& metricsService = otel::metrics::MetricsService::instance();
|
||||
otel::metrics::CounterOptions options{
|
||||
.serverStatusOptions = otel::metrics::ServerStatusOptions{.dottedPath = "test.metric1",
|
||||
.role = ClusterRole::None}};
|
||||
auto& counter = metricsService.createInt64Counter(otel::metrics::MetricNames::kTest1,
|
||||
"description",
|
||||
otel::metrics::MetricUnit::kSeconds,
|
||||
{.inServerStatus = true});
|
||||
options);
|
||||
counter.add(11);
|
||||
|
||||
BSONObj result = runCommand(BSON("serverStatus" << 1 << "otelMetrics" << 1));
|
||||
ASSERT_TRUE(result.hasField("otelMetrics"));
|
||||
BSONObj resultObj = runCommand(BSON("serverStatus" << 1 << "metrics" << 1));
|
||||
ASSERT_TRUE(resultObj.hasField("metrics"));
|
||||
BSONObj metricsObj = resultObj.getObjectField("metrics");
|
||||
ASSERT_EQ(metricsObj["test"]["metric1"].Long(), 11);
|
||||
}
|
||||
|
||||
BSONObj otelMetrics = result.getObjectField("otelMetrics");
|
||||
ASSERT_TRUE(otelMetrics.hasField("test_only.metric1_seconds"));
|
||||
EXPECT_EQ(otelMetrics.getIntField("test_only.metric1_seconds"), 11);
|
||||
TEST_F(ServerStatusServersTest, IncludeUnderOtelMetricsSection) {
|
||||
auto& metricsService = otel::metrics::MetricsService::instance();
|
||||
otel::metrics::CounterOptions options{
|
||||
.inServerStatus = true,
|
||||
};
|
||||
auto& counter = metricsService.createInt64Counter(otel::metrics::MetricNames::kTest1,
|
||||
"description",
|
||||
otel::metrics::MetricUnit::kSeconds,
|
||||
options);
|
||||
counter.add(11);
|
||||
|
||||
BSONObj resultObj = runCommand(BSON("serverStatus" << 1 << "otelMetrics" << 1));
|
||||
ASSERT_TRUE(resultObj.hasField("otelMetrics"));
|
||||
BSONObj otelMetricsObj = resultObj.getObjectField("otelMetrics");
|
||||
ASSERT_EQ(otelMetricsObj["test_only.metric1_seconds"].Long(), 11);
|
||||
}
|
||||
|
||||
TEST_F(ServerStatusServersTest, IncludeUnderOtelMetricsSectionAndMetricsSection) {
|
||||
auto& metricsService = otel::metrics::MetricsService::instance();
|
||||
|
||||
otel::metrics::CounterOptions flatOptions{
|
||||
.inServerStatus = true,
|
||||
};
|
||||
auto& flatCounter = metricsService.createInt64Counter(otel::metrics::MetricNames::kTest1,
|
||||
"description",
|
||||
otel::metrics::MetricUnit::kSeconds,
|
||||
flatOptions);
|
||||
flatCounter.add(11);
|
||||
|
||||
otel::metrics::CounterOptions nestedOptions{
|
||||
.serverStatusOptions = otel::metrics::ServerStatusOptions{.dottedPath = "test.metric2",
|
||||
.role = ClusterRole::None},
|
||||
};
|
||||
auto& nestedCounter = metricsService.createInt64Counter(otel::metrics::MetricNames::kTest2,
|
||||
"description",
|
||||
otel::metrics::MetricUnit::kSeconds,
|
||||
nestedOptions);
|
||||
nestedCounter.add(22);
|
||||
|
||||
BSONObj resultObj =
|
||||
runCommand(BSON("serverStatus" << 1 << "otelMetrics" << 1 << "metrics" << 1));
|
||||
ASSERT_TRUE(resultObj.hasField("otelMetrics"));
|
||||
ASSERT_TRUE(resultObj.hasField("metrics"));
|
||||
|
||||
BSONObj otelMetricsObj = resultObj.getObjectField("otelMetrics");
|
||||
ASSERT_EQ(otelMetricsObj["test_only.metric1_seconds"].Long(), 11);
|
||||
|
||||
BSONObj metricsObj = resultObj.getObjectField("metrics");
|
||||
ASSERT_EQ(metricsObj["test"]["metric2"].Long(), 22);
|
||||
}
|
||||
|
||||
TEST_F(ServerStatusServersTest, ExcludeWhenServerStatusOptionsAndInServerStatusNotset) {
|
||||
auto& metricsService = otel::metrics::MetricsService::instance();
|
||||
otel::metrics::CounterOptions options{};
|
||||
ASSERT_FALSE(options.serverStatusOptions.has_value());
|
||||
ASSERT_FALSE(options.inServerStatus);
|
||||
|
||||
auto& counter = metricsService.createInt64Counter(otel::metrics::MetricNames::kTest1,
|
||||
"description",
|
||||
otel::metrics::MetricUnit::kSeconds,
|
||||
options);
|
||||
counter.add(11);
|
||||
|
||||
BSONObj resultObj =
|
||||
runCommand(BSON("serverStatus" << 1 << "otelMetrics" << 1 << "metrics" << 1));
|
||||
ASSERT_FALSE(resultObj.hasField("otelMetrics"));
|
||||
// The "metrics" section may still exist because of non-otel serverStatus metrics.
|
||||
if (resultObj.hasField("metrics")) {
|
||||
BSONObj metricsObj = resultObj.getObjectField("metrics");
|
||||
ASSERT_FALSE(metricsObj.hasField("test_only")) << metricsObj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class ServerStatusServersRoleTestFixture : public ServiceContextTest {
|
||||
public:
|
||||
void setUp() override {
|
||||
ServiceContextTest::setUp();
|
||||
ReadWriteConcernDefaults::create(getService(), _lookupMock.getFetchDefaultsFn());
|
||||
}
|
||||
|
||||
void tearDown() override {
|
||||
otel::metrics::MetricsService::instance().clearForTests();
|
||||
ServiceContextTest::tearDown();
|
||||
}
|
||||
|
||||
protected:
|
||||
otel::metrics::Counter<int64_t>& createCounter(otel::metrics::MetricsService& metricsService,
|
||||
otel::metrics::MetricName metricName,
|
||||
std::string dottedPath,
|
||||
ClusterRole role) {
|
||||
return metricsService.createInt64Counter(metricName,
|
||||
"description",
|
||||
otel::metrics::MetricUnit::kSeconds,
|
||||
otel::metrics::CounterOptions{
|
||||
.serverStatusOptions =
|
||||
otel::metrics::ServerStatusOptions{
|
||||
.dottedPath = std::move(dottedPath),
|
||||
.role = role,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
BSONObj getMetricsSection(StringData pathPrefix) {
|
||||
Service* const service = getServiceContext()->getService();
|
||||
ServiceContext::UniqueClient client =
|
||||
service->makeClient("ServerStatusServersRoleTestFixture");
|
||||
AlternativeClientRegion acr(client);
|
||||
auto opCtx = cc().makeOperationContext();
|
||||
DBDirectClient dbclient(opCtx.get());
|
||||
|
||||
BSONObj resultObj;
|
||||
// Specify none: 1 to exclude all other sections.
|
||||
dbclient.runCommand(DatabaseName::kAdmin,
|
||||
BSON("serverStatus" << 1 << "none" << 1 << "metrics" << 1),
|
||||
resultObj);
|
||||
ASSERT_OK(getStatusFromWriteCommandReply(resultObj));
|
||||
|
||||
ASSERT_TRUE(resultObj.hasField("metrics"));
|
||||
BSONObj metricsObj = resultObj.getObjectField("metrics");
|
||||
return metricsObj.getObjectField(pathPrefix).getOwned();
|
||||
}
|
||||
|
||||
private:
|
||||
// Allows for commands to not specify a default read/write concern.
|
||||
ReadWriteConcernDefaultsLookupMock _lookupMock;
|
||||
};
|
||||
|
||||
class ServerStatusServersRoleShardTest : public virtual service_context_test::ShardRoleOverride,
|
||||
public ServerStatusServersRoleTestFixture {
|
||||
|
||||
void setUp() override {
|
||||
ServerStatusServersRoleTestFixture::setUp();
|
||||
// Initialize the serviceEntryPoint so that DBDirectClient can function.
|
||||
getService()->setServiceEntryPoint(std::make_unique<ServiceEntryPointShardRole>());
|
||||
|
||||
const auto service = getServiceContext();
|
||||
auto replCoord =
|
||||
std::make_unique<repl::ReplicationCoordinatorMock>(service, repl::ReplSettings{});
|
||||
ASSERT_OK(replCoord->setFollowerMode(repl::MemberState::RS_PRIMARY));
|
||||
repl::ReplicationCoordinator::set(service, std::move(replCoord));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ServerStatusServersRoleShardTest, MergesNoneAndShardMetricTreesExcludesRouter) {
|
||||
auto& metricsService = otel::metrics::MetricsService::instance();
|
||||
createCounter(metricsService,
|
||||
otel::metrics::MetricNames::kTestShardMergeNone,
|
||||
"test.noneMetric",
|
||||
ClusterRole::None)
|
||||
.add(11);
|
||||
createCounter(metricsService,
|
||||
otel::metrics::MetricNames::kTestShardMergeShard,
|
||||
"test.shardMetric",
|
||||
ClusterRole::ShardServer)
|
||||
.add(22);
|
||||
createCounter(metricsService,
|
||||
otel::metrics::MetricNames::kTestShardMergeRouter,
|
||||
"test.routerMetric",
|
||||
ClusterRole::RouterServer)
|
||||
.add(33);
|
||||
|
||||
BSONObj section = getMetricsSection("test");
|
||||
EXPECT_EQ(section.getIntField("noneMetric"), 11);
|
||||
EXPECT_EQ(section.getIntField("shardMetric"), 22);
|
||||
ASSERT_FALSE(section.hasField("routerMetric")) << section.toString();
|
||||
}
|
||||
|
||||
class ServerStatusServersRoleRouterTest : public virtual service_context_test::RouterRoleOverride,
|
||||
public ServerStatusServersRoleTestFixture {
|
||||
void setUp() override {
|
||||
ServerStatusServersRoleTestFixture::setUp();
|
||||
// Initialize the serviceEntryPoint so that DBDirectClient can function.
|
||||
getService()->setServiceEntryPoint(std::make_unique<ServiceEntryPointRouterRole>());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ServerStatusServersRoleRouterTest, MergesNoneAndRouterMetricTreesExcludesShard) {
|
||||
auto& metricsService = otel::metrics::MetricsService::instance();
|
||||
createCounter(metricsService,
|
||||
otel::metrics::MetricNames::kTestRouterMergeNone,
|
||||
"test.noneMetric",
|
||||
ClusterRole::None)
|
||||
.add(11);
|
||||
createCounter(metricsService,
|
||||
otel::metrics::MetricNames::kTestRouterMergeShard,
|
||||
"test.shardMetric",
|
||||
ClusterRole::ShardServer)
|
||||
.add(22);
|
||||
createCounter(metricsService,
|
||||
otel::metrics::MetricNames::kTestRouterMergeRouter,
|
||||
"test.routerMetric",
|
||||
ClusterRole::RouterServer)
|
||||
.add(33);
|
||||
|
||||
BSONObj section = getMetricsSection("test");
|
||||
EXPECT_EQ(section.getIntField("noneMetric"), 11);
|
||||
ASSERT_FALSE(section.hasField("shardMetric")) << section.toString();
|
||||
EXPECT_EQ(section.getIntField("routerMetric"), 33);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@ -156,6 +156,12 @@ public:
|
||||
static constexpr MetricName kTest4 = {"test_only.metric4"};
|
||||
static constexpr MetricName kTest5 = {"test_only.metric5"};
|
||||
static constexpr MetricName kTest6 = {"test_only.metric6"};
|
||||
static constexpr MetricName kTestShardMergeNone = {"test_only.shard_merge_none"};
|
||||
static constexpr MetricName kTestShardMergeShard = {"test_only.shard_merge_shard"};
|
||||
static constexpr MetricName kTestShardMergeRouter = {"test_only.shard_merge_router"};
|
||||
static constexpr MetricName kTestRouterMergeNone = {"test_only.router_merge_none"};
|
||||
static constexpr MetricName kTestRouterMergeShard = {"test_only.router_merge_shard"};
|
||||
static constexpr MetricName kTestRouterMergeRouter = {"test_only.router_merge_router"};
|
||||
// camelCase is not allowed.
|
||||
static constexpr MetricName kTestInvalid = {"test_only.Metric"};
|
||||
};
|
||||
|
||||
@ -589,4 +589,17 @@ void MetricsService::appendMetricsForServerStatus(BSONObjBuilder& bsonBuilder) c
|
||||
identifierAndMetric.metric);
|
||||
}
|
||||
}
|
||||
void MetricsService::clearForTests() {
|
||||
stdx::lock_guard lock(_mutex);
|
||||
#ifdef MONGO_CONFIG_OTEL
|
||||
_observableInstruments.clear();
|
||||
#endif
|
||||
for (auto& [name, identAndMetric] : _metrics) {
|
||||
auto& opts = identAndMetric.identifier.serverStatusOptions;
|
||||
if (opts.has_value()) {
|
||||
globalMetricTreeSet()[opts->role].removeForTests(opts->dottedPath);
|
||||
}
|
||||
}
|
||||
_metrics.clear();
|
||||
}
|
||||
} // namespace mongo::otel::metrics
|
||||
|
||||
@ -214,6 +214,12 @@ public:
|
||||
*/
|
||||
void appendMetricsForServerStatus(BSONObjBuilder& bsonBuilder) const;
|
||||
|
||||
/**
|
||||
* Used in unit tests only. Removes all metrics registered by this MetricsService from the
|
||||
* internal map and from the serverStatus metric trees.
|
||||
*/
|
||||
void clearForTests();
|
||||
|
||||
#ifdef MONGO_CONFIG_OTEL
|
||||
/**
|
||||
* Initializes the metrics service by registering metrics created before initialization with the
|
||||
|
||||
@ -53,10 +53,13 @@ namespace {
|
||||
class MetricsServiceTest : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
clearGlobalMetricTreeSetForTests();
|
||||
metricsService = std::make_unique<MetricsService>();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
metricsService->clearForTests();
|
||||
}
|
||||
|
||||
std::unique_ptr<MetricsService> metricsService;
|
||||
};
|
||||
|
||||
@ -221,10 +224,12 @@ template <typename T>
|
||||
class MetricCreationTest : public MetricsServiceTest {};
|
||||
|
||||
using testing::_;
|
||||
using testing::AnyOf;
|
||||
using testing::Contains;
|
||||
using testing::ElementsAre;
|
||||
using testing::ElementsAreArray;
|
||||
using testing::Matcher;
|
||||
using testing::Not;
|
||||
using testing::UnorderedElementsAre;
|
||||
using unittest::match::BSONElementEQ;
|
||||
using unittest::match::BSONObjElements;
|
||||
@ -398,7 +403,7 @@ TYPED_TEST(MetricCreationTest, ExceptionWhenSameNameButDifferentServerStatusOpti
|
||||
|
||||
MetricOptions<TypeParam> optionsRoleNone{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = sharedPath,
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
MetricCreator<TypeParam>::create(this->metricsService.get(),
|
||||
MetricNames::kTest1,
|
||||
@ -408,7 +413,7 @@ TYPED_TEST(MetricCreationTest, ExceptionWhenSameNameButDifferentServerStatusOpti
|
||||
|
||||
MetricOptions<TypeParam> optionsShardRole{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = sharedPath,
|
||||
.role = ClusterRole{ClusterRole::ShardServer},
|
||||
.role = ClusterRole::ShardServer,
|
||||
}};
|
||||
ASSERT_THROWS_CODE(MetricCreator<TypeParam>::create(this->metricsService.get(),
|
||||
MetricNames::kTest1,
|
||||
@ -649,7 +654,7 @@ using SerializeMetricsTreeTest = MetricsServiceTest;
|
||||
TEST_F(SerializeMetricsTreeTest, Counter) {
|
||||
CounterOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ingress.openConnections",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, options);
|
||||
@ -664,7 +669,7 @@ TEST_F(SerializeMetricsTreeTest, Counter) {
|
||||
TEST_F(SerializeMetricsTreeTest, Histogram) {
|
||||
HistogramOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ops.latencyHistogram",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& histogram = metricsService->createDoubleHistogram(
|
||||
MetricNames::kTest2, "description", MetricUnit::kSeconds, options);
|
||||
@ -680,7 +685,7 @@ TEST_F(SerializeMetricsTreeTest, Histogram) {
|
||||
TEST_F(SerializeMetricsTreeTest, RoleShard) {
|
||||
CounterOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ingress.openConnections",
|
||||
.role = ClusterRole{ClusterRole::ShardServer},
|
||||
.role = ClusterRole::ShardServer,
|
||||
}};
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, options);
|
||||
@ -693,17 +698,29 @@ TEST_F(SerializeMetricsTreeTest, RoleShard) {
|
||||
|
||||
BSONObjBuilder noneBuilder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::None].appendTo(noneBuilder);
|
||||
ASSERT_TRUE(noneBuilder.obj().isEmpty());
|
||||
BSONObj noneObj = noneBuilder.obj();
|
||||
ASSERT_THAT(noneObj["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
|
||||
BSONObjBuilder routerBuilder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::RouterServer].appendTo(routerBuilder);
|
||||
ASSERT_TRUE(routerBuilder.obj().isEmpty());
|
||||
BSONObj routerObj = routerBuilder.obj();
|
||||
ASSERT_THAT(routerObj["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
}
|
||||
|
||||
TEST_F(SerializeMetricsTreeTest, RoleRouter) {
|
||||
CounterOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ingress.openConnections",
|
||||
.role = ClusterRole{ClusterRole::RouterServer},
|
||||
.role = ClusterRole::RouterServer,
|
||||
}};
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, options);
|
||||
@ -716,17 +733,29 @@ TEST_F(SerializeMetricsTreeTest, RoleRouter) {
|
||||
|
||||
BSONObjBuilder noneBuilder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::None].appendTo(noneBuilder);
|
||||
ASSERT_TRUE(noneBuilder.obj().isEmpty());
|
||||
BSONObj noneObj = noneBuilder.obj();
|
||||
ASSERT_THAT(noneObj["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
|
||||
BSONObjBuilder shardBuilder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::ShardServer].appendTo(shardBuilder);
|
||||
ASSERT_TRUE(shardBuilder.obj().isEmpty());
|
||||
BSONObj shardObj = shardBuilder.obj();
|
||||
ASSERT_THAT(shardObj["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
}
|
||||
|
||||
TEST_F(SerializeMetricsTreeTest, RoleNone) {
|
||||
CounterOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ingress.openConnections",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, options);
|
||||
@ -738,26 +767,36 @@ TEST_F(SerializeMetricsTreeTest, RoleNone) {
|
||||
|
||||
BSONObjBuilder shardBuilder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::ShardServer].appendTo(shardBuilder);
|
||||
ASSERT_TRUE(shardBuilder.obj().isEmpty());
|
||||
BSONObj shardObj = shardBuilder.obj();
|
||||
ASSERT_THAT(shardObj["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
|
||||
BSONObjBuilder routerBuilder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::RouterServer].appendTo(routerBuilder);
|
||||
ASSERT_TRUE(routerBuilder.obj().isEmpty());
|
||||
BSONObj routerObj = routerBuilder.obj();
|
||||
ASSERT_THAT(routerObj["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
}
|
||||
|
||||
TEST_F(SerializeMetricsTreeTest, SamePathDifferentMetricNamesDifferentRoles) {
|
||||
const std::string dottedPath = "counter";
|
||||
|
||||
CounterOptions shardOptions{
|
||||
.serverStatusOptions = ServerStatusOptions{.dottedPath = dottedPath,
|
||||
.role = ClusterRole{ClusterRole::ShardServer}}};
|
||||
CounterOptions shardOptions{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = dottedPath, .role = ClusterRole::ShardServer}};
|
||||
auto& shardCounter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, shardOptions);
|
||||
shardCounter.add(7);
|
||||
|
||||
CounterOptions routerOptions{
|
||||
.serverStatusOptions = ServerStatusOptions{.dottedPath = dottedPath,
|
||||
.role = ClusterRole{ClusterRole::RouterServer}}};
|
||||
CounterOptions routerOptions{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = dottedPath, .role = ClusterRole::RouterServer}};
|
||||
auto& routerCounter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest2, "description", MetricUnit::kSeconds, routerOptions);
|
||||
routerCounter.add(9);
|
||||
@ -774,7 +813,7 @@ TEST_F(SerializeMetricsTreeTest, SamePathDifferentMetricNamesDifferentRoles) {
|
||||
TEST_F(SerializeMetricsTreeTest, SharedPrefixSiblingLeaves) {
|
||||
CounterOptions optionsA{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "common.metricA",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& counterA = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, optionsA);
|
||||
@ -782,7 +821,7 @@ TEST_F(SerializeMetricsTreeTest, SharedPrefixSiblingLeaves) {
|
||||
|
||||
CounterOptions optionsB{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "common.metricB",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& counterB = metricsService->createInt64Counter(
|
||||
MetricNames::kTest2, "description", MetricUnit::kSeconds, optionsB);
|
||||
@ -801,7 +840,7 @@ TEST_F(SerializeMetricsTreeTest, SharedPrefixSiblingLeaves) {
|
||||
TEST_F(SerializeMetricsTreeTest, SharedPrefixShallowAndDeep) {
|
||||
CounterOptions shallowOptions{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "common.shallowMetric",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& shallowCounter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, shallowOptions);
|
||||
@ -809,7 +848,7 @@ TEST_F(SerializeMetricsTreeTest, SharedPrefixShallowAndDeep) {
|
||||
|
||||
CounterOptions deepOptions{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "common.nested.deepMetric",
|
||||
.role = ClusterRole{ClusterRole::None},
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& deepCounter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest2, "description", MetricUnit::kSeconds, deepOptions);
|
||||
@ -1156,5 +1195,113 @@ TEST_F(CreateHistogramTest, RecordsDoubleValuesExplicitBoundaries) {
|
||||
EXPECT_EQ(data2.count, 1);
|
||||
}
|
||||
}
|
||||
using ClearForTestsTest = MetricsServiceTest;
|
||||
|
||||
TEST_F(ClearForTestsTest, RemovesFromBothMetricsServiceAndServerStatusTree) {
|
||||
CounterOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ingress.openConnections",
|
||||
.role = ClusterRole::None,
|
||||
}};
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds, options);
|
||||
counter.add(11);
|
||||
|
||||
metricsService->clearForTests();
|
||||
|
||||
OtelMetricsCapturer metricsCapturer(*metricsService);
|
||||
if (metricsCapturer.canReadMetrics()) {
|
||||
ASSERT_THROWS_CODE(metricsCapturer.readInt64Counter(MetricNames::kTest1),
|
||||
DBException,
|
||||
ErrorCodes::KeyNotFound);
|
||||
}
|
||||
{
|
||||
BSONObjBuilder builder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole::None].appendTo(builder);
|
||||
ASSERT_THAT(builder.obj()["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClearForTestsTest, RemovesFromAllServerStatusTrees) {
|
||||
struct RoleAndMetricName {
|
||||
ClusterRole role;
|
||||
const MetricName& name;
|
||||
};
|
||||
for (auto [role, name] : {RoleAndMetricName{ClusterRole::None, MetricNames::kTest1},
|
||||
RoleAndMetricName{ClusterRole::ShardServer, MetricNames::kTest2},
|
||||
RoleAndMetricName{ClusterRole::RouterServer, MetricNames::kTest3}}) {
|
||||
CounterOptions options{.serverStatusOptions = ServerStatusOptions{
|
||||
.dottedPath = "ingress.openConnections",
|
||||
.role = role,
|
||||
}};
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
name, "description", MetricUnit::kOperations, options);
|
||||
counter.add(11);
|
||||
|
||||
BSONObjBuilder builder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole(role)].appendTo(builder);
|
||||
ASSERT_EQ(builder.obj()["metrics"]["ingress"]["openConnections"].Long(), 11);
|
||||
}
|
||||
|
||||
metricsService->clearForTests();
|
||||
|
||||
for (auto role : {ClusterRole::None, ClusterRole::ShardServer, ClusterRole::RouterServer}) {
|
||||
BSONObjBuilder builder;
|
||||
mongo::globalMetricTreeSet()[ClusterRole(role)].appendTo(builder);
|
||||
ASSERT_THAT(builder.obj()["metrics"],
|
||||
AnyOf(IsBSONElement(_, BSONType::eoo, _),
|
||||
IsBSONElement(_,
|
||||
BSONType::object,
|
||||
Matcher<BSONObj>(Not(BSONObjElements(
|
||||
Contains(IsBSONElement("ingress", _, _))))))));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClearForTestsTest, ClearsObservableCallbacks) {
|
||||
OtelMetricsCapturer metricsCapturer(*metricsService);
|
||||
if (!metricsCapturer.canReadMetrics()) {
|
||||
return;
|
||||
}
|
||||
auto& counter = metricsService->createInt64Counter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds);
|
||||
counter.add(11);
|
||||
ASSERT_EQ(metricsCapturer.readInt64Counter(MetricNames::kTest1), 11);
|
||||
|
||||
metricsService->clearForTests();
|
||||
|
||||
// Re-register the same metric name. If the old observable callback was not cleared, triggering
|
||||
// an export would invoke it with a dangling pointer (crash), or report the stale value 11.
|
||||
metricsService->createInt64Counter(MetricNames::kTest1, "description", MetricUnit::kSeconds);
|
||||
ASSERT_EQ(metricsCapturer.readInt64Counter(MetricNames::kTest1), 0);
|
||||
}
|
||||
|
||||
TEST_F(ClearForTestsTest, AllowsReregistrationWithDifferentOptions) {
|
||||
OtelMetricsCapturer metricsCapturer(*metricsService);
|
||||
metricsService->createInt64Counter(MetricNames::kTest1, "description", MetricUnit::kSeconds);
|
||||
metricsService->clearForTests();
|
||||
auto& counter =
|
||||
metricsService->createInt64Counter(MetricNames::kTest1, "description", MetricUnit::kBytes);
|
||||
counter.add(5);
|
||||
if (metricsCapturer.canReadMetrics()) {
|
||||
EXPECT_EQ(metricsCapturer.readInt64Counter(MetricNames::kTest1), 5);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ClearForTestsTest, AllowsReregistrationWithDifferentType) {
|
||||
OtelMetricsCapturer metricsCapturer(*metricsService);
|
||||
metricsService->createInt64Counter(MetricNames::kTest1, "description", MetricUnit::kSeconds);
|
||||
metricsService->clearForTests();
|
||||
auto& counter = metricsService->createDoubleCounter(
|
||||
MetricNames::kTest1, "description", MetricUnit::kSeconds);
|
||||
counter.add(5.0);
|
||||
if (metricsCapturer.canReadMetrics()) {
|
||||
EXPECT_DOUBLE_EQ(metricsCapturer.readDoubleCounter(MetricNames::kTest1), 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mongo::otel::metrics
|
||||
|
||||
Loading…
Reference in New Issue
Block a user