From 82c9af28b110c77127b2fa8a888547a50305d632 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Mon, 9 Oct 2023 20:02:33 +0300 Subject: [PATCH 01/17] fix arithmetic of the event count --- src/akarsu/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/akarsu/__main__.py b/src/akarsu/__main__.py index f5bea35..e2c74db 100644 --- a/src/akarsu/__main__.py +++ b/src/akarsu/__main__.py @@ -22,7 +22,7 @@ def main() -> None: print(f"{'Count':>10}{'Event Type':^20}{'Filename(function)':<50}") for event, count in Counter(events).most_common(): event_type, file_name, func_name = event - counter[event_type] += 1 + counter[event_type] += count print(f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}") print(f"\nTotal number of events: {counter.total()}") From 8136917f7d4d598124a998a8c32ca837ef048db7 Mon Sep 17 00:00:00 2001 From: Ege Akman Date: Mon, 9 Oct 2023 20:08:54 +0300 Subject: [PATCH 02/17] Update .pre-commit-config.yaml - Use black's pre-commit mirror, which is 2x faster since it uses mypyc-compiled black - Add additional checks which might come in handy --- .pre-commit-config.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11cf8a8..d4ff08b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - - repo: https://github.com/psf/black + - repo: https://github.com/psf/black-pre-commit-mirror rev: 23.9.1 hooks: - id: black @@ -47,6 +47,10 @@ repos: hooks: - id: end-of-file-fixer files: "\\.(py|.txt|.yaml|.json|.in|.md|.toml|.cfg|.html|.yml)$" + - id: check-case-conflict + - id: check-merge-conflict + - id: check-yaml + - id: trailing-whitespace - repo: local hooks: From 16322ddaaa6246c2339194d54f339bc4a5240a26 Mon Sep 17 00:00:00 2001 From: Ege Akman Date: Mon, 9 Oct 2023 20:33:34 +0300 Subject: [PATCH 03/17] Add pre-commit checks workflow --- .github/pre-commit.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/pre-commit.yaml diff --git a/.github/pre-commit.yaml b/.github/pre-commit.yaml new file mode 100644 index 0000000..7f45b41 --- /dev/null +++ b/.github/pre-commit.yaml @@ -0,0 +1,17 @@ +name: pre-commit checks + +on: [push, pull_request, workflow_dispatch] + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + - uses: pre-commit/action@v3.0.0 From 83a37b935c3a04dd1f949ad667c8a20a9c8f4195 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Mon, 9 Oct 2023 22:18:54 +0300 Subject: [PATCH 04/17] add workflow folder to github actions --- .github/{ => workflows}/pre-commit.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/pre-commit.yaml (100%) diff --git a/.github/pre-commit.yaml b/.github/workflows/pre-commit.yaml similarity index 100% rename from .github/pre-commit.yaml rename to .github/workflows/pre-commit.yaml From e4893268744e9f01a09b59a8635052a2add47da6 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Tue, 10 Oct 2023 02:01:36 +0300 Subject: [PATCH 05/17] add github actions for tests --- .github/workflows/test.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6510e7a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test + +on: [push] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.12"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install Package + run: pip install -e . + + - name: Run Tests + run: python -m unittest discover -v From 3477a1a71028f7102bee8a32d7b0789a91658a47 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Tue, 10 Oct 2023 02:02:07 +0300 Subject: [PATCH 06/17] remove unittest hook --- .pre-commit-config.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4ff08b..74c0d11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,12 +51,3 @@ repos: - id: check-merge-conflict - id: check-yaml - id: trailing-whitespace - - - repo: local - hooks: - - id: unittests - name: run unit tests - entry: python -m unittest - language: system - pass_filenames: false - args: ["discover"] From c37c0b55f720f53e13b4263216abd2b3483bbb73 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Tue, 10 Oct 2023 02:02:21 +0300 Subject: [PATCH 07/17] add tox support --- tox.ini | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ec9a419 --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +[tox] +envlist = py312, pre-commit +isolated_build = true + +[testenv] +commands = python -m unittest discover -v + +[testenv:pre-commit] +skip_install = true +deps = pre-commit +commands = pre-commit run --all-files From b02f9b58a08aa1a032736fb53d3f342b1e0bda19 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Tue, 10 Oct 2023 02:09:17 +0300 Subject: [PATCH 08/17] set encoding as utf-8 for tests --- tests/test_akarsu.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_akarsu.py b/tests/test_akarsu.py index 27bee8e..9f832fb 100644 --- a/tests/test_akarsu.py +++ b/tests/test_akarsu.py @@ -1,8 +1,14 @@ +import sys import textwrap import unittest from akarsu.akarsu import Akarsu +# Set encoding to UTF-8 when testing on windows-latest +if sys.platform == "win32": + sys.stdin.reconfigure(encoding="utf-8") + sys.stdout.reconfigure(encoding="utf-8") + class TestNine(unittest.TestCase): def check_events(self, events, expected_events): From 483257467d7ba19c864f73fccadddd91b6852783 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 02:16:10 +0300 Subject: [PATCH 09/17] add new argument to show only the function calls --- src/akarsu/__main__.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/akarsu/__main__.py b/src/akarsu/__main__.py index e2c74db..50da3f8 100644 --- a/src/akarsu/__main__.py +++ b/src/akarsu/__main__.py @@ -1,9 +1,12 @@ import argparse import io from collections import Counter +from typing import Final from akarsu.akarsu import Akarsu +CALL_EVENTS: Final[list[str]] = ["C_CALL", "PY_CALL"] + def main() -> None: parser = argparse.ArgumentParser( @@ -11,6 +14,13 @@ def main() -> None: ) parser.add_argument("-v", "--version", action="version", version="0.1.0") parser.add_argument("-f", "--file", type=str, help="Path to the file") + parser.add_argument( + "-c", + "--calls", + action="store_true", + default=False, + help="Show only the function calls", + ) args = parser.parse_args() if file := args.file: @@ -23,7 +33,12 @@ def main() -> None: for event, count in Counter(events).most_common(): event_type, file_name, func_name = event counter[event_type] += count - print(f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}") + fmt = f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}" + if args.calls: + if event_type in CALL_EVENTS: + print(fmt) + else: + print(fmt) print(f"\nTotal number of events: {counter.total()}") for event_type, count in counter.most_common(): From 45ffccf10bdc55cd945e8bd181eb73bfe213a0db Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 02:22:34 +0300 Subject: [PATCH 10/17] delete hexadecimal addresses specified in the repr of functions --- src/akarsu/akarsu.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/akarsu/akarsu.py b/src/akarsu/akarsu.py index 005e33c..886e30b 100644 --- a/src/akarsu/akarsu.py +++ b/src/akarsu/akarsu.py @@ -1,3 +1,4 @@ +import re import sys import types from typing import Any, Final, cast @@ -20,6 +21,7 @@ (EVENTS.STOP_ITERATION, "STOP ITERATION"), ) EVENT_SET: Final[int] = EVENTS.CALL + sum(ev for ev, _ in TRACKED_EVENTS) +PATTERN: Final[str] = r" at 0x[0-9a-f]+" class Akarsu: @@ -27,6 +29,10 @@ def __init__(self, code: str, file_name: str) -> None: self.code = code self.file_name = file_name + def format_func_name(self, event: tuple[str, str, str]) -> tuple[str, str, str]: + event_type, file_name, func_name = event + return (event_type, file_name, re.sub(PATTERN, "", func_name)) + def profile(self) -> list[tuple[str, str, str]]: events = [] @@ -64,6 +70,10 @@ def record_call( MONITOR.set_events(TOOL, 0) MONITOR.free_tool_id(TOOL) - events = [e for e in events[2:-3] if "____wrapper____" not in e[2]] + events = [ + self.format_func_name(event) + for event in events[2:-3] + if "____wrapper____" not in event[2] + ] return events From 75ef6bd438305ec88666fb81fa158cc9d9be3d51 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 02:28:14 +0300 Subject: [PATCH 11/17] move empty file checks from profiler to __main__.py --- src/akarsu/__main__.py | 35 ++++++++++++----------- src/akarsu/akarsu.py | 65 +++++++++++++++++++----------------------- tests/test_akarsu.py | 6 ---- 3 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/akarsu/__main__.py b/src/akarsu/__main__.py index 50da3f8..8ed1ad7 100644 --- a/src/akarsu/__main__.py +++ b/src/akarsu/__main__.py @@ -1,5 +1,4 @@ import argparse -import io from collections import Counter from typing import Final @@ -24,25 +23,27 @@ def main() -> None: args = parser.parse_args() if file := args.file: - with io.open(file) as fp: + with open(file) as fp: source = fp.read() - events = Akarsu(source, args.file).profile() - counter: Counter = Counter() - - print(f"{'Count':>10}{'Event Type':^20}{'Filename(function)':<50}") - for event, count in Counter(events).most_common(): - event_type, file_name, func_name = event - counter[event_type] += count - fmt = f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}" - if args.calls: - if event_type in CALL_EVENTS: + + if source := source.strip(): + events = Akarsu(source, args.file).profile() + counter: Counter = Counter() + + print(f"{'Count':>10}{'Event Type':^20}{'Filename(function)':<50}") + for event, count in Counter(events).most_common(): + event_type, file_name, func_name = event + counter[event_type] += count + fmt = f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}" + if args.calls: + if event_type in CALL_EVENTS: + print(fmt) + else: print(fmt) - else: - print(fmt) - print(f"\nTotal number of events: {counter.total()}") - for event_type, count in counter.most_common(): - print(f" {event_type} = {count}") + print(f"\nTotal number of events: {counter.total()}") + for event_type, count in counter.most_common(): + print(f" {event_type} = {count}") if __name__ == "__main__": diff --git a/src/akarsu/akarsu.py b/src/akarsu/akarsu.py index 886e30b..c014bec 100644 --- a/src/akarsu/akarsu.py +++ b/src/akarsu/akarsu.py @@ -35,45 +35,40 @@ def format_func_name(self, event: tuple[str, str, str]) -> tuple[str, str, str]: def profile(self) -> list[tuple[str, str, str]]: events = [] + indented_code = "\n".join(f"\t{line}" for line in self.code.splitlines()) + source = f"def ____wrapper____():\n{indented_code}\n____wrapper____()" + code = compile(source, self.file_name, "exec") - if code := self.code.strip(): - indented_code = "\n".join(f"\t{line}" for line in code.splitlines()) - source = f"def ____wrapper____():\n{indented_code}\n____wrapper____()" - code = compile(source, self.file_name, "exec") # type:ignore + for event, event_name in TRACKED_EVENTS: - for event, event_name in TRACKED_EVENTS: - - def record( - *args: tuple[types.CodeType, int], event_name: str = event_name - ) -> None: - code = cast(types.CodeType, args[0]) - events.append((event_name, code.co_filename, code.co_name)) - - MONITOR.register_callback(TOOL, event, record) - - def record_call( - code: types.CodeType, offset: int, obj: Any, arg: Any + def record( + *args: tuple[types.CodeType, int], event_name: str = event_name ) -> None: - file_name = code.co_filename - if isinstance(obj, PY_CALLABLES): - events.append(("PY_CALL", file_name, obj.__code__.co_name)) - else: - events.append(("C_CALL", file_name, str(obj))) + code = cast(types.CodeType, args[0]) + events.append((event_name, code.co_filename, code.co_name)) + + MONITOR.register_callback(TOOL, event, record) - MONITOR.use_tool_id(TOOL, "Akarsu") - MONITOR.register_callback(TOOL, EVENTS.CALL, record_call) - MONITOR.set_events(TOOL, EVENT_SET) - try: - exec(code) - except: - pass - MONITOR.set_events(TOOL, 0) - MONITOR.free_tool_id(TOOL) + def record_call(code: types.CodeType, offset: int, obj: Any, arg: Any) -> None: + file_name = code.co_filename + if isinstance(obj, PY_CALLABLES): + events.append(("PY_CALL", file_name, obj.__code__.co_name)) + else: + events.append(("C_CALL", file_name, str(obj))) - events = [ - self.format_func_name(event) - for event in events[2:-3] - if "____wrapper____" not in event[2] - ] + MONITOR.use_tool_id(TOOL, "Akarsu") + MONITOR.register_callback(TOOL, EVENTS.CALL, record_call) + MONITOR.set_events(TOOL, EVENT_SET) + try: + exec(code) + except: + pass + MONITOR.set_events(TOOL, 0) + MONITOR.free_tool_id(TOOL) + events = [ + self.format_func_name(event) + for event in events[2:-3] + if "____wrapper____" not in event[2] + ] return events diff --git a/tests/test_akarsu.py b/tests/test_akarsu.py index 9f832fb..e7d6957 100644 --- a/tests/test_akarsu.py +++ b/tests/test_akarsu.py @@ -49,12 +49,6 @@ def test_profile_generator(self): ] self.check_events(events, expected_events) - def test_profile_empty_code(self): - code = "" - events = Akarsu(code, "").profile() - expected_events = [] - self.check_events(events, expected_events) - def test_profile_nested_functions(self): source = textwrap.dedent(""" def foo(): From d7bef92a5a6897bae98d7380ac5193659f8c019c Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 22:14:07 +0300 Subject: [PATCH 12/17] handle file not found error --- src/akarsu/__main__.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/akarsu/__main__.py b/src/akarsu/__main__.py index 8ed1ad7..227615f 100644 --- a/src/akarsu/__main__.py +++ b/src/akarsu/__main__.py @@ -1,4 +1,5 @@ import argparse +import sys from collections import Counter from typing import Final @@ -23,8 +24,12 @@ def main() -> None: args = parser.parse_args() if file := args.file: - with open(file) as fp: - source = fp.read() + try: + with open(file) as fp: + source = fp.read() + except FileNotFoundError: + print(f"{file} not found.") + sys.exit(1) if source := source.strip(): events = Akarsu(source, args.file).profile() @@ -33,13 +38,11 @@ def main() -> None: print(f"{'Count':>10}{'Event Type':^20}{'Filename(function)':<50}") for event, count in Counter(events).most_common(): event_type, file_name, func_name = event - counter[event_type] += count - fmt = f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}" - if args.calls: - if event_type in CALL_EVENTS: - print(fmt) - else: - print(fmt) + if (args.calls and event_type in CALL_EVENTS) or not args.calls: + counter[event_type] += count + print( + f"{count:>10}{event_type:^20}{f'{file_name}({func_name})':<50}" + ) print(f"\nTotal number of events: {counter.total()}") for event_type, count in counter.most_common(): From 1fe46d2672ac940df3b4d3b57e0cfbff61932c5e Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 22:16:04 +0300 Subject: [PATCH 13/17] add ruff --- .pre-commit-config.yaml | 15 +++++++-------- pyproject.toml | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 74c0d11..1b265fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,12 +4,6 @@ repos: hooks: - id: black - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - args: ["--profile", "black", "--filter-files"] - - repo: https://github.com/hakancelikdev/unimport rev: 1.0.0 hooks: @@ -17,7 +11,7 @@ repos: args: [--remove, --include-star-import] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.6.0 hooks: - id: mypy exclude: docs @@ -43,7 +37,7 @@ repos: args: [--prose-wrap=always, --print-width=88] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: end-of-file-fixer files: "\\.(py|.txt|.yaml|.json|.in|.md|.toml|.cfg|.html|.yml)$" @@ -51,3 +45,8 @@ repos: - id: check-merge-conflict - id: check-yaml - id: trailing-whitespace + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 + hooks: + - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 06e9b26..004c564 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,6 @@ akarsu = "akarsu.__main__:main" target-version = ["py312"] preview = true -[tool.isort] -profile = "black" - [tool.docformatter] recursive = true wrap-summaries = 79 @@ -51,3 +48,16 @@ warn_unused_configs = true no_strict_optional = true ignore_missing_imports = true show_error_codes = true + +[tool.ruff] +select = [ + "E", # pycodestyle + "F", # pyflakes + "UP", # pyupgrade, + "I", # isort +] +mccabe = { max-complexity = 14 } +target-version = "py312" + +[tool.ruff.per-file-ignores] +'src/akarsu/akarsu.py' = ["E722"] From dbb8d780fc445954e61092ea173d583c05220e4c Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 22:19:54 +0300 Subject: [PATCH 14/17] add changelog --- CHANGELOG.MD | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 CHANGELOG.MD diff --git a/CHANGELOG.MD b/CHANGELOG.MD new file mode 100644 index 0000000..5c1b531 --- /dev/null +++ b/CHANGELOG.MD @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and +this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0] - 2023-10-12 + +### Added + +- The `-c` argument has been added to show only function calls. + +### Changed + +- The hexadecimal addresses specified in the `repr` of the functions are deleted. + +### Fixed + +- Fixed an sum bug in the event count. + +[0.2.0]: https://github.com/furkanonder/akarsu/releases/tag/0.2.0 + From f704ffc875763e5b98a1685c2588294d7d344702 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 22:22:05 +0300 Subject: [PATCH 15/17] update package version --- pyproject.toml | 2 +- src/akarsu/__main__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 004c564..886f2ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "akarsu" -version = "0.1.0" +version = "0.2.0" description = "New Generation Profiler based on PEP 669" readme = "README.md" requires-python = ">=3.12" diff --git a/src/akarsu/__main__.py b/src/akarsu/__main__.py index 227615f..4f0839c 100644 --- a/src/akarsu/__main__.py +++ b/src/akarsu/__main__.py @@ -12,7 +12,7 @@ def main() -> None: parser = argparse.ArgumentParser( description="New Generation Profiler based on PEP 669" ) - parser.add_argument("-v", "--version", action="version", version="0.1.0") + parser.add_argument("-v", "--version", action="version", version="0.2.0") parser.add_argument("-f", "--file", type=str, help="Path to the file") parser.add_argument( "-c", From d905fe877aa42cf0998de31b14a2f2131b40cc7f Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 22:25:28 +0300 Subject: [PATCH 16/17] remove whitespace --- CHANGELOG.MD | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 5c1b531..4ae2419 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -20,4 +20,3 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Fixed an sum bug in the event count. [0.2.0]: https://github.com/furkanonder/akarsu/releases/tag/0.2.0 - From 9bbe28996168428bd1603803a33b59a0f15da0c4 Mon Sep 17 00:00:00 2001 From: furkanonder Date: Thu, 12 Oct 2023 22:27:32 +0300 Subject: [PATCH 17/17] mention new argument in the readme --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 96213dd..2b12747 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,25 @@ Total number of events: 8 C_CALL = 1 C_RETURN = 1 ``` + +--- + +If you want to show only the function calls in the output, you can use the `-c` or +`--calls` argument. + +```sh +akarsu -c -f example.py +``` + +Output: + +``` + Count Event Type Filename(function) + 1 PY_CALL example.py(bar) + 1 PY_CALL example.py(foo) + 1 C_CALL example.py() + +Total number of events: 3 + PY_CALL = 2 + C_CALL = 1 +```