Skip to main content

Python code timing utilities

Project description

py_code_timing

This package provides a comprehensive range of Python code timing utilities.

Features and usage

CodeTiming, start and elapsed

The class CodeTiming provides the basic timing functionality, operating rather like a stopwatch. The basic pattern of use goes like this:

    tobj = CodeTiming()
    ...
    tobj.start()
    ...
    cur_time = tobj.elapsed()
    ...
    tobj.start()
    ...
    cur_time = tobj.elapsed()
    ...

The call to start begins the timing process. The code between start and elapsed is timed, with the measured duration returned from the elapsed call. Timing stops with the call to elapsed, and only resumes when start` is called again, etc.

sample

Additionally, it can be useful to capture intermediate timings in between start and elapsed. This can be done using a call to sample, as in this example:

    tobj = CodeTiming()
    ...
    tobj.start()
    ...
    cur_time1 = tobj.sample()
    ...
    cur_time2 = tobj.sample()
    ...
    cur_time = tobj.elapsed()

As for elapsed, the sample call returns the duration since the start. However, unlike elapsed, using sampledoesn't stop timing.

Named timing objects for gathering statistics

More complex scenarios, such as loops and complex control flows may easily require timing statistics to be gathered. This can be done by using named timing objects, as in the following example.

    tobj = CodeTiming('MyCode')
    ...
    for i in range(...):
        ...
	    tobj.start()
        ...
	    cur_time = tobj.elapsed()
	    ...

Statistics are gathered against the name "MyCode"so that each time the code between startandelapsedis executed, the measured duration will then contribute to the statistics for the named object "MyCode". Thesample` method could also be used as shown earlier, but doesn't contribute to the timing statistics.

Getting statistics info (1) -- get_stats

To access the current statistics for a timing object, use the function get_statsto obtain a dictionary containing the statistics info:

    stats_map = tobj.get_stats()

This returns a dictionary with the keys: mean, std-dev, max, min, events and total, as in this example:

     {'mean': 2504, 'std_dev': 2, 'max': 2507, 'min': 2501, 'events': 5, 'total': 12519}

where the entries are:

  • mean -- Average of timings
  • std_dev -- Standard deviation of timings
  • max -- Maximum timing
  • min -- Minimum timing
  • events -- Number of timing events measured
  • total -- Total time measured

The timings above are given in milliseconds (default). See the Quick Reference below for how to change the timing unit (seconds or milliseconds).

Getting all statistics info (2) -- get_stats_for_name

The classmethod get_stats_for_name takes a given name (e.g. "MyCode") and returns a dictionary containing the stats info for that named timing object. This method fails if there is no timing object having the given name.

    my_code_stats = CodeTiming.get_stats_for_name("MyCode")

Getting all statistics info (3) -- get_all_stats

Finally, the classmethod get_all_stats provides a dictionary of named timing objects:

    timing_map = CodeTiming.get_all_stats()

The resulting dictionary only contains entries for named timing objects having timing data -- timing objects with no timing data are omitted.

Timing function calls -- Using the @timing decorator

The @timing decorator provides a way to time individual functions. Given a function definition, the @timing decorator adds a named timing object (with the same name as the function) to gather timing statistics of calls for that function.

    @timing
    def my_function(..., ...):
        ...
        return result  
    ...
    ... my_function( ... , ...) ...
    ...        

All (outermost) calls of timed functions like my_function contribute to their individual statistics. Because functions could be recursively defined (i.e. reentrant), timing of any inner calls is naturally included within the outermost call and therefore should not be measured separately. The @timing decorator approach dynamically tracks inner calls and avoids the inaccuracy due to the potential for "double-counting" these calls.

Since timed functions use named timed objects, the statistical timing info for those functions can be retrieved using classmethods get_stats_for_name or get_all_stats mentioned earlier.

Using timing objects in with-statements

Timing objects can be used in Python's with-statements. In particular, such timing objects can be named, enabling their statistics to be retrieved, if necessary, by using the get_all_stats classmethod, as outlined below.

    with CodeTiming("MyCode") as tobj:
        ...
        tobj.start()
        ...
        cur_time = tobj.elapsed()
        ...
          
    ...
    timing_map = CodeTiming.get_all_stats()
    my_code_stats = timing_map["MyCode"]
    ...

Using autostart

