summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/executor/SConscript1
-rw-r--r--src/mongo/executor/split_timer.h204
-rw-r--r--src/mongo/executor/split_timer_test.cpp230
-rw-r--r--src/mongo/transport/session_workflow.cpp317
4 files changed, 598 insertions, 154 deletions
diff --git a/src/mongo/executor/SConscript b/src/mongo/executor/SConscript
index d4d706ec871..a4b691b7084 100644
--- a/src/mongo/executor/SConscript
+++ b/src/mongo/executor/SConscript
@@ -332,6 +332,7 @@ env.CppUnitTest(
'network_interface_mock_test_fixture.cpp',
'network_interface_tl_test.cpp',
'scoped_task_executor_test.cpp',
+ 'split_timer_test.cpp',
'task_executor_cursor_test.cpp',
'thread_pool_task_executor_test.cpp',
],
diff --git a/src/mongo/executor/split_timer.h b/src/mongo/executor/split_timer.h
new file mode 100644
index 00000000000..32f956ed8dc
--- /dev/null
+++ b/src/mongo/executor/split_timer.h
@@ -0,0 +1,204 @@
+/**
+ * Copyright (C) 2022-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 <array>
+#include <boost/optional.hpp>
+#include <fmt/format.h>
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/logv2/log.h"
+#include "mongo/util/duration.h"
+#include "mongo/util/timer.h"
+
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
+
+namespace mongo {
+
+namespace split_timer_detail {
+
+/**
+ * Takes an invokable `f` and a pack of optionals `as...`.
+ * Invokes `f(*as...)`, returning the result as an optional.
+ * If any of `as...` are disengaged, then `f` is not
+ * invoked, and a disengaged result is returned.
+ */
+template <typename F,
+ typename... As,
+ typename R = std::invoke_result_t<F, decltype(*std::declval<As>())...>>
+boost::optional<R> applyViaOptionals(F&& f, As&&... as) {
+ if (!(as && ...))
+ return {};
+ return std::invoke(f, *as...);
+}
+
+/** Rule of Zero utility. Starts true, becomes false when moved from or dismissed. */
+class UniqueActive {
+public:
+ UniqueActive() = default;
+
+ UniqueActive(UniqueActive&& other) noexcept : _active{other.dismiss()} {}
+
+ UniqueActive& operator=(UniqueActive&& other) noexcept {
+ if (this != &other)
+ _active = other.dismiss();
+ return *this;
+ }
+
+ explicit operator bool() const {
+ return _active;
+ }
+
+ /** Returns true if dismiss changed this object's state. */
+ bool dismiss() {
+ return std::exchange(_active, false);
+ }
+
+private:
+ bool _active = true;
+};
+} // namespace split_timer_detail
+
+/**
+ * Acts as a split timer which captures times elapsed at various points throughout a single
+ * SessionWorkflow loop. The SessionWorkflow loop is expected to construct this
+ * object when timing should begin, call this object's `notify` function at
+ * appropriate times throughout the workflow, and destroy it when the loop work is done.
+ *
+ * Requirements on Policy:
+ * - TimeSplitIdType and IntervalIdType packed enums.
+ * - static constexpr properties:
+ * - size_t numIntervalIds and numTimesplitIds for enum extents.
+ * - toIdx(e) to convert these enums to size_t.
+ * - getName(e) to convert these enums to StringData (intervals should end in "Millis").
+ * - TimeSplitIdType getStartSplit(IntervalIdType).
+ * - TimeSplitIdType getEndSplit(IntervalIdType).
+ * - nonstatic onStart and onFinish member functions, taking a pointer to this SplitTimer.
+ * - nonstatic makeTimer to initialize Timer.
+ */
+template <typename Policy>
+class SplitTimer {
+private:
+ template <typename E>
+ static constexpr size_t _idx(E e) {
+ return Policy::toIdx(e);
+ }
+
+public:
+ using TimeSplitIdType = typename Policy::TimeSplitIdType;
+ using IntervalIdType = typename Policy::IntervalIdType;
+
+ SplitTimer() : SplitTimer{Policy{}} {}
+ SplitTimer(Policy policy) : _policy{std::move(policy)} {
+ _policy.onStart(this);
+ }
+
+ SplitTimer& operator=(SplitTimer&&) = default;
+ SplitTimer(SplitTimer&&) = default;
+
+ /**
+ * Idempotent: if already inactive (moved-from), does nothing.
+ * If active: sets to inactive, captures the elapsed time for the `done`
+ * TimeSplitId, logs any necessary metrics.
+ */
+ ~SplitTimer() {
+ if (!_active)
+ return;
+ _policy.onFinish(this);
+ }
+
+ /**
+ * Captures the elapsed time and associates it with `split`. A second call with the same `split`
+ * will overwrite the previous. It is expected that this gets called for all splits other than
+ * TimerSplit::start and TimerSplit::done.
+ */
+ void notify(TimeSplitIdType split) {
+ using namespace fmt::literals;
+ invariant(!_topSplit || _idx(*_topSplit) <= _idx(split),
+ "Notify out of order: {} then {}"_format(_policy.getName(*_topSplit),
+ _policy.getName(split)));
+ _topSplit = split;
+
+ _splits[_idx(split)] = _timer.elapsed();
+ }
+
+ /**
+ * Returns the time elapsed between the two splits corresponding to `start` and `end`.
+ * If either is disengaged, a disengaged optional will be returned.
+ */
+ boost::optional<Microseconds> getSplitInterval(IntervalIdType id) const {
+ return split_timer_detail::applyViaOptionals(std::minus<>{},
+ _splits[_idx(_policy.getEndSplit(id))],
+ _splits[_idx(_policy.getStartSplit(id))]);
+ }
+
+ /**
+ * Appends the intervals defined in Policy::intervalDefs to `builder`.
+ * Logs the whole builder if there's a problem.
+ */
+ void appendIntervals(BSONObjBuilder& builder) const {
+ using namespace fmt::literals;
+ for (size_t i = 0; i != Policy::numIntervalIds; ++i) {
+ IntervalIdType iId{i};
+ auto dt = getSplitInterval(iId);
+ if (!dt)
+ continue;
+ if (*dt < Microseconds{0})
+ LOGV2_FATAL(6983001,
+ "Negative time interval",
+ "dt"_attr = dt->toString(),
+ "splits"_attr = _splitsToBSON());
+ builder.append(_policy.getName(iId), durationCount<Milliseconds>(*dt));
+ }
+ }
+
+private:
+ using SplitsArray = std::array<boost::optional<Microseconds>, Policy::numTimeSplitIds>;
+
+ BSONObj _splitsToBSON() const {
+ BSONObjBuilder bob;
+ for (size_t i = 0; i != Policy::numTimeSplitIds; ++i) {
+ TimeSplitIdType ts{i};
+ auto&& t = _splits[_idx(ts)];
+ bob.append(_policy.getName(ts), t ? t->toString() : "");
+ }
+ return bob.obj();
+ }
+
+ Policy _policy;
+ split_timer_detail::UniqueActive _active;
+ Timer _timer = _policy.makeTimer();
+ SplitsArray _splits;
+ boost::optional<TimeSplitIdType> _topSplit;
+};
+} // namespace mongo
+
+#undef MONGO_LOGV2_DEFAULT_COMPONENT
diff --git a/src/mongo/executor/split_timer_test.cpp b/src/mongo/executor/split_timer_test.cpp
new file mode 100644
index 00000000000..4f5b3695b1f
--- /dev/null
+++ b/src/mongo/executor/split_timer_test.cpp
@@ -0,0 +1,230 @@
+/**
+ * Copyright (C) 2022-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.
+ */
+
+#include "mongo/executor/split_timer.h"
+
+#include <array>
+#include <boost/optional.hpp>
+#include <fmt/format.h>
+
+#include "mongo/base/string_data.h"
+#include "mongo/unittest/assert_that.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/duration.h"
+#include "mongo/util/tick_source_mock.h"
+
+namespace mongo {
+namespace {
+
+namespace m = unittest::match;
+using namespace fmt::literals;
+
+/** Match that `x` converts to `true` and that `*x` matches `m`. */
+template <typename M>
+class WhenDereferenced : public m::Matcher {
+public:
+ explicit WhenDereferenced(M&& m) : _m{std::move(m)} {}
+
+ std::string describe() const {
+ return "WhenDereferenced({})"_format(_m.describe());
+ }
+
+ template <typename X>
+ m::MatchResult match(const X& x) const {
+ if (!x)
+ return {false, "converts to a false bool value"};
+ return _m.match(*x);
+ }
+
+private:
+ M _m;
+};
+
+/** Match that `static_cast<bool>(x)` matches `m`. */
+template <typename M>
+class WhenBool : public m::Matcher {
+public:
+ explicit WhenBool(M&& m) : _m{std::move(m)} {}
+
+ std::string describe() const {
+ return "WhenBool({})"_format(_m.describe());
+ }
+
+ template <typename X>
+ m::MatchResult match(const X& x) const {
+ return _m.match(static_cast<bool>(x));
+ }
+
+private:
+ M _m;
+};
+
+enum class SomeTimeSplitId : size_t {
+ a,
+ b,
+ c,
+};
+
+enum class SomeIntervalId : size_t {
+ ab,
+ ac,
+};
+
+struct IDef {
+ SomeIntervalId iId;
+ StringData name;
+ SomeTimeSplitId start;
+ SomeTimeSplitId end;
+};
+
+static constexpr auto iDefs = std::array{
+ IDef{SomeIntervalId::ab, "abMillis"_sd, SomeTimeSplitId::a, SomeTimeSplitId::b},
+ IDef{SomeIntervalId::ac, "acMillis"_sd, SomeTimeSplitId::a, SomeTimeSplitId::c},
+};
+
+struct SomePolicy {
+ using TimeSplitIdType = SomeTimeSplitId;
+ using IntervalIdType = SomeIntervalId;
+
+ static constexpr size_t numTimeSplitIds = 3;
+ static constexpr size_t numIntervalIds = 2;
+
+ static constexpr size_t toIdx(TimeSplitIdType e) {
+ return static_cast<size_t>(e);
+ }
+ static constexpr size_t toIdx(IntervalIdType e) {
+ return static_cast<size_t>(e);
+ }
+
+ static constexpr StringData getName(TimeSplitIdType e) {
+ constexpr auto arr = std::array{"a"_sd, "b"_sd, "c"_sd};
+ return arr[toIdx(e)];
+ }
+ static constexpr StringData getName(IntervalIdType e) {
+ return iDefs[toIdx(e)].name;
+ }
+
+ static constexpr TimeSplitIdType getStartSplit(IntervalIdType e) {
+ return iDefs[toIdx(e)].start;
+ }
+ static constexpr TimeSplitIdType getEndSplit(IntervalIdType e) {
+ return iDefs[toIdx(e)].end;
+ }
+
+ void onStart(SplitTimer<SomePolicy>* t) {
+ mockOnStart(t);
+ }
+ void onFinish(SplitTimer<SomePolicy>* t) {
+ mockOnFinish(t);
+ }
+
+ Timer makeTimer() {
+ return Timer{clock};
+ }
+
+ std::function<void(SplitTimer<SomePolicy>*)> mockOnStart;
+ std::function<void(SplitTimer<SomePolicy>*)> mockOnFinish;
+ TickSource* clock;
+};
+
+class SplitTimerTest : public unittest::Test {
+public:
+ SplitTimerTest() {
+ policy.mockOnStart = [&](auto&&...) { ++starts; };
+ policy.mockOnFinish = [&](auto&&...) { ++finishes; };
+ policy.clock = &clock;
+ }
+
+ template <typename Pol>
+ BSONObj makeSplitTimerReport(const SplitTimer<Pol>& splitTimer) {
+ BSONObjBuilder bob;
+ splitTimer.appendIntervals(bob);
+ return bob.obj();
+ };
+
+ int starts = 0;
+ int finishes = 0;
+ TickSourceMock<Milliseconds> clock;
+ SomePolicy policy;
+};
+
+TEST_F(SplitTimerTest, BasicGetSplitInterval) {
+ struct Trial {
+ Milliseconds ab;
+ Milliseconds ac;
+ Milliseconds err = Milliseconds{5};
+ };
+ static constexpr auto trials = std::array{
+ Trial{Milliseconds{0}, Milliseconds{0}},
+ Trial{Milliseconds{0}, Milliseconds{10}},
+ Trial{Milliseconds{10}, Milliseconds{30}},
+ };
+ auto matchDerefRange = [](auto lo, auto hi) {
+ return WhenDereferenced(m::AllOf(m::Ge(lo), m::Le(hi)));
+ };
+
+ for (auto&& [ab, ac, err] : trials) {
+ starts = 0;
+ finishes = 0;
+ {
+ SplitTimer t{policy};
+ ASSERT_EQ(starts, 1);
+ ASSERT_THAT(t.getSplitInterval(SomeIntervalId::ab), WhenBool(m::Eq(false)));
+ t.notify(SomeTimeSplitId::a);
+
+ clock.advance(ab);
+ t.notify(SomeTimeSplitId::b);
+ ASSERT_THAT(t.getSplitInterval(SomeIntervalId::ab),
+ matchDerefRange(ab - err, ab + err));
+ ASSERT_THAT(t.getSplitInterval(SomeIntervalId::ac), WhenBool(m::Eq(false)));
+
+ clock.advance(ac - ab);
+ t.notify(SomeTimeSplitId::c);
+ ASSERT_THAT(t.getSplitInterval(SomeIntervalId::ac),
+ matchDerefRange(ac - err, ac + err));
+
+ ASSERT_EQ(finishes, 0);
+ }
+ ASSERT_EQ(finishes, 1);
+ }
+}
+
+TEST_F(SplitTimerTest, BSONFormatting) {
+ SplitTimer t{policy};
+ t.notify(SomeTimeSplitId::a);
+ clock.advance(Milliseconds{10});
+ t.notify(SomeTimeSplitId::b);
+ clock.advance(Milliseconds{20});
+ t.notify(SomeTimeSplitId::c);
+ ASSERT_BSONOBJ_EQ(makeSplitTimerReport(t),
+ BSONObjBuilder{}.append("abMillis", 10).append("acMillis", 30).obj());
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/transport/session_workflow.cpp b/src/mongo/transport/session_workflow.cpp
index 4f9b6c8e578..b96d5a430d0 100644
--- a/src/mongo/transport/session_workflow.cpp
+++ b/src/mongo/transport/session_workflow.cpp
@@ -42,6 +42,7 @@
#include "mongo/db/query/kill_cursors_gen.h"
#include "mongo/db/stats/counters.h"
#include "mongo/db/traffic_recorder.h"
+#include "mongo/executor/split_timer.h"
#include "mongo/logv2/log.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/platform/mutex.h"
@@ -61,15 +62,168 @@
#include "mongo/util/net/ssl_manager.h"
#include "mongo/util/net/ssl_peer_info.h"
-#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kNetwork
-
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kExecutor
namespace mongo {
namespace transport {
namespace {
+
MONGO_FAIL_POINT_DEFINE(doNotSetMoreToCome);
MONGO_FAIL_POINT_DEFINE(beforeCompressingExhaustResponse);
+namespace metrics_detail {
+
+/** Applies X(id) for each SplitId */
+#define EXPAND_TIME_SPLIT_IDS(X) \
+ X(started) \
+ X(receivedWork) \
+ X(processedWork) \
+ X(sentResponse) \
+ X(done) \
+ /**/
+
+/**
+ * Applies X(id, startSplit, endSplit) for each IntervalId.
+ *
+ * This table defines the intervals of a per-command `SessionWorkflow` loop
+ * iteration as reported to a `SplitTimer`. The splits are time points, and the
+ * `intervals` are durations between notable pairs of them.
+ *
+ * [started]
+ * | [receivedWork]
+ * | | [processedWork]
+ * | | | [sentResponse]
+ * | | | | [done]
+ * |<------------->| total
+ * | |<--------->| active
+ * |<->| | | | receivedWork
+ * | |<->| | | processWork
+ * | | |<->| | sendResponse
+ * | | | |<->| finalize
+ */
+#define EXPAND_INTERVAL_IDS(X) \
+ X(total, started, done) \
+ X(active, receivedWork, done) \
+ X(receiveWork, started, receivedWork) \
+ X(processWork, receivedWork, processedWork) \
+ X(sendResponse, processedWork, sentResponse) \
+ X(finalize, sentResponse, done) \
+ /**/
+
+#define X_ID(id, ...) id,
+enum class IntervalId : size_t { EXPAND_INTERVAL_IDS(X_ID) };
+enum class TimeSplitId : size_t { EXPAND_TIME_SPLIT_IDS(X_ID) };
+#undef X_ID
+
+/** Trait for the count of the elements in a packed enum. */
+template <typename T>
+static constexpr size_t enumExtent = 0;
+
+#define X_COUNT(...) +1
+template <>
+constexpr inline size_t enumExtent<IntervalId> = EXPAND_INTERVAL_IDS(X_COUNT);
+template <>
+constexpr inline size_t enumExtent<TimeSplitId> = EXPAND_TIME_SPLIT_IDS(X_COUNT);
+#undef X_COUNT
+
+struct TimeSplitDef {
+ TimeSplitId id;
+ StringData name;
+};
+
+struct IntervalDef {
+ IntervalId id;
+ StringData name;
+ TimeSplitId start;
+ TimeSplitId end;
+};
+
+constexpr inline auto timeSplitDefs = std::array{
+#define X(id) TimeSplitDef{TimeSplitId::id, #id ""_sd},
+ EXPAND_TIME_SPLIT_IDS(X)
+#undef X
+};
+
+constexpr inline auto intervalDefs = std::array{
+#define X(id, start, end) \
+ IntervalDef{IntervalId::id, #id "Millis"_sd, TimeSplitId::start, TimeSplitId::end},
+ EXPAND_INTERVAL_IDS(X)
+#undef X
+};
+
+#undef EXPAND_TIME_SPLIT_IDS
+#undef EXPAND_INTERVAL_IDS
+
+struct SplitTimerPolicy {
+ using TimeSplitIdType = TimeSplitId;
+ using IntervalIdType = IntervalId;
+
+ static constexpr size_t numTimeSplitIds = enumExtent<TimeSplitIdType>;
+ static constexpr size_t numIntervalIds = enumExtent<IntervalIdType>;
+
+ template <typename E>
+ static constexpr size_t toIdx(E e) {
+ return static_cast<size_t>(e);
+ }
+
+ static constexpr StringData getName(IntervalIdType iId) {
+ return intervalDefs[toIdx(iId)].name;
+ }
+
+ static constexpr TimeSplitIdType getStartSplit(IntervalIdType iId) {
+ return intervalDefs[toIdx(iId)].start;
+ }
+
+ static constexpr TimeSplitIdType getEndSplit(IntervalIdType iId) {
+ return intervalDefs[toIdx(iId)].end;
+ }
+
+ static constexpr StringData getName(TimeSplitIdType tsId) {
+ return timeSplitDefs[toIdx(tsId)].name;
+ }
+
+ void onStart(SplitTimer<SplitTimerPolicy>* splitTimer) {
+ splitTimer->notify(TimeSplitIdType::started);
+ }
+
+ void onFinish(SplitTimer<SplitTimerPolicy>* splitTimer) {
+ splitTimer->notify(TimeSplitIdType::done);
+ auto t = splitTimer->getSplitInterval(IntervalIdType::active);
+ if (MONGO_likely(!t || *t < Milliseconds{serverGlobalParams.slowMS.load()}))
+ return;
+ BSONObjBuilder bob;
+ splitTimer->appendIntervals(bob);
+ LOGV2(6983000, "Slow SessionWorkflow loop", "elapsed"_attr = bob.obj());
+ }
+
+ Timer makeTimer() {
+ return Timer{};
+ }
+};
+
+class SessionWorkflowMetrics {
+public:
+ void start() {
+ _t.emplace();
+ }
+ void received() {
+ _t->notify(TimeSplitId::receivedWork);
+ }
+ void processed() {
+ _t->notify(TimeSplitId::processedWork);
+ }
+ void sent() {
+ _t->notify(TimeSplitId::sentResponse);
+ }
+ void finish() {
+ _t.reset();
+ }
+
+private:
+ boost::optional<SplitTimer<SplitTimerPolicy>> _t;
+};
+} // namespace metrics_detail
+
/**
* Given a request and its already generated response, checks for exhaust flags. If exhaust is
* allowed, produces the subsequent request message, and modifies the response message to indicate
@@ -146,153 +300,8 @@ bool killExhaust(const Message& in, ServiceEntryPoint* sep, Client* client) {
}
return false;
}
-} // namespace
-
-/**
- * Acts as a split timer which captures times elapsed at various points throughout a single
- * SessionWorkflow loop. The SessionWorkflow loop itself is expected to (1) construct this object
- * when timing should begin, and (2) call this object's `notifySplit` function at appropriate times
- * throughout the workflow.
- */
-class SessionWorkflowMetrics {
- /**
- * NOTE: when updating these, ensure:
- * - These are all contiguous.
- * - NumEntries is the highest constant.
- * - The public constexprs are up to date.
- * - The ranges in logSlowLoop are still correct.
- */
- using Started_T = std::integral_constant<size_t, 0>;
- using SourcedWork_T = std::integral_constant<size_t, 1>;
- using ProcessedWork_T = std::integral_constant<size_t, 2>;
- using SentResponse_T = std::integral_constant<size_t, 3>;
- using Done_T = std::integral_constant<size_t, 4>;
- using NumEntries_T = std::integral_constant<size_t, 5>;
- static constexpr NumEntries_T NumEntries{};
-
-public:
- /**
- * These constants act as tags for moments in a single SessionWorkflow loop.
- */
- static constexpr Started_T Started{};
- static constexpr SourcedWork_T SourcedWork{};
- static constexpr ProcessedWork_T ProcessedWork{};
- static constexpr SentResponse_T SentResponse{};
- static constexpr Done_T Done{};
-
- template <typename Split_T>
- struct SplitInRange {
- static constexpr bool value = Split_T::value >= Started && Split_T::value < NumEntries;
- };
-
- SessionWorkflowMetrics() {
- _splits[Started] = Microseconds{0};
- }
-
- SessionWorkflowMetrics(SessionWorkflowMetrics&& other) {
- *this = std::move(other);
- }
- SessionWorkflowMetrics& operator=(SessionWorkflowMetrics&& other) {
- if (&other == this) {
- return *this;
- }
-
- _isFinalized = other._isFinalized;
- _timer = std::move(other._timer);
- _splits = std::move(other._splits);
-
- // The moved-from object should avoid extraneous logging.
- other._isFinalized = true;
-
- return *this;
- }
-
- ~SessionWorkflowMetrics() {
- finalize();
- }
-
- /**
- * Captures the elapsed time and associates it with `split`. A second call with the same `split`
- * will overwrite the previous. It is expected that this gets called for all splits other than
- * Start and Done.
- */
- template <typename Split_T, typename std::enable_if_t<SplitInRange<Split_T>::value, int> = 0>
- void notifySplit(Split_T split) {
- _splits[split] = _timer.elapsed();
- }
-
- /**
- * If not already finalized, captures the elapsed time for the `Done` Split and outputs metrics
- * as a log if the criteria for logging is met. Calling `finalize` explicitly is not required
- * because it is invoked by the destructor, however an early call can be done if this object's
- * destruction needs to be defered for any reason.
- */
- void finalize() {
- if (_isFinalized)
- return;
- _isFinalized = true;
- notifySplit(Done);
-
- auto activeTime = microsBetween(SourcedWork, SentResponse).value_or(Microseconds{0});
- if (MONGO_unlikely(durationCount<Milliseconds>(activeTime) >=
- serverGlobalParams.slowMS.load())) {
- logSlowLoop();
- }
- }
-
-private:
- bool _isFinalized{false};
- Timer _timer{};
- std::array<boost::optional<Microseconds>, NumEntries> _splits{};
-
- /**
- * Returns the time elapsed between the two splits corresponding to `startIdx` and `endIdx`.
- * The split time for `startIdx` is assumed to have happened before the split at `endIdx`.
- * Both `startIdx` and `endIdx` are assumed to have had captured times. If not, an optional with
- * no value will be returned.
- */
- boost::optional<Microseconds> microsBetween(size_t startIdx, size_t endIdx) const {
- auto atEnd = _splits[endIdx];
- auto atStart = _splits[startIdx];
- if (!atStart || !atEnd)
- return {};
- return *atEnd - *atStart;
- }
-
- /**
- * Appends an attribute to `attr` corresponding to a range. Returns whether a negative range was
- * encountered.
- */
- template <size_t N>
- bool addAttr(const char (&name)[N],
- size_t startIdx,
- size_t endIdx,
- logv2::DynamicAttributes& attr) {
- if (auto optTime = microsBetween(startIdx, endIdx)) {
- attr.add(name, duration_cast<Milliseconds>(*optTime));
- return *optTime < Microseconds{0};
- }
- return false;
- }
-
- void logSlowLoop() {
- bool neg = false;
- logv2::DynamicAttributes attr;
-
- neg |= addAttr("totalElapsed", Started, Done, attr);
- neg |= addAttr("activeElapsed", SourcedWork, Done, attr);
- neg |= addAttr("sourceWorkElapsed", Started, SourcedWork, attr);
- neg |= addAttr("processWorkElapsed", SourcedWork, ProcessedWork, attr);
- neg |= addAttr("sendResponseElapsed", ProcessedWork, SentResponse, attr);
- neg |= addAttr("finalizeElapsed", SentResponse, Done, attr);
- if (neg) {
- attr.add("note", "Negative time range found. This indicates something went wrong.");
- }
-
- LOGV2(6983000, "Slow SessionWorkflow loop", attr);
- }
-};
+} // namespace
class SessionWorkflow::Impl {
public:
@@ -404,7 +413,7 @@ private:
std::unique_ptr<WorkItem> _work;
std::unique_ptr<WorkItem> _nextWork; /**< created by exhaust responses */
- boost::optional<SessionWorkflowMetrics> _metrics{};
+ metrics_detail::SessionWorkflowMetrics _metrics;
};
class SessionWorkflow::Impl::WorkItem {
@@ -654,7 +663,7 @@ void SessionWorkflow::Impl::startNewLoop(const Status& executorStatus) {
return;
}
- _metrics = SessionWorkflowMetrics();
+ _metrics.start();
makeReadyFutureWith([this] {
if (_nextWork) {
@@ -662,18 +671,18 @@ void SessionWorkflow::Impl::startNewLoop(const Status& executorStatus) {
} else {
receiveMessage();
}
- _metrics->notifySplit(SessionWorkflowMetrics::SourcedWork);
+ _metrics.received();
return processMessage();
})
.then([this] {
- _metrics->notifySplit(SessionWorkflowMetrics::ProcessedWork);
+ _metrics.processed();
if (_work->hasOut()) {
sendMessage();
- _metrics->notifySplit(SessionWorkflowMetrics::SentResponse);
+ _metrics.sent();
}
})
.getAsync([this, anchor = shared_from_this()](Status status) {
- _metrics = {};
+ _metrics.finish();
scheduleNewLoop(std::move(status));
});
}