|
"""Quick and dirty script to profile setuptools. |
|
|
|
Assumption: the script runs in a virtualenv with all the dependencies already |
|
installed. Nevertheless it also runs ``get_requires_for_...`` to include its |
|
time in the profiling because in most real world scenarios it will count. |
|
""" |
|
import argparse |
|
import cProfile |
|
import os |
|
import shutil |
|
from contextlib import contextmanager |
|
from tempfile import TemporaryDirectory |
|
|
|
import pyinstrument |
|
from setuptools import build_meta as backend |
|
|
|
|
|
CACHE = ["myproj.egg-info", "build"] |
|
|
|
|
|
def run_backend(output_dir, metadata_dir): |
|
backend.get_requires_for_build_sdist() |
|
sdist = backend.build_sdist(output_dir) |
|
print(f" ------------- sdist ----------- {sdist}") |
|
|
|
backend.get_requires_for_build_wheel() |
|
dist_info = backend.prepare_metadata_for_build_wheel(metadata_dir) |
|
dist_info_dir = os.path.join(metadata_dir, dist_info) |
|
wheel = backend.build_wheel(output_dir, None, dist_info_dir) |
|
print(f" ------------- wheel ----------- {wheel}") |
|
|
|
|
|
def run_experiment(profiler_context): |
|
for d in CACHE: |
|
if os.path.isdir(d): |
|
shutil.rmtree(d) |
|
|
|
name = profiler_context.func.__name__.replace("_context", "") |
|
print(f"======================== Exp. {name} =========================") |
|
|
|
with TemporaryDirectory() as tmp: |
|
output_dir = os.path.join(tmp, "dist") |
|
os.mkdir(output_dir) |
|
metadata_dir = os.path.join(tmp, "metadata") |
|
os.mkdir(metadata_dir) |
|
|
|
with profiler_context: |
|
run_backend(output_dir, metadata_dir) |
|
|
|
|
|
@contextmanager |
|
def cprofile_context(save_file): |
|
with cProfile.Profile() as pr: |
|
yield |
|
save_file = save_file or "cprofile.pstats" |
|
pr.dump_stats(save_file) |
|
print(f"\n\nINFO: Output saved to {save_file}\n\n") |
|
print( |
|
f" You can run `.venv/bin/snakeviz {save_file}` for visualisation\n" |
|
f" (don't forget to increase `Depth` to the maximum).\n\n" |
|
) |
|
|
|
|
|
@contextmanager |
|
def pyinstrument_context(save_file): |
|
with pyinstrument.Profiler() as profiler: |
|
yield |
|
|
|
profiler.print() |
|
html = profiler.output_html() |
|
save_file = save_file or "pyinstrument.html" |
|
with open(save_file, "w") as f: |
|
f.write(html) |
|
print(f"\n\nINFO: Output saved to {save_file}\n\n") |
|
|
|
|
|
if __name__ == "__main__": |
|
parser = argparse.ArgumentParser() |
|
parser.add_argument( |
|
"--cprofile", |
|
action="store_const", |
|
dest="context", |
|
const=cprofile_context, |
|
) |
|
parser.add_argument( |
|
"--pyinstrument", |
|
action="store_const", |
|
dest="context", |
|
const=pyinstrument_context, |
|
) |
|
parser.add_argument("-o", "--output-file") |
|
parser.set_defaults(context=cprofile_context) |
|
args = parser.parse_args() |
|
run_experiment(args.context(args.output_file)) |