]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
time: Add basic support for std::chrono based time
authorAdam C. Emerson <aemerson@redhat.com>
Mon, 14 Sep 2015 16:18:57 +0000 (12:18 -0400)
committerSage Weil <sage@redhat.com>
Fri, 18 Dec 2015 20:15:03 +0000 (15:15 -0500)
Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
src/CMakeLists.txt
src/common/Makefile.am
src/common/ceph_time.cc [new file with mode: 0644]
src/common/ceph_time.h [new file with mode: 0644]
src/test/CMakeLists.txt
src/test/Makefile.am
src/test/common/test_time.cc [new file with mode: 0644]

index 41c6eec3a6c4771b8d33e3df0db117226b12ff00..127e5e5c569e9a42b661f474cead1d240fc97cc3 100644 (file)
@@ -217,6 +217,7 @@ set(libcommon_files
   common/escape.c
   common/io_priority.cc
   common/Clock.cc
+  common/ceph_time.cc
   common/Throttle.cc
   common/Timer.cc
   common/Finisher.cc
index 182295f9e6d629f970620c2ef109438618a3f3c8..64ce61443bd783a5e93c8062dcf4abf40ede5635 100644 (file)
@@ -14,6 +14,7 @@ libcommon_internal_la_SOURCES = \
        common/cmdparse.cc \
        common/escape.c \
        common/io_priority.cc \
+       common/ceph_time.cc \
        common/Clock.cc \
        common/Throttle.cc \
        common/Timer.cc \
@@ -263,7 +264,8 @@ noinst_HEADERS += \
        common/valgrind.h \
        common/TracepointProvider.h \
        common/event_socket.h \
-       common/PluginRegistry.h
+       common/PluginRegistry.h \
+       common/ceph_time.h
 
 if ENABLE_XIO
 noinst_HEADERS += \
diff --git a/src/common/ceph_time.cc b/src/common/ceph_time.cc
new file mode 100644 (file)
index 0000000..a18a316
--- /dev/null
@@ -0,0 +1,112 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+// For ceph_timespec
+#include "include/types.h"
+
+#include "ceph_context.h"
+#include "ceph_time.h"
+#include "config.h"
+
+namespace ceph {
+  namespace time_detail {
+    real_clock::time_point real_clock::now(const CephContext* cct) noexcept {
+      struct timespec ts;
+      clock_gettime(CLOCK_REALTIME, &ts);
+      // TODO: After we get the time-literal configuration patch in,
+      // just add the configured duration.
+      if (cct)
+       ts.tv_sec += cct->_conf->clock_offset;
+      return from_timespec(ts);
+    }
+
+    void real_clock::to_ceph_timespec(const time_point& t,
+                                     struct ceph_timespec& ts) {
+      ts.tv_sec = to_time_t(t);
+      ts.tv_nsec = (t.time_since_epoch() % seconds(1)).count();
+    }
+    struct ceph_timespec real_clock::to_ceph_timespec(const time_point& t) {
+      struct ceph_timespec ts;
+      to_ceph_timespec(t, ts);
+      return ts;
+    }
+    real_clock::time_point real_clock::from_ceph_timespec(
+      const struct ceph_timespec& ts) {
+      return time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
+    }
+
+    coarse_real_clock::time_point coarse_real_clock::now(
+      const CephContext* cct) noexcept {
+      struct timespec ts;
+      clock_gettime(CLOCK_REALTIME_COARSE, &ts);
+      // TODO: After we get the time-literal configuration patch in,
+      // just add the configured duration.
+      if (cct)
+       ts.tv_sec += cct->_conf->clock_offset;
+      return from_timespec(ts);
+    }
+
+    void coarse_real_clock::to_ceph_timespec(const time_point& t,
+                                            struct ceph_timespec& ts) {
+      ts.tv_sec = to_time_t(t);
+      ts.tv_nsec = (t.time_since_epoch() % seconds(1)).count();
+    }
+    struct ceph_timespec coarse_real_clock::to_ceph_timespec(
+      const time_point& t) {
+      struct ceph_timespec ts;
+      to_ceph_timespec(t, ts);
+      return ts;
+    }
+    real_clock::time_point coarse_real_clock::from_ceph_timespec(
+      const struct ceph_timespec& ts) {
+      return time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
+    }
+  };
+
+  using std::chrono::duration_cast;
+  using std::chrono::seconds;
+  using std::chrono::microseconds;
+
+  std::ostream& operator<<(std::ostream& m, const mono_time& t) {
+    return m << std::chrono::duration<double>(t.time_since_epoch()).count()
+            << "s";
+  }
+
+  std::ostream& operator<<(std::ostream& m, const timespan& t) {
+    return m << std::chrono::duration<double>(t).count() << "s";
+  }
+
+  std::ostream& operator<<(std::ostream& m, const real_time& t) {
+    m.setf(std::ios::right);
+    char oldfill = m.fill();
+    m.fill('0');
+    // localtime.  this looks like an absolute time.
+    //  aim for http://en.wikipedia.org/wiki/ISO_8601
+    struct tm bdt;
+    time_t tt = ceph::real_clock::to_time_t(t);
+    localtime_r(&tt, &bdt);
+    m << std::setw(4) << (bdt.tm_year+1900)  // 2007 -> '07'
+      << '-' << std::setw(2) << (bdt.tm_mon+1)
+      << '-' << std::setw(2) << bdt.tm_mday
+      << ' '
+      << std::setw(2) << bdt.tm_hour
+      << ':' << std::setw(2) << bdt.tm_min
+      << ':' << std::setw(2) << bdt.tm_sec
+      << "." << std::setw(6) << duration_cast<microseconds>(
+       t.time_since_epoch() % seconds(1));
+    m.fill(oldfill);
+    m.unsetf(std::ios::right);
+    return m;
+  }
+};
diff --git a/src/common/ceph_time.h b/src/common/ceph_time.h
new file mode 100644 (file)
index 0000000..a634e7f
--- /dev/null
@@ -0,0 +1,383 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef COMMON_CEPH_TIME_H
+#define COMMON_CEPH_TIME_H
+
+#include <chrono>
+#include <ctime>
+
+#include "include/encoding.h"
+
+class CephContext;
+struct ceph_timespec;
+
+namespace ceph {
+  namespace time_detail {
+    using std::chrono::duration_cast;
+    using std::chrono::seconds;
+    using std::chrono::microseconds;
+    using std::chrono::nanoseconds;
+    // Currently we use a 64-bit count of nanoseconds.
+
+    // We could, if we wished, use a struct holding a uint64_t count
+    // of seconds and a uint32_t count of nanoseconds.
+
+    // At least this way we can change it to something else if we
+    // want.
+    typedef uint64_t rep;
+
+    // A concrete duration, unsigned. The timespan Ceph thinks in.
+    typedef std::chrono::duration<rep, std::nano> timespan;
+
+
+    // Like the above but signed.
+    typedef int64_t signed_rep;
+
+    typedef std::chrono::duration<signed_rep, std::nano> signedspan;
+
+    // We define our own clocks so we can have our choice of all time
+    // sources supported by the operating system. With the standard
+    // library the resolution and cost are unspecified. (For example,
+    // the libc++ system_clock class gives only microsecond
+    // resolution.)
+
+    // One potential issue is that we should accept system_clock
+    // timepoints in user-facing APIs alongside (or instead of)
+    // ceph::real_clock times.
+    class real_clock {
+    public:
+      typedef timespan duration;
+      typedef duration::rep rep;
+      typedef duration::period period;
+      // The second template parameter defaults to the clock's duration
+      // type.
+      typedef std::chrono::time_point<real_clock> time_point;
+      static constexpr const bool is_steady = false;
+
+      static time_point now() noexcept {
+       struct timespec ts;
+       clock_gettime(CLOCK_REALTIME, &ts);
+       return from_timespec(ts);
+      }
+      // We need a version of 'now' that can take a CephContext for
+      // introducing configurable clock skew.
+      static time_point now(const CephContext* cct) noexcept;
+
+      // Allow conversion to/from any clock with the same interface as
+      // std::chrono::system_clock)
+      template<typename Clock, typename Duration>
+      static time_point to_system_time_point(
+       const std::chrono::time_point<Clock, Duration>& t) {
+       return time_point(seconds(Clock::to_time_t(t)) +
+                         duration_cast<duration>(t.time_since_epoch() %
+                                                 seconds(1)));
+      }
+      template<typename Clock, typename Duration>
+      static std::chrono::time_point<Clock, Duration> to_system_time_point(
+       const time_point& t) {
+       return (Clock::from_time_t(to_time_t(t)) +
+               duration_cast<Duration>(t.time_since_epoch() % seconds(1)));
+      }
+
+      static time_t to_time_t(const time_point& t) noexcept {
+       return duration_cast<seconds>(t.time_since_epoch()).count();
+      }
+      static time_point from_time_t(const time_t& t) noexcept {
+       return time_point(seconds(t));
+      }
+
+      static void to_timespec(const time_point& t, struct timespec& ts) {
+       ts.tv_sec = to_time_t(t);
+       ts.tv_nsec = (t.time_since_epoch() % seconds(1)).count();
+      }
+      static struct timespec to_timespec(const time_point& t) {
+       struct timespec ts;
+       to_timespec(t, ts);
+       return ts;
+      }
+      static time_point from_timespec(const struct timespec& ts) {
+       return time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
+      }
+
+      static void to_ceph_timespec(const time_point& t,
+                                  struct ceph_timespec& ts);
+      static struct ceph_timespec to_ceph_timespec(const time_point& t);
+      static time_point from_ceph_timespec(const struct ceph_timespec& ts);
+
+      static void to_timeval(const time_point& t, struct timeval& tv) {
+       tv.tv_sec = to_time_t(t);
+       tv.tv_usec = duration_cast<microseconds>(t.time_since_epoch() %
+                                                seconds(1)).count();
+      }
+      static struct timeval to_timeval(const time_point& t) {
+       struct timeval tv;
+       to_timeval(t, tv);
+       return tv;
+      }
+      static time_point from_timeval(const struct timeval& tv) {
+       return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec));
+      }
+
+      static double to_double(const time_point& t) {
+       return std::chrono::duration<double>(t.time_since_epoch()).count();
+      }
+      static time_point from_double(const double d) {
+       return time_point(duration_cast<duration>(
+                           std::chrono::duration<double>(d)));
+      }
+    };
+
+    class coarse_real_clock {
+    public:
+      typedef timespan duration;
+      typedef duration::rep rep;
+      typedef duration::period period;
+      // The second template parameter defaults to the clock's duration
+      // type.
+      typedef std::chrono::time_point<real_clock> time_point;
+      static constexpr const bool is_steady = false;
+
+      static time_point now() noexcept {
+       struct timespec ts;
+       clock_gettime(CLOCK_REALTIME_COARSE, &ts);
+       return from_timespec(ts);
+      }
+      static time_point now(const CephContext* cct) noexcept;
+
+      static time_t to_time_t(const time_point& t) noexcept {
+       return duration_cast<seconds>(t.time_since_epoch()).count();
+      }
+      static time_point from_time_t(const time_t t) noexcept {
+       return time_point(seconds(t));
+      }
+
+      static void to_timespec(const time_point& t, struct timespec& ts) {
+       ts.tv_sec = to_time_t(t);
+       ts.tv_nsec = (t.time_since_epoch() % seconds(1)).count();
+      }
+      static struct timespec to_timespec(const time_point& t) {
+       struct timespec ts;
+       to_timespec(t, ts);
+       return ts;
+      }
+      static time_point from_timespec(const struct timespec& ts) {
+       return time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
+      }
+
+      static void to_ceph_timespec(const time_point& t,
+                                  struct ceph_timespec& ts);
+      static struct ceph_timespec to_ceph_timespec(const time_point& t);
+      static time_point from_ceph_timespec(const struct ceph_timespec& ts);
+
+      static void to_timeval(const time_point& t, struct timeval& tv) {
+       tv.tv_sec = to_time_t(t);
+       tv.tv_usec = duration_cast<microseconds>(t.time_since_epoch() %
+                                                seconds(1)).count();
+      }
+      static struct timeval to_timeval(const time_point& t) {
+       struct timeval tv;
+       to_timeval(t, tv);
+       return tv;
+      }
+      static time_point from_timeval(const struct timeval& tv) {
+       return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec));
+      }
+
+      static double to_double(const time_point& t) {
+       return std::chrono::duration<double>(t.time_since_epoch()).count();
+      }
+      static time_point from_double(const double d) {
+       return time_point(duration_cast<duration>(
+                           std::chrono::duration<double>(d)));
+      }
+    };
+
+    class mono_clock {
+    public:
+      typedef timespan duration;
+      typedef duration::rep rep;
+      typedef duration::period period;
+      typedef std::chrono::time_point<mono_clock> time_point;
+      static constexpr const bool is_steady = true;
+
+      static time_point now() noexcept {
+       struct timespec ts;
+       clock_gettime(CLOCK_MONOTONIC, &ts);
+       return time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
+      }
+
+      // A monotonic clock's timepoints are only meaningful to the
+      // computer on which they were generated. Thus having an
+      // optional skew is meaningless.
+    };
+
+    class coarse_mono_clock {
+    public:
+      typedef timespan duration;
+      typedef duration::rep rep;
+      typedef duration::period period;
+      typedef std::chrono::time_point<mono_clock> time_point;
+      static constexpr const bool is_steady = true;
+
+      static time_point now() noexcept {
+       struct timespec ts;
+       clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+       return time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
+      }
+    };
+  } // namespace time_detail
+
+  // duration is the concrete time representation for our code in the
+  // case that we are only interested in durations between now and the
+  // future. Using it means we don't have to have EVERY function that
+  // deals with a duration be a template. We can do so for user-facing
+  // APIs, however.
+  using time_detail::timespan;
+
+  // Similar to the above but for durations that can specify
+  // differences between now and a time point in the past.
+  using time_detail::signedspan;
+
+  // High-resolution real-time clock
+  using time_detail::real_clock;
+
+  // Low-resolution but preusmably faster real-time clock
+  using time_detail::coarse_real_clock;
+
+
+  // High-resolution monotonic clock
+  using time_detail::mono_clock;
+
+  // Low-resolution but, I would hope or there's no point, faster
+  // monotonic clock
+  using time_detail::coarse_mono_clock;
+
+  // Please note that the coarse clocks are disjoint. You cannot
+  // subtract a real_clock timepoint from a coarse_real_clock
+  // timepoint as, from C++'s perspective, they are disjoint types.
+
+  // This is not necessarily bad. If I sample a mono_clock and then a
+  // coarse_mono_clock, the coarse_mono_clock's time could potentially
+  // be previous to the mono_clock's time (just due to differing
+  // resolution) which would be Incorrect.
+
+  // This is not horrible, though, since you can use an idiom like
+  // mono_clock::timepoint(coarsepoint.time_since_epoch()) to unwrap
+  // and rewrap if you know what you're doing.
+
+
+  // Actual wall-clock times
+  typedef real_clock::time_point real_time;
+
+  // Monotonic times should never be serialized or communicated
+  // between machines, since they are incomparable. Thus we also don't
+  // make any provision for converting between
+  // std::chrono::steady_clock time and ceph::mono_clock time.
+  typedef mono_clock::time_point mono_time;
+
+  template<typename Rep1, typename Ratio1, typename Rep2, typename Ratio2>
+  auto floor(const std::chrono::duration<Rep1, Ratio1>& duration,
+            const std::chrono::duration<Rep2, Ratio2>& precision) ->
+    typename std::common_type<std::chrono::duration<Rep1, Ratio1>,
+                             std::chrono::duration<Rep2, Ratio2> >::type {
+    return duration - (duration % precision);
+  }
+
+  template<typename Rep1, typename Ratio1, typename Rep2, typename Ratio2>
+  auto ceil(const std::chrono::duration<Rep1, Ratio1>& duration,
+           const std::chrono::duration<Rep2, Ratio2>& precision) ->
+    typename std::common_type<std::chrono::duration<Rep1, Ratio1>,
+                             std::chrono::duration<Rep2, Ratio2> >::type {
+    auto tmod = duration % precision;
+    return duration - tmod + (tmod > tmod.zero() ? 1 : 0) * precision;
+  }
+
+  template<typename Clock, typename Duration, typename Rep, typename Ratio>
+  auto floor(const std::chrono::time_point<Clock, Duration>& timepoint,
+            const std::chrono::duration<Rep, Ratio>& precision) ->
+    std::chrono::time_point<Clock,
+                           typename std::common_type<
+                             Duration, std::chrono::duration<Rep, Ratio>
+                             >::type> {
+    return std::chrono::time_point<
+      Clock, typename std::common_type<
+       Duration, std::chrono::duration<Rep, Ratio> >::type>(
+         floor(timepoint.time_since_epoch(), precision));
+  }
+  template<typename Clock, typename Duration, typename Rep, typename Ratio>
+  auto ceil(const std::chrono::time_point<Clock, Duration>& timepoint,
+           const std::chrono::duration<Rep, Ratio>& precision) ->
+    std::chrono::time_point<Clock,
+                           typename std::common_type<
+                             Duration,
+                             std::chrono::duration<Rep, Ratio> >::type> {
+    return std::chrono::time_point<
+      Clock, typename std::common_type<
+       Duration, std::chrono::duration<Rep, Ratio> >::type>(
+         ceil(timepoint.time_since_epoch(), precision));
+  }
+
+  static inline timespan make_timespan(const double d) {
+    return std::chrono::duration_cast<timespan>(
+      std::chrono::duration<double>(d));
+  }
+
+  std::ostream& operator<<(std::ostream& m, const timespan& t);
+  std::ostream& operator<<(std::ostream& m, const real_time& t);
+  std::ostream& operator<<(std::ostream& m, const mono_time& t);
+} // namespace ceph
+
+// We need these definitions to be able to hande ::encode/::decode on
+// time points.
+
+template<typename Clock, typename Duration>
+void encode(const std::chrono::time_point<Clock, Duration>& t,
+           ceph::bufferlist &bl) {
+  struct timespec ts = Clock::to_timespec();
+  // A 32 bit count of seconds causes me vast unhappiness.
+  ::encode((uint32_t) ts.tv_sec, bl);
+  ::encode((uint32_t) ts.tv_nsec, bl);
+}
+
+template<typename Clock, typename Duration>
+void decode(std::chrono::time_point<Clock, Duration>& t,
+           bufferlist::iterator& p) {
+  struct timespec ts;
+  ::decode((uint32_t&) ts.tv_sec, p);
+  ::decode((uint32_t&) ts.tv_nsec, p);
+
+  t = Clock::from_timespec(ts);
+}
+
+// C++ Overload Resolution requires that our encode/decode functions
+// be defined in the same namespace as the type. So we need this
+// to handle things like ::encode(std::vector<ceph::real_time // > >)
+
+namespace std {
+  namespace chrono {
+    template<typename Clock, typename Duration>
+    void encode(const time_point<Clock, Duration>& t,
+               ceph::bufferlist &bl) {
+      ::encode(t, bl);
+    }
+
+    template<typename Clock, typename Duration>
+    void decode(time_point<Clock, Duration>& t, bufferlist::iterator &p) {
+      ::decode(t, p);
+    }
+  } // namespace chrono
+} // namespace std
+
+#endif // COMMON_CEPH_TIME_H
index 82377732e4a7834a6fa8df13c7a8b82567cd34bb..04b136765b0876e5c5a32f22267b1e8a95adde64 100644 (file)
@@ -536,6 +536,25 @@ target_link_libraries(unittest_sloppy_crc_map global
 set_target_properties(unittest_sloppy_crc_map
   PROPERTIES COMPILE_FLAGS ${UNITTEST_CXX_FLAGS})
 
+# unittest_time
+set(unittest_time_srcs
+  common/test_time.cc
+  ${CMAKE_SOURCE_DIR}/src/common/ceph_time.cc
+  )
+add_executable(unittest_time
+  ${unittest_time_srcs}
+  $<TARGET_OBJECTS:heap_profiler_objs>
+  )
+target_link_libraries(unittest_time
+  global
+  ${BLKID_LIBRARIES}
+  ${CMAKE_DL_LIBS}
+  ${TCMALLOC_LIBS}
+  ${UNITTEST_LIBS}
+  )
+set_target_properties(unittest_time
+  PROPERTIES COMPILE_FLAGS ${UNITTEST_CXX_FLAGS})
+
 # unittest_util
 add_executable(unittest_util EXCLUDE_FROM_ALL
   common/test_util.cc
index 86ff39180eaa15592ee9d6d635243fed9cbac18e..29c1d61db9002d44787991d82de949d00d3530d1 100644 (file)
@@ -190,6 +190,11 @@ unittest_sloppy_crc_map_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 unittest_sloppy_crc_map_LDADD = $(UNITTEST_LDADD) $(CEPH_GLOBAL)
 check_TESTPROGRAMS += unittest_sloppy_crc_map
 
+unittest_time_SOURCES = test/common/test_time.cc
+unittest_time_CXXFLAGS = $(UNITTEST_CXXFLAGS)
+unittest_time_LDADD = $(LIBCOMMON) -lm $(UNITTEST_LDADD) $(CRYPTO_LIBS) $(EXTRALIBS)
+check_TESTPROGRAMS += unittest_time
+
 unittest_util_SOURCES = test/common/test_util.cc
 unittest_util_CXXFLAGS = $(UNITTEST_CXXFLAGS)
 unittest_util_LDADD = $(LIBCOMMON) -lm $(UNITTEST_LDADD) $(CRYPTO_LIBS) $(EXTRALIBS)
diff --git a/src/test/common/test_time.cc b/src/test/common/test_time.cc
new file mode 100644 (file)
index 0000000..e363085
--- /dev/null
@@ -0,0 +1,145 @@
+
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include <ctime>
+
+#include "common/ceph_time.h"
+#include "include/rados.h"
+#include "gtest/gtest.h"
+
+
+using ceph::real_clock;
+using ceph::real_time;
+
+using ceph::real_clock;
+using ceph::real_time;
+
+using ceph::coarse_real_clock;
+using ceph::coarse_mono_clock;
+
+using ceph::timespan;
+using ceph::signedspan;
+
+using std::chrono::seconds;
+using std::chrono::microseconds;
+using std::chrono::nanoseconds;
+
+static_assert(!real_clock::is_steady, "ceph::real_clock must not be steady.");
+static_assert(!coarse_real_clock::is_steady,
+             "ceph::coarse_real_clock must not be steady.");
+
+static_assert(mono_clock::is_steady, "ceph::mono_clock must be steady.");
+static_assert(coarse_mono_clock::is_steady,
+             "ceph::coarse_mono_clock must be steady.");
+
+// Before this file was written.
+static constexpr uint32_t bs = 1440701569;
+static constexpr uint32_t bns = 123456789;
+static constexpr uint32_t bus = 123456;
+static constexpr time_t btt = bs;
+static constexpr struct timespec bts = { bs, bns };
+static constexpr struct ceph_timespec bcts = { bs, bns };
+static constexpr struct timeval btv = { bs, bus };
+static constexpr double bd = bs + ((double)bns / 1000000000.);
+
+template<typename Clock>
+static void system_clock_sanity() {
+  static constexpr typename Clock::time_point brt(seconds(bs)
+                                                 + nanoseconds(bns));
+  const typename Clock::time_point now(Clock::now());
+
+  ASSERT_GT(now, brt);
+
+  ASSERT_GT(Clock::to_time_t(now), btt);
+
+  ASSERT_GT(Clock::to_timespec(now).tv_sec, bts.tv_sec);
+  ASSERT_LT(Clock::to_timespec(now).tv_nsec, 1000000000L);
+
+  ASSERT_GT(Clock::to_ceph_timespec(now).tv_sec, bcts.tv_sec);
+  ASSERT_LT(Clock::to_ceph_timespec(now).tv_nsec, 1000000000UL);
+
+  ASSERT_GT(Clock::to_timeval(now).tv_sec, btv.tv_sec);
+  ASSERT_LT(Clock::to_timeval(now).tv_usec, 1000000L);
+}
+
+template<typename Clock>
+static void system_clock_conversions() {
+  static constexpr typename Clock::time_point brt(seconds(bs) +
+                                                 nanoseconds(bns));
+
+  ASSERT_EQ(Clock::to_time_t(brt), btt);
+  ASSERT_EQ(Clock::from_time_t(btt) + nanoseconds(bns), brt);
+
+  {
+    const struct timespec tts = Clock::to_timespec(brt);
+    ASSERT_EQ(tts.tv_sec, bts.tv_sec);
+    ASSERT_EQ(tts.tv_nsec, bts.tv_nsec);
+  }
+  ASSERT_EQ(Clock::from_timespec(bts), brt);
+  {
+    struct timespec tts;
+    Clock::to_timespec(brt, tts);
+    ASSERT_EQ(tts.tv_sec, bts.tv_sec);
+    ASSERT_EQ(tts.tv_nsec, bts.tv_nsec);
+  }
+
+  {
+    const struct ceph_timespec tcts = Clock::to_ceph_timespec(brt);
+    ASSERT_EQ(tcts.tv_sec, bcts.tv_sec);
+    ASSERT_EQ(tcts.tv_nsec, bcts.tv_nsec);
+  }
+  ASSERT_EQ(Clock::from_ceph_timespec(bcts), brt);
+  {
+    struct ceph_timespec tcts;
+    Clock::to_ceph_timespec(brt, tcts);
+    ASSERT_EQ(tcts.tv_sec, bcts.tv_sec);
+    ASSERT_EQ(tcts.tv_nsec, bcts.tv_nsec);
+  }
+
+  {
+    const struct timeval ttv = Clock::to_timeval(brt);
+    ASSERT_EQ(ttv.tv_sec, btv.tv_sec);
+    ASSERT_EQ(ttv.tv_usec, btv.tv_usec);
+  }
+  ASSERT_EQ(Clock::from_timeval(btv), brt - nanoseconds(bns - bus * 1000));
+  {
+    struct timeval ttv;
+    Clock::to_timeval(brt, ttv);
+    ASSERT_EQ(ttv.tv_sec, btv.tv_sec);
+    ASSERT_EQ(ttv.tv_usec, btv.tv_usec);
+  }
+
+  ASSERT_EQ(Clock::to_double(brt), bd);
+  // Fudge factor
+  ASSERT_LT(abs((Clock::from_double(bd) - brt).count()), 30);
+}
+
+TEST(RealClock, Sanity) {
+  system_clock_sanity<real_clock>();
+}
+
+
+TEST(RealClock, Conversions) {
+  system_clock_conversions<real_clock>();
+}
+
+TEST(CoarseRealClock, Sanity) {
+  system_clock_sanity<coarse_real_clock>();
+}
+
+
+TEST(CoarseRealClock, Conversions) {
+  system_clock_conversions<coarse_real_clock>();
+}