Skip to content

Commit 85e48c7

Browse files
author
DrGeoff
committed
Add timing report
1 parent 7c65b21 commit 85e48c7

File tree

7 files changed

+515
-86
lines changed

7 files changed

+515
-86
lines changed

src/compiletools/apptools.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def add_base_arguments(cap, argv=None, variant=None):
8080
)
8181
cap.add("--version", action="version", version=__version__)
8282
cap.add("-?", action="help", help="Help")
83+
cap.add(
84+
"--time",
85+
action="store_true",
86+
default=False,
87+
help="Time the execution of each subprocess (similar to gcc/clang -time flag)",
88+
)
8389

8490
if rich_rst_available and sys.version_info.major == 3 and sys.version_info.minor >= 9:
8591
cap.add("--man", "--doc", action=DocumentationAction)

src/compiletools/cake.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import compiletools.findtargets
1616
import compiletools.jobs
1717
import compiletools.wrappedos
18+
import compiletools.timing
1819

1920

2021
class Cake(object):
@@ -220,23 +221,27 @@ def process(self):
220221
if self.args.verbose > 4:
221222
print("Early scanning. Cake determining targets and implied files")
222223

223-
self._createctobjs()
224+
with compiletools.timing.time_operation("create_ct_objects"):
225+
self._createctobjs()
224226
recreateobjs = False
225227
if self.args.static and len(self.args.static) == 1:
226-
self.args.static.extend(
227-
self.hunter.required_source_files(self.args.static[0])
228-
)
228+
with compiletools.timing.time_operation("static_source_hunting"):
229+
self.args.static.extend(
230+
self.hunter.required_source_files(self.args.static[0])
231+
)
229232
recreateobjs = True
230233

231234
if self.args.dynamic and len(self.args.dynamic) == 1:
232-
self.args.dynamic.extend(
233-
self.hunter.required_source_files(self.args.dynamic[0])
234-
)
235+
with compiletools.timing.time_operation("dynamic_source_hunting"):
236+
self.args.dynamic.extend(
237+
self.hunter.required_source_files(self.args.dynamic[0])
238+
)
235239
recreateobjs = True
236240

237241
if self.args.auto:
238-
findtargets = compiletools.findtargets.FindTargets(self.args)
239-
findtargets.process(self.args)
242+
with compiletools.timing.time_operation("target_detection"):
243+
findtargets = compiletools.findtargets.FindTargets(self.args)
244+
findtargets.process(self.args)
240245
recreateobjs = True
241246

242247
if recreateobjs:
@@ -246,15 +251,18 @@ def process(self):
246251
# targets. And recreate the ct objects
247252
if self.args.verbose > 4:
248253
print("Cake recreating objects and reparsing for second stage processing")
249-
compiletools.apptools.substitutions(self.args, verbose=0)
250-
self._createctobjs()
254+
with compiletools.timing.time_operation("recreate_ct_objects"):
255+
compiletools.apptools.substitutions(self.args, verbose=0)
256+
self._createctobjs()
251257

252258
compiletools.apptools.verboseprintconfig(self.args)
253259

254260
if self.args.filelist:
255-
self._callfilelist()
261+
with compiletools.timing.time_operation("filelist_generation"):
262+
self._callfilelist()
256263
else:
257-
self._callmakefile()
264+
with compiletools.timing.time_operation("makefile_creation_and_execution"):
265+
self._callmakefile()
258266

259267
def clear_cache(self):
260268
""" Only useful in test scenarios where you need to reset to a pristine state """
@@ -279,6 +287,10 @@ def main(argv=None):
279287

280288
args = compiletools.apptools.parseargs(cap, argv)
281289

