Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Util] Time utility updates #2300

Merged
merged 8 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,22 @@ if test "x$use_thread_local" = xyes || { test "x$use_thread_local" = xauto && te
LDFLAGS="$TEMP_LDFLAGS"
fi

dnl check for gmtime_r(), fallback to gmtime_s() if that is unavailable
dnl fail if neither are available.
AC_MSG_CHECKING(for gmtime_r)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <ctime>]],
[[ gmtime_r((const time_t *) nullptr, (struct tm *) nullptr); ]])],
[ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_GMTIME_R, 1, [Define this symbol if gmtime_r is available]) ],
[ AC_MSG_RESULT(no);
AC_MSG_CHECKING(for gmtime_s);
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <ctime>]],
[[ gmtime_s((struct tm *) nullptr, (const time_t *) nullptr); ]])],
[ AC_MSG_RESULT(yes)],
[ AC_MSG_RESULT(no); AC_MSG_ERROR(Both gmtime_r and gmtime_s are unavailable) ]
)
]
)

# Check for different ways of gathering OS randomness
AC_MSG_CHECKING(for Linux getrandom syscall)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h>
Expand Down Expand Up @@ -1452,6 +1468,7 @@ AC_SUBST(SODIUM_LIBS)
AC_SUBST(ZMQ_LIBS)
AC_SUBST(LIBZCASH_LIBS)
AC_SUBST(QR_LIBS)
AC_SUBST(HAVE_GMTIME_R)
AC_SUBST(HAVE_FDATASYNC)
AC_SUBST(HAVE_FULLFSYNC)
AC_SUBST(HAVE_O_CLOEXEC)
Expand Down
9 changes: 9 additions & 0 deletions doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ It is now possible for a single configuration file to set different options for
The `addnode=`, `connect=`, `port=`, `bind=`, `rpcport=`, `rpcbind=`, and `wallet=` options will only apply to mainnet when specified in the configuration file, unless a network is specified.


#### Logging

The log timestamp format is now ISO 8601 (e.g. "2021-02-28T12:34:56Z").


#### Automatic Backup File Naming

The file extension applied to automatic backups is now in ISO 8601 basic notation (e.g. "20210228T123456Z"). The basic notation is used to prevent illegal `:` characters from appearing in the filename.

*version* Change log
==============

Expand Down
3 changes: 2 additions & 1 deletion src/Makefile.bench.include
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ bench_bench_pivx_SOURCES = \
bench/lockedpool.cpp \
bench/perf.cpp \
bench/perf.h \
bench/prevector.cpp
bench/prevector.cpp \
bench/util_time.cpp

nodist_bench_bench_pivx_SOURCES = $(GENERATED_TEST_FILES)

Expand Down
42 changes: 42 additions & 0 deletions src/bench/util_time.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "bench/bench.h"

#include "utiltime.h"

static void BenchTimeDeprecated(benchmark::State& state)
{
while (state.KeepRunning()) {
(void)GetTime();
}
}

static void BenchTimeMock(benchmark::State& state)
{
SetMockTime(111);
while (state.KeepRunning()) {
(void)GetTime<std::chrono::seconds>();
}
SetMockTime(0);
}

static void BenchTimeMillis(benchmark::State& state)
{
while (state.KeepRunning()) {
(void)GetTime<std::chrono::milliseconds>();
}
}

static void BenchTimeMillisSys(benchmark::State& state)
{
while (state.KeepRunning()) {
(void)GetTimeMillis();
}
}

