Skip to content

Commit 56f6b77

Browse files
author
Shubh Bapna
committed
measure time for different steps in bootstrapping and build-sequence
Signed-off-by: Shubh Bapna <[email protected]>
1 parent 0fdcd46 commit 56f6b77

File tree

6 files changed

+86
-27
lines changed

6 files changed

+86
-27
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:
@@ -198,6 +194,7 @@ def _createenv(self) -> None:
198194
logger.info("installed dependencies into build environment in %s", self.path)
199195

200196

197+
@metrics.timeit(description="prepare build environment")
201198
def prepare_build_environment(
202199
*,
203200
ctx: context.WorkContext,

src/fromager/commands/bootstrap.py

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
clickext,
1010
context,
1111
dependency_graph,
12+
metrics,
1213
progress,
1314
requirements_file,
1415
resolver,
@@ -162,6 +163,8 @@ def bootstrap(
162163
f"Could not produce a pip compatible constraints file. Please review {constraints_filename} for more details"
163164
)
164165

166+
metrics.summarize("Bootstrapping")
167+
165168

166169
def write_constraints_file(
167170
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,
@@ -191,6 +192,7 @@ def build_sequence(
191192
)
192193
)
193194
print(wheel_filename)
195+
metrics.summarize("Building")
194196

195197
_summary(wkctx, entries)
196198

src/fromager/metrics.py

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

4748

49+
@metrics.timeit(description="download source")
4850
def download_source(
4951
*,
5052
ctx: context.WorkContext,
@@ -70,6 +72,7 @@ def download_source(
7072
return source_path
7173

7274

75+
@metrics.timeit(description="resolve source")
7376
def resolve_source(
7477
*,
7578
ctx: context.WorkContext,
@@ -204,17 +207,6 @@ def download_url(
204207
return outfile
205208

206209

207-
def _sdist_root_name(source_filename: pathlib.Path) -> str:
208-
base_name = pathlib.Path(source_filename).name
209-
if base_name.endswith(".tar.gz"):
210-
ext_to_strip = ".tar.gz"
211-
elif base_name.endswith(".zip"):
212-
ext_to_strip = ".zip"
213-
else:
214-
raise ValueError(f"Do not know how to work with {source_filename}")
215-
return base_name[: -len(ext_to_strip)]
216-
217-
218210
def _takes_arg(f: typing.Callable, arg_name: str) -> bool:
219211
sig = inspect.signature(f)
220212
return arg_name in sig.parameters
@@ -354,6 +346,7 @@ def read_build_meta(unpack_dir: pathlib.Path) -> dict:
354346
return json.load(f)
355347

356348

349+
@metrics.timeit(description="prepare source")
357350
def prepare_source(
358351
*,
359352
ctx: context.WorkContext,
@@ -434,6 +427,7 @@ def prepare_new_source(
434427
vendor_rust.vendor_rust(req, source_root_dir)
435428

436429

430+
@metrics.timeit(description="build sdist")
437431
def build_sdist(
438432
*,
439433
ctx: context.WorkContext,

src/fromager/wheels.py

+4-11
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
@@ -19,12 +18,7 @@
1918
from packaging.utils import canonicalize_name, parse_wheel_filename
2019
from packaging.version import Version
2120

22-
from . import (
23-
external_commands,
24-
overrides,
25-
resolver,
26-
sources,
27-
)
21+
from . import external_commands, metrics, overrides, resolver, sources
2822

2923
if typing.TYPE_CHECKING:
3024
from . import build_environment, context
@@ -115,6 +109,7 @@ def default_add_extra_metadata_to_wheels(
115109
raise NotImplementedError
116110

117111

112+
@metrics.timeit(description="add extra metadata to wheels")
118113
def add_extra_metadata_to_wheels(
119114
*,
120115
ctx: context.WorkContext,
@@ -235,6 +230,7 @@ def add_extra_metadata_to_wheels(
235230
raise FileNotFoundError("Could not locate new wheels file")
236231

237232

233+
@metrics.timeit(description="build wheels")
238234
def build_wheel(
239235
*,
240236
ctx: context.WorkContext,
@@ -270,8 +266,6 @@ def build_wheel(
270266
)
271267
extra_environ["DIST_EXTRA_CONFIG"] = str(dist_extra_cfg)
272268

273-
# Start the timer
274-
start = datetime.now().replace(microsecond=0)
275269
overrides.find_and_invoke(
276270
req.name,
277271
"build_wheel",
@@ -285,7 +279,6 @@ def build_wheel(
285279
version=version,
286280
)
287281
# End the timer
288-
end = datetime.now().replace(microsecond=0)
289282
wheels = list(ctx.wheels_build.glob("*.whl"))
290283
if len(wheels) != 1:
291284
raise FileNotFoundError("Could not locate built wheels")
@@ -298,7 +291,6 @@ def build_wheel(
298291
sdist_root_dir=sdist_root_dir,
299292
wheel_file=wheels[0],
300293
)
301-
logger.info(f"{req.name}: built wheel '{wheel}' in {end - start}")
302294
return wheel
303295

304296

@@ -380,6 +372,7 @@ def get_wheel_server_urls(ctx: context.WorkContext, req: Requirement) -> list[st
380372
return servers
381373

382374

375+
@metrics.timeit(description="resolve wheel")
383376
def resolve_prebuilt_wheel(
384377
*,
385378
ctx: context.WorkContext,

0 commit comments

Comments
 (0)