290+
# Initialize timing if enabled
291+
timing_enabled = hasattr(args, 'time') and args.time
292+
compiletools.timing.initialize_timer(timing_enabled)
293+
282294
if not any([args.filename, args.static, args.dynamic, args.tests, args.auto]):
283295
print(
284296
"Nothing for cake to do. Did you mean cake --auto? Use cake --help for help."
@@ -290,7 +302,8 @@ def main(argv=None):
290302

291303
try:
292304
cake = Cake(args)
293-
cake.process()
305+
with compiletools.timing.time_operation("total_build_time"):
306+
cake.process()
294307
# For testing purposes, clear out the memcaches for the times when main is called more than once.
295308
cake.clear_cache()
296309
except IOError as ioe:
@@ -305,5 +318,9 @@ def main(argv=None):
305318
return 1
306319
else:
307320
raise
321+
322+
# Report timing information if enabled
323+
if timing_enabled:
324+
compiletools.timing.report_timing(args.verbose)
308325

309326
return 0

src/compiletools/headerdeps.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from compiletools.diskcache import diskcache
1414
from compiletools.simple_preprocessor import SimplePreprocessor
1515
from compiletools.file_analyzer import create_file_analyzer
16+
import compiletools.timing
1617

1718

1819

@@ -59,15 +60,16 @@ def _process_impl(self, realpath):
5960
def process(self, filename):
6061
"""Return the set of dependencies for a given filename"""
6162
realpath = compiletools.wrappedos.realpath(filename)
62-
try:
63-
result = self._process_impl(realpath)
64-
except IOError:
65-
# If there was any error the first time around, an error correcting removal would have occured
66-
# So strangely, the best thing to do is simply try again
67-
result = None
63+
with compiletools.timing.time_operation(f"header_dependency_analysis_{os.path.basename(filename)}"):
64+
try:
65+
result = self._process_impl(realpath)
66+
except IOError:
67+
# If there was any error the first time around, an error correcting removal would have occured
68+
# So strangely, the best thing to do is simply try again
69+
result = None
6870

69-
if not result:
70-
result = self._process_impl(realpath)
71+
if not result:
72+
result = self._process_impl(realpath)
7173

7274
return result
7375

@@ -175,25 +177,29 @@ def _process_conditional_compilation(self, text):
175177
@functools.lru_cache(maxsize=None)
176178
def _create_include_list(self, realpath):
177179
"""Internal use. Create the list of includes for the given file"""
178-
max_read_size = getattr(self.args, 'max_file_read_size', 0)
179-
180-
# Use FileAnalyzer for efficient file reading and pattern detection
181-
# Note: create_file_analyzer() handles StringZilla/Legacy fallback internally
182-
from compiletools.file_analyzer import create_file_analyzer
183-
analyzer = create_file_analyzer(realpath, max_read_size, self.args.verbose)
184-
analysis_result = analyzer.analyze()
185-
text = analysis_result.text
186-
187-
# Process conditional compilation - this updates self.defined_macros as it encounters #define
188-
processed_text = self._process_conditional_compilation(text)
189-
190-
# The pattern is intended to match all include statements but
191-
# not the ones with either C or C++ commented out.
192-
pat = re.compile(
193-
r'/\*.*?\*/|//.*?$|^[\s]*#include[\s]*["<][\s]*([\S]*)[\s]*[">]',
194-
re.MULTILINE | re.DOTALL,
195-
)
196-
return [group for group in pat.findall(processed_text) if group]
180+
with compiletools.timing.time_operation(f"include_analysis_{os.path.basename(realpath)}"):
181+
max_read_size = getattr(self.args, 'max_file_read_size', 0)
182+
183+
# Use FileAnalyzer for efficient file reading and pattern detection
184+
# Note: create_file_analyzer() handles StringZilla/Legacy fallback internally
185+
from compiletools.file_analyzer import create_file_analyzer
186+
with compiletools.timing.time_operation(f"file_read_{os.path.basename(realpath)}"):
187+
analyzer = create_file_analyzer(realpath, max_read_size, self.args.verbose)
188+
analysis_result = analyzer.analyze()
189+
text = analysis_result.text
190+
191+
# Process conditional compilation - this updates self.defined_macros as it encounters #define
192+
with compiletools.timing.time_operation(f"conditional_compilation_{os.path.basename(realpath)}"):
193+
processed_text = self._process_conditional_compilation(text)
194+
195+
# The pattern is intended to match all include statements but
196+
# not the ones with either C or C++ commented out.
197+
with compiletools.timing.time_operation(f"pattern_matching_{os.path.basename(realpath)}"):
198+
pat = re.compile(
199+
r'/\*.*?\*/|//.*?$|^[\s]*#include[\s]*["<][\s]*([\S]*)[\s]*[">]',
200+
re.MULTILINE | re.DOTALL,
201+
)
202+
return [group for group in pat.findall(processed_text) if group]
197203

198204
def _generate_tree_impl(self, realpath, node=None):
199205
"""Return a tree that describes the header includes

src/compiletools/magicflags.py

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import compiletools.configutils
1414
import compiletools.apptools
1515
from compiletools.file_analyzer import create_file_analyzer
16+
import compiletools.timing
1617

1718

1819
def create(args, headerdeps):
@@ -79,7 +80,8 @@ def readfile(self, filename):
7980
raise NotImplemented
8081

8182
def __call__(self, filename):
82-
return self.parse(filename)
83+
with compiletools.timing.time_operation(f"magic_flags_analysis_{os.path.basename(filename)}"):
84+
return self.parse(filename)
8385

8486
def _handle_source(self, flag, text):
8587
# Find the include before the //#SOURCE=
@@ -160,43 +162,49 @@ def _parse(self, filename):
160162
# When used in the "usual" fashion this is true.
161163
# However, it is possible to call directly so we must
162164
# ensure that the headerdeps exist manually.
163-
self._headerdeps.process(filename)
165+
with compiletools.timing.time_operation(f"magic_flags_headerdeps_{os.path.basename(filename)}"):
166+
self._headerdeps.process(filename)
164167

165-
text = self.readfile(filename)
166-
flagsforfilename = defaultdict(list)
167-
168-
for match in self.magicpattern.finditer(text):
169-
magic, flag = match.groups()
170-
171-
# If the magic was SOURCE then fix up the path in the flag
172-
if magic == "SOURCE":
173-
flag = self._handle_source(flag, text)
174-
175-
# If the magic was INCLUDE then modify that into the equivalent CPPFLAGS, CFLAGS, and CXXFLAGS
176-
if magic == "INCLUDE":
177-
extrafff = self._handle_include(flag)
178-
for key, values in extrafff.items():
179-
for value in values:
180-
flagsforfilename[key].append(value)
181-
182-
# If the magic was PKG-CONFIG then call pkg-config
183-
if magic == "PKG-CONFIG":
184-
extrafff = self._handle_pkg_config(flag)
185-
for key, values in extrafff.items():
186-
for value in values:
187-
flagsforfilename[key].append(value)
188-
189-
flagsforfilename[magic].append(flag)
190-
if self._args.verbose >= 5:
191-
print(
192-
"Using magic flag {0}={1} extracted from {2}".format(
193-
magic, flag, filename
194-
)
195-
)
168+
with compiletools.timing.time_operation(f"magic_flags_readfile_{os.path.basename(filename)}"):
169+
text = self.readfile(filename)
196170

197-
# Deduplicate all flags while preserving order
198-
for key in flagsforfilename:
199-
flagsforfilename[key] = compiletools.utils.ordered_unique(flagsforfilename[key])
171+
with compiletools.timing.time_operation(f"magic_flags_parsing_{os.path.basename(filename)}"):
172+
flagsforfilename = defaultdict(list)
173+
174+
for match in self.magicpattern.finditer(text):
175+
magic, flag = match.groups()
176+
177+
# If the magic was SOURCE then fix up the path in the flag
178+
if magic == "SOURCE":
179+
flag = self._handle_source(flag, text)
180+
181+
# If the magic was INCLUDE then modify that into the equivalent CPPFLAGS, CFLAGS, and CXXFLAGS
182+
if magic == "INCLUDE":
183+
with compiletools.timing.time_operation(f"magic_flags_include_handling_{flag}"):
184+
extrafff = self._handle_include(flag)
185+
for key, values in extrafff.items():
186+
for value in values:
187+
flagsforfilename[key].append(value)
188+
189+
# If the magic was PKG-CONFIG then call pkg-config
190+
if magic == "PKG-CONFIG":
191+
with compiletools.timing.time_operation(f"magic_flags_pkgconfig_{flag}"):
192+
extrafff = self._handle_pkg_config(flag)
193+
for key, values in extrafff.items():
194+
for value in values:
195+
flagsforfilename[key].append(value)
196+
197+
flagsforfilename[magic].append(flag)
198+
if self._args.verbose >= 5:
199+
print(
200+
"Using magic flag {0}={1} extracted from {2}".format(
201+
magic, flag, filename
202+
)
203+
)
204+
205+
# Deduplicate all flags while preserving order
206+
for key in flagsforfilename:
207+
flagsforfilename[key] = compiletools.utils.ordered_unique(flagsforfilename[key])
200208

201209
return flagsforfilename
202210

src/compiletools/makefile.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import compiletools.hunter
1414
import compiletools.namer
1515
import compiletools.configutils
16+
import compiletools.timing
1617

1718

1819
class Rule:
@@ -110,9 +111,24 @@ def _create_link_rule(
110111
all_magic_ldflags.extend(magic_flags.get("LINKFLAGS", [])) # For backward compatibility with cake
111112
all_magic_ldflags = compiletools.utils.ordered_unique(all_magic_ldflags)
112113
recipe = ""
113-
if self.args.verbose >= 1:
114+
115+
# Add timing for link operations if enabled
116+
link_timing_prefix = ""
117+
link_timing_suffix = ""
118+
if hasattr(self.args, 'time') and self.args.time and self.args.verbose >= 1:
119+
link_timing_prefix = "@START=$$(date +%s%N); "
120+
link_timing_suffix = f"; END=$$(date +%s%N); echo \"... linking {outputname} ($$(( ($$END-$$START)/1000000 ))ms)\""
121+
122+
if self.args.verbose >= 1 and not (hasattr(self.args, 'time') and self.args.time):
114123
recipe += " ".join(["+@echo ...", outputname, ";"])
115-
recipe += " ".join([linker, "-o", outputname] + list(object_names) + list(all_magic_ldflags) + [linkerflags])
124+
125+
link_flags = [linker, "-o", outputname] + list(object_names) + list(all_magic_ldflags) + [linkerflags]
126+
if hasattr(self.args, 'time') and self.args.time and self.args.verbose >= 3:
127+
# Add -time flag for detailed linker timing
128+
link_flags.insert(1, "-time")
129+
130+
link_cmd = " ".join(link_flags)
131+
recipe += link_timing_prefix + link_cmd + link_timing_suffix
116132
return Rule(target=outputname, prerequisites=allprerequisites, recipe=recipe)
117133

118134

@@ -561,19 +577,34 @@ def _create_compile_rule_for_source(self, filename):
561577

562578
magicflags = self.hunter.magicflags(filename)
563579
recipe = ""
564-
if self.args.verbose >= 1:
580+
581+
# Add timing wrapper if enabled
582+
timing_prefix = ""
583+
timing_suffix = ""
584+
if hasattr(self.args, 'time') and self.args.time and self.args.verbose >= 1:
585+
# Simple timing with elapsed time display
586+
timing_prefix = "@START=$$(date +%s%N); "
587+
timing_suffix = f"; END=$$(date +%s%N); echo \"... {filename} ($$(( ($$END-$$START)/1000000 ))ms)\""
588+
589+
if self.args.verbose >= 1 and not (hasattr(self.args, 'time') and self.args.time):
565590
recipe = " ".join(["@echo ...", filename, ";"])
591+
566592
magic_cpp_flags = magicflags.get("CPPFLAGS", [])
593+
compile_cmd = ""
567594
if compiletools.wrappedos.isc(filename):
568595
magic_c_flags = magicflags.get("CFLAGS", [])
569-
recipe += " ".join(
570-
[self.args.CC, self.args.CFLAGS] + list(magic_cpp_flags) + list(magic_c_flags) + ["-c", "-o", obj_name, filename]
571-
)
596+
compile_flags = [self.args.CC, self.args.CFLAGS] + list(magic_cpp_flags) + list(magic_c_flags)
597+
if hasattr(self.args, 'time') and self.args.time and self.args.verbose >= 3:
598+
compile_flags.append("-time")
599+
compile_cmd = " ".join(compile_flags + ["-c", "-o", obj_name, filename])
572600
else:
573601
magic_cxx_flags = magicflags.get("CXXFLAGS", [])
574-
recipe += " ".join(
575-
[self.args.CXX, self.args.CXXFLAGS] + list(magic_cpp_flags) + list(magic_cxx_flags) + ["-c", "-o", obj_name, filename]
576-
)
602+
compile_flags = [self.args.CXX, self.args.CXXFLAGS] + list(magic_cpp_flags) + list(magic_cxx_flags)
603+
if hasattr(self.args, 'time') and self.args.time and self.args.verbose >= 3:
604+
compile_flags.append("-time")
605+
compile_cmd = " ".join(compile_flags + ["-c", "-o", obj_name, filename])
606+
607+
recipe += timing_prefix + compile_cmd + timing_suffix
577608

578609
if self.args.verbose >= 3:
579610
print("Creating rule for ", obj_name)

0 commit comments

Comments
 (0)