Skip to content

Commit

Permalink
Merge pull request #2 from furkanonder/develop
Browse files Browse the repository at this point in the history
Release 0.2.0
  • Loading branch information
furkanonder authored Oct 12, 2023
2 parents 2a99d0e + 9bbe289 commit 5875883
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 73 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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
26 changes: 10 additions & 16 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
repos:
- repo: https://github.com/psf/black
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1
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:
- id: unimport
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
Expand All @@ -43,16 +37,16 @@ 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)$"
- id: check-case-conflict
- id: check-merge-conflict
- id: check-yaml
- id: trailing-whitespace

- repo: local
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.292
hooks:
- id: unittests
name: run unit tests
entry: python -m unittest
language: system
pass_filenames: false
args: ["discover"]
- id: ruff
22 changes: 22 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(<built-in function isinstance>)
Total number of events: 3
PY_CALL = 2
C_CALL = 1
```
18 changes: 14 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -37,9 +37,6 @@ akarsu = "akarsu.__main__:main"
target-version = ["py312"]
preview = true

[tool.isort]
profile = "black"

[tool.docformatter]
recursive = true
wrap-summaries = 79
Expand All @@ -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"]
51 changes: 35 additions & 16 deletions src/akarsu/__main__.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
import argparse
import io
import sys
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(
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",
"--calls",
action="store_true",
default=False,
help="Show only the function calls",
)
args = parser.parse_args()

if file := args.file:
with io.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] += 1
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():
print(f" {event_type} = {count}")
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()
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
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():
print(f" {event_type} = {count}")


if __name__ == "__main__":
Expand Down
67 changes: 36 additions & 31 deletions src/akarsu/akarsu.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
import sys
import types
from typing import Any, Final, cast
Expand All @@ -20,50 +21,54 @@
(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:
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 = []
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:

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))
for event, event_name in TRACKED_EVENTS:

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 = [e for e in events[2:-3] if "____wrapper____" not in e[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
12 changes: 6 additions & 6 deletions tests/test_akarsu.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -43,12 +49,6 @@ def test_profile_generator(self):
]
self.check_events(events, expected_events)

def test_profile_empty_code(self):
code = ""
events = Akarsu(code, "<string>").profile()
expected_events = []
self.check_events(events, expected_events)

def test_profile_nested_functions(self):
source = textwrap.dedent("""
def foo():
Expand Down
11 changes: 11 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 5875883

Please sign in to comment.