Skip to content

Commit f00dcf2

Browse files
authored
Merge pull request #445 from shubhbapna/time-measurement
feat: measure time for different steps in bootstrapping and build-sequence
2 parents 43b62eb + b9308d2 commit f00dcf2

File tree

7 files changed

+110
-22
lines changed

7 files changed

+110
-22
lines changed

src/fromager/build_environment.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313

1414
from packaging.requirements import Requirement
1515

16-
from . import (
17-
dependencies,
18-
external_commands,
19-
resolver,
20-
)
16+
from . import dependencies, external_commands, metrics, resolver
2117
from .requirements_file import RequirementType
2218

2319
if typing.TYPE_CHECKING:
@@ -201,6 +197,7 @@ def _createenv(self) -> None:
201197
logger.info("installed dependencies into build environment in %s", self.path)
202198

203199

200+
@metrics.timeit(description="prepare build environment")
204201
def prepare_build_environment(
205202
*,
206203
ctx: context.WorkContext,

src/fromager/commands/bootstrap.py

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
clickext,
1111
context,
1212
dependency_graph,
13+
metrics,
1314
progress,
1415
requirements_file,
1516
resolver,
@@ -169,6 +170,8 @@ def bootstrap(
169170
f"Could not produce a pip compatible constraints file. Please review {constraints_filename} for more details"
170171
)
171172

173+
metrics.summarize(wkctx, "Bootstrapping")
174+
172175

173176
def write_constraints_file(
174177
graph: dependency_graph.DependencyGraph,

src/fromager/commands/build.py

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
clickext,
2323
context,
2424
hooks,
25+
metrics,
2526
overrides,
2627
progress,
2728
server,
@@ -202,6 +203,7 @@ def build_sequence(
202203
)
203204
)
204205
print(wheel_filename)
206+
metrics.summarize(wkctx, "Building")
205207

206208
_summary(wkctx, entries)
207209

src/fromager/context.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import collections
12
import logging
23
import os
34
import pathlib
@@ -71,6 +72,12 @@ def __init__(
7172

7273
self.dependency_graph = dependency_graph.DependencyGraph()
7374

75+
# storing metrics
76+
self.time_store: dict[str, dict[str, float]] = collections.defaultdict(
77+
dict[str, float]
78+
)
79+
self.time_description_store: dict[str, str] = {}
80+
7481
@property
7582
def pip_wheel_server_args(self) -> list[str]:
7683
args = ["--index-url", self.wheel_server_url]

src/fromager/metrics.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import functools
2+
import logging
3+
import time
4+
import typing
5+
from datetime import timedelta
6+
7+
from packaging.requirements import Requirement
8+
from packaging.version import Version
9+
10+
from . import context
11+
12+
13+
def timeit(description: str) -> typing.Callable:
14+
def timeit_decorator(func: typing.Callable) -> typing.Callable:
15+
@functools.wraps(func)
16+
def wrapper_timeit(
17+
*,
18+
ctx: context.WorkContext,
19+
req: Requirement | None = None,
20+
**kwargs: typing.Any,
21+
) -> typing.Any:
22+
ctx.time_description_store[func.__name__] = description
23+
24+
start = time.perf_counter()
25+
ret = func(ctx=ctx, req=req, **kwargs)
26+
end = time.perf_counter()
27+
# get the logger for the module from which this function was called
28+
logger = logging.getLogger(func.__module__)
29+
version = (
30+
kwargs.get("version")
31+
or kwargs.get("dist_version")
32+
or kwargs.get("resolved_version")
33+
)
34+
if not version:
35+
version = _extract_version_from_return(ret)
36+
37+
runtime = end - start
38+
39+
if req:
40+
logger.debug(
41+
f"{req.name}: {func.__name__} took {timedelta(seconds=runtime)} to {description}"
42+
)
43+
else:
44+
logger.debug(
45+
f"{func.__name__} took {timedelta(seconds=runtime)} to {description}"
46+
)
47+
48+
if req and version:
49+
# store total time spent calling that function for a particular version of that req
50+
ctx.time_store[f"{req.name}=={version}"][func.__name__] = (
51+
ctx.time_store[f"{req.name}=={version}"].get(func.__name__, 0)
52+
+ runtime
53+
)
54+
55+
return ret
56+
57+
return wrapper_timeit
58+
59+
return timeit_decorator
60+
61+
62+
def summarize(ctx: context.WorkContext, prefix: str) -> None:
63+
logger = logging.getLogger(__name__)
64+
for req in sorted(ctx.time_store.keys()):
65+
total_time = sum(ctx.time_store[req].values())
66+
log = f"{prefix} {req} took {timedelta(seconds=total_time)} total"
67+
for fn_name, time_taken in ctx.time_store[req].items():
68+
log += f", {timedelta(seconds=time_taken)} to {ctx.time_description_store[fn_name]}"
69+
logger.info(log)
70+
71+
72+
def _extract_version_from_return(ret: typing.Any) -> Version | None:
73+
try:
74+
for r in ret:
75+
if isinstance(r, Version):
76+
return r
77+
except Exception:
78+
if isinstance(ret, Version):
79+
return ret
80+
return None

src/fromager/sources.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from . import (
1919
dependencies,
2020
external_commands,
21+
metrics,
2122
overrides,
2223
pyproject,
2324
requirements_file,
@@ -47,6 +48,7 @@ def get_source_type(ctx: context.WorkContext, req: Requirement) -> str:
4748
return str(source_type)
4849

4950

51+
@metrics.timeit(description="download source")
5052
def download_source(
5153
*,
5254
ctx: context.WorkContext,
@@ -72,6 +74,7 @@ def download_source(
7274
return source_path
7375

7476

77+
@metrics.timeit(description="resolve source")
7578
def resolve_source(
7679
*,
7780
ctx: context.WorkContext,
@@ -211,17 +214,6 @@ def download_url(
211214
return outfile
212215

213216

214-
def _sdist_root_name(source_filename: pathlib.Path) -> str:
215-
base_name = pathlib.Path(source_filename).name
216-
if base_name.endswith(".tar.gz"):
217-
ext_to_strip = ".tar.gz"
218-
elif base_name.endswith(".zip"):
219-
ext_to_strip = ".zip"
220-
else:
221-
raise ValueError(f"Do not know how to work with {source_filename}")
222-
return base_name[: -len(ext_to_strip)]
223-
224-
225217
def _takes_arg(f: typing.Callable, arg_name: str) -> bool:
226218
sig = inspect.signature(f)
227219
return arg_name in sig.parameters
@@ -342,6 +334,7 @@ def read_build_meta(unpack_dir: pathlib.Path) -> dict:
342334
return json.load(f)
343335

344336

337+
@metrics.timeit(description="prepare source")
345338
def prepare_source(
346339
*,
347340
ctx: context.WorkContext,
@@ -422,6 +415,7 @@ def prepare_new_source(
422415
vendor_rust.vendor_rust(req, source_root_dir)
423416

424417

418+
@metrics.timeit(description="build sdist")
425419
def build_sdist(
426420
*,
427421
ctx: context.WorkContext,

src/fromager/wheels.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import textwrap
1111
import typing
1212
import zipfile
13-
from datetime import datetime
1413
from urllib.parse import urlparse
1514

1615
import elfdeps
@@ -21,7 +20,14 @@
2120
from packaging.utils import BuildTag, canonicalize_name, parse_wheel_filename
2221
from packaging.version import Version
2322

24-
from . import external_commands, overrides, requirements_file, resolver, sources
23+
from . import (
24+
external_commands,
25+
metrics,
26+
overrides,
27+
requirements_file,
28+
resolver,
29+
sources,
30+
)
2531

2632
if typing.TYPE_CHECKING:
2733
from . import build_environment, context
@@ -131,6 +137,7 @@ def default_add_extra_metadata_to_wheels(
131137
raise NotImplementedError
132138

133139

140+
@metrics.timeit(description="add extra metadata to wheels")
134141
def add_extra_metadata_to_wheels(
135142
*,
136143
ctx: context.WorkContext,
@@ -246,6 +253,7 @@ def add_extra_metadata_to_wheels(
246253
raise FileNotFoundError("Could not locate new wheels file")
247254

248255

256+
@metrics.timeit(description="build wheels")
249257
def build_wheel(
250258
*,
251259
ctx: context.WorkContext,
@@ -281,8 +289,6 @@ def build_wheel(
281289
)
282290
extra_environ["DIST_EXTRA_CONFIG"] = str(dist_extra_cfg)
283291

284-
# Start the timer
285-
start = datetime.now().replace(microsecond=0)
286292
overrides.find_and_invoke(
287293
req.name,
288294
"build_wheel",
@@ -296,7 +302,6 @@ def build_wheel(
296302
version=version,
297303
)
298304
# End the timer
299-
end = datetime.now().replace(microsecond=0)
300305
wheels = list(ctx.wheels_build.glob("*.whl"))
301306
if len(wheels) != 1:
302307
raise FileNotFoundError("Could not locate built wheels")
@@ -309,7 +314,6 @@ def build_wheel(
309314
sdist_root_dir=sdist_root_dir,
310315
wheel_file=wheels[0],
311316
)
312-
logger.info(f"{req.name}: built wheel '{wheel}' in {end - start}")
313317
return wheel
314318

315319

@@ -387,6 +391,7 @@ def get_wheel_server_urls(ctx: context.WorkContext, req: Requirement) -> list[st
387391
return servers
388392

389393

394+
@metrics.timeit(description="resolve wheel")
390395
def resolve_prebuilt_wheel(
391396
*,
392397
ctx: context.WorkContext,

0 commit comments

Comments
 (0)