{"id":16891536,"url":"https://github.com/rsalmei/about-time","last_synced_at":"2025-08-02T22:13:26.850Z","repository":{"id":32899230,"uuid":"144532163","full_name":"rsalmei/about-time","owner":"rsalmei","description":"A cool helper for tracking time and throughput of code blocks, with beautiful human friendly renditions.","archived":false,"fork":false,"pushed_at":"2022-12-22T02:01:17.000Z","size":93,"stargazers_count":60,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-31T11:08:09.555Z","etag":null,"topics":["measure","metrics","python","statistics","stats","time","timing","timings","tracker","tracking"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rsalmei.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"rsalmei","ko_fi":"rsalmei","custom":["https://www.buymeacoffee.com/rsalmei","https://www.paypal.com/donate?business=6SWSHEB5ZNS5N\u0026no_recurring=0\u0026item_name=I%27m+the+author+of+alive-progress%2C+clearly+and+about-time.+Thank+you+for+appreciating+my+work%21\u0026currency_code=USD"]}},"created_at":"2018-08-13T05:10:54.000Z","updated_at":"2025-02-24T22:12:12.000Z","dependencies_parsed_at":"2023-01-14T22:35:14.685Z","dependency_job_id":null,"html_url":"https://github.com/rsalmei/about-time","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsalmei%2Fabout-time","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsalmei%2Fabout-time/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsalmei%2Fabout-time/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsalmei%2Fabout-time/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rsalmei","download_url":"https://codeload.github.com/rsalmei/about-time/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247657281,"owners_count":20974345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["measure","metrics","python","statistics","stats","time","timing","timings","tracker","tracking"],"created_at":"2024-10-13T17:07:42.567Z","updated_at":"2025-08-02T22:13:26.830Z","avatar_url":"https://github.com/rsalmei.png","language":"Python","funding_links":["https://github.com/sponsors/rsalmei","https://ko-fi.com/rsalmei","https://www.buymeacoffee.com/rsalmei","https://www.paypal.com/donate?business=6SWSHEB5ZNS5N\u0026no_recurring=0\u0026item_name=I%27m+the+author+of+alive-progress%2C+clearly+and+about-time.+Thank+you+for+appreciating+my+work%21\u0026currency_code=USD"],"categories":[],"sub_categories":[],"readme":"[\u003cimg align=\"right\" src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" width=\"217px\" height=\"51x\"\u003e](https://www.buymeacoffee.com/rsalmei)\n[\u003cimg align=\"right\" alt=\"Donate with PayPal button\" src=\"https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif\"\u003e](https://www.paypal.com/donate?business=6SWSHEB5ZNS5N\u0026no_recurring=0\u0026item_name=I%27m+the+author+of+alive-progress%2C+clearly+and+about-time.+Thank+you+for+appreciating+my+work%21\u0026currency_code=USD)\n\n# about-time\n\n### A cool helper for tracking time and throughput of code blocks, with beautiful human friendly renditions.\n\n[![Coverage](https://img.shields.io/badge/coverage-100%25-green.svg)]()\n[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/rsalmei/about-time/graphs/commit-activity)\n[![PyPI version](https://img.shields.io/pypi/v/about-time.svg)](https://pypi.python.org/pypi/about-time/)\n[![PyPI pyversions](https://img.shields.io/pypi/pyversions/about-time.svg)](https://pypi.python.org/pypi/about-time/)\n[![PyPI status](https://img.shields.io/pypi/status/about-time.svg)](https://pypi.python.org/pypi/about-time/)\n[![PyPI Downloads](https://pepy.tech/badge/about-time)](https://pepy.tech/project/about-time)\n\n## What does it do?\n\nDid you ever need to measure the duration of an operation? Yeah, this is easy.\n\nBut how to:\n- measure the duration of two or more blocks at the same time, including the whole duration?\n- instrument a code to cleanly retrieve durations in one line, to log or send to time series databases?\n- easily see human friendly durations in *s* (seconds), *ms* (milliseconds), *µs* (microseconds) and even *ns* (nanoseconds)?\n- easily see human friendly counts with SI prefixes like *k*, *M*, *G*, *T*, etc?\n- measure the actual throughput of a block? (this is way harder, since it needs to measure both duration and number of iterations)\n- easily see human friendly throughputs in \"/second\", \"/minute\", \"/hour\" or even \"/day\", including SI prefixes?\n\nYes, it can get tricky! More interesting details about [duration](https://github.com/rsalmei/about-time#the-human-duration-magic) and [throughput](https://github.com/rsalmei/about-time#the-human-throughput-magic).\n\u003cbr\u003eIf you'd tried to do it without these magic, it would probably get messy and immensely pollute the code being instrumented.\n\nI have the solution, behold!\n\n```python\nfrom about_time import about_time\n\n\ndef some_func():\n    import time\n    time.sleep(85e-3)\n    return True\n\n\ndef main():\n    with about_time() as t1:  # \u003c-- use it like a context manager!\n\n        t2 = about_time(some_func)  # \u003c-- use it with any callable!!\n\n        t3 = about_time(x * 2 for x in range(56789))  # \u003c-- use it with any iterable or generator!!!\n        data = [x for x in t3]  # then just iterate!\n\n    print(f'total: {t1.duration_human}')\n    print(f'  some_func: {t2.duration_human} -\u003e result: {t2.result}')\n    print(f'  generator: {t3.duration_human} -\u003e {t3.count_human} elements, throughput: {t3.throughput_human}')\n```\n\nThis `main()` function prints:\n```\ntotal: 95.6ms\n  some_func: 89.7ms -\u003e result: True\n  generator: 5.79ms -\u003e 56.8k elements, throughput: 9.81M/s\n```\n\nHow cool is that? 😲👏\n\nYou can also get the duration in seconds if needed:\n```\nIn [7]: t1.duration\nOut[7]: 0.09556673200064251\n```\nBut `95.6ms` is way better, isn't it? The same with `count` and `throughput`!\n\nSo, `about_time` measures code blocks, both time and throughput, and converts them to beautiful human friendly representations! 👏\n\n## Get it\n\nJust install with pip:\n\n```bash\n❯ pip install about-time\n```\n\n## Use it\n\nThere are three modes of operation: context manager, callable and throughput. Let's dive in.\n\n### 1. Use it like a context manager:\n\n```python\nfrom about_time import about_time\n\nwith about_time() as t:\n    expensive()  # the code to be measured...\n\nprint(f'The whole block took: {t.duration_human}')\n```\n\nThis way you can nicely wrap any amount of code.\n\n\u003e In this mode, there are the basic fields `duration` and `duration_human`.\n\n### 2. Use it with any callable:\n\n```python\nfrom about_time import about_time\n\nt = about_time(some_func)\n\nprint(f'The whole block took: {t.duration_human}')\nprint(f'And the result was: {t.result}')\n\n```\n\nThis way you have a nice one liner, and do not need to increase the indent of your code.\n\n\u003e In this mode, there is an additional field `result`, with the return of the callable.\n\nIf the callable have params, you can use a `lambda` or (📌 new) simply send them:\n\n```python\ndef add(n, m):\n    return n + m\n\n\nt = about_time(add, 1, 41)\n# or:\nt = about_time(add, n=1, m=41)\n# or even:\nt = about_time(lambda: add(1, 41))\n\n```\n\n### 3. Use it with any iterable or generator:\n\n```python\nfrom about_time import about_time\n\nt = about_time(iterable)\nfor item in t:\n    print(item)  # process item.\n\nprint(f'The whole block took: {t.duration_human}')\nprint(f'It was detected {t.count_human} elements')\nprint(f'The throughput was: {t.throughput_human}')\n```\n\nThis way `about_time` also extracts the number of iterations, and with the measured duration it calculates the throughput of the whole loop! It's especially useful with generators, which do not have length.\n\n\u003e In this mode, there are the additional fields `count`, `count_human`, `throughput` and `throughput_human`.\n\nCool tricks under the hood:\n- you can use it even with generator expressions, anything that is iterable to python!\n- you can consume it not only in a `for` loop, but also in { list | dict | set } comprehensions, `map()`s, `filter()`s, `sum()`s, `max()`s, `list()`s, etc, thus any function that expects an iterator! 👏\n- the timer only starts when the first element is queried, so you can initialize whatever you need before entering the loop! 👏\n- the `count`/`count_human` and `throughput`/`throughput_human` fields are updated in **real time**, so you can use them even inside the loop!\n\n## Features:\n\nAccording to the SI standard, there are 1000 bytes in a `kilobyte`.\n\u003cbr\u003eThere is another standard called IEC that has 1024 bytes in a `kibibyte`, but this is only useful when measuring things that are naturally a power of two, e.g. a stick of RAM.\n\nBe careful to not render IEC quantities with SI scaling, which would be incorrect. But I still support it, if you really want to ;)\n\nBy default, this will use SI, `1000` divisor, and `no space` between values and scales/units. SI uses prefixes: `k`, `M`, `G`, `T`, `P`, `E`, `Z`, and `Y`.\n\nThese are the optional features:\n- `iec` =\u003e use IEC instead of SI: `Ki`, `Mi`, `Gi`, `Ti`, `Pi`, `Ei`, `Zi`, `Yi` (implies `1024`);\n- `1024` =\u003e use `1024` divisor — if `iec` is not enabled, use prefixes: `K`, `M`, `G`, `T`, `P`, `E`, `Z`, and `Y` (note the upper 'K');\n- `space` =\u003e include a space between values and scales/units everywhere: `48 B` instead of `48B`, `15.6 µs` instead of `15.6µs`, and `12.4 kB/s` instead of `12.4kB/s`.\n\nTo change them, just use the properties:\n\n```python\nfrom about_time import FEATURES\n\nFEATURES.feature_1024\nFEATURES.feature_iec\nFEATURES.feature_space\n```\n\nFor example, to enable spaces between scales/units:\n```python\nfrom about_time import FEATURES\n\nFEATURES.feature_space = True\n```\n\n## The human duration magic\n\nI've used just one key concept in designing the human duration features: cleanliness.\n\u003e `3.44s` is more meaningful than `3.43584783784s`, and `14.1us` is much nicer than `.0000141233333s`.\n\nSo what I do is: round values to at most two decimal places (three significant digits), and find the best scale unit to represent them, minimizing resulting values smaller than `1`. The search for the best unit considers even the rounding been applied!\n\u003e `0.000999999` does not end up as `999.99us` (truncate) nor `1000.0us` (bad unit), but is auto-upgraded to the next unit `1.0ms`!\n\nThe `duration_human` units change seamlessly from nanoseconds to hours.\n- values smaller than 60 seconds are always rendered as \"num.D[D]unit\", with one or two decimals;\n- from 1 minute onward it changes to \"H:MM:SS\".\n\nIt feels much more humane, humm? ;)\n\nSome examples:\n\n| duration (float seconds) | duration_human |\n|:------------------------:|:--------------:|\n|       .00000000185       |    '1.85ns'    |\n|      .000000999996       |    '1.00µs'    |\n|          .00001          |    '10.0µs'    |\n|         .0000156         |    '15.6µs'    |\n|           .01            |    '10.0ms'    |\n|      .0141233333333      |    '14.1ms'    |\n|         .1099999         |    '110ms'     |\n|         .1599999         |    '160ms'     |\n|          .8015           |    '802ms'     |\n|         3.434999         |    '3.43s'     |\n|          59.999          |   '0:01:00'    |\n|           68.5           |   '0:01:08'    |\n|         125.825          |   '0:02:05'    |\n|         4488.395         |   '1:14:48'    |\n\n## The human throughput magic\n\nI've made the `throughput_human` with a similar logic. It is funny how much trickier \"throughput\" is to the human brain!\n\u003e If something took `1165263 seconds` to handle `123 items`, how fast did it go? It's not obvious...\n\nIt doesn't help even if we divide the duration by the number of items, `9473 seconds/item`, which still does not mean much. How fast was that? We can't say.\n\u003cbr\u003eHow many items did we do per time unit?\n\u003e Oh, we just need to invert it, so `0,000105555569858 items/second`, there it is! 😂\n\nTo make some sense of it we need to multiply that by 3600 (seconds in an hour) to get `0.38/h`, which is much better, and again by 24 (hours in a day) to finally get `9.12/d`!! Now we know how fast that process was! \\o/ As you see, it's not easy at all.\n\nThe `throughput_human` unit changes seamlessly from per-second, per-minute, per-hour, and per-day.\n\u003cbr\u003eIt also automatically inserts SI-prefixes, like k, M, and G. 👍\n\n| duration (float seconds) | number of elements | throughput_human |\n|:------------------------:|:------------------:|:----------------:|\n|           1\\.            |         10         |     '10.0/s'     |\n|           1\\.            |        2500        |    '2.50k/s'     |\n|           1\\.            |      1825000       |    '1.82M/s'     |\n|           2\\.            |         1          |     '30.0/m'     |\n|           2\\.            |         10         |     '5.00/s'     |\n|    1.981981981981982     |         11         |     '5.55/s'     |\n|          100\\.           |         10         |     '6.00/m'     |\n|          1600\\.          |         3          |     '6.75/h'     |\n|           .99            |         1          |     '1.01/s'     |\n|        1165263\\.         |        123         |     '9.12/d'     |\n\n## Accuracy\n\n`about_time` supports all versions of python, but in pythons \u003e= `3.3` it performs even better, with much higher resolution and smaller propagation of errors, thanks to the new `time.perf_counter`. In older versions, it uses `time.time` as usual.\n\n## Changelog highlights:\n- 4.2.2: fix optional precision parameter not being actually optional; add `py.typed` in the distribution to satisfy mypy and other type checking tools; update build system and github workflow; drop Python 3.7 and include 3.12, 3.13, and 3.14.\n- 4.2.1: makes fixed precision actually gain more resolution, when going from a default 1 to 2 decimals\n- 4.2.0: support for fixed precision, useful when one needs output without varying lengths; official Python 3.11 support\n- 4.1.0: enable to cache features within closures, to improve performance for https://github.com/rsalmei/alive-progress\n- 4.0.0: new version, modeled after my Rust implementation in https://crates.io/crates/human-repr; includes new global features, new objects for each operation, and especially, new simpler human friendly representations; supports Python 3.7+\n- 3.3.0: new interfaces for count_human and throughput_human; support more common Kbyte for base 2 (1024), leaving IEC one as an alternate\n- 3.2.2: support IEC kibibyte standard for base 2 (1024)\n- 3.2.1: support divisor in throughput_human\n- 3.2.0: both durations and throughputs now use 3 significant digits; throughputs now include SI-prefixes\n- 3.1.1: make `duration_human()` and `throughput_human()` available for external use\n- 3.1.0: include support for parameters in callable mode; official support for python 3.8, 3.9 and 3.10\n- 3.0.0: greatly improved the counter/throughput mode, with a single argument and working in real time\n- 2.0.0: feature complete, addition of callable and throughput modes\n- 1.0.0: first public release, context manager mode\n\n## License\n\nThis software is licensed under the MIT License. See the LICENSE file in the top distribution directory for the full license text.\n\n\n---\nMaintaining an open source project is hard and time-consuming, and I've put much ❤️ and effort into this.\n\nIf you've appreciated my work, you can back me up with a donation! Thank you 😊\n\n[\u003cimg align=\"right\" src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" width=\"217px\" height=\"51x\"\u003e](https://www.buymeacoffee.com/rsalmei)\n[\u003cimg align=\"right\" alt=\"Donate with PayPal button\" src=\"https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif\"\u003e](https://www.paypal.com/donate?business=6SWSHEB5ZNS5N\u0026no_recurring=0\u0026item_name=I%27m+the+author+of+alive-progress%2C+clearly+and+about-time.+Thank+you+for+appreciating+my+work%21\u0026currency_code=USD)\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsalmei%2Fabout-time","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frsalmei%2Fabout-time","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsalmei%2Fabout-time/lists"}