From 837806ab05e145c098d956fc73d5a9f86456353d Mon Sep 17 00:00:00 2001 From: DennisSHCheung <40588409+DennisSHCheung@users.noreply.github.com> Date: Wed, 27 May 2026 10:08:03 +1000 Subject: [PATCH] SERVER-120069 Instrument the storage layer with OpenTelemetry Metrics (#53848) GitOrigin-RevId: 4f19ab5bf8733201b10d1ded3d6988faba5a99c3 --- src/mongo/db/BUILD.bazel | 2 + src/mongo/db/mongod_main.cpp | 4 + src/mongo/otel/metrics/BUILD.bazel | 2 + .../otel/metrics/instrumentation/BUILD.bazel | 71 +++++ .../metrics/instrumentation/disk_metrics.h | 72 +++++ .../instrumentation/disk_metrics_linux.cpp | 209 +++++++++++++++ .../instrumentation/disk_metrics_noop.cpp | 44 +++ .../instrumentation/disk_metrics_test.cpp | 250 ++++++++++++++++++ .../instrumentation/system_mount_metrics.h | 72 +++++ .../system_mount_metrics_linux.cpp | 218 +++++++++++++++ .../system_mount_metrics_noop.cpp | 45 ++++ .../system_mount_metrics_test.cpp | 124 +++++++++ src/mongo/otel/metrics/metric_names.h | 14 +- 13 files changed, 1126 insertions(+), 1 deletion(-) create mode 100644 src/mongo/otel/metrics/instrumentation/BUILD.bazel create mode 100644 src/mongo/otel/metrics/instrumentation/disk_metrics.h create mode 100644 src/mongo/otel/metrics/instrumentation/disk_metrics_linux.cpp create mode 100644 src/mongo/otel/metrics/instrumentation/disk_metrics_noop.cpp create mode 100644 src/mongo/otel/metrics/instrumentation/disk_metrics_test.cpp create mode 100644 src/mongo/otel/metrics/instrumentation/system_mount_metrics.h create mode 100644 src/mongo/otel/metrics/instrumentation/system_mount_metrics_linux.cpp create mode 100644 src/mongo/otel/metrics/instrumentation/system_mount_metrics_noop.cpp create mode 100644 src/mongo/otel/metrics/instrumentation/system_mount_metrics_test.cpp diff --git a/src/mongo/db/BUILD.bazel b/src/mongo/db/BUILD.bazel index 6cd756730c5..c4b03edc9f0 100644 --- a/src/mongo/db/BUILD.bazel +++ b/src/mongo/db/BUILD.bazel @@ -2810,6 +2810,8 @@ mongo_cc_library( "startup_warnings_mongod", "//src/mongo/db/storage:backup_cursor_hooks", "//src/mongo/db/storage:disk_space_monitor", + "//src/mongo/otel/metrics/instrumentation:disk_metrics", + "//src/mongo/otel/metrics/instrumentation:system_mount_metrics", "//src/mongo/db/storage:oplog_truncation", "//src/mongo/db/storage:storage_control", "system_index", diff --git a/src/mongo/db/mongod_main.cpp b/src/mongo/db/mongod_main.cpp index e9b37e1bd26..bd2dcb39142 100644 --- a/src/mongo/db/mongod_main.cpp +++ b/src/mongo/db/mongod_main.cpp @@ -230,6 +230,8 @@ #include "mongo/executor/task_executor_pool.h" #include "mongo/executor/thread_pool_task_executor.h" #include "mongo/logv2/log.h" +#include "mongo/otel/metrics/instrumentation/disk_metrics.h" +#include "mongo/otel/metrics/instrumentation/system_mount_metrics.h" #include "mongo/otel/metrics/metrics_initialization.h" #include "mongo/otel/traces/trace_initialization.h" #include "mongo/platform/atomic_word.h" @@ -1011,6 +1013,8 @@ ExitCode _initAndListen(ServiceContext* serviceContext) { startFLECrud(serviceContext); DiskSpaceMonitor::start(serviceContext); + installSystemMountOtelMetrics(serviceContext); + installDiskOtelMetrics(serviceContext); if (!storageEngine->storesFilesInDbPath()) { LOGV2(7333400, "The index builds DiskSpaceMonitor action which periodically checks if we " diff --git a/src/mongo/otel/metrics/BUILD.bazel b/src/mongo/otel/metrics/BUILD.bazel index 18796afd498..92b3bbef13c 100644 --- a/src/mongo/otel/metrics/BUILD.bazel +++ b/src/mongo/otel/metrics/BUILD.bazel @@ -381,5 +381,7 @@ test_suite( ":otel_metric_name_validation_test", ":otel_metric_server_status_adapter_test", ":server_status_metric_name_validation_test", + "//src/mongo/otel/metrics/instrumentation:disk_metrics_test", + "//src/mongo/otel/metrics/instrumentation:system_mount_metrics_test", ], ) diff --git a/src/mongo/otel/metrics/instrumentation/BUILD.bazel b/src/mongo/otel/metrics/instrumentation/BUILD.bazel new file mode 100644 index 00000000000..c145fd13952 --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/BUILD.bazel @@ -0,0 +1,71 @@ +load("//bazel:mongo_src_rules.bzl", "mongo_cc_library", "mongo_cc_unit_test") + +package(default_visibility = ["//visibility:public"]) + +exports_files( + glob([ + "*.h", + ]), +) + +mongo_cc_library( + name = "disk_metrics", + srcs_select = [{ + "@platforms//os:linux": ["disk_metrics_linux.cpp"], + "//conditions:default": ["disk_metrics_noop.cpp"], + }], + deps = [ + "//src/mongo/db:service_context", + ] + select({ + "@platforms//os:linux": [ + "//src/mongo/otel/metrics:otel_metrics_service", + "//src/mongo/util:periodic_runner", + "//src/mongo/util:procparser", + ], + "//conditions:default": [], + }), +) + +mongo_cc_unit_test( + name = "disk_metrics_test", + srcs = ["disk_metrics_test.cpp"], + tags = ["mongo_unittest_fourth_group"], + deps = [ + ":disk_metrics", + "//src/mongo/db:service_context_test_fixture", + "//src/mongo/db/auth:authmocks", + "//src/mongo/db/shard_role:service_context_non_d", + "//src/mongo/otel/metrics:metrics_test_util", + ], +) + +mongo_cc_library( + name = "system_mount_metrics", + srcs_select = [{ + "@platforms//os:linux": ["system_mount_metrics_linux.cpp"], + "//conditions:default": ["system_mount_metrics_noop.cpp"], + }], + deps = [ + "//src/mongo/db:service_context", + ] + select({ + "@platforms//os:linux": [ + "//src/mongo/otel/metrics:otel_metrics_service", + "//src/mongo/util:periodic_runner", + "//src/mongo/util:procparser", + ], + "//conditions:default": [], + }), +) + +mongo_cc_unit_test( + name = "system_mount_metrics_test", + srcs = ["system_mount_metrics_test.cpp"], + tags = ["mongo_unittest_fifth_group"], + deps = [ + ":system_mount_metrics", + "//src/mongo/db:service_context_test_fixture", + "//src/mongo/db/auth:authmocks", + "//src/mongo/db/shard_role:service_context_non_d", + "//src/mongo/otel/metrics:metrics_test_util", + ], +) diff --git a/src/mongo/otel/metrics/instrumentation/disk_metrics.h b/src/mongo/otel/metrics/instrumentation/disk_metrics.h new file mode 100644 index 00000000000..559b20e4ee4 --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/disk_metrics.h @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/bson/bsonobj.h" +#include "mongo/util/modules.h" + +#include +#include +#include + +namespace mongo { + +class ServiceContext; + +/** + * Owns the OpenTelemetry instruments for disk I/O metrics. The set of devices is fixed at + * construction time: disks that appear after startup are not tracked. + */ +class DiskMetrics { +public: + /** + * Registers per-device counters for each of the provided disk devices. + */ + explicit DiskMetrics(std::vector disks); + ~DiskMetrics(); + + /** + * Walks the BSON and adds deltas to the registered counters. Devices not declared at + * construction time are ignored. + */ + void update(BSONObj disksBson); + +private: + class Impl; + std::unique_ptr _impl; +}; + +/** + * Registers OpenTelemetry disk I/O counters and starts a periodic job that samples + * once per second. No-op on unsupported platforms. + */ +MONGO_MOD_PUBLIC void installDiskOtelMetrics(ServiceContext* svcCtx); + +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/disk_metrics_linux.cpp b/src/mongo/otel/metrics/instrumentation/disk_metrics_linux.cpp new file mode 100644 index 00000000000..59f57cdb19d --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/disk_metrics_linux.cpp @@ -0,0 +1,209 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/service_context.h" +#include "mongo/logv2/log.h" +#include "mongo/logv2/log_severity_suppressor.h" +#include "mongo/otel/metrics/instrumentation/disk_metrics.h" +#include "mongo/otel/metrics/metric_unit.h" +#include "mongo/otel/metrics/metrics_counter.h" +#include "mongo/otel/metrics/metrics_service.h" +#include "mongo/util/duration.h" +#include "mongo/util/periodic_runner.h" +#include "mongo/util/procparser.h" + +#include +#include + +#include +#include + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl + +namespace mongo { + +namespace { + +using otel::metrics::Counter; +using otel::metrics::DynamicMetricNameMaker; +using otel::metrics::MetricsService; +using otel::metrics::MetricUnit; + +constexpr StringData kDiskStatsPath = "/proc/diskstats"_sd; +constexpr StringData kSysBlockPath = "/sys/block"_sd; + +struct DiskCounters { + Counter* reads{nullptr}; + Counter* readSectors{nullptr}; + Counter* readTimeMs{nullptr}; + Counter* writes{nullptr}; + Counter* writeSectors{nullptr}; + Counter* writeTimeMs{nullptr}; + Counter* ioTimeMs{nullptr}; + Counter* ioQueuedMs{nullptr}; +}; + +struct DiskMetricsState { + std::unique_ptr metrics; + std::vector diskNames; + std::vector diskViews; + PeriodicJobAnchor job; +}; + +const auto getDiskMetricsState = ServiceContext::declareDecoration(); + +} // namespace + +class DiskMetrics::Impl { +public: + explicit Impl(std::vector disks) : _disks(std::move(disks)) { + _instruments.resize(_disks.size()); + + for (size_t i = 0; i < _disks.size(); ++i) { + const std::string& disk = _disks[i]; + const auto makeCounter = + [&](StringData field, std::string desc, MetricUnit unit) -> Counter* { + std::string fullName = fmt::format("systemMetrics.disks.{}.{}", disk, field); + return &MetricsService::instance().createInt64Counter( + DynamicMetricNameMaker::make(StringData{fullName}), std::move(desc), unit); + }; + + _instruments[i].reads = makeCounter( + "reads", "Number of read operations completed", MetricUnit::kOperations); + _instruments[i].readSectors = + makeCounter("read_sectors", "Number of sectors read", MetricUnit::kCount); + _instruments[i].readTimeMs = + makeCounter("read_time_ms", "Time spent reading", MetricUnit::kMilliseconds); + _instruments[i].writes = makeCounter( + "writes", "Number of write operations completed", MetricUnit::kOperations); + _instruments[i].writeSectors = + makeCounter("write_sectors", "Number of sectors written", MetricUnit::kCount); + _instruments[i].writeTimeMs = + makeCounter("write_time_ms", "Time spent writing", MetricUnit::kMilliseconds); + _instruments[i].ioTimeMs = makeCounter( + "io_time_ms", "Time disk was busy doing I/O", MetricUnit::kMilliseconds); + _instruments[i].ioQueuedMs = makeCounter("io_queued_ms", + "Weighted time spent in the disk I/O queue", + MetricUnit::kMilliseconds); + } + } + + void update(BSONObj disksBson) { + if (!_previousBson) { + _previousBson = std::move(disksBson); + return; + } + + for (size_t i = 0; i < _disks.size(); ++i) { + const BSONElement prev = (*_previousBson)[_disks[i]]; + const BSONElement curr = disksBson[_disks[i]]; + + if (prev.type() != BSONType::object || curr.type() != BSONType::object) { + continue; + } + + addDeltas(_instruments[i], prev.Obj(), curr.Obj()); + } + + _previousBson = std::move(disksBson); + } + +private: + void addDeltas(DiskCounters& instr, const BSONObj& prev, const BSONObj& curr) { + const auto delta = [&](StringData field) { + return std::max(0LL, curr[field].safeNumberLong() - prev[field].safeNumberLong()); + }; + + instr.reads->add(delta("reads")); + instr.readSectors->add(delta("read_sectors")); + instr.readTimeMs->add(delta("read_time_ms")); + instr.writes->add(delta("writes")); + instr.writeSectors->add(delta("write_sectors")); + instr.writeTimeMs->add(delta("write_time_ms")); + instr.ioTimeMs->add(delta("io_time_ms")); + instr.ioQueuedMs->add(delta("io_queued_ms")); + } + + std::vector _disks; + std::vector _instruments; + boost::optional _previousBson; +}; + +DiskMetrics::DiskMetrics(std::vector disks) + : _impl(std::make_unique(std::move(disks))) {} + +DiskMetrics::~DiskMetrics() = default; + +void DiskMetrics::update(BSONObj disksBson) { + _impl->update(std::move(disksBson)); +} + +void installDiskOtelMetrics(ServiceContext* svcCtx) { + auto& state = getDiskMetricsState(svcCtx); + + state.diskNames = procparser::findPhysicalDisks(kSysBlockPath); + if (state.diskNames.empty()) { + return; + } + + state.diskViews.reserve(state.diskNames.size()); + for (const auto& name : state.diskNames) { + state.diskViews.push_back(name); + } + + state.metrics = std::make_unique(state.diskNames); + + state.job = svcCtx->getPeriodicRunner()->makeJob(PeriodicRunner::PeriodicJob{ + "DiskOtelMetrics", + [&state](Client*) { + BSONObjBuilder builder; + Status s = + procparser::parseProcDiskStatsFile(kDiskStatsPath, state.diskViews, &builder); + if (s.isOK()) { + state.metrics->update(builder.obj()); + } else { + static logv2::SeveritySuppressor suppressor( + Minutes{1}, logv2::LogSeverity::Warning(), logv2::LogSeverity::Debug(3)); + + if (auto sev = suppressor(); shouldLog(MONGO_LOGV2_DEFAULT_COMPONENT, sev)) { + LOGV2_DEBUG( + 12006910, sev.toInt(), "Failed to collect disk stats", "error"_attr = s); + } + } + }, + Seconds(1), + false /*isKillableByStepdown*/}); + + state.job.start(); +} + +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/disk_metrics_noop.cpp b/src/mongo/otel/metrics/instrumentation/disk_metrics_noop.cpp new file mode 100644 index 00000000000..07b0ac8fa79 --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/disk_metrics_noop.cpp @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/otel/metrics/instrumentation/disk_metrics.h" + +namespace mongo { + +class DiskMetrics::Impl {}; + +DiskMetrics::DiskMetrics(std::vector) : _impl(std::make_unique()) {} + +DiskMetrics::~DiskMetrics() = default; + +void DiskMetrics::update(BSONObj) {} + +void installDiskOtelMetrics(ServiceContext*) {} + +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/disk_metrics_test.cpp b/src/mongo/otel/metrics/instrumentation/disk_metrics_test.cpp new file mode 100644 index 00000000000..7f63fd31087 --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/disk_metrics_test.cpp @@ -0,0 +1,250 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/otel/metrics/instrumentation/disk_metrics.h" + +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/otel/metrics/metrics_test_util.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using otel::metrics::DynamicMetricNameMaker; +using otel::metrics::OtelMetricsCapturer; + +constexpr StringData kSda = "sda"_sd; +constexpr StringData kSdb = "sdb"_sd; +constexpr StringData kSdc = "sdc"_sd; + +// All tests register DiskMetrics with this fixed device set. The global MetricsService retains +// registrations across tests within the same binary, so every test must use the same device set. +const std::vector kTestDevices = {std::string(kSda), std::string(kSdb)}; + +constexpr StringData kSdaReads = "systemMetrics.disks.sda.reads"_sd; +constexpr StringData kSdaReadSectors = "systemMetrics.disks.sda.read_sectors"_sd; +constexpr StringData kSdaReadTimeMs = "systemMetrics.disks.sda.read_time_ms"_sd; +constexpr StringData kSdaWrites = "systemMetrics.disks.sda.writes"_sd; +constexpr StringData kSdaWriteSectors = "systemMetrics.disks.sda.write_sectors"_sd; +constexpr StringData kSdaWriteTimeMs = "systemMetrics.disks.sda.write_time_ms"_sd; +constexpr StringData kSdaIoTimeMs = "systemMetrics.disks.sda.io_time_ms"_sd; +constexpr StringData kSdaIoQueuedMs = "systemMetrics.disks.sda.io_queued_ms"_sd; +constexpr StringData kSdbWrites = "systemMetrics.disks.sdb.writes"_sd; +constexpr StringData kSdcWrites = "systemMetrics.disks.sdc.writes"_sd; + +BSONObj makeDiskBson(StringData device, + long long reads, + long long readSectors, + long long readTimeMs, + long long writes, + long long writeSectors, + long long writeTimeMs, + long long ioTimeMs, + long long ioQueuedMs) { + BSONObjBuilder b; + { + BSONObjBuilder sub(b.subobjStart(device)); + sub.appendNumber("reads", reads); + sub.appendNumber("read_sectors", readSectors); + sub.appendNumber("read_time_ms", readTimeMs); + sub.appendNumber("writes", writes); + sub.appendNumber("write_sectors", writeSectors); + sub.appendNumber("write_time_ms", writeTimeMs); + sub.appendNumber("io_time_ms", ioTimeMs); + sub.appendNumber("io_queued_ms", ioQueuedMs); + } + return b.obj(); +} + +class DiskOtelMetricsTest : public unittest::Test { +protected: + void setUp() override { + if (!OtelMetricsCapturer::canReadMetrics()) { + GTEST_SKIP() << "Skipping test: OTel metrics unavailable on this platform"; + } + } + + OtelMetricsCapturer _capturer; + DiskMetrics _metrics{kTestDevices}; +}; + +TEST_F(DiskOtelMetricsTest, FirstUpdateSetsBaseline) { + ASSERT_DOES_NOT_THROW( + _metrics.update(makeDiskBson(kSda, 10, 20, 50, 50, 100, 300, 1000, 1200))); +} + +TEST_F(DiskOtelMetricsTest, SecondUpdateEmitsDeltas) { + _metrics.update(makeDiskBson(kSda, + /*reads=*/10, + /*readSectors=*/20, + /*readTimeMs=*/50, + /*writes=*/50, + /*writeSectors=*/100, + /*writeTimeMs=*/300, + /*ioTimeMs=*/1000, + /*ioQueuedMs=*/1200)); + + _metrics.update(makeDiskBson(kSda, + /*reads=*/25, + /*readSectors=*/50, + /*readTimeMs=*/120, + /*writes=*/70, + /*writeSectors=*/130, + /*writeTimeMs=*/500, + /*ioTimeMs=*/1500, + /*ioQueuedMs=*/1800)); + + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReads)), 15); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReadSectors)), 30); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReadTimeMs)), 70); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWrites)), 20); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWriteSectors)), 30); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWriteTimeMs)), 200); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaIoTimeMs)), 500); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaIoQueuedMs)), 600); +} + +TEST_F(DiskOtelMetricsTest, MultipleDeltasAccumulate) { + _metrics.update(makeDiskBson(kSda, 50, 100, 200, 100, 200, 500, 1000, 1500)); + _metrics.update(makeDiskBson(kSda, 60, 120, 260, 105, 210, 560, 1100, 1650)); + _metrics.update(makeDiskBson(kSda, 80, 160, 380, 115, 230, 680, 1300, 1950)); + + // reads delta: (60-50) + (80-60) = 10 + 20 = 30 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReads)), 30); + // read_sectors delta: (120-100) + (160-120) = 20 + 40 = 60 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReadSectors)), 60); + // read_time_ms delta: (260-200) + (380-260) = 60 + 120 = 180 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReadTimeMs)), 180); + // writes delta: (105-100) + (115-105) = 5 + 10 = 15 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWrites)), 15); + // write_sectors delta: (210-200) + (230-210) = 10 + 20 = 30 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWriteSectors)), 30); + // write_time_ms delta: (560-500) + (680-560) = 60 + 120 = 180 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWriteTimeMs)), 180); + // io_time_ms delta: (1100-1000) + (1300-1100) = 100 + 200 = 300 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaIoTimeMs)), 300); + // io_queued_ms delta: (1650-1500) + (1950-1650) = 150 + 300 = 450 + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaIoQueuedMs)), 450); +} + +TEST_F(DiskOtelMetricsTest, UndeclaredDevicesAreIgnored) { + _metrics.update(makeDiskBson(kSda, 0, 0, 0, 0, 0, 0, 0, 0)); + + BSONObjBuilder both; + { + BSONObjBuilder sub(both.subobjStart(kSda)); + sub.appendNumber("reads", 10LL); + sub.appendNumber("read_sectors", 20LL); + sub.appendNumber("read_time_ms", 50LL); + sub.appendNumber("writes", 20LL); + sub.appendNumber("write_sectors", 40LL); + sub.appendNumber("write_time_ms", 100LL); + sub.appendNumber("io_time_ms", 200LL); + sub.appendNumber("io_queued_ms", 300LL); + } + { + // sdc was never registered — its data must be ignored. + BSONObjBuilder sub(both.subobjStart(kSdc)); + sub.appendNumber("reads", 100LL); + sub.appendNumber("read_sectors", 200LL); + sub.appendNumber("read_time_ms", 500LL); + sub.appendNumber("writes", 500LL); + sub.appendNumber("write_sectors", 1000LL); + sub.appendNumber("write_time_ms", 3000LL); + sub.appendNumber("io_time_ms", 5000LL); + sub.appendNumber("io_queued_ms", 7000LL); + } + _metrics.update(both.obj()); + + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWrites)), 20); + + // sdc was never registered, so its metric name does not exist in the service. + ASSERT_THROWS_CODE(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdcWrites)), + DBException, + ErrorCodes::KeyNotFound); +} + +TEST_F(DiskOtelMetricsTest, MultipleRegisteredDevicesTrackedIndependently) { + BSONObjBuilder baseline; + { + BSONObjBuilder sub(baseline.subobjStart(kSda)); + sub.appendNumber("reads", 40LL); + sub.appendNumber("read_sectors", 80LL); + sub.appendNumber("read_time_ms", 200LL); + sub.appendNumber("writes", 100LL); + sub.appendNumber("write_sectors", 200LL); + sub.appendNumber("write_time_ms", 500LL); + sub.appendNumber("io_time_ms", 1000LL); + sub.appendNumber("io_queued_ms", 1500LL); + } + { + BSONObjBuilder sub(baseline.subobjStart(kSdb)); + sub.appendNumber("reads", 20LL); + sub.appendNumber("read_sectors", 40LL); + sub.appendNumber("read_time_ms", 100LL); + sub.appendNumber("writes", 50LL); + sub.appendNumber("write_sectors", 80LL); + sub.appendNumber("write_time_ms", 200LL); + sub.appendNumber("io_time_ms", 400LL); + sub.appendNumber("io_queued_ms", 600LL); + } + _metrics.update(baseline.obj()); + + BSONObjBuilder next; + { + BSONObjBuilder sub(next.subobjStart(kSda)); + sub.appendNumber("reads", 50LL); + sub.appendNumber("read_sectors", 100LL); + sub.appendNumber("read_time_ms", 250LL); + sub.appendNumber("writes", 110LL); + sub.appendNumber("write_sectors", 220LL); + sub.appendNumber("write_time_ms", 560LL); + sub.appendNumber("io_time_ms", 1100LL); + sub.appendNumber("io_queued_ms", 1650LL); + } + { + BSONObjBuilder sub(next.subobjStart(kSdb)); + sub.appendNumber("reads", 23LL); + sub.appendNumber("read_sectors", 46LL); + sub.appendNumber("read_time_ms", 115LL); + sub.appendNumber("writes", 55LL); + sub.appendNumber("write_sectors", 90LL); + sub.appendNumber("write_time_ms", 230LL); + sub.appendNumber("io_time_ms", 440LL); + sub.appendNumber("io_queued_ms", 660LL); + } + _metrics.update(next.obj()); + + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaReads)), 10); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdaWrites)), 10); + ASSERT_EQ(_capturer.readInt64Counter(DynamicMetricNameMaker::make(kSdbWrites)), 5); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/system_mount_metrics.h b/src/mongo/otel/metrics/instrumentation/system_mount_metrics.h new file mode 100644 index 00000000000..f956e3d3f7c --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/system_mount_metrics.h @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/bson/bsonobj.h" +#include "mongo/util/modules.h" + +#include +#include +#include + +namespace mongo { + +class ServiceContext; + +/** + * Owns the OpenTelemetry instruments for filesystem mount metrics. The set of mountpoints is + * fixed at construction time and mounts that appear after startup are not tracked. + */ +class SystemMountMetrics { +public: + /** + * Registers per-mountpoint gauges for each of the provided mountpoints. + */ + explicit SystemMountMetrics(std::vector mountpoints); + ~SystemMountMetrics(); + + /** + * Walks the BSON and pushes the values to the registered gauges. Mountpoints not declared at + * construction time are ignored. + */ + void update(const BSONObj& mountsBson); + +private: + class Impl; + std::unique_ptr _impl; +}; + +/** + * Registers OpenTelemetry mount filesystem gauges and starts a periodic job that samples + * OS-level mount stats once per second. No-op on unsupported platforms. + */ +MONGO_MOD_PUBLIC void installSystemMountOtelMetrics(ServiceContext* svcCtx); + +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/system_mount_metrics_linux.cpp b/src/mongo/otel/metrics/instrumentation/system_mount_metrics_linux.cpp new file mode 100644 index 00000000000..a4138f717bc --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/system_mount_metrics_linux.cpp @@ -0,0 +1,218 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/service_context.h" +#include "mongo/logv2/log.h" +#include "mongo/logv2/log_severity_suppressor.h" +#include "mongo/otel/metrics/instrumentation/system_mount_metrics.h" +#include "mongo/otel/metrics/metric_unit.h" +#include "mongo/otel/metrics/metrics_gauge.h" +#include "mongo/otel/metrics/metrics_service.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/duration.h" +#include "mongo/util/periodic_runner.h" +#include "mongo/util/procparser.h" + +#include +#include +#include +#include +#include + +#include + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl + +namespace mongo { + +namespace { + +using otel::metrics::DynamicMetricNameMaker; +using otel::metrics::Gauge; +using otel::metrics::MetricsService; +using otel::metrics::MetricUnit; + +struct MountGauges { + Gauge* capacity{nullptr}; + Gauge* available{nullptr}; + Gauge* free{nullptr}; +}; + +struct MountOtelMetricsState { + std::unique_ptr metrics; + PeriodicJobAnchor job; +}; + +const auto getMountOtelMetricsState = ServiceContext::declareDecoration(); + +constexpr StringData kMountInfoPath = "/proc/self/mountinfo"_sd; + +// Sanitize a mount path for use as a metric name segment: +// "/" -> "root" +// "/data" -> "data" +// "/boot/efi" -> "boot.efi" +std::string sanitizeMountpoint(StringData path) { + if (path == "/") { + return "root"; + } + + // Strip leading slash and replace interior slashes with dots. + std::string result(path.substr(1)); + std::replace(result.begin(), result.end(), '/', '.'); + return result; +} + +std::vector enumerateMountpoints() { + BSONObjBuilder probe; + + const Status status = procparser::parseProcSelfMountStatsFile(kMountInfoPath, &probe); + + if (!status.isOK()) { + LOGV2_WARNING(12006900, + "Failed to enumerate mountpoints for OTel system mount metrics", + "error"_attr = status); + return {}; + } + + const BSONObj probeObj = probe.obj(); + std::vector mountpoints; + std::ranges::transform(probeObj, std::back_inserter(mountpoints), [](const BSONElement& entry) { + return std::string(entry.fieldName()); + }); + + std::ranges::sort(mountpoints); + mountpoints.erase(std::ranges::unique(mountpoints).begin(), mountpoints.end()); + + return mountpoints; +} + +std::unique_ptr makeMetrics() { + auto mountpoints = enumerateMountpoints(); + if (mountpoints.empty()) { + return nullptr; + } + + try { + return std::make_unique(std::move(mountpoints)); + } catch (const DBException& ex) { + LOGV2_ERROR( + 12006901, "Failed to register OTel system mount metrics", "error"_attr = ex.toStatus()); + return nullptr; + } +} + +} // namespace + +class SystemMountMetrics::Impl { +public: + explicit Impl(std::vector mountpoints) : _mountpoints(std::move(mountpoints)) { + _instruments.resize(_mountpoints.size()); + + for (size_t i = 0; i < _mountpoints.size(); ++i) { + const auto& mountpoint = _mountpoints[i]; + const auto sanitized = sanitizeMountpoint(mountpoint); + + const auto makeGauge = + [&](StringData field, std::string desc, MetricUnit unit) -> Gauge* { + std::string fullName = fmt::format("systemMetrics.mounts.{}.{}", sanitized, field); + return &MetricsService::instance().createInt64Gauge( + DynamicMetricNameMaker::make(StringData{fullName}), std::move(desc), unit); + }; + + _instruments[i].capacity = + makeGauge("capacity", "Total filesystem capacity in bytes", MetricUnit::kBytes); + _instruments[i].available = + makeGauge("available", "Filesystem space available in bytes", MetricUnit::kBytes); + _instruments[i].free = + makeGauge("free", "Total free filesystem space in bytes", MetricUnit::kBytes); + } + } + + void update(const BSONObj& mountsBson) { + for (size_t i = 0; i < _mountpoints.size(); ++i) { + const BSONElement entry = mountsBson[_mountpoints[i]]; + if (entry.type() != BSONType::object) { + continue; + } + + const BSONObj stats = entry.Obj(); + _instruments[i].capacity->set(stats["capacity"].safeNumberLong()); + _instruments[i].available->set(stats["available"].safeNumberLong()); + _instruments[i].free->set(stats["free"].safeNumberLong()); + } + } + +private: + std::vector _mountpoints; + std::vector _instruments; +}; + +SystemMountMetrics::SystemMountMetrics(std::vector mountpoints) + : _impl(std::make_unique(std::move(mountpoints))) {} + +SystemMountMetrics::~SystemMountMetrics() = default; + +void SystemMountMetrics::update(const BSONObj& mountsBson) { + _impl->update(mountsBson); +} + +void installSystemMountOtelMetrics(ServiceContext* svcCtx) { + auto metrics = makeMetrics(); + if (!metrics) { + return; + } + + auto& state = getMountOtelMetricsState(svcCtx); + state.metrics = std::move(metrics); + state.job = svcCtx->getPeriodicRunner()->makeJob(PeriodicRunner::PeriodicJob{ + "SystemMountOtelMetrics", + [&state](Client*) { + BSONObjBuilder builder; + Status s = procparser::parseProcSelfMountStatsFile(kMountInfoPath, &builder); + if (s.isOK()) { + state.metrics->update(builder.obj()); + } else { + static logv2::SeveritySuppressor suppressor( + Minutes{1}, logv2::LogSeverity::Warning(), logv2::LogSeverity::Debug(3)); + + if (auto sev = suppressor(); shouldLog(MONGO_LOGV2_DEFAULT_COMPONENT, sev)) { + LOGV2_DEBUG( + 12006902, sev.toInt(), "Failed to collect mount stats", "error"_attr = s); + } + } + }, + Seconds(1), + false /*isKillableByStepdown*/}); + + state.job.start(); +} + +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/system_mount_metrics_noop.cpp b/src/mongo/otel/metrics/instrumentation/system_mount_metrics_noop.cpp new file mode 100644 index 00000000000..d4606f4a5b4 --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/system_mount_metrics_noop.cpp @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/otel/metrics/instrumentation/system_mount_metrics.h" + +namespace mongo { + +class SystemMountMetrics::Impl {}; + +SystemMountMetrics::SystemMountMetrics(std::vector) + : _impl(std::make_unique()) {} + +SystemMountMetrics::~SystemMountMetrics() = default; + +void SystemMountMetrics::update(const BSONObj&) {} + +void installSystemMountOtelMetrics(ServiceContext*) {} + +} // namespace mongo diff --git a/src/mongo/otel/metrics/instrumentation/system_mount_metrics_test.cpp b/src/mongo/otel/metrics/instrumentation/system_mount_metrics_test.cpp new file mode 100644 index 00000000000..7178227eaed --- /dev/null +++ b/src/mongo/otel/metrics/instrumentation/system_mount_metrics_test.cpp @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2026-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/otel/metrics/instrumentation/system_mount_metrics.h" + +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/otel/metrics/metrics_test_util.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + +using otel::metrics::DynamicMetricNameMaker; +using otel::metrics::OtelMetricsCapturer; + +constexpr StringData kDataMount = "/data"_sd; +constexpr StringData kTmpMount = "/tmp"_sd; + +// All tests register SystemMountMetrics with this fixed mountpoint set. The global MetricsService +// retains registrations across tests within the same binary, so every test must use the same set. +const std::vector kTestMountpoints = {std::string(kDataMount)}; + +constexpr StringData kDataCapacity = "systemMetrics.mounts.data.capacity"_sd; +constexpr StringData kDataAvailable = "systemMetrics.mounts.data.available"_sd; +constexpr StringData kDataFree = "systemMetrics.mounts.data.free"_sd; +constexpr StringData kTmpCapacity = "systemMetrics.mounts.tmp.capacity"_sd; + +BSONObj makeMountsBson(StringData mountpoint, + long long capacity, + long long available, + long long free) { + BSONObjBuilder b; + { + BSONObjBuilder sub(b.subobjStart(mountpoint)); + sub.appendNumber("capacity", capacity); + sub.appendNumber("available", available); + sub.appendNumber("free", free); + } + return b.obj(); +} + +class SystemMountOtelMetricsTest : public unittest::Test { +protected: + void setUp() override { + if (!OtelMetricsCapturer::canReadMetrics()) { + GTEST_SKIP() << "Skipping test: OTel metrics unavailable on this platform"; + } + } + + OtelMetricsCapturer _capturer; + SystemMountMetrics _metrics{kTestMountpoints}; +}; + +TEST_F(SystemMountOtelMetricsTest, UpdateSetsGaugeValues) { + _metrics.update(makeMountsBson(kDataMount, 1000, 400, 500)); + + ASSERT_EQ(_capturer.readInt64Gauge(DynamicMetricNameMaker::make(kDataCapacity)), 1000); + ASSERT_EQ(_capturer.readInt64Gauge(DynamicMetricNameMaker::make(kDataAvailable)), 400); + ASSERT_EQ(_capturer.readInt64Gauge(DynamicMetricNameMaker::make(kDataFree)), 500); +} + +TEST_F(SystemMountOtelMetricsTest, UpdateIgnoresUndeclaredMountpoints) { + _metrics.update(makeMountsBson(kDataMount, 1000, 400, 500)); + + BSONObjBuilder both; + { + BSONObjBuilder sub(both.subobjStart(kDataMount)); + sub.appendNumber("capacity", 2000LL); + sub.appendNumber("available", 800LL); + sub.appendNumber("free", 1000LL); + } + { + // /tmp was never registered — its data must be ignored. + BSONObjBuilder sub(both.subobjStart(kTmpMount)); + sub.appendNumber("capacity", 500LL); + sub.appendNumber("available", 200LL); + sub.appendNumber("free", 250LL); + } + _metrics.update(both.obj()); + + ASSERT_EQ(_capturer.readInt64Gauge(DynamicMetricNameMaker::make(kDataCapacity)), 2000); + + // /tmp was never registered, so its metric name does not exist in the service. + ASSERT_THROWS_CODE(_capturer.readInt64Gauge(DynamicMetricNameMaker::make(kTmpCapacity)), + DBException, + ErrorCodes::KeyNotFound); +} + +TEST_F(SystemMountOtelMetricsTest, UpdateIsIdempotentForSameValues) { + BSONObj bson = makeMountsBson(kDataMount, 8000, 3000, 4000); + _metrics.update(bson); + _metrics.update(bson); + + ASSERT_EQ(_capturer.readInt64Gauge(DynamicMetricNameMaker::make(kDataCapacity)), 8000); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/otel/metrics/metric_names.h b/src/mongo/otel/metrics/metric_names.h index 29e0c6c0ea9..35bf03122af 100644 --- a/src/mongo/otel/metrics/metric_names.h +++ b/src/mongo/otel/metrics/metric_names.h @@ -60,7 +60,6 @@ public: */ constexpr MetricName(StringData name, Passkey) : _name(name) {} constexpr MetricName(StringData name, Passkey) : _name(name) {} - constexpr StringData getName() const { return _name; } @@ -79,6 +78,19 @@ class MONGO_MOD_FILE_PRIVATE MetricNameMaker{public : static constexpr MetricNam } // namespace otel::metrics }; // namespace mongo +/** + * Helper to create MetricName instances with runtime-constructed names (e.g. names that embed + * device names or mount paths discovered at startup). Requires N&O review since dynamic names + * cannot be audited at compile time. + * + * TODO(SERVER-127521): Ensure ServerStatusOptions is boost::none for any runtime metric. + */ +class MONGO_MOD_PUBLIC DynamicMetricNameMaker{ + public : static MetricName make(StringData name){return MetricNameMaker::make(name); +} +} +; + /** * Central registry of OpenTelemetry metric names used in the server. When adding a new metric to * the server, please add an entry to MetricNames grouped under your team name.