1#!/usr/bin/env python
2
3""" systimes() user and system timer implementations for use by
4    pybench.
5
6    This module implements various different strategies for measuring
7    performance timings. It tries to choose the best available method
8    based on the platform and available tools.
9
10    On Windows, it is recommended to have the Mark Hammond win32
11    package installed. Alternatively, the Thomas Heller ctypes
12    packages can also be used.
13
14    On Unix systems, the standard resource module provides the highest
15    resolution timings. Unfortunately, it is not available on all Unix
16    platforms.
17
18    If no supported timing methods based on process time can be found,
19    the module reverts to the highest resolution wall-clock timer
20    instead. The system time part will then always be 0.0.
21
22    The module exports one public API:
23
24    def systimes():
25
26        Return the current timer values for measuring user and system
27        time as tuple of seconds (user_time, system_time).
28
29    Copyright (c) 2006, Marc-Andre Lemburg (mal@egenix.com). See the
30    documentation for further information on copyrights, or contact
31    the author. All Rights Reserved.
32
33"""
34
35from __future__ import print_function
36
37import time, sys
38
39#
40# Note: Please keep this module compatible to Python 1.5.2.
41#
42# TODOs:
43#
44# * Add ctypes wrapper for new clock_gettime() real-time POSIX APIs;
45#   these will then provide nano-second resolution where available.
46#
47# * Add a function that returns the resolution of systimes()
48#   values, ie. systimesres().
49#
50
51### Choose an implementation
52
53SYSTIMES_IMPLEMENTATION = None
54USE_CTYPES_GETPROCESSTIMES = 'ctypes GetProcessTimes() wrapper'
55USE_WIN32PROCESS_GETPROCESSTIMES = 'win32process.GetProcessTimes()'
56USE_RESOURCE_GETRUSAGE = 'resource.getrusage()'
57USE_PROCESS_TIME_CLOCK = 'time.clock() (process time)'
58USE_WALL_TIME_CLOCK = 'time.clock() (wall-clock)'
59USE_WALL_TIME_TIME = 'time.time() (wall-clock)'
60
61if sys.platform[:3] == 'win':
62    # Windows platform
63    try:
64        import win32process
65    except ImportError:
66        try:
67            import ctypes
68        except ImportError:
69            # Use the wall-clock implementation time.clock(), since this
70            # is the highest resolution clock available on Windows
71            SYSTIMES_IMPLEMENTATION = USE_WALL_TIME_CLOCK
72        else:
73            SYSTIMES_IMPLEMENTATION = USE_CTYPES_GETPROCESSTIMES
74    else:
75        SYSTIMES_IMPLEMENTATION = USE_WIN32PROCESS_GETPROCESSTIMES
76else:
77    # All other platforms
78    try:
79        import resource
80    except ImportError:
81        pass
82    else:
83        SYSTIMES_IMPLEMENTATION = USE_RESOURCE_GETRUSAGE
84
85# Fall-back solution
86if SYSTIMES_IMPLEMENTATION is None:
87    # Check whether we can use time.clock() as approximation
88    # for systimes()
89    start = time.clock()
90    time.sleep(0.1)
91    stop = time.clock()
92    if stop - start < 0.001:
93        # Looks like time.clock() is usable (and measures process
94        # time)
95        SYSTIMES_IMPLEMENTATION = USE_PROCESS_TIME_CLOCK
96    else:
97        # Use wall-clock implementation time.time() since this provides
98        # the highest resolution clock on most systems
99        SYSTIMES_IMPLEMENTATION = USE_WALL_TIME_TIME
100
101### Implementations
102
103def getrusage_systimes():
104    return resource.getrusage(resource.RUSAGE_SELF)[:2]
105
106def process_time_clock_systimes():
107    return (time.clock(), 0.0)
108
109def wall_clock_clock_systimes():
110    return (time.clock(), 0.0)
111
112def wall_clock_time_systimes():
113    return (time.time(), 0.0)
114
115# Number of clock ticks per second for the values returned
116# by GetProcessTimes() on Windows.
117#
118# Note: Ticks returned by GetProcessTimes() are 100ns intervals on
119# Windows XP. However, the process times are only updated with every
120# clock tick and the frequency of these is somewhat lower: depending
121# on the OS version between 10ms and 15ms. Even worse, the process
122# time seems to be allocated to process currently running when the
123# clock interrupt arrives, ie. it is possible that the current time
124# slice gets accounted to a different process.
125
126WIN32_PROCESS_TIMES_TICKS_PER_SECOND = 1e7
127
128def win32process_getprocesstimes_systimes():
129    d = win32process.GetProcessTimes(win32process.GetCurrentProcess())
130    return (d['UserTime'] / WIN32_PROCESS_TIMES_TICKS_PER_SECOND,
131            d['KernelTime'] / WIN32_PROCESS_TIMES_TICKS_PER_SECOND)
132
133def ctypes_getprocesstimes_systimes():
134    creationtime = ctypes.c_ulonglong()
135    exittime = ctypes.c_ulonglong()
136    kerneltime = ctypes.c_ulonglong()
137    usertime = ctypes.c_ulonglong()
138    rc = ctypes.windll.kernel32.GetProcessTimes(
139        ctypes.windll.kernel32.GetCurrentProcess(),
140        ctypes.byref(creationtime),
141        ctypes.byref(exittime),
142        ctypes.byref(kerneltime),
143        ctypes.byref(usertime))
144    if not rc:
145        raise TypeError('GetProcessTimes() returned an error')
146    return (usertime.value / WIN32_PROCESS_TIMES_TICKS_PER_SECOND,
147            kerneltime.value / WIN32_PROCESS_TIMES_TICKS_PER_SECOND)
148
149# Select the default for the systimes() function
150
151if SYSTIMES_IMPLEMENTATION is USE_RESOURCE_GETRUSAGE:
152    systimes = getrusage_systimes
153
154elif SYSTIMES_IMPLEMENTATION is USE_PROCESS_TIME_CLOCK:
155    systimes = process_time_clock_systimes
156
157elif SYSTIMES_IMPLEMENTATION is USE_WALL_TIME_CLOCK:
158    systimes = wall_clock_clock_systimes
159
160elif SYSTIMES_IMPLEMENTATION is USE_WALL_TIME_TIME:
161    systimes = wall_clock_time_systimes
162
163elif SYSTIMES_IMPLEMENTATION is USE_WIN32PROCESS_GETPROCESSTIMES:
164    systimes = win32process_getprocesstimes_systimes
165
166elif SYSTIMES_IMPLEMENTATION is USE_CTYPES_GETPROCESSTIMES:
167    systimes = ctypes_getprocesstimes_systimes
168
169else:
170    raise TypeError('no suitable systimes() implementation found')
171
172def processtime():
173
174    """ Return the total time spent on the process.
175
176        This is the sum of user and system time as returned by
177        systimes().
178
179    """
180    user, system = systimes()
181    return user + system
182
183### Testing
184
185def some_workload():
186    x = 0
187    for i in range(10000000):
188        x = x + 1
189
190def test_workload():
191    print('Testing systimes() under load conditions')
192    t0 = systimes()
193    some_workload()
194    t1 = systimes()
195    print('before:', t0)
196    print('after:', t1)
197    print('differences:', (t1[0] - t0[0], t1[1] - t0[1]))
198    print()
199
200def test_idle():
201    print('Testing systimes() under idle conditions')
202    t0 = systimes()
203    time.sleep(1)
204    t1 = systimes()
205    print('before:', t0)
206    print('after:', t1)
207    print('differences:', (t1[0] - t0[0], t1[1] - t0[1]))
208    print()
209
210if __name__ == '__main__':
211    print('Using %s as timer' % SYSTIMES_IMPLEMENTATION)
212    print()
213    test_workload()
214    test_idle()
215