{"id":22451853,"url":"https://github.com/isac322/loopmon","last_synced_at":"2025-08-02T00:32:27.749Z","repository":{"id":38210196,"uuid":"462788615","full_name":"isac322/loopmon","owner":"isac322","description":"Lightweight event loop monitor","archived":false,"fork":false,"pushed_at":"2023-12-13T16:41:24.000Z","size":67,"stargazers_count":8,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-18T17:09:04.522Z","etag":null,"topics":["async-python","asyncio","eventloop","lightweight","monitor","monitoring","python","python3"],"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/isac322.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-23T15:15:20.000Z","updated_at":"2023-12-03T06:30:49.000Z","dependencies_parsed_at":"2023-02-17T13:01:49.880Z","dependency_job_id":null,"html_url":"https://github.com/isac322/loopmon","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isac322%2Floopmon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isac322%2Floopmon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isac322%2Floopmon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isac322%2Floopmon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/isac322","download_url":"https://codeload.github.com/isac322/loopmon/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228419605,"owners_count":17916772,"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":["async-python","asyncio","eventloop","lightweight","monitor","monitoring","python","python3"],"created_at":"2024-12-06T06:08:59.551Z","updated_at":"2024-12-06T06:08:59.645Z","avatar_url":"https://github.com/isac322.png","language":"Python","readme":"# loopmon - Lightweight Event Loop monitoring\n\n\n[![Codecov](https://img.shields.io/codecov/c/gh/isac322/loopmon?style=flat-square\u0026logo=codecov)](https://app.codecov.io/gh/isac322/loopmon)\n[![Dependabot Status](https://flat.badgen.net/github/dependabot/isac322/loopmon?icon=github)](https://github.com/isac322/loopmon/network/dependencies)\n[![PyPI](https://img.shields.io/pypi/v/loopmon?label=pypi\u0026logo=pypi\u0026style=flat-square)](https://pypi.org/project/loopmon/)\n[![PyPI - Wheel](https://img.shields.io/pypi/wheel/loopmon?style=flat-square\u0026logo=pypi)](https://pypi.org/project/loopmon/)\n[![Python Version](https://img.shields.io/pypi/pyversions/loopmon.svg?style=flat-square\u0026logo=python)](https://pypi.org/project/loopmon/)\n[![GitHub last commit (branch)](https://img.shields.io/github/last-commit/isac322/loopmon/master?logo=github\u0026style=flat-square)](https://github.com/isac322/loopmon/commits/master)\n[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/isac322/loopmon/ci.yml?branch=master\u0026logo=github\u0026style=flat-square)](https://github.com/isac322/loopmon/actions)\n[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)\n\nloopmon is a lightweight library that can detect throttling of event loops.\nFor example, you can detect long-running coroutine or whether the blocking function is invoked inside the event loop.\n\n\n## Usage\n\n```python\nimport asyncio\nimport time\nfrom datetime import datetime\nimport loopmon\n\nasync def print_collected_data(lag: float, tasks: int, data_at: datetime) -\u003e None:\n    print(f'event loop lag: {lag:.3f}, running tasks: {tasks}, at {data_at}')\n\nasync def main() -\u003e None:\n    loopmon.create(interval=0.5, callbacks=[print_collected_data])\n    # Simple I/O bound coroutine does not occur event loop lag\n    await asyncio.sleep(0.2)\n    # Blocking function call\n    time.sleep(1)\n\nif __name__ == '__main__':\n    asyncio.run(main())\n```\n\nwill prints:\n\n```\nevent loop lag: 0.000, running tasks: 2, at 2022-02-24 13:29:05.367330+00:00\nevent loop lag: 1.001, running tasks: 1, at 2022-02-24 13:29:06.468622+00:00\n```\n\nYou can check other [examples](https://github.com/isac322/loopmon/tree/master/examples).\n\nI recommend you to add `loopmon.create(...)` on beginning of async function if you are not familiar with handling loop itself.\nBut you can also control creation, installation or staring of monitor via `EventLoopMonitor.start()` or `EventLoopMonitor.install_to_loop()`.\n\n## Features\n\n- Detects event loop lag\n  - Detects event loop running on other thread. [example](https://github.com/isac322/loopmon/blob/master/examples/06_monitoring_another_thread.py)\n- Collect how many tasks are running in the event loop\n- Customize monitoring start and end points\n- Customize monitoring interval\n- Customize collected metrics through callbacks\n- 100% type annotated\n- Zero dependency (except `typing-extentions`)\n\n\n## How it works\n\nEvent loop is single threaded and based on Cooperative Scheduling.\nSo if there is a task that does not yield to another tasks, any tasks on the loop can not be executed.\nAnd starvation also happens when there are too many tasks that a event loop can not handle.\n\nCurrently `loopmon.SleepEventLoopMonitor` is one and only monitor implementation.\nIt periodically sleeps with remembering time just before sleep, and compares the time after awake.\nThe starvation happen if the difference bigger than its sleeping interval.\n\n\n#### pseudo code of `SleepEventLoopMonitor`\n\n```python\nwhile True:\n    before = loop.time()\n    await asyncio.sleep(interval)\n    lag = loop.time() - before - interval\n    tasks = len(asyncio.all_tasks(loop))\n    data_at = datetime.now(timezone.utc)\n    for c in callbacks:\n        loop.create_task(c(lag, tasks, data_at))\n```\n\n## Integration examples\n\n### Prometheus\n\n\n```python\nfrom datetime import datetime\nfrom functools import partial\nimport loopmon\nfrom prometheus_client import Gauge\n\nasync def collect_lag(gauge: Gauge, lag: float, _: int, __: datetime) -\u003e None:\n    gauge.set(lag)\n\nasync def main(gauge: Gauge) -\u003e None:\n    loopmon.create(interval=0.5, callbacks=[partial(collect_lag, gauge)])\n    ...\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisac322%2Floopmon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fisac322%2Floopmon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisac322%2Floopmon/lists"}