Teuchos Package Browser (Single Doxygen Collection)  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
stacked_timer.cpp
Go to the documentation of this file.
1 // @HEADER
2 // @HEADER
3 
10 #include "Teuchos_DefaultComm.hpp"
11 #include <sstream>
12 #include <thread> // std::this_thread::sleep_for;
13 #include <tuple>
14 #include <regex>
15 #include <iterator>
16 #include <limits>
17 
18 #if defined(HAVE_TEUCHOS_KOKKOS_PROFILING) && defined(HAVE_TEUCHOSCORE_KOKKOSCORE)
19 #include "Kokkos_Core.hpp"
20 #endif
21 
22 TEUCHOS_UNIT_TEST(PerformanceMonitorBase, UnsortedMergeUnion) {
23 
25 
26  Teuchos::Array<std::string> a,b, tmp_a, tmp_b;
27 
28  a.push_back("foo");
29  a.push_back("bar");
30  a.push_back("car");
31 
32  b.push_back("car");
33  b.push_back("bar");
34  b.push_back("cat");
35 
36 
37  tmp_a=a;
38  tmp_b=b;
40  TEST_EQUALITY(tmp_b.size(),4);
41  TEST_EQUALITY(tmp_b[0], "car");
42  TEST_EQUALITY(tmp_b[1], "bar");
43  TEST_EQUALITY(tmp_b[2], "cat");
44  TEST_EQUALITY(tmp_b[3], "foo");
45 }
46 
47 TEUCHOS_UNIT_TEST(PerformanceMonitorBase, UnsortedMergeIntersection) {
48 
50 
51  Teuchos::Array<std::string> a,b, tmp_a, tmp_b;
52 
53  a.push_back("foo");
54  a.push_back("bar");
55  a.push_back("car");
56 
57  b.push_back("car");
58  b.push_back("bar");
59  b.push_back("cat");
60 
61 
62  tmp_a=a;
63  tmp_b=b;
65  TEST_EQUALITY(tmp_b.size(),2);
66  TEST_EQUALITY(tmp_b[0], "car");
67  TEST_EQUALITY(tmp_b[1], "bar");
68 }
69 
70 TEUCHOS_UNIT_TEST(StackedTimer, Basic)
71 {
73  const int myRank = Teuchos::rank(*comm);
74 
75  Teuchos::StackedTimer timer("My New Timer");
76  timer.start("Total Time");
77  {
78  for (int i=0; i < 10; ++i) {
79 
80  timer.start("Assembly");
81  std::this_thread::sleep_for(std::chrono::milliseconds{100});
82  timer.stop("Assembly");
83 
84  timer.start("Solve");
85  {
86  timer.start("Prec");
87  std::this_thread::sleep_for(std::chrono::milliseconds{50});
88  timer.stop("Prec");
89 
90  // Test different timers on different mpi processes
91  if (myRank == 0 ) {
92  const std::string label = "Rank 0 ONLY";
93  timer.start(label);
94  std::this_thread::sleep_for(std::chrono::milliseconds{50});
95  TEST_ASSERT((timer.findTimer("My New Timer@Total Time@Solve@Rank 0 ONLY")).running);
96  timer.stop(label);
97  TEST_ASSERT(not (timer.findTimer("My New Timer@Total Time@Solve@Rank 0 ONLY")).running);
98  } else {
99  timer.start("Not Rank 0");
100  std::this_thread::sleep_for(std::chrono::milliseconds{50});
101  TEST_ASSERT((timer.findTimer("My New Timer@Total Time@Solve@Not Rank 0")).running);
102  timer.stop("Not Rank 0");
103  TEST_ASSERT(not (timer.findTimer("My New Timer@Total Time@Solve@Not Rank 0")).running);
104  }
105  }
106  timer.stop("Solve");
107 
108  }
109  }
110  timer.stop("Total Time");
111  timer.stopBaseTimer();
112 
113  TEST_EQUALITY((timer.findTimer("My New Timer@Total Time")).count, 1);
114  TEST_EQUALITY((timer.findTimer("My New Timer@Total Time@Assembly")).count, 10);
115  TEST_EQUALITY((timer.findTimer("My New Timer@Total Time@Solve")).count, 10);
116  TEST_EQUALITY((timer.findTimer("My New Timer@Total Time@Solve@Prec")).count, 10);
117 
118  // Test for exception for bad timer name
119  TEST_THROW(timer.findTimer("Testing misspelled timer name!"),std::runtime_error);
120 
121  // Pre-aggregation
122  if (myRank == 0) {
123  TEST_EQUALITY((timer.findTimer("My New Timer@Total Time@Solve@Rank 0 ONLY")).count, 10);
124  }
125  else {
126  TEST_EQUALITY((timer.findTimer("My New Timer@Total Time@Solve@Not Rank 0")).count, 10);
127  }
128 
130  options.output_histogram=true;
131  options.num_histogram=3;
132  options.print_warnings=false;
133 
134  // Get the report
135  std::stringstream sout1;
136  timer.report(sout1, comm, options);
137 
138  // Make sure can call report() multiple times, i.e. aggregation
139  // resets correctly for each call report()
140  std::stringstream sout2;
141  timer.report(sout2, comm, options);
142  TEST_EQUALITY(sout1.str(),sout2.str());
143 
144  // Gold file results (timer name,expected runtime,number of calls)
145  std::vector<std::tuple<std::string,double,unsigned long>> lineChecks;
146  lineChecks.push_back(std::make_tuple("My New Timer:",2.0,1));
147  lineChecks.push_back(std::make_tuple("Total Time:",2.0,1));
148  lineChecks.push_back(std::make_tuple("Assembly:",1.0,10));
149  lineChecks.push_back(std::make_tuple("Solve:",1.0,10));
150  lineChecks.push_back(std::make_tuple("Prec:",0.5,10));
151 
152  // Check the report() output. Read the first few lines and parse the
153  // expected timer label, the runtime and the counts.
154  //
155  // * NOTE: The report only combines values to a single MPI process, so
156  // only check on that process.
157  // * NOTE: regex not supported in gcc until 4.9. Can drop this check
158  // when Trilinos drops support for gcc 4.8.
159 #if !defined(__GNUC__) \
160  || ( defined(__GNUC__) && (__GNUC__ > 4) ) \
161  || ( defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC__MINOR__ > 8) )
162 
163  if (myRank == 0) {
164  const double timerTolerance = 0.25; // +- 0.25 seconds
165  std::istringstream is(sout1.str());
166  for (const auto& check : lineChecks) {
167 
168  std::string line;
169  std::getline(is,line);
170  std::smatch regexSMatch;
171  std::regex timerName(std::get<0>(check));
172  std::regex_search(line,regexSMatch,timerName);
173  TEST_ASSERT(not regexSMatch.empty());
174 
175  // Split string to get time and count
176  std::regex delimiter(":\\s|\\s\\[|\\]\\s");
177  std::sregex_token_iterator tok(line.begin(), line.end(),delimiter,-1);
178 
179  const std::string timeAsString = (++tok)->str();
180  const double time = std::stod(timeAsString);
181  TEST_FLOATING_EQUALITY(time,std::get<1>(check),timerTolerance);
182 
183  const std::string countAsString = (++tok)->str();
184  const unsigned long count = std::stoul(countAsString);
185  TEST_EQUALITY(count,std::get<2>(check));
186  }
187  }
188 #endif
189 
190  // Print to screen
191  out << "\n### Printing default report ###" << std::endl;
193  timer.report(out, comm, defaultOptions);
194 
195  // Test some options
196  out << "\n### Printing aligned_column with timers names on left ###" << std::endl;
197  options.output_fraction = true;
198  options.output_total_updates = true;
199  options.output_minmax = true;
200  options.output_histogram = true;
201  options.num_histogram = 3;
202  options.align_columns = true;
203  timer.report(out, comm, options);
204 
205  // Toggle names before values
207  out << "\n### Printing aligned_column with timers names on right ###" << std::endl;
208  options.print_names_before_values = false;
209  timer.report(out, comm, options);
210 }
211 
212 TEUCHOS_UNIT_TEST(StackedTimer, UnitTestSupport)
213 {
215  const int myRank = Teuchos::rank(*comm);
216 
217  const auto timeMonitorDefaultStackedTimer = Teuchos::TimeMonitor::getStackedTimer();
218  const auto timer = Teuchos::rcp(new Teuchos::StackedTimer("Total Time", false));
219  timer->startBaseTimer();
220  for (int i=0; i < 10; ++i) {
221  timer-> start("Subtask");
222  timer->incrementUpdates();
223  timer->incrementUpdates(2);
224  timer-> stop("Subtask");
225  }
226  timer->stopBaseTimer();
227 
228  // If users want to set timer values for unit testing, force them to
229  // const_cast the timer by returning a const Timer object.
230  auto top_timer = const_cast<Teuchos::BaseTimer*>(timer->findBaseTimer("Total Time"));
231  auto sub_timer = const_cast<Teuchos::BaseTimer*>(timer->findBaseTimer("Total Time@Subtask"));
232  TEST_ASSERT(top_timer != nullptr);
233  TEST_ASSERT(sub_timer != nullptr);
234 
235  // Test for exception for bad timer name
236  TEST_THROW(timer->findBaseTimer("Testing misspelled timer name!"),std::runtime_error);
237 
238  {
239  TEST_EQUALITY(top_timer->numCalls(),1);
240  TEST_EQUALITY(top_timer->numUpdates(),0);
241  TEST_EQUALITY(sub_timer->numCalls(),10);
242  TEST_EQUALITY(sub_timer->numUpdates(),30);
243  }
244 
245  // Test the serial version of report
246  if (myRank == 0)
247  timer->report(out);
248 
249  // Override timers for unit testing
250  top_timer->setAccumulatedTime(5000.0);
251  top_timer->overrideNumCallsForUnitTesting(2);
252  top_timer->overrideNumUpdatesForUnitTesting(3);
253  sub_timer->setAccumulatedTime(4000.0);
254  sub_timer->overrideNumCallsForUnitTesting(4);
255  sub_timer->overrideNumUpdatesForUnitTesting(5);
256  {
257  const double timerTolerance = 100.0 * std::numeric_limits<double>::epsilon();
258  TEST_FLOATING_EQUALITY(5000.0,top_timer->accumulatedTime(),timerTolerance);
259  TEST_EQUALITY(top_timer->numCalls(),2);
260  TEST_EQUALITY(top_timer->numUpdates(),3);
261  TEST_FLOATING_EQUALITY(4000.0,sub_timer->accumulatedTime(),timerTolerance);
262  TEST_EQUALITY(sub_timer->numCalls(),4);
263  TEST_EQUALITY(sub_timer->numUpdates(),5);
264  }
265 
266  if (myRank == 0)
267  timer->report(out);
268 }
269 
270 TEUCHOS_UNIT_TEST(StackedTimer, TimeMonitorInteroperability)
271 {
273 
274  const auto diffTimer = Teuchos::TimeMonitor::getNewTimer("Diffusion Term");
275  const auto rxnTimer = Teuchos::TimeMonitor::getNewTimer("Reaction Term");
276  const auto precTimer = Teuchos::TimeMonitor::getNewTimer("Prec");
277  const auto gmresTimer = Teuchos::TimeMonitor::getNewTimer("GMRES");
278 
279  // Test the set and get stacked timer methods on TimeMonitor
280  const auto timeMonitorDefaultStackedTimer = Teuchos::TimeMonitor::getStackedTimer();
281  const auto timer = Teuchos::rcp(new Teuchos::StackedTimer("TM:Interoperability"));
282  TEST_ASSERT(nonnull(timeMonitorDefaultStackedTimer));
283  TEST_ASSERT(nonnull(timer));
284  TEST_ASSERT(timeMonitorDefaultStackedTimer != timer);
287 
288  timer->start("Total Time");
289  {
290  for (int i=0; i < 10; ++i) {
291 
292  timer->start("Assembly");
293  {
294  {
295  Teuchos::TimeMonitor tm(*diffTimer);
296  std::this_thread::sleep_for(std::chrono::milliseconds{25});
297  }
298  {
299  Teuchos::TimeMonitor tm(*rxnTimer);
300  std::this_thread::sleep_for(std::chrono::milliseconds{75});
301  }
302  // Remainder
303  std::this_thread::sleep_for(std::chrono::milliseconds{100});
304  }
305  timer->stop("Assembly");
306  timer->start("Solve");
307  {
308  {
309  Teuchos::TimeMonitor tm(*precTimer);
310  std::this_thread::sleep_for(std::chrono::milliseconds{50});
311  }
312  {
313  Teuchos::TimeMonitor tm(*gmresTimer);
314  std::this_thread::sleep_for(std::chrono::milliseconds{50});
315  }
316  // Remainder
317  std::this_thread::sleep_for(std::chrono::milliseconds{100});
318  }
319  timer->stop("Solve");
320  std::this_thread::sleep_for(std::chrono::milliseconds{100});
321  }
322  }
323  timer->stop("Total Time");
324  timer->stopBaseTimer();
325 
326  assert(size(*comm)>0);
327 
328  TEST_EQUALITY((timer->findTimer("TM:Interoperability@Total Time")).count, 1);
329  TEST_EQUALITY((timer->findTimer("TM:Interoperability@Total Time@Assembly")).count, 10);
330 
331  // Make sure the TimeMonitor added the timers
332 #ifdef HAVE_TEUCHOS_ADD_TIME_MONITOR_TO_STACKED_TIMER
333  TEST_EQUALITY((timer->findTimer("TM:Interoperability@Total Time@Solve@Prec")).count, 10);
334  TEST_EQUALITY((timer->findTimer("TM:Interoperability@Total Time@Solve@GMRES")).count, 10);
335 #endif
336 
338  out << "\n### Printing default report ###" << std::endl;
339  options.output_histogram=true;
340  options.num_histogram=3;
341  options.output_fraction=true;
342  timer->report(out, comm, options);
343 
344  out << "\n### Printing aligned_column with timers names on left ###" << std::endl;
345  options.align_columns = true;
346  timer->report(out, comm, options);
347 
348  out << "\n### Printing aligned_column with timers names on right ###" << std::endl;
349  // options.print_names_before_values=false requires that
350  // options.align_output=true. The code will automatically fix this
351  // and print a warning if warnings are enabled. Testing this here by
352  // specifying the incorrect logic.
353  options.align_columns = false;
354  options.print_names_before_values = false;
355  timer->report(out, comm, options);
356 
357  //Testing limited number of levels in printing
358  out << "\n### Printing with max_levels=2 ###" << std::endl;
359  options.max_levels=2;
360  options.align_columns = true;
361  options.print_names_before_values = true;
362  timer->report(out, comm, options);
363 }
364 
365 // Overlapping timers are not allowed in a StackedTimer, but are in
366 // TimeMonitor. Since StackedTimer is automatically used in
367 // TimeMonitor by default, we have seen this error - a throw from the
368 // stacked timer. In every instance so far, the intention was not to
369 // actually overlap but a constructor/destructor ordering issue
370 // (usually involving RCPs). To prevent tests from failing,
371 // StackedTimer now automatically shuts itself off if it detects
372 // overlaped timers in a TimeMonitor instance, reports a warning on
373 // how to fix and allows the code to continue runnning. Where this has
374 // occurred in Trilinos is when a TimeMonitor object is stored in an
375 // RCP and then the RCP is reassigned to a new timer. The intention
376 // was to stop one and start another. But the destruction of one and
377 // the creation of the new one occurs in the wrong order. This test
378 // demonstrates the issue.
379 TEUCHOS_UNIT_TEST(StackedTimer, OverlappingTimersException)
380 {
381  Teuchos::StackedTimer timer("My Timer");
382  timer.start("Outer");
383  timer.start("Inner");
384  // Should stop inner before outer
385  TEST_THROW(timer.stop("Outer"),std::runtime_error);
386  timer.stop("Inner");
387  timer.stop("Outer");
388  timer.stopBaseTimer();
389 }
390 
391 
392 #ifdef HAVE_TEUCHOS_ADD_TIME_MONITOR_TO_STACKED_TIMER
393 TEUCHOS_UNIT_TEST(StackedTimer, OverlappingTimersViaRCP)
394 {
395  const auto precTimer = Teuchos::TimeMonitor::getNewTimer("Prec");
396  const auto gmresTimer = Teuchos::TimeMonitor::getNewTimer("GMRES");
397 
399  timer = Teuchos::rcp(new Teuchos::TimeMonitor(*gmresTimer));
400 
402 }
403 #endif
404 
405 // Use our own main to initialize kokkos before calling
406 // runUnitTestsFromMain(). The kokkos space_time_stack profiler seg
407 // faults due to inconsistent push/pop of timers in the teuchos unit
408 // test startup code. By calling initialize here we can use the
409 // space_time_stack profiler with this unit test.
410 int main( int argc, char* argv[] )
411 {
412  // Note that the dtor for GlobalMPISession will call
413  // Kokkos::finalize_all().
414  Teuchos::GlobalMPISession mpiSession(&argc, &argv);
415 #if defined(HAVE_TEUCHOS_KOKKOS_PROFILING) && defined(HAVE_TEUCHOSCORE_KOKKOSCORE)
416  Kokkos::initialize(argc,argv);
417 #endif
418  {
419  Teuchos::FancyOStream out(Teuchos::rcpFromRef(std::cout));
420  out.setOutputToRootOnly(0);
421  }
423 
424  auto return_val = Teuchos::UnitTestRepository::runUnitTestsFromMain(argc, argv);
425 #if defined(HAVE_TEUCHOS_KOKKOS_PROFILING) && defined(HAVE_TEUCHOSCORE_KOKKOSCORE)
426  if (Kokkos::is_initialized())
427  Kokkos::finalize_all();
428 #endif
429  return return_val;
430 }
#define TEST_ASSERT(v1)
Assert the given statement is true.
void stop(const std::string &name, const bool pop_kokkos_profiling_region=true)
#define TEST_EQUALITY(v1, v2)
Assert the equality of v1 and v2.
#define TEST_THROW(code, ExceptType)
Assert that the statement &#39;code&#39; throws the exception &#39;ExceptType&#39; (otherwise the test fails)...
#define TEUCHOS_UNIT_TEST(TEST_GROUP, TEST_NAME)
Macro for defining a (non-templated) unit test.
static Teuchos::RCP< const Comm< OrdinalType > > getComm()
Return the default global communicator.
Initialize, finalize, and query the global MPI session.
bool is_null(const ArrayRCP< T > &p)
Returns true if p.get()==NULL.
static RCP< Time > getNewTimer(const std::string &name)
Return a new timer with the given name (class method).
Unit testing support.
static int runUnitTestsFromMain(int argc, char *argv[])
Run the unit tests from main() passing in (argc, argv).
TEUCHOS_DEPRECATED RCP< T > rcp(T *p, Dealloc_T dealloc, bool owns_mem)
Deprecated.
void start(const std::string name, const bool push_kokkos_profiling_region=true)
std::ostream subclass that performs the magic of indenting data sent to an std::ostream object among ...
the basic timer used elsewhere, uses MPI_Wtime for time
Unit testing support.
static void setStackedTimer(const Teuchos::RCP< Teuchos::StackedTimer > &t)
Sets the StackedTimer that the TimeMonitor will use to insert timings into.
static void setGloballyReduceTestResult(const bool globallyReduceUnitTestResult)
Set if the unit tests should reduce pass/fail across processes.
basic_FancyOStream & setOutputToRootOnly(const int rootRank)
Set the stream to print only on the (MPI) process with the given rank.
int size(const Comm< Ordinal > &comm)
Get the number of processes in the communicator.
void unsortedMergePair(const Array< std::string > &localNames, Array< std::string > &globalNames, const ECounterSetOp setOp)
#define TEST_FLOATING_EQUALITY(v1, v2, tol)
Assert the relative floating-point equality of rel_error(v1,v2) &lt;= tol.
int main(int argc, char *argv[])
void push_back(const value_type &x)
A MPI utilities class, providing methods for initializing, finalizing, and querying the global MPI se...
static const Teuchos::RCP< Teuchos::StackedTimer > & getStackedTimer()
Returns the StackedTimer used by the TimeMonitor.
void report(std::ostream &os)
size_type size() const
Scope protection wrapper for Teuchos::Time, with timer reporting functionality.
Smart reference counting pointer class for automatic garbage collection.
BaseTimer::TimeInfo findTimer(const std::string &name)
This class allows one to push and pop timers on and off a stack.
Provides common capabilities for collecting and reporting performance data across processors...
Struct for controlling output options like histograms.
A scope-safe timer wrapper class, that can compute global timer statistics.
bool nonnull(const ArrayRCP< T > &p)
Returns true if p.get()!=NULL.