/** * Copyright (C) 2018-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 #include #include #include #include "mongo/base/static_assert.h" #include "mongo/platform/overflow_arithmetic.h" #include "mongo/stdx/chrono.h" #include "mongo/stdx/type_traits.h" #include "mongo/util/assert_util.h" #include "mongo/util/mongoutils/str.h" namespace mongo { template class StringBuilderImpl; template class Duration; using Nanoseconds = Duration; using Microseconds = Duration; using Milliseconds = Duration; using Seconds = Duration>; using Minutes = Duration>; using Hours = Duration>; // // Streaming output operators for common duration types. Writes the numerical value followed by // an abbreviated unit, without a space. // // E.g., std::cout << Minutes{5} << std::endl; should produce the following: // 5min // std::ostream& operator<<(std::ostream& os, Nanoseconds ns); std::ostream& operator<<(std::ostream& os, Microseconds us); std::ostream& operator<<(std::ostream& os, Milliseconds ms); std::ostream& operator<<(std::ostream& os, Seconds s); std::ostream& operator<<(std::ostream& os, Minutes m); std::ostream& operator<<(std::ostream& os, Hours h); template StringBuilderImpl& operator<<(StringBuilderImpl& os, Nanoseconds ns); template StringBuilderImpl& operator<<(StringBuilderImpl& os, Microseconds us); template StringBuilderImpl& operator<<(StringBuilderImpl& os, Milliseconds ms); template StringBuilderImpl& operator<<(StringBuilderImpl& os, Seconds s); template StringBuilderImpl& operator<<(StringBuilderImpl& os, Minutes m); template StringBuilderImpl& operator<<(StringBuilderImpl& os, Hours h); template using HigherPrecisionDuration = typename std::conditional::value, Duration1, Duration2>::type; /** * Casts from one Duration precision to another. * * May throw a AssertionException if "from" is of lower-precision type and is outside the range of * the ToDuration. For example, Seconds::max() cannot be represented as a Milliseconds, and so * attempting to cast that value to Milliseconds will throw an exception. */ template ToDuration duration_cast(const Duration& from) { using FromOverTo = std::ratio_divide; if (ToDuration::template isHigherPrecisionThan>()) { typename ToDuration::rep toCount; uassert(ErrorCodes::DurationOverflow, "Overflow casting from a lower-precision duration to a higher-precision duration", !mongoSignedMultiplyOverflow64(from.count(), FromOverTo::num, &toCount)); return ToDuration{toCount}; } return ToDuration{from.count() / FromOverTo::den}; } template inline ToDuration duration_cast(const stdx::chrono::duration& d) { return duration_cast(Duration{d.count()}); } /** * Convenience method for reading the count of a duration with specified units. * * Use when logging or comparing to integers, to ensure that you're using * the units you intend. * * E.g., log() << durationCount(some duration) << " seconds"; */ template inline long long durationCount(DIn d) { return duration_cast(d).count(); } template inline long long durationCount(const stdx::chrono::duration& d) { return durationCount(Duration{d.count()}); } /** * Type representing a duration using a 64-bit counter. * * The Period template argument is a std::ratio describing the units of the duration type. * * This type's behavior is similar to std::chrono::duration, but instead of undefined behavior on * overflows and other conversions, throws exceptions. */ template class Duration { public: MONGO_STATIC_ASSERT_MSG(Period::num > 0, "Duration::period's numerator must be positive"); MONGO_STATIC_ASSERT_MSG(Period::den > 0, "Duration::period's denominator must be positive"); using rep = int64_t; using period = Period; /** * Type with static bool "value" set to true if this Duration type is higher precision than * OtherDuration. That is, if OtherDuration::period > period. */ template struct IsHigherPrecisionThan { using OtherOverThis = std::ratio_divide; MONGO_STATIC_ASSERT_MSG( OtherOverThis::den == 1 || OtherOverThis::num == 1, "Mongo duration types are only compatible with each other when one's period " "is an even multiple of the other's."); static constexpr bool value = OtherOverThis::den == 1 && OtherOverThis::num != 1; }; /** * Type with static bool "value" set to true if this Duration type is lower precision than * OtherDuration. That is, if OtherDuration::period > period. */ template struct IsLowerPrecisionThan { using OtherOverThis = std::ratio_divide; MONGO_STATIC_ASSERT_MSG( OtherOverThis::den == 1 || OtherOverThis::num == 1, "Mongo duration types are only compatible with each other when one's period " "is an even multiple of the other's."); static constexpr bool value = OtherOverThis::num == 1 && OtherOverThis::den != 1; }; /** * Function that returns true if period > OtherDuration::period. */ template constexpr static bool isHigherPrecisionThan() { return IsHigherPrecisionThan::value; } /** * Function that returns true if period < OtherDuration::period. */ template constexpr static bool isLowerPrecisionThan() { return IsLowerPrecisionThan::value; } static constexpr Duration zero() { return Duration{}; } static constexpr Duration min() { return Duration{std::numeric_limits::min()}; } static constexpr Duration max() { return Duration{std::numeric_limits::max()}; } /** * Constructs the zero duration. */ constexpr Duration() = default; /** * Constructs a duration representing "r" periods. */ template < typename Rep2, stdx::enable_if_t::value && std::is_integral::value, int> = 0> constexpr explicit Duration(const Rep2& r) : _count(r) { MONGO_STATIC_ASSERT_MSG( std::is_signed::value || sizeof(Rep2) < sizeof(rep), "Durations must be constructed from values of integral type that are " "representable as 64-bit signed integers"); } /** * Constructs a higher-precision duration from a lower-precision one, as by duration_cast. * * Throws a AssertionException if "from" is out of the range of this duration type. * * It is a compilation error to attempt a conversion from higher-precision to lower-precision by * this constructor. */ template /*implicit*/ Duration(const Duration& from) : Duration(duration_cast(from)) { MONGO_STATIC_ASSERT_MSG( !isLowerPrecisionThan>(), "Use duration_cast to convert from higher precision Duration types to lower " "precision ones"); } stdx::chrono::system_clock::duration toSystemDuration() const { using SystemDuration = stdx::chrono::system_clock::duration; return SystemDuration{duration_cast>(*this).count()}; } /** * Returns the number of periods represented by this duration. * * It is better to use durationCount(value), since it makes the unit of the * count clear at the call site. */ constexpr rep count() const { return _count; } /** * Compares this duration to another duration of the same type. * * Returns 1, -1 or 0 depending on whether this duration is greater than, less than or equal to * the other duration, respectively. */ constexpr int compare(const Duration& other) const { return (count() > other.count()) ? 1 : (count() < other.count()) ? -1 : 0; } /** * Compares this duration to a lower-precision duration, "other". */ template int compare(const Duration& other) const { if (isLowerPrecisionThan>()) { return -other.compare(*this); } using OtherOverThis = std::ratio_divide; rep otherCount; if (mongoSignedMultiplyOverflow64(other.count(), OtherOverThis::num, &otherCount)) { return other.count() < 0 ? 1 : -1; } if (count() < otherCount) { return -1; } if (count() > otherCount) { return 1; } return 0; } constexpr Duration operator+() const { return *this; } Duration operator-() const { uassert(ErrorCodes::DurationOverflow, "Cannot negate the minimum duration", *this != min()); return Duration(-count()); } // // In-place arithmetic operators // Duration& operator++() { return (*this) += Duration{1}; } Duration operator++(int) { auto result = *this; *this += Duration{1}; return result; } Duration operator--() { return (*this) -= Duration{1}; } Duration operator--(int) { auto result = *this; *this -= Duration{1}; return result; } Duration& operator+=(const Duration& other) { uassert(ErrorCodes::DurationOverflow, str::stream() << "Overflow while adding " << other << " to " << *this, !mongoSignedAddOverflow64(count(), other.count(), &_count)); return *this; } Duration& operator-=(const Duration& other) { uassert(ErrorCodes::DurationOverflow, str::stream() << "Overflow while subtracting " << other << " from " << *this, !mongoSignedSubtractOverflow64(count(), other.count(), &_count)); return *this; } template Duration& operator*=(const Rep2& scale) { MONGO_STATIC_ASSERT_MSG( std::is_integral::value && std::is_signed::value, "Durations may only be multiplied by values of signed integral type"); uassert(ErrorCodes::DurationOverflow, str::stream() << "Overflow while multiplying " << *this << " by " << scale, !mongoSignedMultiplyOverflow64(count(), scale, &_count)); return *this; } template Duration& operator/=(const Rep2& scale) { MONGO_STATIC_ASSERT_MSG(std::is_integral::value && std::is_signed::value, "Durations may only be divided by values of signed integral type"); uassert(ErrorCodes::DurationOverflow, str::stream() << "Overflow while dividing " << *this << " by -1", (count() != min().count() || scale != -1)); _count /= scale; return *this; } private: rep _count = {}; }; template constexpr bool operator==(const Duration& lhs, const Duration& rhs) { return lhs.compare(rhs) == 0; } template constexpr bool operator!=(const Duration& lhs, const Duration& rhs) { return lhs.compare(rhs) != 0; } template constexpr bool operator<(const Duration& lhs, const Duration& rhs) { return lhs.compare(rhs) < 0; } template constexpr bool operator<=(const Duration& lhs, const Duration& rhs) { return lhs.compare(rhs) <= 0; } template constexpr bool operator>(const Duration& lhs, const Duration& rhs) { return lhs.compare(rhs) > 0; } template constexpr bool operator>=(const Duration& lhs, const Duration& rhs) { return lhs.compare(rhs) >= 0; } /** * Returns the sum of two durations, "lhs" and "rhs". */ template < typename LhsPeriod, typename RhsPeriod, typename ReturnDuration = HigherPrecisionDuration, Duration>> ReturnDuration operator+(const Duration& lhs, const Duration& rhs) { ReturnDuration result = lhs; result += rhs; return result; } /** * Returns the result of subtracting "rhs" from "lhs". */ template < typename LhsPeriod, typename RhsPeriod, typename ReturnDuration = HigherPrecisionDuration, Duration>> ReturnDuration operator-(const Duration& lhs, const Duration& rhs) { ReturnDuration result = lhs; result -= rhs; return result; } /** * Returns the product of a duration "d" and a unitless integer, "scale". */ template Duration operator*(Duration d, const Rep2& scale) { d *= scale; return d; } template Duration operator*(const Rep2& scale, Duration d) { d *= scale; return d; } /** * Returns duration "d" divided by unitless integer "scale". */ template Duration operator/(Duration d, const Rep2& scale) { d /= scale; return d; } } // namespace mongo