summaryrefslogtreecommitdiff
path: root/src/mongo/util
diff options
context:
space:
mode:
authorAmirsaman Memaripour <amirsaman.memaripour@mongodb.com>2022-01-07 15:53:37 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-01-07 17:20:56 +0000
commit3f951c777d1002909c6e5cef1a58556e040ebac8 (patch)
tree9f681e4b8b25e5cf8e9322c43e32bd58cd3d85e9 /src/mongo/util
parent997bade5afb420cdf369d7fc66d7cb9498230635 (diff)
downloadmongo-3f951c777d1002909c6e5cef1a58556e040ebac8.tar.gz
SERVER-59700 Add programming support for tracepoints
Diffstat (limited to 'src/mongo/util')
-rw-r--r--src/mongo/util/SConscript22
-rw-r--r--src/mongo/util/tracing_support.cpp164
-rw-r--r--src/mongo/util/tracing_support.h148
-rw-r--r--src/mongo/util/tracing_support_test.cpp134
4 files changed, 468 insertions, 0 deletions
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript
index 43cd11078f3..3a2a26934ac 100644
--- a/src/mongo/util/SConscript
+++ b/src/mongo/util/SConscript
@@ -291,6 +291,28 @@ env.Library(
]
)
+env.Library(
+ target='tracing_support',
+ source=[
+ 'tracing_support.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ]
+)
+
+env.CppUnitTest(
+ target='tracing_support_test',
+ source=[
+ 'tracing_support_test.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/util/clock_source_mock',
+ '$BUILD_DIR/mongo/util/tracing_support',
+ ],
+)
+
+
env.CppUnitTest(
target='thread_safety_context_test',
source=[
diff --git a/src/mongo/util/tracing_support.cpp b/src/mongo/util/tracing_support.cpp
new file mode 100644
index 00000000000..e3ee5b173ce
--- /dev/null
+++ b/src/mongo/util/tracing_support.cpp
@@ -0,0 +1,164 @@
+/**
+ * Copyright (C) 2021-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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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.
+ */
+
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault
+
+#include <deque>
+
+#include "mongo/util/tracing_support.h"
+
+#include "mongo/base/init.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/logv2/log.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/static_immortal.h"
+#include "mongo/util/system_clock_source.h"
+
+namespace mongo {
+
+namespace {
+
+class BasicTracerFactory final : public Tracer::Factory {
+public:
+ BasicTracerFactory(std::string name, Tracer* tracer)
+ : _name(std::move(name)), _tracer(tracer) {}
+
+ class BasicSpan final : public Tracer::Span {
+ public:
+ BasicSpan(BSONObjBuilder bob, std::shared_ptr<Tracer> tracer)
+ : _bob(std::move(bob)), _tracer(std::move(tracer)) {
+ _bob.append("started"_sd, _tracer->getClockSource()->now());
+ }
+
+ ~BasicSpan() {
+ _spans = boost::none;
+ _bob.append("stopped"_sd, _tracer->getClockSource()->now());
+ }
+
+ BSONObjBuilder makeSubSpan(std::string name) {
+ if (!_spans) {
+ _spans.emplace(_bob.subobjStart("spans"_sd));
+ }
+ return _spans->subobjStart(name);
+ }
+
+ private:
+ BSONObjBuilder _bob;
+ boost::optional<BSONObjBuilder> _spans;
+ const std::shared_ptr<Tracer> _tracer;
+ };
+
+ Tracer::ScopedSpan startSpan(std::string name) override {
+ if (_spans.empty()) {
+ // We're starting a new root span, so erase the most recent trace.
+ _trace = boost::none;
+ }
+ auto span = std::make_unique<BasicSpan>(_makeObjBuilder(std::move(name)),
+ _tracer->shared_from_this());
+ _spans.push_back(span.get());
+ return Tracer::ScopedSpan(span.release(), [this](Tracer::Span* span) {
+ invariant(span == _spans.back(), "Spans must go out of scope in the order of creation");
+ _spans.pop_back();
+ delete span;
+
+ if (_spans.empty()) {
+ _trace.emplace(_builder->obj());
+ _builder = boost::none;
+ }
+ });
+ }
+
+ boost::optional<BSONObj> getLatestTrace() const override {
+ return _trace;
+ }
+
+private:
+ BSONObjBuilder _makeObjBuilder(std::string spanName) {
+ if (_spans.empty()) {
+ // This is the root span.
+ _builder.emplace();
+ _builder->append("tracer"_sd, _name);
+ return _builder->subobjStart(spanName);
+ } else {
+ // This is a child for the currently active span.
+ auto& activeSpan = *_spans.back();
+ return activeSpan.makeSubSpan(std::move(spanName));
+ }
+ }
+
+ const std::string _name;
+ Tracer* const _tracer;
+
+ std::deque<BasicSpan*> _spans;
+ boost::optional<BSONObjBuilder> _builder;
+ boost::optional<BSONObj> _trace;
+};
+
+boost::optional<TracerProvider>& getTraceProvider() {
+ static StaticImmortal<boost::optional<TracerProvider>> provider;
+ return *provider;
+}
+
+MONGO_INITIALIZER(InitializeTraceProvider)(InitializerContext*) {
+ // The following checks if the tracer is already initialized. This allows declaring another
+ // `MONGO_INITIALIZER` that can precede the following and initialize the tracer with a custom
+ // clock source. This is especially helpful for mocking the clock source in unit-tests.
+ if (auto& provider = getTraceProvider(); provider.has_value()) {
+ return;
+ }
+
+ LOGV2_OPTIONS(5970001,
+ {logv2::LogTag::kStartupWarnings},
+ "Operation tracing is enabled. This may have performance implications.");
+ TracerProvider::initialize(std::make_unique<SystemClockSource>()); // NOLINT
+}
+
+} // namespace
+
+Tracer::Tracer(std::string name, ClockSource* clkSource) : _clkSource(clkSource) {
+ _factory = std::make_unique<BasicTracerFactory>(std::move(name), this);
+}
+
+void TracerProvider::initialize(std::unique_ptr<ClockSource> clkSource) { // NOLINT
+ auto& provider = getTraceProvider();
+ invariant(!provider.has_value(), "already initialized");
+ provider.emplace(TracerProvider(std::move(clkSource)));
+}
+
+TracerProvider& TracerProvider::get() { // NOLINT
+ auto& provider = getTraceProvider();
+ invariant(provider.has_value(), "not initialized");
+ return provider.get();
+}
+
+std::shared_ptr<Tracer> TracerProvider::getTracer(std::string name) {
+ return std::make_shared<Tracer>(name, _clkSource.get());
+}
+
+} // namespace mongo
diff --git a/src/mongo/util/tracing_support.h b/src/mongo/util/tracing_support.h
new file mode 100644
index 00000000000..a41db1983cb
--- /dev/null
+++ b/src/mongo/util/tracing_support.h
@@ -0,0 +1,148 @@
+/**
+ * Copyright (C) 2021-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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 <memory>
+#include <string>
+
+#include <boost/optional.hpp>
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/util/clock_source.h"
+
+namespace mongo {
+
+/**
+ * Handles span creation and provides a compatible interface to `opentelemetry::trace::Tracer`.
+ * This class does not support concurrent accesses, and should not be used in production.
+ *
+ * Spans are organized in a hierarchy. Once a new span is created, through calling `startSpan()`,
+ * it will be added as a child to the active span, and replaces its parent as the new active span.
+ * When there is no active span, the newly created span is considered as the root span.
+ *
+ * Once the root span goes out of scope, the collected trace is serialized into a BSON object, and
+ * may be retrieved through `getLatestTrace()`. The trace object remains valid until a new root span
+ * is created. This interface is not compatible with `opentelemetry::trace::Tracer`, and fills the
+ * gap for `opentelemetry` exporters.
+ *
+ * Here is an example on how to create spans and retrieve traces:
+ * ```
+ * void f1(std::shared_ptr<Tracer> tracer) {
+ * auto root = tracer->startSpan("root");
+ * sleepFor(Milliseconds(1));
+ * {
+ * auto child = tracer->startSpan("child");
+ * sleepFor(Milliseconds(2));
+ * }
+ * }
+ *
+ * void f2() {
+ * auto tracer = TracerProvider::get().getTracer("myTracer");
+ * f1(tracer);
+ * BSONObj trace = tracer->getLatestTrace();
+ * }
+ * ```
+ *
+ * The above code will produce the following `BSONObj`:
+ * ```
+ * {
+ * "tracer": "myTracer",
+ * "root": {
+ * "started": 2021-11-04T00:00:00.000,
+ * "spans": {
+ * "child": {
+ * "started": 2021-11-04T00:00:00.001,
+ * "stopped": 2021-11-04T00:00:00.003,
+ * },
+ * },
+ * "stopped": 2021-11-04T00:00:00.003,
+ * },
+ * }
+ * ```
+ */
+class Tracer : public std::enable_shared_from_this<Tracer> {
+public:
+ Tracer(std::string name, ClockSource* clkSource);
+
+ class Span {
+ public:
+ virtual ~Span() = default;
+ };
+
+ using ScopedSpan = std::unique_ptr<Span, std::function<void(Span*)>>;
+
+ class Factory {
+ public:
+ virtual ~Factory() = default;
+
+ virtual ScopedSpan startSpan(std::string) = 0;
+
+ virtual boost::optional<BSONObj> getLatestTrace() const = 0;
+ };
+
+ ScopedSpan startSpan(std::string name) {
+ return _factory->startSpan(std::move(name));
+ }
+
+ boost::optional<BSONObj> getLatestTrace() const {
+ return _factory->getLatestTrace();
+ }
+
+ ClockSource* getClockSource() {
+ return _clkSource;
+ }
+
+private:
+ ClockSource* const _clkSource;
+ std::unique_ptr<Factory> _factory;
+};
+
+/**
+ * The factory class for constructing instances of `Tracer`.
+ * Consider the following before using this class:
+ * Must be initialized first by calling `initialize()`, and implements the singleton pattern.
+ * Once initialized, multiple threads my concurrently call into `get()` and `getTracer()`.
+ */
+class TracerProvider {
+public:
+ explicit TracerProvider(std::unique_ptr<ClockSource> clkSource)
+ : _clkSource(std::move(clkSource)) {}
+
+ static void initialize(std::unique_ptr<ClockSource> clkSource);
+
+ static TracerProvider& get();
+
+ std::shared_ptr<Tracer> getTracer(std::string name);
+
+private:
+ std::unique_ptr<ClockSource> _clkSource;
+};
+
+} // namespace mongo
diff --git a/src/mongo/util/tracing_support_test.cpp b/src/mongo/util/tracing_support_test.cpp
new file mode 100644
index 00000000000..804069cf8e6
--- /dev/null
+++ b/src/mongo/util/tracing_support_test.cpp
@@ -0,0 +1,134 @@
+/**
+ * Copyright (C) 2021-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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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.
+ */
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest
+
+#include <memory>
+
+#include "mongo/base/init.h"
+#include "mongo/bson/bsonmisc.h"
+#include "mongo/logv2/log.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/clock_source_mock.h"
+#include "mongo/util/tracing_support.h"
+
+/*
+ * We use `NOLINT` throughout this file since usages of `TracerProvider` aren't meant to be used in
+ * production, but we still wanted to be able to commit unit tests.
+ */
+
+namespace mongo {
+namespace {
+std::unique_ptr<ClockSource> makeClockSource() {
+ return std::make_unique<ClockSourceMock>();
+}
+
+void advanceTime(std::shared_ptr<Tracer>& tracer, Milliseconds duration) {
+ ClockSourceMock* clk = dynamic_cast<ClockSourceMock*>(tracer->getClockSource());
+ clk->advance(duration);
+}
+
+// Uses the mocked clock source to initialize the trace provider.
+MONGO_INITIALIZER_GENERAL(InitializeTraceProviderForTest, (), ("InitializeTraceProvider"))
+(InitializerContext*) {
+ TracerProvider::initialize(makeClockSource()); // NOLINT
+}
+
+static constexpr auto kTracerName = "MyTracer";
+} // namespace
+
+DEATH_TEST(TracingSupportTest, CannotInitializeTwice, "invariant") {
+ TracerProvider::initialize(makeClockSource()); // NOLINT
+}
+
+DEATH_TEST(TracingSupportTest, SpansMustCloseInOrder, "invariant") {
+ auto tracer = TracerProvider::get().getTracer(kTracerName); // NOLINT
+ auto outerSpan = tracer->startSpan("outer span");
+ auto innerSpan = tracer->startSpan("inner span");
+ outerSpan.reset();
+}
+
+TEST(TracingSupportTest, TraceIsInitiallyEmpty) {
+ auto tracer = TracerProvider::get().getTracer(kTracerName); // NOLINT
+ ASSERT_FALSE(tracer->getLatestTrace());
+}
+
+TEST(TracingSupportTest, TraceIsEmptyWithActiveSpans) {
+ auto tracer = TracerProvider::get().getTracer(kTracerName); // NOLINT
+ auto span = tracer->startSpan("some span");
+ ASSERT_FALSE(tracer->getLatestTrace());
+}
+
+TEST(TracingSupportTest, BasicUsage) {
+ const auto kSpanDuration = Seconds(5);
+ auto tracer = TracerProvider::get().getTracer(kTracerName); // NOLINT
+ const auto startTime = tracer->getClockSource()->now();
+
+ {
+ auto rootSpan = tracer->startSpan("root");
+ advanceTime(tracer, kSpanDuration);
+ {
+ auto childSpan = tracer->startSpan("child");
+ advanceTime(tracer, kSpanDuration);
+ {
+ {
+ auto grandChildOne = tracer->startSpan("grand child #1");
+ advanceTime(tracer, kSpanDuration);
+ }
+ {
+ auto grandChildTwo = tracer->startSpan("grand child #2");
+ advanceTime(tracer, kSpanDuration);
+ }
+ }
+ }
+ }
+
+ const auto trace = tracer->getLatestTrace();
+ ASSERT_TRUE(trace);
+
+ const auto expected = BSON(
+ "tracer"
+ << kTracerName << "root"
+ << BSON("started"
+ << startTime << "spans"
+ << BSON("child" << BSON(
+ "started"
+ << startTime + kSpanDuration << "spans"
+ << BSON("grand child #1"
+ << BSON("started" << startTime + 2 * kSpanDuration << "stopped"
+ << startTime + 3 * kSpanDuration)
+ << "grand child #2"
+ << BSON("started" << startTime + 3 * kSpanDuration << "stopped"
+ << startTime + 4 * kSpanDuration))
+ << "stopped" << startTime + 4 * kSpanDuration))
+ << "stopped" << startTime + 4 * kSpanDuration));
+ ASSERT_BSONOBJ_EQ(expected, trace.get());
+}
+
+} // namespace mongo