1 /*******************************************************************************
2 
3         Copyright:
4             Copyright (c) 2007 Kris Bell.
5             Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
6             All rights reserved.
7 
8         License:
9             Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
10             See LICENSE_TANGO.txt for details.
11 
12         Version: Feb 2007: Initial release
13 
14         Authors: Kris
15 
16 *******************************************************************************/
17 
18 module ocean.time.StopWatch;
19 
20 // CLOCK_MONOTONIC and clock_gettime will be added to core.sys.posix.time in
21 // tangort v1.7.0, see tangort issue #6.
22 import core.sys.posix.time; // clockid_t, timespec
23 
24 version (UnitTest)
25 {
26     import ocean.core.Test;
27 }
28 
29 extern (C) private
30 {
31     enum: clockid_t
32     {
33         CLOCK_MONOTONIC = 1
34     }
35 
36     int clock_gettime(clockid_t clk_id, timespec* t);
37 }
38 
39 /*******************************************************************************
40 
41         Timer for measuring small intervals, such as the duration of a
42         subroutine or other reasonably small period.
43         ---
44         StopWatch elapsed;
45 
46         elapsed.start;
47 
48         // do something
49         // ...
50 
51         double i = elapsed.stop;
52         ulong us = elapsed.microsec;
53         ---
54 
55         The measured interval is either an integer value in units of
56         microseconds -- returned by `microsec` -- or a floating-point value in
57         units of seconds with fractions -- returned by `stop`.
58         The integer value has always a precision of 1µs and is recommended if
59         you want to compare it with reference values such as checking if it's
60         below or above 1ms (e.g. `elapsed.microsec <= 1000`).
61         Although floating-point representation seems often more convenient, bear
62         in mind that
63           - The precision is relative to the value (i.e. the greater the values
64             the lower the absolute precision).
65           - Comparing fractions can yield unexpected results due to rounding and
66             because decimal fractions have no exact binary floating-point
67             representation. To avoid surprises like this using the integer
68             represenation of time spans is in general recommended.
69 
70         There is some minor overhead in using StopWatch, so take that into
71         account
72 
73 *******************************************************************************/
74 
75 public struct StopWatch
76 {
77          // TODO: From tangort v1.7.0 import clock_gettime and CLOCK_MONOTONIC.
78         import core.sys.posix.time: timespec;
79 
80         import ocean.core.ExceptionDefinitions : PlatformException;
81 
82 
83         private ulong  started;
84         private enum double multiplier = 1.0 / 1_000_000.0;
85 
86         /***********************************************************************
87 
88                 Start the timer
89 
90         ***********************************************************************/
91 
92         void start ()
93         {
94                 started = timer;
95         }
96 
97         /***********************************************************************
98 
99                 Return elapsed duration (in seconds) since start()
100 
101         ***********************************************************************/
102 
103         double sec ()
104         {
105                 return multiplier * (&this).microsec;
106         }
107 
108         /***********************************************************************
109 
110                 Return elapsed time since the last start() as microseconds
111 
112         ***********************************************************************/
113 
114         ulong microsec ()
115         {
116                  return (timer >= started)? (timer - started) : 0;
117         }
118 
119         /***********************************************************************
120 
121                 Return the current time as an Interval
122 
123         ***********************************************************************/
124 
125         private static ulong timer ()
126         {
127                 timespec t;
128                 if (clock_gettime(CLOCK_MONOTONIC, &t))
129                     throw new PlatformException ("Timer :: CLOCK_MONOTONIC is not available");
130 
131                 return t.tv_sec * 1_000_000UL + t.tv_nsec / 1_000UL;
132         }
133 }
134 
135 unittest
136 {
137     StopWatch t;
138     t.start;
139 
140     auto wait_time = timespec(0, 10_000);  // 10_000 ns = 10 microsec
141 
142     // validate that stopwatch time counts up
143     nanosleep(&wait_time, null);
144     auto stopwatch_sec = t.sec;
145     test!(">")(stopwatch_sec, 0);
146 
147     // validate that checking the elapsed time doesn't stop the timer
148     nanosleep(&wait_time, null);
149     auto stopwatch_sec_again = t.sec;
150     test!(">")(stopwatch_sec_again, stopwatch_sec);
151 }