QMCPACK
TimerManager.cpp
Go to the documentation of this file.
1 //////////////////////////////////////////////////////////////////////////////////////
2 // This file is distributed under the University of Illinois/NCSA Open Source License.
3 // See LICENSE file in top directory for details.
4 //
5 // Copyright (c) 2016 Jeongnim Kim and QMCPACK developers.
6 //
7 // File developed by: Ken Esler, kpesler@gmail.com, University of Illinois at Urbana-Champaign
8 // Jeongnim Kim, jeongnim.kim@gmail.com, University of Illinois at Urbana-Champaign
9 // Jeremy McMinnis, jmcminis@gmail.com, University of Illinois at Urbana-Champaign
10 //
11 // File created by: Ken Esler, kpesler@gmail.com, University of Illinois at Urbana-Champaign
12 //////////////////////////////////////////////////////////////////////////////////////
13 
14 
15 /** @file NewTimer.cpp
16  * @brief Implements timer_manager
17  */
18 #include "TimerManager.h"
19 #include <limits>
20 #include <cstdio>
21 #include <algorithm>
22 #include <array>
23 #include <stdexcept>
24 #include <libxml/xmlwriter.h>
25 #include "Configuration.h"
26 #include "Concurrency/OpenMP.h"
27 #include "Message/Communicate.h"
28 #include "Message/CommOperators.h"
29 
30 #include <array>
31 #include <string_view>
32 
33 namespace qmcplusplus
34 {
35 namespace
36 {
37 const std::array<std::string, num_timer_levels> timer_level_names = {"none", "coarse", "medium", "fine"};
38 
39 const char TIMER_STACK_SEPARATOR = '/';
40 
41 std::unique_ptr<TimerManager<NewTimer>> global_timer_manager;
42 } // namespace
43 
45 {
46  if (!global_timer_manager)
47  global_timer_manager = std::make_unique<TimerManager<NewTimer>>();
48  return *global_timer_manager;
49 }
50 
51 NewTimer& createGlobalTimer(const std::string& myname, timer_levels mylevel)
52 {
53  return *getGlobalTimerManager().createTimer(myname, mylevel);
54 }
55 
56 template<class TIMER>
58 {
59  if (t.get_name().find(TIMER_STACK_SEPARATOR) != std::string::npos)
60  app_log() << "Warning: Timer name (" << t.get_name() << ") should not contain the character "
61  << TIMER_STACK_SEPARATOR << std::endl;
62 
63  if (timer_name_to_id.find(t.get_name()) == timer_name_to_id.end())
64  {
65  t.set_id(max_timer_id);
66  timer_id_name[t.get_id()] = t.get_name();
67  timer_name_to_id[t.get_name()] = t.get_id();
68  if (max_timer_id >= std::numeric_limits<timer_id_t>::max())
69  {
70  max_timers_exceeded = true;
71  app_log() << "Number of timers exceeds limit (" << static_cast<int>(std::numeric_limits<timer_id_t>::max())
72  << "). Adjust timer_id_t in NewTimer.h and recompile." << std::endl;
73  }
74  else
75  max_timer_id++;
76  }
77  else
78  t.set_id(timer_name_to_id[t.get_name()]);
79 
80  t.set_active_by_timer_threshold(timer_threshold);
81 }
82 
83 template<class TIMER>
84 TIMER* TimerManager<TIMER>::createTimer(const std::string& myname, timer_levels mytimer)
85 {
86  TIMER* t = nullptr;
87  {
88  const std::lock_guard<std::mutex> lock(timer_list_lock_);
89  timer_storage_.push_back(std::make_unique<TIMER>(myname, this, mytimer));
90  t = timer_storage_.back().get();
91  initializeTimer(*t);
92  }
93  return t;
94 }
95 
96 template<class TIMER>
98 {
99  // current_timer() can be nullptr when the stack was empty.
100  if (t == current_timer())
101  {
102  std::cerr << "Timer " << t->get_name()
103  << " instance is already at the top of the stack. "
104  "start() is being called again. This often happens when stop() is not paired properly with start(). "
105  << "ScopedTimer uses RAII and manages timer start/stop more safely." << std::endl;
106  throw std::runtime_error("TimerManager push_timer error!");
107  }
108  else
109  CurrentTimerStack.push_back(t);
110 }
111 
112 template<class TIMER>
114 {
115  TIMER* stack_top = current_timer();
116  if (stack_top == nullptr)
117  {
118  std::cerr << "Timer stack pop failed on an empty stack! Requested \"" << t->get_name() << "\"." << std::endl;
119  throw std::runtime_error("TimerManager pop_timer error!");
120  }
121  else if (t != stack_top)
122  {
123  std::cerr << "Timer stack pop not matching push! "
124  << "Expecting \"" << t->get_name() << "\" but \"" << stack_top->get_name() << "\" is on the top."
125  << std::endl;
126  throw std::runtime_error("TimerManager pop_timer error!");
127  }
128  else
129  CurrentTimerStack.pop_back();
130 }
131 
132 template<class TIMER>
134 {
135  for (int i = 0; i < timer_storage_.size(); i++)
136  timer_storage_[i]->reset();
137 }
138 
139 template<class TIMER>
141 {
142  timer_threshold = threshold;
143  for (int i = 0; i < timer_storage_.size(); i++)
144  timer_storage_[i]->set_active_by_timer_threshold(timer_threshold);
145 }
146 
147 template<class TIMER>
148 void TimerManager<TIMER>::set_timer_threshold(const std::string& threshold)
149 {
150  const auto it = std::find(timer_level_names.begin(), timer_level_names.end(), threshold);
151  if (it != timer_level_names.end())
152  set_timer_threshold(static_cast<timer_levels>(std::distance(timer_level_names.begin(), it)));
153  else
154  {
155  std::cerr << "Unknown timer level: " << threshold << " , current level: " << timer_level_names[timer_threshold]
156  << std::endl;
157  }
158 }
159 
160 template<class TIMER>
162 {
163  return timer_level_names[timer_threshold];
164 }
165 
166 
167 template<class TIMER>
169 {
170  for (int i = 0; i < timer_storage_.size(); ++i)
171  {
172  TIMER& timer = *timer_storage_[i];
173  nameList_t::iterator it(p.nameList.find(timer.get_name()));
174  if (it == p.nameList.end())
175  {
176  int ind = p.nameList.size();
177  p.nameList[timer.get_name()] = ind;
178  p.timeList.push_back(timer.get_total());
179  p.callList.push_back(timer.get_num_calls());
180  }
181  else
182  {
183  int ind = (*it).second;
184  p.timeList[ind] += timer.get_total();
185  p.callList[ind] += timer.get_num_calls();
186  }
187  }
188 
189  if (comm)
190  {
191  comm->allreduce(p.timeList);
192  comm->allreduce(p.callList);
193  }
194 }
195 
197 {
198  double time;
199  double calls;
200 
202  {
203  time += pd.time;
204  calls += pd.calls;
205  return *this;
206  }
207 };
208 
209 int get_level(const std::string& stack_name)
210 {
211  int level = 0;
212  for (int i = 0; i < stack_name.length(); i++)
213  if (stack_name[i] == TIMER_STACK_SEPARATOR)
214  level++;
215 
216  return level;
217 }
218 
219 std::string get_leaf_name(const std::string& stack_name)
220 {
221  int pos = stack_name.find_last_of(TIMER_STACK_SEPARATOR);
222  if (pos == std::string::npos)
223  return stack_name;
224 
225  return stack_name.substr(pos + 1, stack_name.length() - pos);
226 }
227 
228 template<class TIMER>
229 void TimerManager<TIMER>::get_stack_name_from_id(const StackKey& key, std::string& stack_name)
230 {
231  for (int i = 0; i < StackKey::max_level; i++)
232  {
233  std::string& timer_name = timer_id_name[key.get_id(i)];
234  if (key.get_id(i) == 0)
235  break;
236  if (i > 0)
237  stack_name += TIMER_STACK_SEPARATOR;
238 
239  stack_name += timer_name;
240  }
241 }
242 
243 template<class TIMER>
245 {
246 #ifdef USE_STACK_TIMERS
247  // Put stacks from all timers into one data structure
248  // By naming the timer stacks as 'timer1/timer2', etc, the ordering done by the
249  // map's keys will also place the stacks in depth-first order.
250  // The order in which sibling timers are encountered in the code is not
251  // preserved. They will be ordered alphabetically instead.
252  std::map<std::string, ProfileData> all_stacks;
253  for (int i = 0; i < timer_storage_.size(); ++i)
254  {
255  TIMER& timer = *timer_storage_[i];
256  for (const auto& [key, time] : timer.get_per_stack_total_time())
257  {
258  ProfileData pd;
259  std::string stack_name;
260  get_stack_name_from_id(key, stack_name);
261  pd.time = timer.get_total(key);
262  pd.calls = timer.get_num_calls(key);
263 
264  all_stacks[stack_name] += pd;
265  }
266  }
267 
268  // Fill in the output data structure (but don't compute exclusive time yet)
269  int idx = 0;
270  for (const auto& [stack_name, data] : all_stacks)
271  {
272  p.nameList[stack_name] = idx;
273  p.names.push_back(stack_name);
274  p.timeList.push_back(data.time);
275  p.timeExclList.push_back(data.time);
276  p.callList.push_back(data.calls);
277  idx++;
278  }
279 
280  // Subtract times of immediate children to get exclusive time
281  for (idx = 0; idx < p.timeList.size(); idx++)
282  {
283  int start_level = get_level(p.names[idx]);
284  for (int i = idx + 1; i < p.timeList.size(); i++)
285  {
286  int level = get_level(p.names[i]);
287  if (level == start_level + 1)
288  p.timeExclList[idx] -= p.timeExclList[i];
289 
290  if (level == start_level)
291  break;
292  }
293  }
294 #endif
295 }
296 
297 template<class TIMER>
299 {
300  if (timer_threshold <= timer_level_none)
301  return;
302 #ifdef ENABLE_TIMERS
303  app_log() << std::endl;
304  app_log() << "Use --enable-timers=<value> command line option to increase or decrease level of timing information"
305  << std::endl;
306 #ifdef USE_STACK_TIMERS
307  if (comm == nullptr || comm->rank() == 0)
308  app_log() << "Stack timer profile" << std::endl;
309  print_stack(comm);
310 #else
311  if (comm == nullptr || comm->rank() == 0)
312  app_log() << "\nFlat profile" << std::endl;
313  print_flat(comm);
314 #endif
315 #endif
316 }
317 
318 template<class TIMER>
320 {
321 #ifdef ENABLE_TIMERS
322  FlatProfileData p;
323 
324  collate_flat_profile(comm, p);
325 
326  if (comm == nullptr || comm->rank() == 0)
327  {
328 #pragma omp master
329  {
330  std::array<char, 256> tmpout;
331  std::map<std::string, int>::iterator it(p.nameList.begin()), it_end(p.nameList.end());
332  while (it != it_end)
333  {
334  int i = (*it).second;
335  int length =
336  std::snprintf(tmpout.data(), tmpout.size(), "%-40s %9.4f %13ld %16.9f %12.6f TIMER\n",
337  (*it).first.c_str(), p.timeList[i], p.callList[i],
338  p.timeList[i] / (static_cast<double>(p.callList[i]) + std::numeric_limits<double>::epsilon()),
339  p.timeList[i] / static_cast<double>(omp_get_max_threads() * comm->size()));
340  if (length < 0)
341  throw std::runtime_error("Error generating timer string");
342  app_log() << std::string_view(tmpout.data(), length);
343  ++it;
344  }
345  }
346  }
347 #endif
348 }
349 
350 
351 void pad_string(const std::string& in, std::string& out, int field_len)
352 {
353  int len = in.size();
354  int pad_len = std::max(field_len - len, 0);
355  std::string pad_str(pad_len, ' ');
356  out = in + pad_str;
357 }
358 
359 template<class TIMER>
361 {
362 #ifdef ENABLE_TIMERS
363  StackProfileData p;
364 
365  collate_stack_profile(comm, p);
366 
367  if (comm == nullptr || comm->rank() == 0)
368  {
370  {
371  app_warning() << "Maximum stack level (" << StackKey::max_level << ") exceeded. Results may be incorrect."
372  << std::endl;
373  app_warning() << "Adjust StackKey in NewTimer.h and recompile." << std::endl;
374  }
375 
376  int indent_len = 2;
377  int max_name_len = 0;
378  for (int i = 0; i < p.names.size(); i++)
379  {
380  std::string stack_name = p.names[i];
381  int level = get_level(stack_name);
382  std::string name = get_leaf_name(stack_name);
383  int name_len = name.size() + indent_len * level;
384  max_name_len = std::max(name_len, max_name_len);
385  }
386 
387  std::array<char, 256> tmpout;
388  std::string timer_name;
389  pad_string("Timer", timer_name, max_name_len);
390 
391  int length = std::snprintf(tmpout.data(), tmpout.size(), "%s %-9s %-9s %-10s %-13s\n", timer_name.c_str(),
392  "Inclusive_time", "Exclusive_time", "Calls", "Time_per_call");
393  if (length < 0)
394  throw std::runtime_error("Error generating timer string");
395  app_log() << std::string_view(tmpout.data(), length);
396 
397  for (int i = 0; i < p.names.size(); i++)
398  {
399  std::string stack_name = p.names[i];
400  int level = get_level(stack_name);
401  std::string name = get_leaf_name(stack_name);
402  std::string indent_str(indent_len * level, ' ');
403  std::string indented_str = indent_str + name;
404  std::string padded_name_str;
405  pad_string(indented_str, padded_name_str, max_name_len);
406  length =
407  std::snprintf(tmpout.data(), tmpout.size(), "%s %9.4f %9.4f %13ld %16.9f\n", padded_name_str.c_str(),
408  p.timeList[i], p.timeExclList[i], p.callList[i],
409  p.timeList[i] / (static_cast<double>(p.callList[i]) + std::numeric_limits<double>::epsilon()));
410  if (length < 0)
411  throw std::runtime_error("Error generating timer string");
412  app_log() << std::string_view(tmpout.data(), length);
413  }
414  }
415 #endif
416 }
417 
418 template<class TIMER>
420 {
421 #if defined(ENABLE_TIMERS) && defined(USE_STACK_TIMERS)
422  StackProfileData p;
423 
424  collate_stack_profile(comm, p);
425 
426  if (comm == nullptr || comm->rank() == 0)
427  {
428  xmlNodePtr timing_root = doc.addChild(root, "timing");
429  doc.addChild(timing_root, "max_stack_level_exceeded", timer_max_level_exceeded ? "yes" : "no");
430  doc.addChild(timing_root, "max_timers_exceeded", max_timers_exceeded ? "yes" : "no");
431  std::vector<xmlNodePtr> node_stack;
432  node_stack.push_back(timing_root);
433  xmlNodePtr current_root = timing_root;
434 
435  for (int i = 0; i < p.names.size(); i++)
436  {
437  std::string stack_name = p.names[i];
438  int level = get_level(stack_name);
439  std::string name = get_leaf_name(stack_name);
440 
441  std::string indent_str(2 * level, ' ');
442 
443  xmlNodePtr timer = doc.addChild(current_root, "timer");
444  doc.addChild(timer, "name", name);
445  doc.addChild(timer, "time_incl", p.timeList[i]);
446  doc.addChild(timer, "time_excl", p.timeExclList[i]);
447  doc.addChild(timer, "calls", p.callList[i]);
448 
449  int next_level = level;
450  if (i + 1 < p.names.size())
451  next_level = get_level(p.names[i + 1]);
452 
453  if (next_level > level)
454  {
455  xmlNodePtr next_node = doc.addChild(timer, "includes");
456  node_stack.push_back(next_node);
457  current_root = next_node;
458  }
459  if (next_level < level)
460  for (int j = 0; j < level - next_level; j++)
461  {
462  node_stack.pop_back();
463  current_root = node_stack.back();
464  }
465  }
466  }
467 
468 #endif
469 }
470 
471 template class TimerManager<NewTimer>;
472 template class TimerManager<FakeTimer>;
473 
474 } // namespace qmcplusplus
class that handles xmlDoc
Definition: Libxml2Doc.h:76
timer_id_t get_id(int idx) const
Definition: NewTimer.h:103
std::ostream & app_warning()
Definition: OutputManager.h:69
helper functions for EinsplineSetBuilder
Definition: Configuration.h:43
timer_manager class.
int rank() const
return the rank
Definition: Communicate.h:116
std::string get_leaf_name(const std::string &stack_name)
void output_timing(Communicate *comm, Libxml2Document &doc, xmlNodePtr root)
void collate_stack_profile(Communicate *comm, StackProfileData &p)
std::ostream & app_log()
Definition: OutputManager.h:65
void print_flat(Communicate *comm)
void initializeTimer(TIMER &t)
Timer accumulates time and call counts.
Definition: NewTimer.h:135
Manager creates timers and handle reports.
Definition: NewTimer.h:40
int size() const
return the number of tasks
Definition: Communicate.h:118
ProfileData & operator+=(const ProfileData &pd)
void print(Communicate *comm)
int get_level(const std::string &stack_name)
Wrapping information on parallelism.
Definition: Communicate.h:68
void allreduce(T &)
omp_int_t omp_get_max_threads()
Definition: OpenMP.h:26
NewTimer & createGlobalTimer(const std::string &myname, timer_levels mylevel)
void collate_flat_profile(Communicate *comm, FlatProfileData &p)
bool timer_max_level_exceeded
Definition: NewTimer.cpp:26
TIMER * createTimer(const std::string &myname, timer_levels mytimer=timer_level_fine)
Create a new timer object registred in this manager. This call is thread-safe.
void pad_string(const std::string &in, std::string &out, int field_len)
void addChild(xmlNodePtr newnode)
Definition: Libxml2Doc.cpp:111
void get_stack_name_from_id(const StackKey &key, std::string &name)
void set_timer_threshold(const timer_levels threshold)
TimerManager< NewTimer > & getGlobalTimerManager()
static const int max_level
Definition: NewTimer.h:76
void print_stack(Communicate *comm)
std::string get_timer_threshold_string() const