123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- #ifndef CPPTIME_H_
- #define CPPTIME_H_
- /**
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Michael Egli
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * \author Michael Egli
- * \copyright Michael Egli
- * \date 11-Jul-2015
- *
- * \file cpptime.h
- *
- * C++11 timer component
- * =====================
- *
- * A portable, header-only C++11 timer component.
- *
- * Overview
- * --------
- *
- * This component can be used to manage a set of timeouts. It is implemented in
- * pure C++11. It is therefore very portable given a compliant compiler.
- *
- * A timeout can be added with one of the `add` functions, and removed with the
- * `remove` function. A timeout can be either one-shot or periodic. In case a
- * timeout is one-shot, the callback is invoked once and the timeout event is
- * then automatically removed. If the timer is periodic, it is never
- * automatically removed, but always renewed.
- *
- * Removing a timeout is possible even from within the callback.
- *
- * Timeout Units
- * -------------
- *
- * The preferred functions for adding timeouts are those that take a
- * `std::chrono::...` argument. However, for convenience, there is also an API
- * that takes a uint64_t. When using this API, all values are expected to be
- * given in microseconds (us).
- *
- * For periodic timeouts, a separate timeout can be specified for the initial
- * (first) timeout, and the periodicity after that.
- *
- * To avoid drifts, times are added by simply adding the period to the intially
- * calculated (or provided) time. Also, we use `wait until` type of API to wait
- * for a timeout instead of a `wait for` API.
- *
- * Data Structure
- * --------------
- *
- * Internally, a std::vector is used to store timeout events. The timer_id
- * returned from the `add` functions are used as index to this vector.
- *
- * In addition, a std::multiset is used that holds all time points when
- * timeouts expire.
- *
- * Using a vector to store timeout events has some implications. It is very
- * fast to remove an event, because the timer_id is the vector's index. On the
- * other hand, this makes it also more complicated to manage the timer_ids. The
- * current solution is to keep track of ids that are freed in order to re-use
- * them. A stack is used for this.
- *
- * Examples
- * --------
- *
- * More examples can be found in the `tests` folder.
- *
- * ~~~
- * CppTime::Timer t;
- * t.add(std::chrono::seconds(1), [](CppTime::timer_id){ std::cout << "got it!"; });
- * std::this_thread::sleep_for(std::chrono::seconds(2));
- * ~~~
- */
- // Includes
- #include <algorithm>
- #include <chrono>
- #include <condition_variable>
- #include <functional>
- #include <mutex>
- #include <set>
- #include <stack>
- #include <string>
- #include <thread>
- #include <utility>
- #include <vector>
- namespace /*CppTime*/ cnstream {
- // Public types
- using std::chrono::duration_cast;
- using std::chrono::microseconds;
- using std::chrono::milliseconds;
- using std::chrono::steady_clock;
- using std::chrono::time_point;
- using timer_id = std::size_t;
- using handler_t = std::function<void(timer_id)>;
- using clock = std::chrono::steady_clock;
- using timestamp = std::chrono::time_point<clock>;
- using duration = std::chrono::microseconds;
- // Private definitions. Do not rely on this namespace.
- namespace detail {
- // The event structure that holds the information about a timer.
- struct Event {
- timer_id id;
- timestamp start;
- duration period;
- handler_t handler;
- bool valid;
- Event() : id(0), start(duration::zero()), period(duration::zero()), handler(nullptr), valid(false) {}
- template <typename Func>
- Event(timer_id id, timestamp start, duration period, Func &&handler)
- : id(id), start(start), period(period), handler(std::forward<Func>(handler)), valid(true) {}
- Event(Event &&r) = default;
- Event &operator=(Event &&ev) = default;
- Event(const Event &r) = delete;
- Event &operator=(const Event &r) = delete;
- };
- // A time event structure that holds the next timeout and a reference to its
- // Event struct.
- struct Time_event {
- timestamp next;
- timer_id ref;
- };
- inline bool operator<(const Time_event &l, const Time_event &r) { return l.next < r.next; }
- } // end namespace detail
- class Timer {
- using scoped_m = std::unique_lock<std::mutex>;
- // Thread and locking variables.
- std::mutex m;
- std::condition_variable cond;
- std::thread worker;
- // Use to terminate the timer thread.
- bool done = false;
- // The vector that holds all active events.
- std::vector<detail::Event> events;
- // Sorted queue that has the next timeout at its top.
- std::multiset<detail::Time_event> time_events;
- // A list of ids to be re-used. If possible, ids are used from this pool.
- std::stack<cnstream::timer_id> free_ids;
- public:
- Timer() : m{}, cond{}, worker{}, events{}, time_events{}, free_ids{} {
- scoped_m lock(m);
- done = false;
- worker = std::thread([this] { run(); });
- }
- ~Timer() {
- scoped_m lock(m);
- done = true;
- lock.unlock();
- cond.notify_all();
- worker.join();
- events.clear();
- time_events.clear();
- while (!free_ids.empty()) {
- free_ids.pop();
- }
- }
- /**
- * Add a new timer.
- *
- * \param when The time at which the handler is invoked.
- * \param handler The callable that is invoked when the timer fires.
- * \param period The periodicity at which the timer fires. Only used for periodic timers.
- */
- timer_id add(const timestamp &when, handler_t &&handler, const duration &period = duration::zero()) {
- scoped_m lock(m);
- timer_id id = 0;
- // Add a new event. Prefer an existing and free id. If none is available, add
- // a new one.
- if (free_ids.empty()) {
- id = events.size();
- detail::Event e(id, when, period, std::move(handler));
- events.push_back(std::move(e));
- } else {
- id = free_ids.top();
- free_ids.pop();
- detail::Event e(id, when, period, std::move(handler));
- events[id] = std::move(e);
- }
- time_events.insert(detail::Time_event{when, id});
- lock.unlock();
- cond.notify_all();
- return id;
- }
- /**
- * Overloaded `add` function that uses a `std::chrono::duration` instead of a
- * `time_point` for the first timeout.
- */
- template <class Rep, class Period>
- inline timer_id add(const std::chrono::duration<Rep, Period> &when, handler_t &&handler,
- const duration &period = duration::zero()) {
- return add(clock::now() + std::chrono::duration_cast<std::chrono::microseconds>(when), std::move(handler), period);
- }
- /**
- * Overloaded `add` function that uses a uint64_t instead of a `time_point` for
- * the first timeout and the period.
- */
- inline timer_id add(const uint64_t when, handler_t &&handler, const uint64_t period = 0) {
- return add(duration(when), std::move(handler), duration(period));
- }
- /**
- * Removes the timer with the given id.
- */
- bool remove(timer_id id) {
- scoped_m lock(m);
- if (events.size() == 0 || events.size() < id) {
- return false;
- }
- events[id].valid = false;
- auto it = std::find_if(time_events.begin(), time_events.end(),
- [&](const detail::Time_event &te) { return te.ref == id; });
- if (it != time_events.end()) {
- free_ids.push(it->ref);
- time_events.erase(it);
- }
- lock.unlock();
- cond.notify_all();
- return true;
- }
- private:
- void run() {
- scoped_m lock(m);
- while (!done) {
- if (time_events.empty()) {
- // Wait for work
- cond.wait(lock);
- } else {
- detail::Time_event te = *time_events.begin();
- if (cnstream::clock::now() >= te.next) {
- // Remove time event
- time_events.erase(time_events.begin());
- // Invoke the handler
- lock.unlock();
- events[te.ref].handler(te.ref);
- lock.lock();
- if (events[te.ref].valid && events[te.ref].period.count() > 0) {
- // The event is valid and a periodic timer.
- te.next += events[te.ref].period;
- time_events.insert(te);
- } else {
- // The event is either no longer valid because it was removed in the
- // callback, or it is a one-shot timer.
- events[te.ref].valid = false;
- free_ids.push(te.ref);
- }
- } else {
- cond.wait_until(lock, te.next);
- }
- }
- }
- }
- };
- /// Timestamp utilities
- /****************************************************************************
- class TimeStamp provides a way to generate unique timestamps which based on
- the epoch time. The default precision of timestamps is in microseconds and
- configurable in class TimeStampBase.
- Samples are as follows:
- uint64_t time_stamp = TimeStamp::Current();
- std::string ts_str = TimeStamp::CurrentToString();
- uint64_t ts2 = TimeStampBase<std::chrono::nanoseconds>::Current();
- ***************************************************************************/
- /**
- * @brief a timestamp generator
- */
- template <typename precision = microseconds>
- class TimeStampBase {
- public:
- /**
- * @brief generate timestamp
- * @return timestamp as uint64_t
- */
- static uint64_t Current() {
- return duration_cast<precision>(std::chrono::system_clock::now().time_since_epoch()).count();
- }
- /**
- * @brief generate timestamp
- * @return timestamp as string
- */
- static std::string CurrentToString() { return std::to_string(Current()); }
- /**
- * @brief generate timestamp and format it to date
- * @return date as string
- */
- static std::string CurrentToDate() {
- uint64_t now = Current();
- time_t now_in_sec = now / 1e6;
- uint64_t remainder = now % 1000000;
- struct tm now_time;
- char buf[80];
- localtime_r(&now_in_sec, &now_time);
- strftime(buf, sizeof(buf), "%Y-%m-%d-%H.%M.%S", &now_time);
- std::string result = buf;
- return result + "." + std::to_string(remainder);
- }
- };
- /**
- * @brief simplified interface
- */
- using TimeStamp = TimeStampBase<>;
- /// Clock utilities
- /****************************************************************************
- Clock classes provides two kinds of clocks. TickClock is a ticker-tape clock
- and TickTockClock is a duration recorder. The default precision is in
- microseconds and configurable in class ClockBase.
- Samples are as follows:
- TickClock tick_clock;
- for (int i = 0; i < 10; ++i) {
- tick_clock.Tick();
- // do something...
- }
- double average_execute_time = tick_clock.ElapsedAverageAsDouble();
- TickTockClock duration_recorder;
- void foo() {
- duration_recorder.Tick();
- // do something...
- duration_recorder.Tock();
- }
- for (int i = 0; i < 10; ++i) {
- foo();
- }
- double average_duration = duration_recorder.ElapsedAverageAsDouble();
- ***************************************************************************/
- enum class ClockType {
- Tick,
- TickTock,
- };
- template <ClockType type, typename precision = std::micro>
- class ClockBase {
- public:
- using Elapsed_t = std::chrono::duration<double, precision>;
- /**
- * @brief calculate total elapsed time
- * @return elapsed duration
- */
- Elapsed_t ElapsedTotal() const { return total_; }
- /**
- * @brief calculate total elapsed time
- * @return elapsed duration as double
- */
- double ElapsedTotalAsDouble() const { return total_.count(); }
- /**
- * @brief calculate average elapsed time
- * @return average elapsed duration
- */
- Elapsed_t ElapsedAverage() const { return times_ == 0 ? Elapsed_t::zero() : total_ / times_; }
- /**
- * @brief calculate average elapsed time
- * @return average elapsed duration as double
- */
- double ElapsedAverageAsDouble() const { return ElapsedAverage().count(); }
- /**
- * @brief clear records
- * @return void
- */
- void Clear() {
- total_ = Elapsed_t::zero();
- times_ = 0;
- }
- protected:
- Elapsed_t total_ = Elapsed_t::zero();
- uint32_t times_ = 0;
- };
- /**
- * @brief a ticker-tape clock
- */
- class TickClock final : public ClockBase<ClockType::Tick> {
- public:
- /**
- * @brief tick
- * @return void
- */
- void Tick() {
- curr_ = steady_clock::now();
- if (!started_) {
- started_ = true;
- } else {
- total_ += curr_ - prev_;
- ++times_;
- }
- prev_ = curr_;
- }
- private:
- time_point<steady_clock> prev_, curr_;
- bool started_ = false;
- };
- /**
- * @brief a duration recorder
- */
- class TickTockClock final : public ClockBase<ClockType::TickTock> {
- public:
- /**
- * @brief record start time
- * @return void
- */
- void Tick() { start_ = steady_clock::now(); }
- /**
- * @brief record end time
- * @return void
- */
- void Tock() {
- end_ = steady_clock::now();
- total_ += end_ - start_;
- ++times_;
- }
- private:
- time_point<steady_clock> start_, end_;
- };
- } // namespace cnstream
- #endif // CPPTIME_H_
|