BENCHMARK(BenchTimeDeprecated/*, 100000000*/);
BENCHMARK(BenchTimeMillis/*, 6000000*/);
BENCHMARK(BenchTimeMillisSys/*, 6000000*/);
BENCHMARK(BenchTimeMock/*, 300000000*/);
4 changes: 2 additions & 2 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ bool AppInitMain()
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
#endif
if (!g_logger->m_log_timestamps)
LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime()));
LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime()));
LogPrintf("Default data directory %s\n", GetDefaultDataDir().string());
LogPrintf("Using data directory %s\n", GetDataDir().string());
LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME)).string());
Expand Down Expand Up @@ -1288,7 +1288,7 @@ bool AppInitMain()
if (nWalletBackups > 0) {
if (fs::exists(backupDir)) {
// Create backup of the wallet
std::string dateTimeStr = DateTimeStrFormat(".%Y-%m-%d-%H-%M", GetTime());
std::string dateTimeStr = FormatISO8601DateTimeForBackup(GetTime());
std::string backupPathStr = backupDir.string();
backupPathStr += "/" + strWalletFile;
std::string sourcePathStr = GetDataDir().string();
Expand Down
16 changes: 13 additions & 3 deletions src/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,19 @@ std::string BCLog::Logger::LogTimestampStr(const std::string &str)
if (!m_log_timestamps)
return str;

if (m_started_new_line)
strStamped = DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime()) + ' ' + str;
else
if (m_started_new_line) {
int64_t nTimeMicros = GetTimeMicros();
strStamped = FormatISO8601DateTime(nTimeMicros/1000000);
if (m_log_time_micros) {
strStamped.pop_back();
strStamped += strprintf(".%06dZ", nTimeMicros % 1000000);
}
int64_t mocktime = GetMockTime();
if (mocktime) {
strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")";
}
strStamped += ' ' + str;
} else
strStamped = str;

if (!str.empty() && str[str.size()-1] == '\n')
Expand Down
2 changes: 1 addition & 1 deletion src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2503,7 +2503,7 @@ void CNode::AskFor(const CInv& inv)
nRequestTime = it->second;
else
nRequestTime = 0;
LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime / 1000000), id);
LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, FormatISO8601Time(nRequestTime / 1000000), id);

// Make sure not to reuse time indexes to keep things in the same order
int64_t nNow = GetTimeMicros() - 1000000;
Expand Down
44 changes: 38 additions & 6 deletions src/test/util_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,24 @@ BOOST_AUTO_TEST_CASE(util_HexStr)
}


BOOST_AUTO_TEST_CASE(util_DateTimeStrFormat)
BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime)
{
BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0), "1970-01-01 00:00:00");
BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0x7FFFFFFF), "2038-01-19 03:14:07");
BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 1317425777), "2011-09-30 23:36:17");
BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M", 1317425777), "2011-09-30 23:36");
BOOST_CHECK_EQUAL(DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", 1317425777), "Fri, 30 Sep 2011 23:36:17 +0000");
BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z");
}

BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTimeForBackup)
{
BOOST_CHECK_EQUAL(FormatISO8601DateTimeForBackup(1586310600), ".20200408T0150Z");
}

BOOST_AUTO_TEST_CASE(util_FormatISO8601Date)
{
BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30");
}

BOOST_AUTO_TEST_CASE(util_FormatISO8601Time)
{
BOOST_CHECK_EQUAL(FormatISO8601Time(1317425777), "23:36:17Z");
}

struct TestArgsManager : public ArgsManager
Expand Down Expand Up @@ -686,6 +697,27 @@ BOOST_AUTO_TEST_CASE(gettime)
BOOST_CHECK((GetTime() & ~0xFFFFFFFFLL) == 0);
}

BOOST_AUTO_TEST_CASE(util_time_GetTime)
{
SetMockTime(111);
// Check that mock time does not change after a sleep
for (const auto& num_sleep : {0, 1}) {
MilliSleep(num_sleep);
BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter
BOOST_CHECK_EQUAL(111, GetTime<std::chrono::seconds>().count());
BOOST_CHECK_EQUAL(111000, GetTime<std::chrono::milliseconds>().count());
BOOST_CHECK_EQUAL(111000000, GetTime<std::chrono::microseconds>().count());
}

SetMockTime(0);
// Check that system time changes after a sleep
const auto ms_0 = GetTime<std::chrono::milliseconds>();
const auto us_0 = GetTime<std::chrono::microseconds>();
MilliSleep(1);
BOOST_CHECK(ms_0 < GetTime<std::chrono::milliseconds>());
BOOST_CHECK(us_0 < GetTime<std::chrono::microseconds>());
}

BOOST_AUTO_TEST_CASE(test_IsDigit)
{
BOOST_CHECK_EQUAL(IsDigit('0'), true);
Expand Down
84 changes: 73 additions & 11 deletions src/utiltime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
#include "utiltime.h"

#include <atomic>

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread.hpp>
#include <ctime>
#include <tinyformat.h>


static std::atomic<int64_t> nMockTime(0); //!< For unit testing
Expand All @@ -30,11 +31,30 @@ int64_t GetTime()
return now;
}

template <typename T>
T GetTime()
{
const std::chrono::seconds mocktime{nMockTime.load(std::memory_order_relaxed)};

return std::chrono::duration_cast<T>(
mocktime.count() ?
mocktime :
std::chrono::microseconds{GetTimeMicros()});
}
template std::chrono::seconds GetTime();
template std::chrono::milliseconds GetTime();
template std::chrono::microseconds GetTime();

