Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
First commit
- Loading branch information
0 parents
commit cdc3fdc
Showing
22 changed files
with
11,074 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Created by .ignore support plugin (hsz.mobi) | ||
### Python template | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
MANIFEST | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
.pytest_cache/ | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
local_settings.py | ||
db.sqlite3 | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
.spyproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# mkdocs documentation | ||
/site | ||
|
||
# mypy | ||
.mypy_cache/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# heartrate | ||
|
||
This library offers a simple real time visualisation of the execution of a Python program. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from heartrate.core import trace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import inspect | ||
import logging | ||
import sys | ||
import threading | ||
import webbrowser | ||
from collections import defaultdict, deque, Counter | ||
from functools import lru_cache | ||
from itertools import islice, takewhile | ||
from traceback import extract_stack | ||
|
||
import pygments | ||
from flask import Flask, render_template, jsonify | ||
# noinspection PyUnresolvedReferences | ||
from pygments.formatters import HtmlFormatter | ||
# noinspection PyUnresolvedReferences | ||
from pygments.lexers import PythonLexer | ||
from werkzeug.routing import PathConverter | ||
|
||
from heartrate import files as files_filters | ||
|
||
logging.getLogger('werkzeug').setLevel(logging.ERROR) | ||
|
||
levels = 10 | ||
|
||
lightnesses = [ | ||
int((i + 1) * 100 / (levels + 1)) | ||
for i in range(levels + 1) | ||
] | ||
|
||
|
||
def highlight_python(code): | ||
return pygments.highlight( | ||
code, | ||
PythonLexer(), | ||
HtmlFormatter(nowrap=True), | ||
) | ||
|
||
|
||
@lru_cache() | ||
def highlighted_lines(path): | ||
with open(path) as f: | ||
code = f.read() | ||
|
||
return list(enumerate(highlight_python(code).splitlines())) | ||
|
||
|
||
def trace( | ||
files=files_filters.contains_regex(r'#\s*heartrate'), | ||
port=9999, | ||
host='127.0.0.1', | ||
browser=False, | ||
): | ||
calling_file = inspect.currentframe().f_back.f_code.co_filename | ||
|
||
@lru_cache(maxsize=None) | ||
def include_file(path): | ||
return path == calling_file or files(path) | ||
|
||
thread_ident = threading.get_ident() | ||
queues = defaultdict(lambda: deque(maxlen=2 ** levels)) | ||
totals = defaultdict(Counter) | ||
|
||
app = Flask(__name__) | ||
|
||
class FileConverter(PathConverter): | ||
regex = '.*?' | ||
|
||
app.url_map.converters['file'] = FileConverter | ||
|
||
@app.route('/') | ||
def index(): | ||
return render_template('index.html', files=sorted(queues.keys())) | ||
|
||
@app.route('/file/<file:path>') | ||
def file_view(path): | ||
return render_template("file.html", path=path, **file_table_context(path)) | ||
|
||
def file_table_context(path): | ||
queue = queues[path] | ||
total = len(queue) | ||
counters = [ | ||
Counter(islice(queue, max(0, total - 2 ** i), total)) | ||
for i in range(levels + 1) | ||
] | ||
|
||
ratios = [ | ||
[ | ||
counter[i + 1] / min(2 ** c, total or 1) | ||
* (c + 1) / levels | ||
for c, counter in enumerate(counters) | ||
] | ||
for i, _ in highlighted_lines(path) | ||
] | ||
|
||
max_ratio = max(map(max, ratios)) or 1 | ||
|
||
rows = [ | ||
( | ||
i + 1, | ||
totals[path][i + 1] or '', | ||
reversed([ | ||
|
||
int(round(ratio / max_ratio * 100)) | ||
for ratio in ratios[i] | ||
]), | ||
line, | ||
) | ||
for i, line in highlighted_lines(path) | ||
] | ||
|
||
return dict(rows=rows, zip=zip, lightnesses=lightnesses) | ||
|
||
@app.route('/table/<file:path>') | ||
def file_table_view(path): | ||
return render_template('file_table.html', **file_table_context(path)) | ||
|
||
@app.route('/stacktrace/') | ||
def stacktrace(): | ||
frame = sys._current_frames()[thread_ident] | ||
return jsonify([ | ||
(path, *rest, highlight_python(code), include_file(path)) | ||
for path, *rest, code in | ||
takewhile( | ||
lambda entry: not ( | ||
'heartrate' in entry[0] | ||
and entry[2] == trace_func.__name__), | ||
extract_stack(frame) | ||
) | ||
]) | ||
|
||
threading.Thread( | ||
target=lambda: app.run( | ||
debug=True, | ||
use_reloader=False, | ||
host=host, | ||
port=port, | ||
), | ||
).start() | ||
|
||
def trace_func(frame, event, _arg): | ||
filename = frame.f_code.co_filename | ||
if event == "call": | ||
if include_file(filename): | ||
return trace_func | ||
|
||
elif event == "line": | ||
lineno = frame.f_lineno | ||
queues[filename].append(lineno) | ||
totals[filename][lineno] += 1 | ||
|
||
sys.settrace(trace_func) | ||
|
||
if browser: | ||
webbrowser.open_new_tab("http://localhost:{port}/file/{calling_file}".format( | ||
port=port, calling_file=calling_file, | ||
)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import re | ||
|
||
|
||
# noinspection PyShadowingBuiltins | ||
def all(_path): | ||
return True | ||
|
||
|
||
def path_contains(*subs): | ||
def func(path): | ||
return any(map(path.__contains__, subs)) | ||
|
||
return func | ||
|
||
|
||
def contains_regex(pattern): | ||
def func(path): | ||
with open(path) as f: | ||
code = f.read() | ||
return bool(re.search(pattern, code)) | ||
|
||
return func |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
.highlight { | ||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace; | ||
} | ||
|
||
#stacktrace pre { | ||
margin: 0.5em; | ||
} | ||
|
||
.code-line { | ||
white-space: pre; | ||
} | ||
|
||
table { | ||
table-layout: fixed; | ||
} | ||
|
||
td { | ||
/*width: 1%;*/ | ||
/*white-space: nowrap;*/ | ||
padding-right: 1em; | ||
} | ||
|
||
.graph > span { | ||
position: absolute; | ||
left: 0; | ||
height: 100%; | ||
top: 0; | ||
bottom: 0; | ||
} | ||
|
||
td.graph { | ||
width: 100px; | ||
max-width: 100px; | ||
position: relative; | ||
} | ||
|
||
/*.highlight {*/ | ||
/*display: grid;*/ | ||
/*grid-template-columns: auto auto minmax(0, 1fr);*/ | ||
/*}*/ | ||
|
||
/*.highlight .column-3 {*/ | ||
/*white-space: pre;*/ | ||
/*}*/ |
Oops, something went wrong.