Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
First commit
  • Loading branch information
alexmojaki committed Apr 24, 2019
0 parents commit cdc3fdc
Show file tree
Hide file tree
Showing 22 changed files with 11,074 additions and 0 deletions.
107 changes: 107 additions & 0 deletions .gitignore
@@ -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/

3 changes: 3 additions & 0 deletions README.md
@@ -0,0 +1,3 @@
# heartrate

This library offers a simple real time visualisation of the execution of a Python program.
1 change: 1 addition & 0 deletions heartrate/__init__.py
@@ -0,0 +1 @@
from heartrate.core import trace
156 changes: 156 additions & 0 deletions heartrate/core.py
@@ -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,
))
22 changes: 22 additions & 0 deletions heartrate/files.py
@@ -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
12 changes: 12 additions & 0 deletions heartrate/static/css/bootstrap.min.css

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions heartrate/static/css/main.css
@@ -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;*/
/*}*/

0 comments on commit cdc3fdc

Please sign in to comment.