diff --git a/CHANGES.md b/CHANGES.md index ebc7bf2..b84ca48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # **Diagnosticism.Python** Changes +## 0.17.0 - 10th July 2026 + +* added `DOOMGram::to_mmm()` and `DOOMGram::to_nmmm()` — compact min/mean/max duration summaries using `nanoseconds_to_string()`; +* updated compatibility to Python 3.18; + + ## 0.16.0 - 27th June 2026 * added `nanoseconds_to_string()`; diff --git a/README.md b/README.md index bc263ec..8819cbe 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ d.log(sev.INFO, "hello") ### Python version compatibility **Diagnosticism.Python** is intended to run on **Python 2.7** and **Python -3.8+**. GitHub Actions exercises **Python 2.7** and **Python 3.8–3.13**. +3.8+**. GitHub Actions exercises **Python 2.7** and **Python 3.8–3.14**. | Requirement | Applies to | | ----------- | ---------- | diff --git a/diagnosticism/__init__.py b/diagnosticism/__init__.py index d65a7db..6592d6d 100644 --- a/diagnosticism/__init__.py +++ b/diagnosticism/__init__.py @@ -9,7 +9,7 @@ __license__ = 'BSD-3-Clause' __maintainer__ = 'Matt Wilson' __status__ = 'Beta' -__version__ = '0.16.0' +__version__ = '0.17.0' import sys diff --git a/diagnosticism/doomgram.py b/diagnosticism/doomgram.py index a3987fc..bf18ace 100644 --- a/diagnosticism/doomgram.py +++ b/diagnosticism/doomgram.py @@ -4,11 +4,11 @@ # Purpose: Definition of the `DOOMGram` class. # # Created: 19th July 2025 -# Updated: 27th August 2025 +# Updated: 10th July 2026 # # Author: Matthew Wilson # -# Copyright (c) 2025, Matthew Wilson and Synesis Information Systems +# Copyright (c) 2025-2026, Matthew Wilson and Synesis Information Systems # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -39,6 +39,11 @@ from .internal import ( _perf_counter_ns, ) +from .time_format import ( + _to_mmm, + _to_nmmm, +) +from .time_format.nanoseconds import nanoseconds_to_string import math @@ -306,6 +311,65 @@ def push_event_time_s(self, time_in_s): self.push_event_time_ns(time_in_s * 1000000000) # 1,000,000,000 + def to_mmm(self, *kwargs): + + # _OVERFLOW = "OVERFLOW" + + count = self._event_count + + if count == 0: + + return '' + + # if self._has_overflowed { + # return _OVERFLOW; + # } + + min_ns = self._min_event_time_ns + max_ns = self._max_event_time_ns + + if count == 1 or min_ns == max_ns: + + return nanoseconds_to_string(min_ns) + else: + mean_ns = int(self._total_event_time_ns / count) + + return _to_mmm( + nanoseconds_to_string(min_ns), + nanoseconds_to_string(mean_ns), + nanoseconds_to_string(max_ns), + ) + + def to_nmmm(self, *kwargs): + + # _OVERFLOW = "OVERFLOW" + + count = self._event_count + + if count == 0: + + return '0:' + + # if self._has_overflowed { + # return str(count) + ':' + _OVERFLOW; + # } + + min_ns = self._min_event_time_ns + max_ns = self._max_event_time_ns + + if count == 1 or min_ns == max_ns: + + return str(count) + ':' + nanoseconds_to_string(min_ns) + else: + mean_ns = int(self._total_event_time_ns / count) + + return _to_nmmm( + count, + nanoseconds_to_string(min_ns), + nanoseconds_to_string(mean_ns), + nanoseconds_to_string(max_ns), + ) + def to_strip(self, **kwargs): """ Converts to string form according to given options diff --git a/diagnosticism/time_format/__init__.py b/diagnosticism/time_format/__init__.py index cf63657..c36d888 100644 --- a/diagnosticism/time_format/__init__.py +++ b/diagnosticism/time_format/__init__.py @@ -41,9 +41,17 @@ import sys if sys.version_info[0] >= 3: - from ._fmt_py3 import _fmt + from ._fmt_py3 import ( + _fmt, + _to_mmm, + _to_nmmm, + ) else: - from ._fmt_py2 import _fmt + from ._fmt_py2 import ( + _fmt, + _to_mmm, + _to_nmmm, + ) _SCALES = ( diff --git a/diagnosticism/time_format/_fmt_py2.py b/diagnosticism/time_format/_fmt_py2.py index 7b1d8dc..cba3f75 100644 --- a/diagnosticism/time_format/_fmt_py2.py +++ b/diagnosticism/time_format/_fmt_py2.py @@ -6,9 +6,9 @@ # Purpose: Python 2.7 implementation of `_fmt()`. # # Created: 24th August 2025 -# Updated: 27th June 2026 +# Updated: 10th July 2026 # -# Copyright (c) 2026-2027, Matthew Wilson and Synesis Information Systems +# Copyright (c) 2025-2026, Matthew Wilson and Synesis Information Systems # All rights reserved. # # ######################################################################## # @@ -39,4 +39,29 @@ def _fmt( return "%s%s.%s%s" % (sign, whole, frac, suffix) +def _to_mmm( + min, + mean, + max, +): + """ + Formats min+mean+max. + """ + + return "%s-%s-%s" % (min, mean, max) + + +def _to_nmmm( + count, + min, + mean, + max, +): + """ + Formats count+min+mean+max. + """ + + return "%d:%s-%s-%s" % (count, min, mean, max) + + # ############################## end of file ############################# # diff --git a/diagnosticism/time_format/_fmt_py3.py b/diagnosticism/time_format/_fmt_py3.py index 158343a..b675e1d 100644 --- a/diagnosticism/time_format/_fmt_py3.py +++ b/diagnosticism/time_format/_fmt_py3.py @@ -6,9 +6,9 @@ # Purpose: Python 3 implementation of `_fmt()`. # # Created: 24th August 2025 -# Updated: 27th June 2026 +# Updated: 10th July 2026 # -# Copyright (c) 2026-2027, Matthew Wilson and Synesis Information Systems +# Copyright (c) 2025-2026, Matthew Wilson and Synesis Information Systems # All rights reserved. # # ######################################################################## # @@ -39,4 +39,29 @@ def _fmt( return f"{sign}{whole}.{frac}{suffix}" +def _to_mmm( + min, + mean, + max, +): + """ + Formats min+mean+max. + """ + + return f"{min}-{mean}-{max}" + + +def _to_nmmm( + count, + min, + mean, + max, +): + """ + Formats count+min+mean+max. + """ + + return f"{count}:{min}-{mean}-{max}" + + # ############################## end of file ############################# # diff --git a/diagnosticism/time_format/nanoseconds.py b/diagnosticism/time_format/nanoseconds.py index 48a0a3d..eca4d1c 100644 --- a/diagnosticism/time_format/nanoseconds.py +++ b/diagnosticism/time_format/nanoseconds.py @@ -8,7 +8,7 @@ # Created: 24th August 2025 # Updated: 27th June 2026 # -# Copyright (c) 2026-2027, Matthew Wilson and Synesis Information Systems +# Copyright (c) 2025-2026, Matthew Wilson and Synesis Information Systems # All rights reserved. # # ######################################################################## # diff --git a/setup.py b/setup.py index 4ecd970..89ef9d5 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ name='diagnosticism', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*', - version='0.16.0', + version='0.17.0', author='Matt Wilson', author_email='matthew@synesis.com.au', @@ -24,6 +24,7 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ], description='Basic diagnostic facilities, for Python', keywords='Diagnostic Diagnostics Logging Trace Tracing Stopwatch', diff --git a/tests/test_doomgram.py b/tests/test_doomgram.py index 27b585f..8f927fe 100755 --- a/tests/test_doomgram.py +++ b/tests/test_doomgram.py @@ -1,4 +1,5 @@ #! /usr/bin/env python3 +# -*- coding: utf-8 -*- from diagnosticism import DOOMGram @@ -7,7 +8,7 @@ class DOOMGram_tester(unittest.TestCase): - def test_empty(self): + def test_EMPTY(self): dg = DOOMGram() @@ -30,11 +31,13 @@ def test_empty(self): self.assertEqual(0, dg.num_events_in_10s()) self.assertEqual(0, dg.num_events_ge_100s()) + self.assertEqual("", dg.to_mmm()) + self.assertEqual("0:", dg.to_nmmm()) self.assertEqual("____________", dg.to_strip()) self.assertEqual("____________", str(dg)) self.assertEqual("DOOMGram(event_count=0, total_event_time_ns=0, min_event_time_ns=None, max_event_time_ns=None, num_events_in_1ns=0, num_events_in_10ns=0, num_events_in_100ns=0, num_events_in_1us=0, num_events_in_10us=0, num_events_in_100us=0, num_events_in_1ms=0, num_events_in_10ms=0, num_events_in_100ms=0, num_events_in_1s=0, num_events_in_10s=0, num_events_ge_100s=0)", repr(dg)) - def test_single_timing_event(self): + def test_SINGLE_TIMING_EVENT(self): dg = DOOMGram() @@ -59,10 +62,12 @@ def test_single_timing_event(self): self.assertEqual(0, dg.num_events_in_10s()) self.assertEqual(0, dg.num_events_ge_100s()) + self.assertEqual("13ms", dg.to_mmm()) + self.assertEqual("1:13ms", dg.to_nmmm()) self.assertEqual("_______a____", dg.to_strip()) self.assertEqual("_______a____", str(dg)) - def test_zero_time_events(self): + def test_ZERO_TIME_EVENTS(self): dg = DOOMGram() @@ -90,10 +95,12 @@ def test_zero_time_events(self): self.assertEqual(0, dg.num_events_in_10s()) self.assertEqual(0, dg.num_events_ge_100s()) + self.assertEqual("0s", dg.to_mmm()) + self.assertEqual("4:0s", dg.to_nmmm()) self.assertEqual("____________", dg.to_strip()) self.assertEqual("____________", str(dg)) - def test_uniform_spread_timings_1(self): + def test_UNIFORM_SPREAD_TIMINGS_1(self): dg = DOOMGram() @@ -130,10 +137,12 @@ def test_uniform_spread_timings_1(self): self.assertEqual(1, dg.num_events_in_10s()) self.assertEqual(1, dg.num_events_ge_100s()) + self.assertEqual("9ns-65.76s-700s", dg.to_mmm()) + self.assertEqual("12:9ns-65.76s-700s", dg.to_nmmm()) self.assertEqual("aaaaaaaaaaaa", dg.to_strip()) self.assertEqual("aaaaaaaaaaaa", str(dg)) - def test_uniform_spread_timings_2(self): + def test_UNIFORM_SPREAD_TIMINGS_2(self): dg = DOOMGram() @@ -169,10 +178,12 @@ def test_uniform_spread_timings_2(self): self.assertEqual(1, dg.num_events_in_10s()) self.assertEqual(1, dg.num_events_ge_100s()) + self.assertEqual("9ns-65.76s-700s", dg.to_mmm()) + self.assertEqual("12:9ns-65.76s-700s", dg.to_nmmm()) self.assertEqual("aaaaaaaaaaaa", dg.to_strip()) self.assertEqual("aaaaaaaaaaaa", str(dg)) - def test_uniform_spread_timings_3(self): + def test_UNIFORM_SPREAD_TIMINGS_3(self): dg = DOOMGram() @@ -208,10 +219,12 @@ def test_uniform_spread_timings_3(self): self.assertEqual(1, dg.num_events_in_10s()) self.assertEqual(1, dg.num_events_ge_100s()) + self.assertEqual("9ns-65.76s-700s", dg.to_mmm()) + self.assertEqual("12:9ns-65.76s-700s", dg.to_nmmm()) self.assertEqual("aaaaaaaaaaaa", dg.to_strip()) self.assertEqual("aaaaaaaaaaaa", str(dg)) - def test_uniform_spread_timings_4(self): + def test_UNIFORM_SPREAD_TIMINGS_4(self): dg = DOOMGram() @@ -244,10 +257,12 @@ def test_uniform_spread_timings_4(self): self.assertEqual(1, dg.num_events_in_10s()) self.assertEqual(1, dg.num_events_ge_100s()) + self.assertEqual("6µs-87.68s-700s", dg.to_mmm()) + self.assertEqual("9:6µs-87.68s-700s", dg.to_nmmm()) self.assertEqual("___aaaaaaaaa", dg.to_strip()) self.assertEqual("___aaaaaaaaa", str(dg)) - def test_several_distinct_timings(self): + def test_SEVERAL_DISTINCT_TIMINGS(self): dg = DOOMGram() @@ -279,10 +294,12 @@ def test_several_distinct_timings(self): self.assertEqual(0, dg.num_events_in_10s()) self.assertEqual(1, dg.num_events_ge_100s()) + self.assertEqual("10ns-39.28s-309s", dg.to_mmm()) + self.assertEqual("8:10ns-39.28s-309s", dg.to_nmmm()) self.assertEqual("_a_aa___aa_a", dg.to_strip()) self.assertEqual("_a_aa___aa_a", str(dg)) - def test_several_intersecting_timings(self): + def test_SEVERAL_INTERSECTING_TIMINGS(self): dg = DOOMGram() @@ -316,10 +333,12 @@ def test_several_intersecting_timings(self): self.assertEqual(0, dg.num_events_in_10s()) self.assertEqual(1, dg.num_events_ge_100s()) + self.assertEqual("11ns-31.85s-309s", dg.to_mmm()) + self.assertEqual("10:11ns-31.85s-309s", dg.to_nmmm()) self.assertEqual("_a_aa___aa_a", dg.to_strip()) self.assertEqual("_a_aa___aa_a", str(dg)) - def test_many_cumulative_timings(self): + def test_MANY_CUMULATIVE_TIMINGS(self): dg = DOOMGram() @@ -350,6 +369,8 @@ def test_many_cumulative_timings(self): self.assertEqual(0, dg.num_events_in_10s()) self.assertEqual(0, dg.num_events_ge_100s()) + self.assertEqual("1ns-909.1µs-1s", dg.to_mmm()) + self.assertEqual("11110:1ns-909.1µs-1s", dg.to_nmmm()) self.assertEqual("e__d__c__b__", dg.to_strip()) self.assertEqual("e__d__c__b__", str(dg)) self.assertEqual("*--d--c--b--", dg.to_strip(range='abcd', zero='-'))