cnstream_timer.hpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #ifndef CPPTIME_H_
  2. #define CPPTIME_H_
  3. /**
  4. * The MIT License (MIT)
  5. *
  6. * Copyright (c) 2015 Michael Egli
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the "Software"), to deal
  10. * in the Software without restriction, including without limitation the rights
  11. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. * THE SOFTWARE.
  25. *
  26. * \author Michael Egli
  27. * \copyright Michael Egli
  28. * \date 11-Jul-2015
  29. *
  30. * \file cpptime.h
  31. *
  32. * C++11 timer component
  33. * =====================
  34. *
  35. * A portable, header-only C++11 timer component.
  36. *
  37. * Overview
  38. * --------
  39. *
  40. * This component can be used to manage a set of timeouts. It is implemented in
  41. * pure C++11. It is therefore very portable given a compliant compiler.
  42. *
  43. * A timeout can be added with one of the `add` functions, and removed with the
  44. * `remove` function. A timeout can be either one-shot or periodic. In case a
  45. * timeout is one-shot, the callback is invoked once and the timeout event is
  46. * then automatically removed. If the timer is periodic, it is never
  47. * automatically removed, but always renewed.
  48. *
  49. * Removing a timeout is possible even from within the callback.
  50. *
  51. * Timeout Units
  52. * -------------
  53. *
  54. * The preferred functions for adding timeouts are those that take a
  55. * `std::chrono::...` argument. However, for convenience, there is also an API
  56. * that takes a uint64_t. When using this API, all values are expected to be
  57. * given in microseconds (us).
  58. *
  59. * For periodic timeouts, a separate timeout can be specified for the initial
  60. * (first) timeout, and the periodicity after that.
  61. *
  62. * To avoid drifts, times are added by simply adding the period to the intially
  63. * calculated (or provided) time. Also, we use `wait until` type of API to wait
  64. * for a timeout instead of a `wait for` API.
  65. *
  66. * Data Structure
  67. * --------------
  68. *
  69. * Internally, a std::vector is used to store timeout events. The timer_id
  70. * returned from the `add` functions are used as index to this vector.
  71. *
  72. * In addition, a std::multiset is used that holds all time points when
  73. * timeouts expire.
  74. *
  75. * Using a vector to store timeout events has some implications. It is very
  76. * fast to remove an event, because the timer_id is the vector's index. On the
  77. * other hand, this makes it also more complicated to manage the timer_ids. The
  78. * current solution is to keep track of ids that are freed in order to re-use
  79. * them. A stack is used for this.
  80. *
  81. * Examples
  82. * --------
  83. *
  84. * More examples can be found in the `tests` folder.
  85. *
  86. * ~~~
  87. * CppTime::Timer t;
  88. * t.add(std::chrono::seconds(1), [](CppTime::timer_id){ std::cout << "got it!"; });
  89. * std::this_thread::sleep_for(std::chrono::seconds(2));
  90. * ~~~
  91. */
  92. // Includes
  93. #include <algorithm>
  94. #include <chrono>
  95. #include <condition_variable>
  96. #include <functional>
  97. #include <mutex>
  98. #include <set>
  99. #include <stack>
  100. #include <string>
  101. #include <thread>
  102. #include <utility>
  103. #include <vector>
  104. namespace /*CppTime*/ cnstream {
  105. // Public types
  106. using std::chrono::duration_cast;
  107. using std::chrono::microseconds;
  108. using std::chrono::milliseconds;
  109. using std::chrono::steady_clock;
  110. using std::chrono::time_point;
  111. using timer_id = std::size_t;
  112. using handler_t = std::function<void(timer_id)>;
  113. using clock = std::chrono::steady_clock;
  114. using timestamp = std::chrono::time_point<clock>;
  115. using duration = std::chrono::microseconds;
  116. // Private definitions. Do not rely on this namespace.
  117. namespace detail {
  118. // The event structure that holds the information about a timer.
  119. struct Event {
  120. timer_id id;
  121. timestamp start;
  122. duration period;
  123. handler_t handler;
  124. bool valid;
  125. Event() : id(0), start(duration::zero()), period(duration::zero()), handler(nullptr), valid(false) {}
  126. template <typename Func>
  127. Event(timer_id id, timestamp start, duration period, Func &&handler)
  128. : id(id), start(start), period(period), handler(std::forward<Func>(handler)), valid(true) {}
  129. Event(Event &&r) = default;
  130. Event &operator=(Event &&ev) = default;
  131. Event(const Event &r) = delete;
  132. Event &operator=(const Event &r) = delete;
  133. };
  134. // A time event structure that holds the next timeout and a reference to its
  135. // Event struct.
  136. struct Time_event {
  137. timestamp next;
  138. timer_id ref;
  139. };
  140. inline bool operator<(const Time_event &l, const Time_event &r) { return l.next < r.next; }
  141. } // end namespace detail
  142. class Timer {
  143. using scoped_m = std::unique_lock<std::mutex>;
  144. // Thread and locking variables.
  145. std::mutex m;
  146. std::condition_variable cond;
  147. std::thread worker;
  148. // Use to terminate the timer thread.
  149. bool done = false;
  150. // The vector that holds all active events.
  151. std::vector<detail::Event> events;
  152. // Sorted queue that has the next timeout at its top.
  153. std::multiset<detail::Time_event> time_events;
  154. // A list of ids to be re-used. If possible, ids are used from this pool.
  155. std::stack<cnstream::timer_id> free_ids;
  156. public:
  157. Timer() : m{}, cond{}, worker{}, events{}, time_events{}, free_ids{} {
  158. scoped_m lock(m);
  159. done = false;
  160. worker = std::thread([this] { run(); });
  161. }
  162. ~Timer() {
  163. scoped_m lock(m);
  164. done = true;
  165. lock.unlock();
  166. cond.notify_all();
  167. worker.join();
  168. events.clear();
  169. time_events.clear();
  170. while (!free_ids.empty()) {
  171. free_ids.pop();
  172. }
  173. }
  174. /**
  175. * Add a new timer.
  176. *
  177. * \param when The time at which the handler is invoked.
  178. * \param handler The callable that is invoked when the timer fires.
  179. * \param period The periodicity at which the timer fires. Only used for periodic timers.
  180. */
  181. timer_id add(const timestamp &when, handler_t &&handler, const duration &period = duration::zero()) {
  182. scoped_m lock(m);
  183. timer_id id = 0;
  184. // Add a new event. Prefer an existing and free id. If none is available, add
  185. // a new one.
  186. if (free_ids.empty()) {
  187. id = events.size();
  188. detail::Event e(id, when, period, std::move(handler));
  189. events.push_back(std::move(e));
  190. } else {
  191. id = free_ids.top();
  192. free_ids.pop();
  193. detail::Event e(id, when, period, std::move(handler));
  194. events[id] = std::move(e);
  195. }
  196. time_events.insert(detail::Time_event{when, id});
  197. lock.unlock();
  198. cond.notify_all();
  199. return id;
  200. }
  201. /**
  202. * Overloaded `add` function that uses a `std::chrono::duration` instead of a
  203. * `time_point` for the first timeout.
  204. */
  205. template <class Rep, class Period>
  206. inline timer_id add(const std::chrono::duration<Rep, Period> &when, handler_t &&handler,
  207. const duration &period = duration::zero()) {
  208. return add(clock::now() + std::chrono::duration_cast<std::chrono::microseconds>(when), std::move(handler), period);
  209. }
  210. /**
  211. * Overloaded `add` function that uses a uint64_t instead of a `time_point` for
  212. * the first timeout and the period.
  213. */
  214. inline timer_id add(const uint64_t when, handler_t &&handler, const uint64_t period = 0) {
  215. return add(duration(when), std::move(handler), duration(period));
  216. }
  217. /**
  218. * Removes the timer with the given id.
  219. */
  220. bool remove(timer_id id) {
  221. scoped_m lock(m);
  222. if (events.size() == 0 || events.size() < id) {
  223. return false;
  224. }
  225. events[id].valid = false;
  226. auto it = std::find_if(time_events.begin(), time_events.end(),
  227. [&](const detail::Time_event &te) { return te.ref == id; });
  228. if (it != time_events.end()) {
  229. free_ids.push(it->ref);
  230. time_events.erase(it);
  231. }
  232. lock.unlock();
  233. cond.notify_all();
  234. return true;
  235. }
  236. private:
  237. void run() {
  238. scoped_m lock(m);
  239. while (!done) {
  240. if (time_events.empty()) {
  241. // Wait for work
  242. cond.wait(lock);
  243. } else {
  244. detail::Time_event te = *time_events.begin();
  245. if (cnstream::clock::now() >= te.next) {
  246. // Remove time event
  247. time_events.erase(time_events.begin());
  248. // Invoke the handler
  249. lock.unlock();
  250. events[te.ref].handler(te.ref);
  251. lock.lock();
  252. if (events[te.ref].valid && events[te.ref].period.count() > 0) {
  253. // The event is valid and a periodic timer.
  254. te.next += events[te.ref].period;
  255. time_events.insert(te);
  256. } else {
  257. // The event is either no longer valid because it was removed in the
  258. // callback, or it is a one-shot timer.
  259. events[te.ref].valid = false;
  260. free_ids.push(te.ref);
  261. }
  262. } else {
  263. cond.wait_until(lock, te.next);
  264. }
  265. }
  266. }
  267. }
  268. };
  269. /// Timestamp utilities
  270. /****************************************************************************
  271. class TimeStamp provides a way to generate unique timestamps which based on
  272. the epoch time. The default precision of timestamps is in microseconds and
  273. configurable in class TimeStampBase.
  274. Samples are as follows:
  275. uint64_t time_stamp = TimeStamp::Current();
  276. std::string ts_str = TimeStamp::CurrentToString();
  277. uint64_t ts2 = TimeStampBase<std::chrono::nanoseconds>::Current();
  278. ***************************************************************************/
  279. /**
  280. * @brief a timestamp generator
  281. */
  282. template <typename precision = microseconds>
  283. class TimeStampBase {
  284. public:
  285. /**
  286. * @brief generate timestamp
  287. * @return timestamp as uint64_t
  288. */
  289. static uint64_t Current() {
  290. return duration_cast<precision>(std::chrono::system_clock::now().time_since_epoch()).count();
  291. }
  292. /**
  293. * @brief generate timestamp
  294. * @return timestamp as string
  295. */
  296. static std::string CurrentToString() { return std::to_string(Current()); }
  297. /**
  298. * @brief generate timestamp and format it to date
  299. * @return date as string
  300. */
  301. static std::string CurrentToDate() {
  302. uint64_t now = Current();
  303. time_t now_in_sec = now / 1e6;
  304. uint64_t remainder = now % 1000000;
  305. struct tm now_time;
  306. char buf[80];
  307. localtime_r(&now_in_sec, &now_time);
  308. strftime(buf, sizeof(buf), "%Y-%m-%d-%H.%M.%S", &now_time);
  309. std::string result = buf;
  310. return result + "." + std::to_string(remainder);
  311. }
  312. };
  313. /**
  314. * @brief simplified interface
  315. */
  316. using TimeStamp = TimeStampBase<>;
  317. /// Clock utilities
  318. /****************************************************************************
  319. Clock classes provides two kinds of clocks. TickClock is a ticker-tape clock
  320. and TickTockClock is a duration recorder. The default precision is in
  321. microseconds and configurable in class ClockBase.
  322. Samples are as follows:
  323. TickClock tick_clock;
  324. for (int i = 0; i < 10; ++i) {
  325. tick_clock.Tick();
  326. // do something...
  327. }
  328. double average_execute_time = tick_clock.ElapsedAverageAsDouble();
  329. TickTockClock duration_recorder;
  330. void foo() {
  331. duration_recorder.Tick();
  332. // do something...
  333. duration_recorder.Tock();
  334. }
  335. for (int i = 0; i < 10; ++i) {
  336. foo();
  337. }
  338. double average_duration = duration_recorder.ElapsedAverageAsDouble();
  339. ***************************************************************************/
  340. enum class ClockType {
  341. Tick,
  342. TickTock,
  343. };
  344. template <ClockType type, typename precision = std::micro>
  345. class ClockBase {
  346. public:
  347. using Elapsed_t = std::chrono::duration<double, precision>;
  348. /**
  349. * @brief calculate total elapsed time
  350. * @return elapsed duration
  351. */
  352. Elapsed_t ElapsedTotal() const { return total_; }
  353. /**
  354. * @brief calculate total elapsed time
  355. * @return elapsed duration as double
  356. */
  357. double ElapsedTotalAsDouble() const { return total_.count(); }
  358. /**
  359. * @brief calculate average elapsed time
  360. * @return average elapsed duration
  361. */
  362. Elapsed_t ElapsedAverage() const { return times_ == 0 ? Elapsed_t::zero() : total_ / times_; }
  363. /**
  364. * @brief calculate average elapsed time
  365. * @return average elapsed duration as double
  366. */
  367. double ElapsedAverageAsDouble() const { return ElapsedAverage().count(); }
  368. /**
  369. * @brief clear records
  370. * @return void
  371. */
  372. void Clear() {
  373. total_ = Elapsed_t::zero();
  374. times_ = 0;
  375. }
  376. protected:
  377. Elapsed_t total_ = Elapsed_t::zero();
  378. uint32_t times_ = 0;
  379. };
  380. /**
  381. * @brief a ticker-tape clock
  382. */
  383. class TickClock final : public ClockBase<ClockType::Tick> {
  384. public:
  385. /**
  386. * @brief tick
  387. * @return void
  388. */
  389. void Tick() {
  390. curr_ = steady_clock::now();
  391. if (!started_) {
  392. started_ = true;
  393. } else {
  394. total_ += curr_ - prev_;
  395. ++times_;
  396. }
  397. prev_ = curr_;
  398. }
  399. private:
  400. time_point<steady_clock> prev_, curr_;
  401. bool started_ = false;
  402. };
  403. /**
  404. * @brief a duration recorder
  405. */
  406. class TickTockClock final : public ClockBase<ClockType::TickTock> {
  407. public:
  408. /**
  409. * @brief record start time
  410. * @return void
  411. */
  412. void Tick() { start_ = steady_clock::now(); }
  413. /**
  414. * @brief record end time
  415. * @return void
  416. */
  417. void Tock() {
  418. end_ = steady_clock::now();
  419. total_ += end_ - start_;
  420. ++times_;
  421. }
  422. private:
  423. time_point<steady_clock> start_, end_;
  424. };
  425. } // namespace cnstream
  426. #endif // CPPTIME_H_