feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.) - Full CRUD API for services, incidents, monitors - Public status page with live data - Incident detail page with timeline - API key authentication - Uptime monitoring scheduler - 13 tests passing - TECHNICAL_DESIGN.md with full spec
This commit is contained in:
commit
902133edd3
4655 changed files with 1342691 additions and 0 deletions
472
venv/lib/python3.11/site-packages/mypyc/annotate.py
Normal file
472
venv/lib/python3.11/site-packages/mypyc/annotate.py
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
"""Generate source code formatted as HTML, with bottlenecks annotated and highlighted.
|
||||
|
||||
Various heuristics are used to detect common issues that cause slower than
|
||||
expected performance.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
from html import escape
|
||||
from typing import Final
|
||||
|
||||
from mypy.build import BuildResult
|
||||
from mypy.nodes import (
|
||||
AssignmentStmt,
|
||||
CallExpr,
|
||||
ClassDef,
|
||||
Decorator,
|
||||
DictionaryComprehension,
|
||||
Expression,
|
||||
ForStmt,
|
||||
FuncDef,
|
||||
GeneratorExpr,
|
||||
IndexExpr,
|
||||
LambdaExpr,
|
||||
MemberExpr,
|
||||
MypyFile,
|
||||
NamedTupleExpr,
|
||||
NameExpr,
|
||||
NewTypeExpr,
|
||||
Node,
|
||||
OpExpr,
|
||||
RefExpr,
|
||||
TupleExpr,
|
||||
TypedDictExpr,
|
||||
TypeInfo,
|
||||
TypeVarExpr,
|
||||
Var,
|
||||
WithStmt,
|
||||
)
|
||||
from mypy.traverser import TraverserVisitor
|
||||
from mypy.types import AnyType, Instance, ProperType, Type, TypeOfAny, get_proper_type
|
||||
from mypy.util import FancyFormatter
|
||||
from mypyc.ir.func_ir import FuncIR
|
||||
from mypyc.ir.module_ir import ModuleIR
|
||||
from mypyc.ir.ops import CallC, LoadLiteral, LoadStatic, Value
|
||||
from mypyc.irbuild.mapper import Mapper
|
||||
|
||||
|
||||
class Annotation:
|
||||
"""HTML annotation for compiled source code"""
|
||||
|
||||
def __init__(self, message: str, priority: int = 1) -> None:
|
||||
# Message as HTML that describes an issue and/or how to fix it.
|
||||
# Multiple messages on a line may be concatenated.
|
||||
self.message = message
|
||||
# If multiple annotations are generated for a single line, only report
|
||||
# the highest-priority ones. Some use cases generate multiple annotations,
|
||||
# and this can be used to reduce verbosity by hiding the lower-priority
|
||||
# ones.
|
||||
self.priority = priority
|
||||
|
||||
|
||||
op_hints: Final = {
|
||||
"PyNumber_Add": Annotation('Generic "+" operation.'),
|
||||
"PyNumber_Subtract": Annotation('Generic "-" operation.'),
|
||||
"PyNumber_Multiply": Annotation('Generic "*" operation.'),
|
||||
"PyNumber_TrueDivide": Annotation('Generic "/" operation.'),
|
||||
"PyNumber_FloorDivide": Annotation('Generic "//" operation.'),
|
||||
"PyNumber_Positive": Annotation('Generic unary "+" operation.'),
|
||||
"PyNumber_Negative": Annotation('Generic unary "-" operation.'),
|
||||
"PyNumber_And": Annotation('Generic "&" operation.'),
|
||||
"PyNumber_Or": Annotation('Generic "|" operation.'),
|
||||
"PyNumber_Xor": Annotation('Generic "^" operation.'),
|
||||
"PyNumber_Lshift": Annotation('Generic "<<" operation.'),
|
||||
"PyNumber_Rshift": Annotation('Generic ">>" operation.'),
|
||||
"PyNumber_Invert": Annotation('Generic "~" operation.'),
|
||||
"PyObject_Call": Annotation("Generic call operation."),
|
||||
"PyObject_CallObject": Annotation("Generic call operation."),
|
||||
"PyObject_RichCompare": Annotation("Generic comparison operation."),
|
||||
"PyObject_GetItem": Annotation("Generic indexing operation."),
|
||||
"PyObject_SetItem": Annotation("Generic indexed assignment."),
|
||||
}
|
||||
|
||||
stdlib_hints: Final = {
|
||||
"functools.partial": Annotation(
|
||||
'"functools.partial" is inefficient in compiled code.', priority=3
|
||||
),
|
||||
"itertools.chain": Annotation(
|
||||
'"itertools.chain" is inefficient in compiled code (hint: replace with for loops).',
|
||||
priority=3,
|
||||
),
|
||||
"itertools.groupby": Annotation(
|
||||
'"itertools.groupby" is inefficient in compiled code.', priority=3
|
||||
),
|
||||
"itertools.islice": Annotation(
|
||||
'"itertools.islice" is inefficient in compiled code (hint: replace with for loop over index range).',
|
||||
priority=3,
|
||||
),
|
||||
"copy.deepcopy": Annotation(
|
||||
'"copy.deepcopy" tends to be slow. Make a shallow copy if possible.', priority=2
|
||||
),
|
||||
}
|
||||
|
||||
CSS = """\
|
||||
.collapsible {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
display: inline;
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px;
|
||||
}
|
||||
"""
|
||||
|
||||
JS = """\
|
||||
document.querySelectorAll('.collapsible').forEach(function(collapsible) {
|
||||
collapsible.addEventListener('click', function() {
|
||||
const content = this.nextElementSibling;
|
||||
if (content.style.display === 'none') {
|
||||
content.style.display = 'block';
|
||||
} else {
|
||||
content.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
"""
|
||||
|
||||
|
||||
class AnnotatedSource:
|
||||
"""Annotations for a single compiled source file."""
|
||||
|
||||
def __init__(self, path: str, annotations: dict[int, list[Annotation]]) -> None:
|
||||
self.path = path
|
||||
self.annotations = annotations
|
||||
|
||||
|
||||
def generate_annotated_html(
|
||||
html_fnam: str, result: BuildResult, modules: dict[str, ModuleIR], mapper: Mapper
|
||||
) -> None:
|
||||
annotations = []
|
||||
for mod, mod_ir in modules.items():
|
||||
path = result.graph[mod].path
|
||||
tree = result.graph[mod].tree
|
||||
assert tree is not None
|
||||
annotations.append(
|
||||
generate_annotations(path or "<source>", tree, mod_ir, result.types, mapper)
|
||||
)
|
||||
html = generate_html_report(annotations)
|
||||
with open(html_fnam, "w") as f:
|
||||
f.write(html)
|
||||
|
||||
formatter = FancyFormatter(sys.stdout, sys.stderr, False)
|
||||
formatted = formatter.style(os.path.abspath(html_fnam), "none", underline=True, bold=True)
|
||||
print(f"\nWrote {formatted} -- open in browser to view\n")
|
||||
|
||||
|
||||
def generate_annotations(
|
||||
path: str, tree: MypyFile, ir: ModuleIR, type_map: dict[Expression, Type], mapper: Mapper
|
||||
) -> AnnotatedSource:
|
||||
anns = {}
|
||||
for func_ir in ir.functions:
|
||||
anns.update(function_annotations(func_ir, tree))
|
||||
visitor = ASTAnnotateVisitor(type_map, mapper)
|
||||
for defn in tree.defs:
|
||||
defn.accept(visitor)
|
||||
anns.update(visitor.anns)
|
||||
for line in visitor.ignored_lines:
|
||||
if line in anns:
|
||||
del anns[line]
|
||||
return AnnotatedSource(path, anns)
|
||||
|
||||
|
||||
def function_annotations(func_ir: FuncIR, tree: MypyFile) -> dict[int, list[Annotation]]:
|
||||
"""Generate annotations based on mypyc IR."""
|
||||
# TODO: check if func_ir.line is -1
|
||||
anns: dict[int, list[Annotation]] = {}
|
||||
for block in func_ir.blocks:
|
||||
for op in block.ops:
|
||||
if isinstance(op, CallC):
|
||||
name = op.function_name
|
||||
ann: str | Annotation | None = None
|
||||
if name == "CPyObject_GetAttr":
|
||||
attr_name = get_str_literal(op.args[1])
|
||||
if attr_name in ("__prepare__", "GeneratorExit", "StopIteration"):
|
||||
# These attributes are internal to mypyc/CPython, and/or accessed
|
||||
# implicitly in generated code. The user has little control over
|
||||
# them.
|
||||
ann = None
|
||||
elif attr_name:
|
||||
ann = f'Get non-native attribute "{attr_name}".'
|
||||
else:
|
||||
ann = "Dynamic attribute lookup."
|
||||
elif name == "PyObject_SetAttr":
|
||||
attr_name = get_str_literal(op.args[1])
|
||||
if attr_name == "__mypyc_attrs__":
|
||||
# This is set implicitly and can't be avoided.
|
||||
ann = None
|
||||
elif attr_name:
|
||||
ann = f'Set non-native attribute "{attr_name}".'
|
||||
else:
|
||||
ann = "Dynamic attribute set."
|
||||
elif name == "PyObject_VectorcallMethod":
|
||||
method_name = get_str_literal(op.args[0])
|
||||
if method_name:
|
||||
ann = f'Call non-native method "{method_name}" (it may be defined in a non-native class, or decorated).'
|
||||
else:
|
||||
ann = "Dynamic method call."
|
||||
elif name in op_hints:
|
||||
ann = op_hints[name]
|
||||
elif name in ("CPyDict_GetItem", "CPyDict_SetItem"):
|
||||
if (
|
||||
isinstance(op.args[0], LoadStatic)
|
||||
and isinstance(op.args[1], LoadLiteral)
|
||||
and func_ir.name != "__top_level__"
|
||||
):
|
||||
load = op.args[0]
|
||||
name = str(op.args[1].value)
|
||||
sym = tree.names.get(name)
|
||||
if (
|
||||
sym
|
||||
and sym.node
|
||||
and load.namespace == "static"
|
||||
and load.identifier == "globals"
|
||||
):
|
||||
if sym.node.fullname in stdlib_hints:
|
||||
ann = stdlib_hints[sym.node.fullname]
|
||||
elif isinstance(sym.node, Var):
|
||||
ann = (
|
||||
f'Access global "{name}" through namespace '
|
||||
+ "dictionary (hint: access is faster if you can make it Final)."
|
||||
)
|
||||
else:
|
||||
ann = f'Access "{name}" through global namespace dictionary.'
|
||||
if ann:
|
||||
if isinstance(ann, str):
|
||||
ann = Annotation(ann)
|
||||
anns.setdefault(op.line, []).append(ann)
|
||||
return anns
|
||||
|
||||
|
||||
class ASTAnnotateVisitor(TraverserVisitor):
|
||||
"""Generate annotations from mypy AST and inferred types."""
|
||||
|
||||
def __init__(self, type_map: dict[Expression, Type], mapper: Mapper) -> None:
|
||||
self.anns: dict[int, list[Annotation]] = {}
|
||||
self.ignored_lines: set[int] = set()
|
||||
self.func_depth = 0
|
||||
self.type_map = type_map
|
||||
self.mapper = mapper
|
||||
|
||||
def visit_func_def(self, o: FuncDef, /) -> None:
|
||||
if self.func_depth > 0:
|
||||
self.annotate(
|
||||
o,
|
||||
"A nested function object is allocated each time statement is executed. "
|
||||
+ "A module-level function would be faster.",
|
||||
)
|
||||
self.func_depth += 1
|
||||
super().visit_func_def(o)
|
||||
self.func_depth -= 1
|
||||
|
||||
def visit_for_stmt(self, o: ForStmt, /) -> None:
|
||||
self.check_iteration([o.expr], "For loop")
|
||||
super().visit_for_stmt(o)
|
||||
|
||||
def visit_dictionary_comprehension(self, o: DictionaryComprehension, /) -> None:
|
||||
self.check_iteration(o.sequences, "Comprehension")
|
||||
super().visit_dictionary_comprehension(o)
|
||||
|
||||
def visit_generator_expr(self, o: GeneratorExpr, /) -> None:
|
||||
self.check_iteration(o.sequences, "Comprehension or generator")
|
||||
super().visit_generator_expr(o)
|
||||
|
||||
def check_iteration(self, expressions: list[Expression], kind: str) -> None:
|
||||
for expr in expressions:
|
||||
typ = self.get_type(expr)
|
||||
if isinstance(typ, AnyType):
|
||||
self.annotate(expr, f'{kind} uses generic operations (iterable has type "Any").')
|
||||
elif isinstance(typ, Instance) and typ.type.fullname in (
|
||||
"typing.Iterable",
|
||||
"typing.Iterator",
|
||||
"typing.Sequence",
|
||||
"typing.MutableSequence",
|
||||
):
|
||||
self.annotate(
|
||||
expr,
|
||||
f'{kind} uses generic operations (iterable has the abstract type "{typ.type.fullname}").',
|
||||
)
|
||||
|
||||
def visit_class_def(self, o: ClassDef, /) -> None:
|
||||
super().visit_class_def(o)
|
||||
if self.func_depth == 0:
|
||||
# Don't complain about base classes at top level
|
||||
for base in o.base_type_exprs:
|
||||
self.ignored_lines.add(base.line)
|
||||
|
||||
for s in o.defs.body:
|
||||
if isinstance(s, AssignmentStmt):
|
||||
# Don't complain about attribute initializers
|
||||
self.ignored_lines.add(s.line)
|
||||
elif isinstance(s, Decorator):
|
||||
# Don't complain about decorator definitions that generate some
|
||||
# dynamic operations. This is a bit heavy-handed.
|
||||
self.ignored_lines.add(s.func.line)
|
||||
|
||||
def visit_with_stmt(self, o: WithStmt, /) -> None:
|
||||
for expr in o.expr:
|
||||
if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr):
|
||||
node = expr.callee.node
|
||||
if isinstance(node, Decorator):
|
||||
if any(
|
||||
isinstance(d, RefExpr)
|
||||
and d.node
|
||||
and d.node.fullname == "contextlib.contextmanager"
|
||||
for d in node.decorators
|
||||
):
|
||||
self.annotate(
|
||||
expr,
|
||||
f'"{node.name}" uses @contextmanager, which is slow '
|
||||
+ "in compiled code. Use a native class with "
|
||||
+ '"__enter__" and "__exit__" methods instead.',
|
||||
priority=3,
|
||||
)
|
||||
super().visit_with_stmt(o)
|
||||
|
||||
def visit_assignment_stmt(self, o: AssignmentStmt, /) -> None:
|
||||
special_form = False
|
||||
if self.func_depth == 0:
|
||||
analyzed: Expression | None = o.rvalue
|
||||
if isinstance(o.rvalue, (CallExpr, IndexExpr, OpExpr)):
|
||||
analyzed = o.rvalue.analyzed
|
||||
if o.is_alias_def or isinstance(
|
||||
analyzed, (TypeVarExpr, NamedTupleExpr, TypedDictExpr, NewTypeExpr)
|
||||
):
|
||||
special_form = True
|
||||
if special_form:
|
||||
# TODO: Ignore all lines if multi-line
|
||||
self.ignored_lines.add(o.line)
|
||||
super().visit_assignment_stmt(o)
|
||||
|
||||
def visit_name_expr(self, o: NameExpr, /) -> None:
|
||||
if ann := stdlib_hints.get(o.fullname):
|
||||
self.annotate(o, ann)
|
||||
|
||||
def visit_member_expr(self, o: MemberExpr, /) -> None:
|
||||
super().visit_member_expr(o)
|
||||
if ann := stdlib_hints.get(o.fullname):
|
||||
self.annotate(o, ann)
|
||||
|
||||
def visit_call_expr(self, o: CallExpr, /) -> None:
|
||||
super().visit_call_expr(o)
|
||||
if (
|
||||
isinstance(o.callee, RefExpr)
|
||||
and o.callee.fullname == "builtins.isinstance"
|
||||
and len(o.args) == 2
|
||||
):
|
||||
arg = o.args[1]
|
||||
self.check_isinstance_arg(arg)
|
||||
elif isinstance(o.callee, RefExpr) and isinstance(o.callee.node, TypeInfo):
|
||||
info = o.callee.node
|
||||
class_ir = self.mapper.type_to_ir.get(info)
|
||||
if (class_ir and not class_ir.is_ext_class) or (
|
||||
class_ir is None and not info.fullname.startswith("builtins.")
|
||||
):
|
||||
self.annotate(
|
||||
o, f'Creating an instance of non-native class "{info.name}" ' + "is slow.", 2
|
||||
)
|
||||
elif class_ir and class_ir.is_augmented:
|
||||
self.annotate(
|
||||
o,
|
||||
f'Class "{info.name}" is only partially native, and '
|
||||
+ "constructing an instance is slow.",
|
||||
2,
|
||||
)
|
||||
elif isinstance(o.callee, RefExpr) and isinstance(o.callee.node, Decorator):
|
||||
decorator = o.callee.node
|
||||
if self.mapper.is_native_ref_expr(o.callee):
|
||||
self.annotate(
|
||||
o,
|
||||
f'Calling a decorated function ("{decorator.name}") is inefficient, even if it\'s native.',
|
||||
2,
|
||||
)
|
||||
|
||||
def check_isinstance_arg(self, arg: Expression) -> None:
|
||||
if isinstance(arg, RefExpr):
|
||||
if isinstance(arg.node, TypeInfo) and arg.node.is_protocol:
|
||||
self.annotate(
|
||||
arg, f'Expensive isinstance() check against protocol "{arg.node.name}".'
|
||||
)
|
||||
elif isinstance(arg, TupleExpr):
|
||||
for item in arg.items:
|
||||
self.check_isinstance_arg(item)
|
||||
|
||||
def visit_lambda_expr(self, o: LambdaExpr, /) -> None:
|
||||
self.annotate(
|
||||
o,
|
||||
"A new object is allocated for lambda each time it is evaluated. "
|
||||
+ "A module-level function would be faster.",
|
||||
)
|
||||
super().visit_lambda_expr(o)
|
||||
|
||||
def annotate(self, o: Node, ann: str | Annotation, priority: int = 1) -> None:
|
||||
if isinstance(ann, str):
|
||||
ann = Annotation(ann, priority=priority)
|
||||
self.anns.setdefault(o.line, []).append(ann)
|
||||
|
||||
def get_type(self, e: Expression) -> ProperType:
|
||||
t = self.type_map.get(e)
|
||||
if t:
|
||||
return get_proper_type(t)
|
||||
return AnyType(TypeOfAny.unannotated)
|
||||
|
||||
|
||||
def get_str_literal(v: Value) -> str | None:
|
||||
if isinstance(v, LoadLiteral) and isinstance(v.value, str):
|
||||
return v.value
|
||||
return None
|
||||
|
||||
|
||||
def get_max_prio(anns: list[Annotation]) -> list[Annotation]:
|
||||
max_prio = max(a.priority for a in anns)
|
||||
return [a for a in anns if a.priority == max_prio]
|
||||
|
||||
|
||||
def generate_html_report(sources: list[AnnotatedSource]) -> str:
|
||||
html = []
|
||||
html.append("<html>\n<head>\n")
|
||||
html.append(f"<style>\n{CSS}\n</style>")
|
||||
html.append("</head>\n")
|
||||
html.append("<body>\n")
|
||||
for src in sources:
|
||||
html.append(f"<h2><tt>{src.path}</tt></h2>\n")
|
||||
html.append("<pre>")
|
||||
src_anns = src.annotations
|
||||
with open(src.path) as f:
|
||||
lines = f.readlines()
|
||||
for i, s in enumerate(lines):
|
||||
s = escape(s)
|
||||
line = i + 1
|
||||
linenum = "%5d" % line
|
||||
if line in src_anns:
|
||||
anns = get_max_prio(src_anns[line])
|
||||
ann_strs = [a.message for a in anns]
|
||||
hint = " ".join(ann_strs)
|
||||
s = colorize_line(linenum, s, hint_html=hint)
|
||||
else:
|
||||
s = linenum + " " + s
|
||||
html.append(s)
|
||||
html.append("</pre>")
|
||||
|
||||
html.append("<script>")
|
||||
html.append(JS)
|
||||
html.append("</script>")
|
||||
|
||||
html.append("</body></html>\n")
|
||||
return "".join(html)
|
||||
|
||||
|
||||
def colorize_line(linenum: str, s: str, hint_html: str) -> str:
|
||||
hint_prefix = " " * len(linenum) + " "
|
||||
line_span = f'<div class="collapsible" style="background-color: #fcc">{linenum} {s}</div>'
|
||||
hint_div = f'<div class="content">{hint_prefix}<div class="hint">{hint_html}</div></div>'
|
||||
return f"<span>{line_span}{hint_div}</span>"
|
||||
Loading…
Add table
Add a link
Reference in a new issue