void SetMockTime(int64_t nMockTimeIn)
{
nMockTime.store(nMockTimeIn, std::memory_order_relaxed);
}

int64_t GetMockTime()
{
return nMockTime.load(std::memory_order_relaxed);
}

int64_t GetTimeMillis()
{
int64_t now = (boost::posix_time::microsec_clock::universal_time() -
Expand All @@ -61,16 +81,6 @@ void MilliSleep(int64_t n)
boost::this_thread::sleep_for(boost::chrono::milliseconds(n));
}

std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime)
{
// std::locale takes ownership of the pointer
std::locale loc(std::locale::classic(), new boost::posix_time::time_facet(pszFormat));
std::stringstream ss;
ss.imbue(loc);
ss << boost::posix_time::from_time_t(nTime);
return ss.str();
}

std::string DurationToDHMS(int64_t nDurationTime)
{
int seconds = nDurationTime % 60;
Expand All @@ -85,3 +95,55 @@ std::string DurationToDHMS(int64_t nDurationTime)
return strprintf("%02dh:%02dm:%02ds", hours, minutes, seconds);
return strprintf("%02dm:%02ds", minutes, seconds);
}

std::string FormatISO8601DateTime(int64_t nTime) {
struct tm ts;
time_t time_val = nTime;
#ifdef HAVE_GMTIME_R
if (gmtime_r(&time_val, &ts) == nullptr) {
#else
if (gmtime_s(&ts, &time_val) != 0) {
#endif
return {};
}
return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec);
}

std::string FormatISO8601DateTimeForBackup(int64_t nTime) {
struct tm ts;
time_t time_val = nTime;
#ifdef HAVE_GMTIME_R
if (gmtime_r(&time_val, &ts) == nullptr) {
#else
if (gmtime_s(&ts, &time_val) != 0) {
#endif
return {};
}
return strprintf(".%04i%02i%02iT%02i%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min);
}

std::string FormatISO8601Date(int64_t nTime) {
struct tm ts;
time_t time_val = nTime;
#ifdef HAVE_GMTIME_R
if (gmtime_r(&time_val, &ts) == nullptr) {
#else
if (gmtime_s(&ts, &time_val) != 0) {
#endif
return {};
}
return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday);
}

std::string FormatISO8601Time(int64_t nTime) {
struct tm ts;
time_t time_val = nTime;
#ifdef HAVE_GMTIME_R
if (gmtime_r(&time_val, &ts) == nullptr) {
#else
if (gmtime_s(&ts, &time_val) != 0) {
#endif
return {};
}
return strprintf("%02i:%02i:%02iZ", ts.tm_hour, ts.tm_min, ts.tm_sec);
}
34 changes: 24 additions & 10 deletions src/utiltime.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin developers
// Copyright (c) 2016-2017 The PIVX developers
// Copyright (c) 2016-2019 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand All @@ -9,25 +9,39 @@

#include <stdint.h>
#include <string>
#include <chrono>

/**
* GetTimeMicros() and GetTimeMillis() both return the system time, but in
* different units. GetTime() returns the sytem time in seconds, but also
* supports mocktime, where the time can be specified by the user, eg for
* testing (eg with the setmocktime rpc, or -mocktime argument).
*
* TODO: Rework these functions to be type-safe (so that we don't inadvertently
* compare numbers with different units, or compare a mocktime to system time).
* DEPRECATED
* Use either GetSystemTimeInSeconds (not mockable) or GetTime<T> (mockable)
*/

int64_t GetTime();
/** Returns the system time (not mockable) */
int64_t GetTimeMillis();
/** Returns the system time (not mockable) */
int64_t GetTimeMicros();
/** Returns the system time (not mockable) */
int64_t GetSystemTimeInSeconds(); // Like GetTime(), but not mockable
/** For testing. Set e.g. with the setmocktime rpc, or -mocktime argument */
void SetMockTime(int64_t nMockTimeIn);
/** For testing */
int64_t GetMockTime();

void MilliSleep(int64_t n);

std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime);
std::string DurationToDHMS(int64_t nDurationTime);

/** Return system time (or mocked time, if set) */
template <typename T>
T GetTime();

/**
* ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date,Time}
* helper functions if possible.
*/
std::string FormatISO8601DateTime(int64_t nTime);
std::string FormatISO8601DateTimeForBackup(int64_t nTime);
std::string FormatISO8601Date(int64_t nTime);
std::string FormatISO8601Time(int64_t nTime);

#endif // BITCOIN_UTILTIME_H
Loading