Timing objects can also be used in autostart mode. This is where timing begins when the object is created and then automatically restarts following calls to elapsed. The start method can still be called and simply restarts the timing. The sample method works as before and doesn't affect the statistics gathered.

The pattern of use with autostart goes as follows:

    tobj = CodeTiming(autostart=True)
    ...
    cur_time = tobj.elapsed()  # first timing
    ...
    cur_time = tobj.elapsed()  # second timing
    ...

Note that, in autostart mode, the start method is not necessary to begin timing -- that happens automatically when the timing object enters autostart mode.

Convenience display utility functions

A number of display functions are provided to give a convenient textual rendering of the stats info as specified above. The display functions are:

  • display_stats -- This function accesses the stats for a particular timing object and renders the information textually.

For example, if the stats info were:

    {'mean': 747, 'std_dev': 144, 'max': 974, 'min': 503, 'events': 30, 'total': 22409}

then the display would be:

     mean: 747ms, std_dev: (+ or -) 144ms, max: 974ms, min: 503ms, events: 30, total: 22s 409ms
  • display_stats_for_name -- This classmethod function accesses a named timing object specified by given name and displays the stats info textually. Fails if there is no named timing object having the given name.
  • display_all_stats -- This classmethod function extracts the stats info for named timing objects and displays the information in a table-like form. As for get_all_stats only those named timimg objects having timing data are displayed -- timed objects having no timing data are omitted.

These functions broadly correspond to the various functions used for getting statistics described earlier.

Display keyword options

Each of the display functions outlined above use the same set of keyword arguments to specify display options.

  • hms -- If True, format timing info in hrs-mins-secs-millisecs format (default: True). Otherwise, format timings numerically in chosen units.

  • shape -- Layout either horizontally or vertically (default: horizontal).

  • skip -- Number of blank lines following (default: 0).


Quick Reference

This section gives a quick summary of the utilities provided.

CodeTiming

Class providing utilities for timing Python code.

class CodeTiming

__init__ (self, name="", timeunit=MILLISECS\_TIMEUNIT, timetype=TOTAL_TIMETYPE, autostart=False) 
  • name -- Optional name of timing object (default : ""). Unnamed (or anonymous) timing objects are independent of each other and can have more than one instance.

  • timeunit -- Unit of time measurement - either seconds or milliseconds (default: milliseconds).

  • timetype -- Type of time measurement (default : total).

    Types available are: + total -- Total system time + process -- Process time + thread -- Thread time

  • autostart -- If True, timing object is in autostart mode (default: False).

    When in autostart mode, timing starts immediately at creation of the timing object and continues indefinitely.

Class Constants

These class constants give standard options (as strings) for various keyword options. Strings are used instead of Enum's to simplify APIs.

	# Timetypes
	TOTAL_TIMETYPE =   "total timetype"
	PROCESS_TIMETYPE = "process timetype"
	THREAD_TIMETYPE =  "thread timetype"
	
	# Timeunits
	SECONDS_TIMEUNIT =   "seconds _timeunit"
	MILLISECS_TIMEUNIT = "millisecs _timeunit"
	
	# Display shape options
	DISPLAY_HORIZONTALLY = "horizontal"
	DISPLAY_VERTICALLY =   "vertical"

Class Parameters

These class parameters affect the behaviour of timing objects in general.

	# If not None, this sets the timeunit globally
	GLOBAL_TIMEUNIT = None
	
	# If not None, this sets the timetype globally
	GLOBAL_TIMETYPE = None
	
    # Timeunit for function timing decorator.
    FUNCTION_TIMEUNIT = None 
    
    # Timetype for function timing decorator.
    FUNCTION_TIMETYPE = None 
	
	# Global enable
	GLOBAL_ENABLE = True
	    
	# Specifies if named timing objects are uniquely defined.
	UNIQUE_OBJECT = True

Timing Methods

These methods provide the timing capabilities for the timing object.

start

Initiates timing for timing object. In general, every start needs to be paired with a In autostart mode, this restarts timing.

    def start(self)

Note: to avoid misleading results, nested use of timing objects is detected

elapsed

Measures duration since the current start, and stops timing -- except in autostart mode, when instead timing continues. By using elapsed, the durations it measures contribute to calculating timing statistics (for both named and anonymous timing objects). Returns the duration measured (or None if timing not started or disabled).

    def elapsed(self)

