/** * Copyright (C) 2020-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 #include #if defined(__linux__) #include #endif // defined(__linux__) #include "mongo/db/operation_cpu_timer.h" #include "mongo/base/error_codes.h" #include "mongo/db/client.h" #include "mongo/db/operation_context.h" #include "mongo/logv2/log.h" #include "mongo/stdx/thread.h" #include "mongo/util/assert_util.h" #include "mongo/util/errno_util.h" #include "mongo/util/fail_point.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault namespace mongo { using namespace fmt::literals; #if defined(__linux__) namespace { // Reads the thread timer, and throws with `InternalError` if that fails. Nanoseconds getThreadCPUTime() { struct timespec t; if (auto ret = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t); ret != 0) { auto ec = lastSystemError(); iassert( Status(ErrorCodes::InternalError, "Unable to get time: {}"_format(errorMessage(ec)))); } return Seconds(t.tv_sec) + Nanoseconds(t.tv_nsec); } MONGO_FAIL_POINT_DEFINE(hangCPUTimerAfterOnThreadAttach); MONGO_FAIL_POINT_DEFINE(hangCPUTimerAfterOnThreadDetach); class PosixTimer final : public OperationCPUTimer { public: PosixTimer(const std::shared_ptr& timers) : OperationCPUTimer(timers) {} ~PosixTimer() = default; Nanoseconds getElapsed() const override; void start() override; void stop() override; void onThreadAttach() override; void onThreadDetach() override; private: bool _timerIsRunning() const; bool _isAttachedToCurrentThread() const; // Returns the elapsed time since the creation of the current thread. Nanoseconds _getThreadTime() const; // Holds the value returned by `_getThreadTime()` at the time of starting/resuming the timer. boost::optional _startedOn; boost::optional _threadId; Nanoseconds _elapsedBeforeInterrupted = Nanoseconds(0); }; Nanoseconds PosixTimer::getElapsed() const { invariant(_isAttachedToCurrentThread(), "Not attached to current thread"); auto elapsed = _elapsedBeforeInterrupted; if (_timerIsRunning()) elapsed += _getThreadTime() - _startedOn.get(); return elapsed; } bool PosixTimer::_timerIsRunning() const { return _startedOn.has_value(); } bool PosixTimer::_isAttachedToCurrentThread() const { return _threadId.has_value() && _threadId.get() == stdx::this_thread::get_id(); } void PosixTimer::start() { invariant(!_timerIsRunning(), "Timer has already started"); _startedOn = _getThreadTime(); _threadId = stdx::this_thread::get_id(); _elapsedBeforeInterrupted = Nanoseconds(0); } void PosixTimer::stop() { invariant(_timerIsRunning(), "Timer is not running"); invariant(_isAttachedToCurrentThread()); _elapsedBeforeInterrupted = getElapsed(); _startedOn.reset(); } void PosixTimer::onThreadAttach() { if (!_timerIsRunning()) return; invariant(!_threadId.has_value(), "Timer has already been attached"); _threadId = stdx::this_thread::get_id(); _startedOn = _getThreadTime(); hangCPUTimerAfterOnThreadAttach.pauseWhileSet(); } void PosixTimer::onThreadDetach() { if (!_timerIsRunning()) return; invariant(_threadId.has_value(), "Timer is not attached"); _threadId.reset(); _elapsedBeforeInterrupted += _getThreadTime() - _startedOn.get(); hangCPUTimerAfterOnThreadDetach.pauseWhileSet(); } Nanoseconds PosixTimer::_getThreadTime() const try { return getThreadCPUTime(); } catch (const ExceptionFor& ex) { // Abort the process as the timer cannot account for the elapsed time. This path is only // reachable if the platform supports CPU time measurement at startup, but returns an error // for a subsequent attempt to get thread-specific CPU consumption. LOGV2_FATAL(4744601, "Failed to read the CPU time for the current thread", "error"_attr = ex); } // Set of timers created by this OperationContext. static auto getCPUTimers = OperationContext::declareDecoration>(); } // namespace OperationCPUTimers* OperationCPUTimers::get(OperationContext* opCtx) { // Checks for time support on POSIX platforms. In particular, it checks for support in presence // of SMP systems. static bool isTimeSupported = [] { clockid_t cid; if (clock_getcpuclockid(0, &cid) != 0) return false; try { getThreadCPUTime(); } catch (const ExceptionFor&) { // Unable to collect the CPU time for the current thread. return false; } return true; }(); if (!isTimeSupported) return nullptr; auto& timers = getCPUTimers(opCtx); if (!timers) { timers = std::make_shared(); } return timers.get(); } std::unique_ptr OperationCPUTimers::makeTimer() { return std::make_unique(shared_from_this()); } #else // not defined(__linux__) OperationCPUTimers* OperationCPUTimers::get(OperationContext*) { return nullptr; } std::unique_ptr OperationCPUTimers::makeTimer() { MONGO_UNREACHABLE; } #endif // defined(__linux__) OperationCPUTimer::OperationCPUTimer(const std::shared_ptr& timers) : _timers(timers) { _it = timers->_add(this); } OperationCPUTimer::~OperationCPUTimer() { // It is possible for an OperationCPUTimer to outlive the OperationCPUTimers container that is // decorated on the OperationContext. For example, a Timer can be owned by an OperationContext // decoration, and may be destructed after the Timers container, an order which we cannot // control. Therefore we must ensure the weak_ptr we hold is still valid. if (auto timers = _timers.lock()) { timers->_remove(_it); } } OperationCPUTimers::Iterator OperationCPUTimers::_add(OperationCPUTimer* timer) { return _timers.insert(_timers.end(), timer); } void OperationCPUTimers::_remove(OperationCPUTimers::Iterator it) { _timers.erase(it); } size_t OperationCPUTimers::count() const { return _timers.size(); } void OperationCPUTimers::onThreadAttach() { for (auto& timer : _timers) { timer->onThreadAttach(); } } void OperationCPUTimers::onThreadDetach() { for (auto& timer : _timers) { timer->onThreadDetach(); } } } // namespace mongo