1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2    Copyright (c) 2012-2021 The plumed team
3    (see the PEOPLE file at the root of the distribution for a list of names)
4 
5    See http://www.plumed.org for more information.
6 
7    This file is part of plumed, version 2.
8 
9    plumed is free software: you can redistribute it and/or modify
10    it under the terms of the GNU Lesser General Public License as published by
11    the Free Software Foundation, either version 3 of the License, or
12    (at your option) any later version.
13 
14    plumed is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU Lesser General Public License for more details.
18 
19    You should have received a copy of the GNU Lesser General Public License
20    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
21 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
22 #ifndef __PLUMED_tools_Stopwatch_h
23 #define __PLUMED_tools_Stopwatch_h
24 
25 #include "Exception.h"
26 #include <string>
27 #include <unordered_map>
28 #include <iosfwd>
29 #include <chrono>
30 
31 namespace PLMD {
32 
33 /**
34 \ingroup TOOLBOX
35 Class implementing stopwatch to time execution.
36 
37 Each instance of this class is a container which
38 can keep track of several named stopwatches at
39 the same time. Access to the stopwatches
40 is obtained using start(), stop(), pause() methods,
41 giving as a parameter the name of the specific stopwatch.
42 Also an empty string can be used (un-named stopwatch).
43 Finally, all the times can be logged using << operator
44 
45 \verbatim
46 #include "Stopwatch.h"
47 
48 int main(){
49   Stopwatch sw;
50   sw.start();
51 
52   sw.start("initialization");
53 // do initialization ...
54   sw.stop("initialization");
55 
56   for(int i=0;i<100;i++){
57     sw.start("loop");
58 // do calculation
59     sw.stop("loop");
60   }
61 
62   sw.stop();
63   return 0;
64 }
65 
66 \endverbatim
67 
68 Using pause a stopwatch can be put on hold until
69 the next start:
70 
71 \verbatim
72 #include "Stopwatch.h"
73 
74 int main(){
75   Stopwatch sw;
76   sw.start();
77 
78   sw.start("initialization");
79 // do initialization ...
80   sw.stop("initialization");
81 
82   for(int i=0;i<100;i++){
83     sw.start("loop");
84 // do calculation
85     sw.pause("loop");
86 // here goes something that we do not want to include
87     sw.start("loop");
88 // do calculation
89     sw.stop("loop");
90   }
91 
92   sw.stop();
93   return 0;
94 }
95 
96 \endverbatim
97 
98 Notice that as of PLUMED 2.5 it is possible to use a slightly modified
99 interface that allow for exception safety. In practice,
100 one can replace a pair of calls to Stopwatch::start() and Stopwatch::stop()
101 with a single call to Stopwatch::startStop(). This call will return an object
102 that, when goes out of scope, will stop the timer.
103 
104 \notice The exception safety interace is highly recommended since it allows
105 to make sure that stopwatches are started and stopped consistently.
106 
107 For instance the following
108 code
109 \verbatim
110   {
111     sw.start("A");
112   // any code
113     sw.stop("A");
114   }
115 \endverbatim
116 can be replaced with
117 \verbatim
118   {
119     auto sww=sw.startStop("A");
120   // any code
121 
122   // stopwatch is stopped when sww goes out of scope
123   }
124 \endverbatim
125 Similarly, Stopwatch::startPause() can be used to replace a pair of
126 Stopwatch::start() and Stopwatch::pause().
127 
128 The older syntax (explicitly calling `Stopwatch::start()` and `Stopwatch::pause()`) is still
129 allowed for backward compatibility.
130 
131 Notice that the returned object is of type `Stopwatch::Handler`.
132 You might be willing to explicitly declare a `Stopwatch::Handler` (instead of using `auto`)
133 when you want to conditionally start the stopwatch. For instance:
134 \verbatim
135   {
136     Stopwatch::Handler handler;
137     if(you_want_to_time_this) handler=sw.startStop();
138     ... do something ...
139   }
140   // in case it was started, the stopwatch will stop here, at the end of the block
141   // in case it was not started, nothing will happen
142 \endverbatim
143 
144 A `Stopwatch::Handler` can not be copied but it can be moved (it behaves like a unique_ptr).
145 Moving it explicitly allows one to transfer it to another `Stopwatch::Handler` with a different scope.
146 For instance, in case you want to conditionally stop the stopwatch you might use something like this:
147 \verbatim
148   {
149     Stopwatch::Handler handler;
150     if(you_want_to_time_this) handler=sw.startStop();
151     ... do something ...
152     if(you_want_to_stop_here) auto h2=std::move(handler);
153     // the previous instruction moves handler to h2 that is then destroyed, stopping the watch
154     // notice that if the stop was not started it will not stop.
155     ... do something else ...
156   }
157   // in case it is running, the stopwatch will stop here, at the end of the block
158 \endverbatim
159 
160 Finally, notice that in order to write the timers on an output file when the
161 Stopwatch is destroyed, one can store a reference to a PLMD::Log by passing it
162 to the Stopwatch constructor.
163 This will make sure timers are written also in case of a premature end.
164 */
165 
166 class Log;
167 
168 /// Return an empty string.
169 /// Inline static so that it can store a static variable (for quicker access)
170 /// without adding a unique global symbol to a library including this header file.
StopwatchEmptyString()171 inline static const std::string & StopwatchEmptyString() noexcept {
172   const static std::string s;
173   return s;
174 }
175 
176 class Stopwatch {
177 
178 public:
179 /// Forward declaration
180   class Watch;
181 /// Auxiliary class for handling exception-safe start/pause and start/stop.
182   class Handler {
183     Watch* watch=nullptr;
184     /// stop (true) or pause (false).
185     /// might be changed to an enum if clearer.
186     bool stop=false;
187     /// Private constructor.
188     /// This is kept private to avoid misuse. Handler objects should
189     /// only be created using startPause() or startStop().
190     /// stop is required to know if the destructor should stop or pause the watch.
191     Handler(Watch* watch,bool stop);
192     /// Allows usage of private constructor
193     friend class Stopwatch;
194   public:
195     /// Default constructor
196     Handler() = default;
197     /// Default copy constructor is deleted (not copyable)
198     Handler(const Handler & handler) = delete;
199     /// Default copy assignment is deleted (not copyable)
200     Handler & operator=(const Handler & handler) = delete;
201     /// Move constructor.
202     Handler(Handler && handler) noexcept;
203     /// Move assignment.
204     Handler & operator=(Handler && handler) noexcept;
205     /// Destructor either stops or pauses the watch
206     ~Handler();
207   };
208 
209 /// Class to store a single stopwatch.
210 /// Class Stopwatch contains a collection of them
211   class Watch {
212 /// Instant in time when Watch was started last time
213     std::chrono::time_point<std::chrono::high_resolution_clock> lastStart;
214 /// Total accumulated time, in nanoseconds
215     long long int total = 0;
216 /// Accumulated time for this lap, in nanoseconds
217     long long int lap = 0;
218 /// Slowest lap so far, in nanoseconds
219     long long int max = 0;
220 /// Fastest lap so far, in nanoseconds
221     long long int min = 0;
222 /// Total number of cycles
223     unsigned cycles = 0;
224 /// count how many times Watch was started (+1) or stopped/paused (-1).
225     unsigned running = 0;
226     enum class State {started, stopped, paused};
227 /// keep track of state
228     State state = State::stopped;
229 /// Allows access to internal data
230     friend class Stopwatch;
231   public:
232 /// start the watch
233     Watch & start();
234 /// stop the watch
235     Watch & stop();
236 /// pause the watch
237     Watch & pause();
238 /// returns a start-stop handler
239     Handler startStop();
240 /// returns a start-pause handler
241     Handler startPause();
242   };
243 
244 private:
245 
246 /// Pointer to a log file.
247 /// If set, the stopwatch is logged in its destructor.
248   Log*mylog=nullptr;
249 
250 /// List of watches.
251 /// Each watch is labeled with a string.
252   std::unordered_map<std::string,Watch> watches;
253 
254 /// Log over stream os.
255   std::ostream& log(std::ostream& os)const;
256 
257 public:
258 // Constructor.
259   explicit Stopwatch() = default;
260 // Constructor.
261 // When destructing, stopwatch is logged.
262 // Make sure that log survives stopwatch. Typically, it should be declared earlier, in order
263 // to be destroyed later.
Stopwatch(Log & log)264   explicit Stopwatch(Log&log): mylog(&log) {}
265 // Destructor.
266   ~Stopwatch();
267 /// Start timer named "name"
268   Stopwatch& start(const std::string&name=StopwatchEmptyString());
269 /// Stop timer named "name"
270   Stopwatch& stop(const std::string&name=StopwatchEmptyString());
271 /// Pause timer named "name"
272   Stopwatch& pause(const std::string&name=StopwatchEmptyString());
273 /// Dump all timers on an ostream
274   friend std::ostream& operator<<(std::ostream&,const Stopwatch&);
275 /// Start with exception safety, then stop.
276 /// Starts the Stopwatch and returns an object that, when goes out of scope,
277 /// stops the watch. This allows Stopwatch to be started and stopped in
278 /// an exception safe manner.
279   Handler startStop(const std::string&name=StopwatchEmptyString());
280 /// Start with exception safety, then pause.
281 /// Starts the Stopwatch and returns an object that, when goes out of scope,
282 /// pauses the watch. This allows Stopwatch to be started and paused in
283 /// an exception safe manner.
284   Handler startPause(const std::string&name=StopwatchEmptyString());
285 };
286 
287 inline
Handler(Watch * watch,bool stop)288 Stopwatch::Handler::Handler(Watch* watch,bool stop) :
289   watch(watch),
290   stop(stop)
291 {
292   watch->start();
293 }
294 
295 inline
~Handler()296 Stopwatch::Handler::~Handler() {
297   if(watch) {
298     if(stop) watch->stop();
299     else watch->pause();
300   }
301 }
302 
303 inline
start(const std::string & name)304 Stopwatch& Stopwatch::start(const std::string & name) {
305   watches[name].start();
306   return *this;
307 }
308 
309 inline
stop(const std::string & name)310 Stopwatch& Stopwatch::stop(const std::string & name) {
311   watches[name].stop();
312   return *this;
313 }
314 
315 inline
pause(const std::string & name)316 Stopwatch& Stopwatch::pause(const std::string & name) {
317   watches[name].pause();
318   return *this;
319 }
320 
321 inline
startStop(const std::string & name)322 Stopwatch::Handler Stopwatch::startStop(const std::string&name) {
323   return watches[name].startStop();
324 }
325 
326 inline
startPause(const std::string & name)327 Stopwatch::Handler Stopwatch::startPause(const std::string&name) {
328   return watches[name].startPause();
329 }
330 
331 inline
Handler(Handler && handler)332 Stopwatch::Handler::Handler(Handler && handler) noexcept :
333   watch(handler.watch),
334   stop(handler.stop)
335 {
336   handler.watch=nullptr;
337 }
338 
339 inline
340 Stopwatch::Handler & Stopwatch::Handler::operator=(Handler && handler) noexcept {
341   if(this!=&handler) {
342     if(watch) {
343       if(stop) {
344         try {
345           watch->stop();
catch(...)346         } catch(...) {
347 // this is to avoid problems with cppcheck, given than this method is declared as
348 // noexcept and stop might throw in case of an internal bug
349           std::terminate();
350         }
351       }
352       else watch->pause();
353     }
354     watch=handler.watch;
355     stop=handler.stop;
356     handler.watch=nullptr;
357   }
358   return *this;
359 }
360 
361 inline
start()362 Stopwatch::Watch & Stopwatch::Watch::start() {
363   state=State::started;
364   running++;
365   lastStart=std::chrono::high_resolution_clock::now();
366   return *this;
367 }
368 
369 inline
stop()370 Stopwatch::Watch & Stopwatch::Watch::stop() {
371   pause();
372   state=State::stopped;
373   cycles++;
374   total+=lap;
375   if(lap>max)max=lap;
376   if(min>lap || cycles==1)min=lap;
377   lap=0;
378   return *this;
379 }
380 
381 inline
pause()382 Stopwatch::Watch & Stopwatch::Watch::pause() {
383   state=State::paused;
384 // In case of an internal bug (non matching start stop within the startStop or startPause interface)
385 // this assertion could fail in a destructor.
386 // If this happens, the program should crash immediately
387   plumed_assert(running>0) << "Non matching start/pause or start/stop commands in a Stopwatch";
388   running--;
389 // notice: with exception safety the following might be converted to a plain error.
390 // I leave it like this for now:
391   if(running!=0) return *this;
392   auto t=std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now()-lastStart);
393   lap+=t.count();
394   return *this;
395 }
396 
397 inline
startStop()398 Stopwatch::Handler Stopwatch::Watch::startStop() {
399   return Handler( this,true );
400 }
401 
402 inline
startPause()403 Stopwatch::Handler Stopwatch::Watch::startPause() {
404   return Handler( this,false );
405 }
406 
407 
408 }
409 
410 
411 #endif
412