sample

Returns duration since the current start -- but doesn't contribute to the statistics for the timing object. This could return None if timing not started or disabled.

    def sample(self)

Stats Access Methods

Statistics are gathered for all timing objects. The methods listed here

get_stats

Produces a dictionary object representing the statistical information gathered by the timing object.

	def get_stats(self)

The fields are mean, std_dev, max, min, events, total and defined as:

  • mean -- Average of timings
  • std_dev -- Standard deviation of timings
  • max -- Maximum timing
  • min -- Minimum timing
  • events -- Number of timing events measured
  • total -- Total time measured

get_stats_for_name

This classmethod accesses the named timing object specified by given name and, if found, produces a dictionary object as described above. Fails if no named timing object exists with given name.

	@classmethod
    def get_stats_for_name(_, name)

get_all_stats

This classmethod produces a dictionary, keyed by name, of corresponding stats dictionary objects for named timing objects. Entries are only given for each named timing object having timing data -- others not having any timing data are omitted.

	@classmethod
    def get_all_stats(_)

Stats Display Methods

As a convenience, a number of display methods are provided for rendering the statistics information for timing objects. These broadly correspond to the various stats access methods given earlier.

display_stats

Renders as a string the dictionary object representing the statistical information gathered by the timing object.

	def display_stats(self, hms=True, shape=DISPLAY_HORIZONTALLY, skip=0)

The keyword arguments are common for all three of these display functions:

  • hms -- If True, format timing info in a "h m s ms" structured format (default: True). Otherwise, format timings numerically in chosen units.

  • shape -- Layout either horizontally or vertically (default: horizontal).

  • skip -- Number of blank lines following (default: 0).

_Note: In vertical layout, the minimum skip value is set to 1 to ensure separation of entries.

Note: HMS format This displays timing information in a structured, easily readable format. For example:

        4s 23ms            (4023 msecs)
        2h 14m 31s 257ms   (8071257 msecs)

display_stats_for_name

This classmethod accesses the named timing object specified by given name and, if found, renders as a string the associated dictionary object as described above. Fails if no named timing object exists with given name.

	@classmethod
    def display_stats_for_name(_, name, hms=True, shape=DISPLAY_HORIZONTALLY, skip=0)

display_all_stats

This classmethod renders as a string the table, keyed by name, of corresponding stats dictionary objects for named timing objects. Entries are only given for each named timing object having timing data -- others not having any timing data are omitted.

	@classmethod
    def display_all_stats(_, hms=True, shape=DISPLAY_HORIZONTALLY, skip=0)

State Setter Methods

These methods are used to configure timing objects.

reset_all

This resets the state of all named timimg objects. Any timing objects in autostart mode are restarted.

    @classmethod
    def reset_all(_): 

reset

This resets the state of a specific timing object. Objects in autostart mode are restarted.

	def reset(self)

set_autostart

Sets autostart mode and restarts timing (default : False). Fails if timing has already begun. This method returns self to enable chaining of setter methods.

	def set_autostart(self)

set_enabled

This is used to enable or disable the timing object (default: enabled). Disabling the object means that statistics are not gathered and that the methods enabled and sample return None. This method returns self to enable chaining of setter methods.

	def set_enabled(self, flag)

set_name

This associates a name with a currently anonymous timing object (i.e. with empty name). This is coordinated so that looking up the object by name (e.g. get_stats_by_name) and accessing it directly (e.g. get_stats) produces the same behaviour. Fails if the object is already named. This method returns self to enable chaining of setter methods.

	def set_name(self, new_name)

Note: When the class parameter UNIQUE_OBJECTis set to True (default: True), the name of each named object is uniquely defined - and failure results when an attempt is made to define two objects having the same name.

However, when UNIQUE_OBJECTis set to False, the names of named objects need not be uniquely defined - that is, there may be several definitions of a timed object all having the same name. However, in all such cases, the resulting object are all alike. This means the statistics gathered by each instance is pooled.

Note Chaining of methods allows for directly applying chaining methods one after the other:

	tobj.set_autostart().set_name("MyTimer")

LICENSE

MIT License

Copyright (c) 2023 Purple Elephant

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_code_timing-0.0.1.tar.gz (18.9 kB view hashes)

Uploaded Source

Built Distribution

py_code_timing-0.0.1-py3-none-any.whl (13.4 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page