DPsim
Timer.cpp
1 /* Copyright 2017-2021 Institute for Automation of Complex Power Systems,
2  * EONERC, RWTH Aachen University
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7  *********************************************************************************/
8 
9 #include <assert.h>
10 #include <thread>
11 
12 #include <dpsim-models/Definitions.h>
13 #include <dpsim/Timer.h>
14 
15 #ifdef HAVE_TIMERFD
16 #include <sys/timerfd.h>
17 #include <unistd.h>
18 #endif /* HAVE_TIMERFD */
19 
20 using namespace DPsim;
21 using CPS::SystemError;
22 
23 Timer::Timer(int flags)
24  : mState(stopped), mOverruns(0), mTicks(0), mFlags(flags) {
25 #ifdef HAVE_TIMERFD
26  mTimerFd = timerfd_create(CLOCK_MONOTONIC, 0);
27  if (mTimerFd < 0) {
28  throw SystemError("Failed to create timerfd");
29  }
30 #else
31  std::cerr << "WARNING: No high resolution timer available. Clock might drift!"
32  << std::endl;
33 #endif
34 }
35 
36 Timer::~Timer() {
37  if (mState == State::running)
38  stop();
39 
40 #ifdef HAVE_TIMERFD
41  close(mTimerFd);
42 #endif
43 }
44 
45 void Timer::sleep() {
46  uint64_t ticks = 0, overruns;
47 
48 #ifdef HAVE_TIMERFD
49  ssize_t bytes;
50 
51  bytes = read(mTimerFd, &ticks, sizeof(ticks));
52  if (bytes < 0) {
53  throw SystemError("Read from timerfd failed");
54  }
55 #else
56  std::this_thread::sleep_until(mNextTick);
57 
58  auto now = IntervalClock::now();
59 
60  while (mNextTick < now) {
61  mNextTick += mTickInterval;
62  ticks++;
63  }
64 
65 #endif
66  overruns = ticks - 1;
67 
68  mOverruns += overruns;
69  mTicks += ticks;
70 
71  if (overruns > 0) {
72  //SPDLOG_LOGGER_WARN(mSLog, "Timer overrun of {} timesteps at {}", overruns, mTime);
73  if (mFlags & Flags::fail_on_overrun)
74  throw OverrunException{overruns};
75  }
76 }
77 
78 void Timer::start() {
79  assert(mState == stopped);
80 
81  mTicks = 0;
82  mOverruns = 0;
83 
84  /* Determine offset between clocks */
85  auto rt = StartClock::now();
86  auto steady = IntervalClock::now();
87 
88  /* This handles the offset between
89  * - IntervalClock (CLOCK_MONOTONIC aka std::chrono::steady_clock) and
90  * - StartClock (CLOCK_REALTIME aka std::chrono::system_clock)
91  */
92  auto start = mStartAt > StartTimePoint()
93  ? mStartAt.time_since_epoch() - rt.time_since_epoch() +
94  steady.time_since_epoch()
95  : steady.time_since_epoch();
96 
97 #ifdef HAVE_TIMERFD
98  int ret;
99  struct itimerspec ts = {.it_interval = to_timespec(mTickInterval),
100  .it_value = to_timespec(start)};
101 
102  ret = timerfd_settime(mTimerFd, TFD_TIMER_ABSTIME, &ts, 0);
103  if (ret < 0) {
104  throw SystemError("Failed to arm timerfd");
105  }
106 #endif
107  mNextTick = IntervalTimePoint(start) + mTickInterval;
108  mState = State::running;
109 }
110 
111 void Timer::stop() {
112  assert(mState == State::running);
113 
114 #ifdef HAVE_TIMERFD
115  int ret;
116  struct itimerspec ts = {.it_interval = {0, 0}, .it_value = {0, 0}};
117 
118  ret = timerfd_settime(mTimerFd, 0, &ts, 0);
119  if (ret < 0) {
120  throw SystemError("Failed to arm timerfd");
121  }
122 #endif
123 
124  mState = State::stopped;
125 }
void start()
Start real-time timer.
Definition: Timer.cpp:78
void stop()
Stop real-time timer.
Definition: Timer.cpp:111
void sleep()
Suspend thread execution until next tick.
Definition: Timer.cpp:45