From 7685f0f2ca4be170f39f35afd2305ad7a8ac2f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Fri, 8 Mar 2024 09:26:05 +0100 Subject: [PATCH 1/4] time: Add 'interval' kwarg to the '@duration' decorator --- CHANGELOG.md | 5 +++++ README.md | 6 ++++++ autopsy/time.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baddb03..9b3c023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +### Added +- `time`: + - Keyword-only argument to `@duration` to report summary only every `interval`. + - A warning is generated when the argument is not used. + ## 0.10.1 - 2024-03-07 ### Fixed - `duration`: diff --git a/README.md b/README.md index 17b078c..10fc78f 100644 --- a/README.md +++ b/README.md @@ -509,3 +509,9 @@ TM.end() TM.summary() return output ``` + +However, in contrast to TimeMeasurer, a keyword-only argument `interval` +can be passed to the decorator to report the summary periodically, and not +on every function call. + +Since `>0.10.1` not passing `interval` generates a warning on start-up. diff --git a/autopsy/time.py b/autopsy/time.py index aab9c06..dc2a566 100644 --- a/autopsy/time.py +++ b/autopsy/time.py @@ -50,6 +50,12 @@ def function(): TM.end() TM.summary() return output + +However, in contrast to TimeMeasurer, a keyword-only argument 'interval' +can be passed to the decorator to report the summary periodically, and not +on every function call. + +Since >0.10.1 not passing 'interval' generates a warning on start-up. """ ###################### # Imports & Globals @@ -58,6 +64,9 @@ def function(): # Py2: Allow import of module with the same name from __future__ import absolute_import +# ReportTimer +from threading import _Timer + # Timing import time @@ -87,6 +96,23 @@ def let_pass(f): return block if DISABLED else let_pass +###################### +# ReportTimer class +###################### + +class ReportTimer(_Timer): + """A thread for reporting the measurer. + + Source: + https://stackoverflow.com/questions/12435211/threading-timer-repeat-function-every-n-seconds + """ + + def run(self): + """Run a thread function every 'self.interval'.""" + while not self.finished.wait(self.interval): + self.function(*self.args, **self.kwargs) + + ###################### # Measurer class ###################### @@ -96,7 +122,7 @@ class Measurer(object): UNITS = ["", "s", "ms", "us", "ns"] - def __init__(self, name = "", unit = ""): + def __init__(self, name = "", unit = "", **kwargs): """Initialize the Measurer class. Arguments @@ -271,6 +297,16 @@ def duration(*args, **kwargs): TM = TimeMeasurer(*args, **kwargs) + if "interval" in kwargs: + report = ReportTimer(kwargs.get("interval"), TM.summary) + report.daemon = True + report.start() + else: + print ( + "Warning: @duration is used without kwarg 'interval', " + "therefore it will report summary on every call." + ) + def wrapper(function, *args, **kwags): def duration_measurer(*args, **kwargs): TM.start() @@ -281,7 +317,18 @@ def duration_measurer(*args, **kwargs): return output return duration_measurer + def wrapper_with_timer(function, *args, **kwags): + def duration_measurer(*args, **kwargs): + TM.start() + output = function(*args, **kwargs) + TM.end() + + return output + return duration_measurer + def let_pass(function, *args, **kwargs): return lambda *x, **y: function(*x, **y) - return let_pass if DISABLED else wrapper + return let_pass if DISABLED else ( + wrapper if "interval" not in kwargs else wrapper_with_timer + ) From a1c6f3c2937c59c2369a90e4b29c28ac11bcf27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Fri, 8 Mar 2024 10:10:47 +0100 Subject: [PATCH 2/4] time: Report minimum value as well --- CHANGELOG.md | 4 ++++ autopsy/time.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b3c023..484cc75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Keyword-only argument to `@duration` to report summary only every `interval`. - A warning is generated when the argument is not used. +### Changed +- `time`: + - Timer summary now also contains minimum value. + ## 0.10.1 - 2024-03-07 ### Fixed - `duration`: diff --git a/autopsy/time.py b/autopsy/time.py index dc2a566..bcbaca8 100644 --- a/autopsy/time.py +++ b/autopsy/time.py @@ -145,6 +145,7 @@ def __init__(self, name = "", unit = "", **kwargs): self._start = 0 self._count = 0 self._sum = 0 + self._min = [] # min([], X) returns the number everytime self._max = 0 self._last = 0 @@ -217,6 +218,7 @@ def updateStatistics(self): """Update internal statistics of the Measurer.""" self._count += 1 self._sum += self._last + self._min = min(self._min, self._last) self._max = max(self._max, self._last) @@ -226,8 +228,9 @@ def summary(self): if self._count == 0: print("%s: EMPTY" % self._name) else: - print("%s: cur=%.4f%s avg=%.4f%s max=%.4f%s" % ( + print("%s: cur=%.4f%s min=%.4f%s avg=%.4f%s max=%.4f%s" % ( self._name, self._last, self._unit, + self._min, self._unit, self._sum / self._count, self._unit, self._max, self._unit )) From 2df09dd11ef76262a15f46c955a492c8c647e1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Mon, 11 Mar 2024 13:17:46 +0100 Subject: [PATCH 3/4] time: Add argument 'filename' to log measured data --- CHANGELOG.md | 2 ++ autopsy/time.py | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 484cc75..eb3e004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `time`: - Keyword-only argument to `@duration` to report summary only every `interval`. - A warning is generated when the argument is not used. + - `interval` can be set to `None` in order to disable automatic statistics. + - Argument `filename` that stores every measurement into the file. ### Changed - `time`: diff --git a/autopsy/time.py b/autopsy/time.py index bcbaca8..b1b0bcb 100644 --- a/autopsy/time.py +++ b/autopsy/time.py @@ -122,7 +122,7 @@ class Measurer(object): UNITS = ["", "s", "ms", "us", "ns"] - def __init__(self, name = "", unit = "", **kwargs): + def __init__(self, name = "", unit = "", filename = "", **kwargs): """Initialize the Measurer class. Arguments @@ -131,11 +131,16 @@ def __init__(self, name = "", unit = "", **kwargs): name of the Measurer unit: str = "" units used by the measurer + filename: str = "" + name of the file to log measured data Raises ------ ValueError when unit is not in UNITS + + IOError + raised by 'open()' """ if unit not in self.UNITS: raise ValueError("unknown unit '%s'" % unit) @@ -148,6 +153,7 @@ def __init__(self, name = "", unit = "", **kwargs): self._min = [] # min([], X) returns the number everytime self._max = 0 self._last = 0 + self._file = open(filename, "a") if filename != "" else None def unitExp(self, unit): @@ -221,6 +227,9 @@ def updateStatistics(self): self._min = min(self._min, self._last) self._max = max(self._max, self._last) + if self._file is not None: + self._file.write("%f\n" % self._last) + @conddisable() def summary(self): @@ -301,9 +310,12 @@ def duration(*args, **kwargs): TM = TimeMeasurer(*args, **kwargs) if "interval" in kwargs: - report = ReportTimer(kwargs.get("interval"), TM.summary) - report.daemon = True - report.start() + if kwargs.get("interval") is not None: + report = ReportTimer(kwargs.get("interval"), TM.summary) + report.daemon = True + report.start() + elif "filename" in kwargs: + kwargs["interval"] = None else: print ( "Warning: @duration is used without kwarg 'interval', " From bfad66d23f64aa4422a496257d2451ac09940a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Mon, 11 Mar 2024 16:30:40 +0100 Subject: [PATCH 4/4] Release version 0.10.2 --- CHANGELOG.md | 1 + package.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb3e004..d672cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +## 0.10.2 - 2024-03-11 ### Added - `time`: - Keyword-only argument to `@duration` to report summary only every `interval`. diff --git a/package.xml b/package.xml index 13807ac..73af3b9 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> autopsy - 0.10.1 + 0.10.2 A set of Python utils for F1Tenth project. Jaroslav Klapálek