PeriodicTask.h
Go to the documentation of this file.
1 /*
2 * This file is part of ArmarX.
3 *
4 * Copyright (C) 2011-2016, High Performance Humanoid Technologies (H2T), Karlsruhe Institute of Technology (KIT), all rights reserved.
5 *
6 * ArmarX is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * ArmarX is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @package ArmarXCore::core
19 * @author Kai Welke (welke at kit dot edu)
20 * @date 2012
21 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
22 * GNU General Public License
23 */
24 
25 #pragma once
26 
27 
28 #include "ThreadList.h"
33 
34 #include <IceUtil/Time.h>
35 #include <IceUtil/Timer.h>
36 
37 #include <cxxabi.h>
38 
39 #include <functional>
40 #include <mutex>
41 #include <time.h>
42 
43 namespace armarx
44 {
45  /**
46  * \class PeriodicTask
47  * \ingroup Threads
48  * The periodic task executes one thread method repeatedly using the time period specified in the constructor.
49  */
50  template <class T>
51  class ARMARXCORE_IMPORT_EXPORT PeriodicTask :
52  virtual public IceUtil::TimerTask,
53  virtual public Logging,
54  virtual protected PeriodicTaskIceBase
55  {
56  public:
57  /**
58  * Typedef for the periodic method. Thread methods need to follow the template
59  * void methodName(void). Parameter passing is not used since methods are members
60  * of the class.
61  */
62  typedef void (T::*method_type)(void);
63 
64  /**
65  * Shared pointer type for convenience.
66  */
68 
69  /**
70  * Constructs a periodic task within the class parent which calls the peridoicFn in
71  * a new thread.
72  *
73  * @param parent the class wheter periodicFn is member
74  * @param peridicFn reference to the periodic function implementation
75  * @param periodMs period time in milliseconds
76  * @param assureMeanInterval If set to TRUE, the scheduler tries to
77  * compensate timeslot exceedings overtime and not only on the next interval
78  * @param name of this thread, that describes what it is doing
79  * @param forceSystemTime If set to false, the Timeserver will be used for timing (if available)
80  *
81  */
82  PeriodicTask(T* parent, method_type periodicFn, int periodMs, bool assureMeanInterval = false, std::string name = "", bool forceSystemTime = true)
83  {
84  shutdown = false;
85  running = false;
86  functionExecuting = false;
87  this->assureMeanInterval = assureMeanInterval;
88  executionCounter = 0;
89  last_cpu_total_time = 0;
90  this->periodicFn = periodicFn;
91  this->parent = parent;
92 
93  if (name.empty())
94  {
95  char* demangled = nullptr;
96  int status = -1;
97  demangled = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status);
98  setName(demangled);
99  free(demangled);
100  }
101  else
102  {
103  setName(name);
104  }
105 
106  intervalMs = periodMs;
107  interval = IceUtil::Time::milliSeconds(periodMs);
108  timer = new armarx::Timer(forceSystemTime);
109  delayWarningTolerance = IceUtil::Time::milliSeconds(5);
110 
111  workloadList.resize(100, 0.f);
112  setTag("PeriodicTask" + this->name);
113 
114  if (customThreadList)
115  {
116  customThreadList->addPeriodicTask(this);
117  }
118 
119  appThreadList = ThreadList::getApplicationThreadList();
120 
121  if (appThreadList)
122  {
123  appThreadList->addPeriodicTask(this);
124  }
125  }
126 
127  /**
128  * Destructor stops the thread and waits for completion.
129  */
130  ~PeriodicTask() override
131  {
132  stop();
133  if (appThreadList)
134  {
135  appThreadList->removePeriodicTask(this);
136  }
137 
138  if (customThreadList)
139  {
140  customThreadList->removePeriodicTask(this);
141  }
142  }
143 
144  /**
145  * @brief Set the name identifying PeriodicTask instances.
146  * @param name string containing the name
147  */
148  void setName(std::string name)
149  {
150  this->name = name;
151  }
152 
153 
154  /**
155  * @brief This method sets \p threadList as PeriodicTask::customThreadList and appends \p this to the list.
156  * @param threadList a pointer to the ThreadList instance.
157  */
158  void setThreadList(ThreadListPtr threadList)
159  {
160  this->customThreadList = threadList;
161 
162  if (isRunning())
163  {
164  threadList->addPeriodicTask(this);
165  }
166  }
167  /**
168  * Starts the thread. Only has effect if the task has not been started. After completion of the task, it can be started again (see isRunning()).
169  */
170  void start()
171  {
172  std::unique_lock lock(dataMutex);
173  auto app = Application::getInstance();
174  if (app && app->getForbidThreadCreation())
175  {
176  throw LocalException() << "Thread creation is now allowed in this application at the point in time! Use Application::getInstance()->getThreadPool() instead.";
177  }
178  if (!running)
179  {
180  shutdown = false;
181  cycleStart = logTime = __now();
182  startTime = logTime.toMicroSeconds();
183  timeExceeded = false;
184  timer->schedule(this, IceUtil::Time::milliSeconds(0));
185  }
186 
187  running = true;
188  }
189 
190  /**
191  * @return The scheduling interval of the thread instance in milliseconds
192  */
193  IceUtil::Int64 getInterval() const
194  {
195  return interval.toMilliSeconds();
196  }
197 
198 
199  /**
200  * @return Set the tolerance value for delay warnings. If the tasks takes longer than (scheduling interval + tolerance), a warning is issued.
201  * @param newToleranceInMilliSeconds The tolerance, in milliseconds.
202  */
203  void setDelayWarningTolerance(int newToleranceInMilliSeconds = 5)
204  {
205  delayWarningTolerance = IceUtil::Time::milliSeconds(newToleranceInMilliSeconds);
206  }
207 
208  /**
209  * @brief changeInterval changes the interval at which the task is executed.
210  * The execution starts the first time again after the duration specified
211  * in the parameter OR if the task is currently executed after this task
212  * + the interval specified - cycleDuration.
213  * @param intervalInMs new execution interval in milliseconds.
214  */
215  void changeInterval(unsigned int intervalInMs)
216  {
217  std::unique_lock lock(dataMutex);
218 
219  if (interval.toMilliSeconds() == intervalInMs)
220  {
221  return;
222  }
223 
224  intervalMs = intervalInMs;
225  interval = IceUtil::Time::milliSeconds(intervalInMs);
226 
227  __changeInterval();
228  }
229 
230  /**
231  * Stops the thread. Only has effect if the task has been started.
232  *
233  *
234  */
235  void stop()
236  {
237  std::unique_lock lock(dataMutex);
238 
239  if (running)
240  {
241  shutdown = true;
242  lock.unlock();
243  // timer->cancel(this);
244  timer->destroy();
245  timer = new armarx::Timer(timer->getUseSystemTime());
246  }
247 
248  running = false;
249  }
250 
251  /**
252  * Retrieve running state of the thread
253  *
254  * @return whether thread is running
255  */
256  bool isRunning() const
257  {
258  return running;
259  }
260 
261  /**
262  * Return the execution state of the thread function.
263  *
264  * @return PeriodicTask::functionExecuting
265  */
266  bool isFunctionExecuting() const
267  {
268  std::unique_lock lock(mutexFunctionExecuting);
269  return functionExecuting;
270  }
271 
272  /**
273  * @return PeriodicTaskIceBase base pointer to the current PeriodicTask instance
274  */
275  const PeriodicTaskIceBase& getStatistics() const
276  {
277  const PeriodicTaskIceBase* base = dynamic_cast<const PeriodicTaskIceBase*>(this);
278  return *base;
279  }
280 
281  private:
282  void runTimerTask() override
283  {
284  {
285  std::unique_lock lock(mutexFunctionExecuting);
286  functionExecuting = true;
287  }
288  IceUtil::Time idleDuration;
289  {
290  std::unique_lock lock(dataMutex);
291  threadId = LogSender::getThreadId();
292 
293  idleDuration = __now() - cycleStart - cycleDuration;
294  cycleStart = __now();
295  lastCycleStartTime = cycleStart.toMicroSeconds();
296  }
297 
298  // call periodic function
299  try
300  {
301  (parent->*periodicFn)();
302  }
303  catch (...)
304  {
306  }
307 
308  {
309  std::unique_lock lock2(dataMutex);
310  std::unique_lock lock(mutexFunctionExecuting);
311  // measure execution time
312  cycleDuration = __now() - cycleStart;
313  lastCycleDuration = cycleDuration.toMicroSeconds();
314  __checkTimeslotUsage();
315  __getCPULoad(idleDuration);
316  __reschedule();
317  functionExecuting = false;
318  }
319  }
320 
321  void __changeInterval()
322  {
323  std::unique_lock lock(mutexFunctionExecuting);
324  startTime = __now().toMicroSeconds();
325  executionCounter = 0;
326 
327  if (functionExecuting)
328  {
329  return;
330  }
331 
332  timer->cancel(this);
333 
334  timer->schedule(this, interval);
335  ARMARX_VERBOSE << "Execution interval changed to " << interval.toMilliSeconds() << " ms." << std::endl;
336 
337  }
338 
339  void __reschedule()
340  {
341  if (isRunning())
342  {
343  IceUtil::Time newInterval;
344  executionCounter++;
345 
346  if (assureMeanInterval)
347  {
348  newInterval = IceUtil::Time::microSeconds(startTime + interval.toMicroSeconds() * executionCounter) - __now();
349  }
350  else
351  {
352  newInterval = interval - IceUtil::Time::microSeconds(lastCycleDuration);
353  }
354 
355  if (newInterval.toMicroSeconds() < 0)
356  {
357  newInterval = IceUtil::Time::microSeconds(0);
358  }
359 
360  if (!shutdown)
361  {
362  timer->schedule(this, newInterval);
363  }
364  }
365  }
366 
367  void __checkTimeslotUsage()
368  {
369  if (interval + delayWarningTolerance < cycleDuration)
370  {
371  timeExceeded = true;
372  executionTime = cycleDuration.toMilliSeconds();
373  }
374 
375  float logInterval = 10.0f;
376 
377  if (timeExceeded)
378  {
379  ARMARX_IMPORTANT << deactivateSpam(logInterval) << "periodic task '" << name << "' took "
380  << executionTime
381  << " [ms] instead of the requested "
382  << interval.toMilliSeconds()
383  << " [ms] (this message is only posted every " << logInterval << " seconds.)";
384  timeExceeded = false;
385  }
386 
387 
388  }
389 
390  void __getCPULoad(IceUtil::Time& idleDuration)
391  {
392  double threadCPUUsage = -1;
393  timespec usage;
394  timespec st;
395 
396  if (clock_gettime(CLOCK_REALTIME, &st) != -1 && clock_gettime(CLOCK_THREAD_CPUTIME_ID, &usage) != -1)
397  {
398  double current_cpu_thread_time = static_cast<double>(usage.tv_sec) + static_cast<double>(usage.tv_nsec) / 1'000'000'000;
399  double current_cpu_total_time = static_cast<double>(st.tv_sec) + static_cast<double>(st.tv_nsec) / 1'000'000'000;
400 
401  if (last_cpu_total_time != 0)
402  {
403  threadCPUUsage = (current_cpu_thread_time - last_cpu_thread_time) / (current_cpu_total_time - last_cpu_total_time);
404  }
405 
406  last_cpu_total_time = current_cpu_total_time;
407  last_cpu_thread_time = current_cpu_thread_time;
408  }
409  else
410  {
411  // store the workload of this task the simple way
412  threadCPUUsage = static_cast<double>(lastCycleDuration) / (lastCycleDuration + idleDuration.toMicroSeconds());
413  }
414 
415  if (threadCPUUsage > 0)
416  {
417  workloadList.push_back(threadCPUUsage);
418 
419  if (workloadList.size() > 100.f * 1.5f)
420  {
421  workloadList.erase(workloadList.begin(), workloadList.begin() + 50);
422  }
423  }
424 
425 
426  }
427 
428  IceUtil::Time __now() const
429  {
430  if (timer->getUseSystemTime())
431  {
432  return IceUtil::Time::now();
433  }
434  else
435  {
436  return TimeUtil::GetTime();
437  }
438  }
439 
440  armarx::TimerPtr timer;
441  T* parent;
442  method_type periodicFn;
443 
444 
446  IceUtil::Time cycleStart;
447  IceUtil::Time cycleDuration;
448  IceUtil::Time logTime;
449  IceUtil::Time delayWarningTolerance;
450  IceUtil::Int64 executionTime;
451  IceUtil::Int64 executionCounter;
452 
453  // IceUtil::Time scheduleTime;
454  // IceUtil::Time scheduledInterval;
455  // IceUtil::Int64 realIntervalSum;
456  // IceUtil::Int64 scheduledIntervalSum;
457  double last_cpu_total_time;
458  double last_cpu_thread_time;
459 
460  bool timeExceeded;
461  //! Avoids Drifting
462  bool assureMeanInterval;
463 
464  mutable std::mutex mutexFunctionExecuting;
465  bool functionExecuting;
466  mutable std::mutex dataMutex;
467 
468  ThreadListPtr customThreadList;
469  ThreadListPtr appThreadList; // prevent deletion of global applist
470 
471  };
472 
473 
474 
475 }
ARMARX_VERBOSE
#define ARMARX_VERBOSE
Definition: Logging.h:180
armarx::PeriodicTask::setName
void setName(std::string name)
Set the name identifying PeriodicTask instances.
Definition: PeriodicTask.h:148
ARMARX_IMPORTANT
#define ARMARX_IMPORTANT
Definition: Logging.h:183
armarx::PeriodicTask::PeriodicTask
PeriodicTask(T *parent, method_type periodicFn, int periodMs, bool assureMeanInterval=false, std::string name="", bool forceSystemTime=true)
Constructs a periodic task within the class parent which calls the peridoicFn in a new thread.
Definition: PeriodicTask.h:82
armarx::PeriodicTask::setDelayWarningTolerance
void setDelayWarningTolerance(int newToleranceInMilliSeconds=5)
Definition: PeriodicTask.h:203
armarx::LogSender::getThreadId
static long getThreadId()
Definition: LogSender.cpp:447
armarx::PeriodicTask::isFunctionExecuting
bool isFunctionExecuting() const
Return the execution state of the thread function.
Definition: PeriodicTask.h:266
armarx::ThreadList::getApplicationThreadList
static ThreadListPtr getApplicationThreadList()
getApplicationThreadList retrieves the ThreadList, that contains all TimerTasks and PeriodicTasks in ...
Definition: ThreadList.cpp:189
armarx::PeriodicTask::changeInterval
void changeInterval(unsigned int intervalInMs)
changeInterval changes the interval at which the task is executed.
Definition: PeriodicTask.h:215
IceInternal::Handle< ThreadList >
armarx::PeriodicTask::setThreadList
void setThreadList(ThreadListPtr threadList)
This method sets threadList as PeriodicTask::customThreadList and appends this to the list.
Definition: PeriodicTask.h:158
deactivateSpam
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition: Logging.cpp:72
armarx::status
status
Definition: FiniteStateMachine.h:259
armarx::interval
Interval< T > interval(T lo, T hi)
Definition: OccupancyGrid.h:26
Timer.h
armarx::PeriodicTask::getInterval
IceUtil::Int64 getInterval() const
Definition: PeriodicTask.h:193
armarx::TaskStatus::isRunning
bool isRunning(Status status)
Returns whether the given task status describes a state where a path is planned.
Definition: PlanningUtil.cpp:67
ThreadList.h
armarx::Application::getInstance
static ApplicationPtr getInstance()
Retrieve shared pointer to the application object.
Definition: Application.cpp:289
armarx::armem::Time
armarx::core::time::DateTime Time
Definition: forward_declarations.h:13
armarx::TimeUtil::GetTime
static IceUtil::Time GetTime(TimeMode timeMode=TimeMode::VirtualTime)
Get the current time.
Definition: TimeUtil.cpp:42
armarx::PeriodicTask::~PeriodicTask
~PeriodicTask() override
Destructor stops the thread and waits for completion.
Definition: PeriodicTask.h:130
armarx::Timer
Timer implementation with TimeServer support.
Definition: Timer.h:58
TimeUtil.h
IceUtil::Handle
Definition: forward_declarations.h:29
armarx::PeriodicTask::isRunning
bool isRunning() const
Retrieve running state of the thread.
Definition: PeriodicTask.h:256
ARMARXCORE_IMPORT_EXPORT
#define ARMARXCORE_IMPORT_EXPORT
Definition: ImportExport.h:38
armarx::PeriodicTask::stop
void stop()
Stops the thread.
Definition: PeriodicTask.h:235
T
float T
Definition: UnscentedKalmanFilterTest.cpp:35
armarx::handleExceptions
void handleExceptions()
Definition: Exception.cpp:141
armarx
This file offers overloads of toIce() and fromIce() functions for STL container types.
Definition: ArmarXTimeserver.cpp:28
armarx::ThreadListPtr
IceInternal::Handle< ThreadList > ThreadListPtr
Definition: RunningTask.h:39
Exception.h
Application.h
armarx::PeriodicTask::start
void start()
Starts the thread.
Definition: PeriodicTask.h:170
armarx::PeriodicTask::getStatistics
const PeriodicTaskIceBase & getStatistics() const
Definition: PeriodicTask.h:275