blob: b118c118fc8ed6fcbc3e9a0cd517171e572e9647 [file] [log] [blame]
Takuto Ikuta13466d02021-01-19 00:19:20 +00001#!/usr/bin/env python3
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -08002# Copyright (c) 2018 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Ho Cheung9f3b33a2023-10-29 05:43:58 +00005r"""Summarize the last ninja build, invoked with ninja's -C syntax.
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -08006
7This script is designed to be automatically run after each ninja build in
8order to summarize the build's performance. Making build performance information
9more visible should make it easier to notice anomalies and opportunities. To use
Bruce Dawsone186e502018-02-12 15:41:11 -080010this script on Windows just set NINJA_SUMMARIZE_BUILD=1 and run autoninja.bat.
11
12On Linux you can get autoninja to invoke this script using this syntax:
13
14$ NINJA_SUMMARIZE_BUILD=1 autoninja -C out/Default/ chrome
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080015
16You can also call this script directly using ninja's syntax to specify the
17output directory of interest:
18
Takuto Ikuta13466d02021-01-19 00:19:20 +000019> python3 post_build_ninja_summary.py -C out/Default
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080020
21Typical output looks like this:
22
23>ninja -C out\debug_component base
24ninja.exe -C out\debug_component base -j 960 -l 48 -d keeprsp
25ninja: Entering directory `out\debug_component'
26[1 processes, 1/1 @ 0.3/s : 3.092s ] Regenerating ninja files
Bruce Dawson0e9afd22019-11-08 18:57:08 +000027Longest build steps:
28 0.1 weighted s to build obj/base/base/trace_log.obj (6.7 s elapsed time)
29 0.2 weighted s to build nasm.exe, nasm.exe.pdb (0.2 s elapsed time)
30 0.3 weighted s to build obj/base/base/win_util.obj (12.4 s elapsed time)
31 1.2 weighted s to build base.dll, base.dll.lib (1.2 s elapsed time)
32Time by build-step type:
33 0.0 s weighted time to generate 6 .lib files (0.3 s elapsed time sum)
34 0.1 s weighted time to generate 25 .stamp files (1.2 s elapsed time sum)
35 0.2 s weighted time to generate 20 .o files (2.8 s elapsed time sum)
36 1.7 s weighted time to generate 4 PEFile (linking) files (2.0 s elapsed
37time sum)
38 23.9 s weighted time to generate 770 .obj files (974.8 s elapsed time sum)
3926.1 s weighted time (982.9 s elapsed time sum, 37.7x parallelism)
40839 build steps completed, average of 32.17/s
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080041
42If no gn clean has been done then results will be for the last non-NULL
43invocation of ninja. Ideas for future statistics, and implementations are
44appreciated.
45
46The "weighted" time is the elapsed time of each build step divided by the number
47of tasks that were running in parallel. This makes it an excellent approximation
48of how "important" a slow step was. A link that is entirely or mostly serialized
49will have a weighted time that is the same or similar to its elapsed time. A
50compile that runs in parallel with 999 other compiles will have a weighted time
51that is tiny."""
52
Daniel Bratella10370c2018-06-11 07:58:59 +000053import argparse
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080054import errno
Bruce Dawson34d90be2020-03-16 23:08:05 +000055import fnmatch
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080056import os
Bruce Dawson8babcb72023-09-11 15:53:27 +000057import subprocess
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080058import sys
59
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080060# The number of long build times to report:
61long_count = 10
62# The number of long times by extension to report
Peter Wene3a42b22020-04-08 21:39:26 +000063long_ext_count = 10
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080064
65
66class Target:
67 """Represents a single line read for a .ninja_log file."""
68 def __init__(self, start, end):
Bruce Dawson6be8afd2018-06-11 20:00:05 +000069 """Creates a target object by passing in the start/end times in seconds
70 as a float."""
71 self.start = start
72 self.end = end
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -080073 # A list of targets, appended to by the owner of this object.
74 self.targets = []
75 self.weighted_duration = 0.0
76
77 def Duration(self):
78 """Returns the task duration in seconds as a float."""
79 return self.end - self.start
80
81 def SetWeightedDuration(self, weighted_duration):
82 """Sets the duration, in seconds, passed in as a float."""
83 self.weighted_duration = weighted_duration
84
85 def WeightedDuration(self):
86 """Returns the task's weighted duration in seconds as a float.
87
88 Weighted_duration takes the elapsed time of the task and divides it
89 by how many other tasks were running at the same time. Thus, it
90 represents the approximate impact of this task on the total build time,
91 with serialized or serializing steps typically ending up with much
92 longer weighted durations.
93 weighted_duration should always be the same or shorter than duration.
94 """
95 # Allow for modest floating-point errors
96 epsilon = 0.000002
Takuto Ikutadf3e5772023-11-16 07:14:49 +000097 if self.weighted_duration > self.Duration() + epsilon:
98 print("%s > %s?" % (self.weighted_duration, self.Duration()))
99 assert self.weighted_duration <= self.Duration() + epsilon
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800100 return self.weighted_duration
101
102 def DescribeTargets(self):
103 """Returns a printable string that summarizes the targets."""
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800104 # Some build steps generate dozens of outputs - handle them sanely.
Bruce Dawson0081a122020-09-26 19:13:23 +0000105 # The max_length was chosen so that it can fit most of the long
106 # single-target names, while minimizing word wrapping.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000107 result = ", ".join(self.targets)
Bruce Dawson0081a122020-09-26 19:13:23 +0000108 max_length = 65
109 if len(result) > max_length:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000110 result = result[:max_length] + "..."
Bruce Dawson0081a122020-09-26 19:13:23 +0000111 return result
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800112
113
114# Copied with some modifications from ninjatracing
115def ReadTargets(log, show_all):
116 """Reads all targets from .ninja_log file |log_file|, sorted by duration.
117
118 The result is a list of Target objects."""
119 header = log.readline()
Junji Watanabec5182992023-10-13 04:52:02 +0000120 # Handle empty ninja_log gracefully by silently returning an empty list of
121 # targets.
Philipp Wollermannc3d210d2023-09-07 16:11:00 +0000122 if not header:
123 return []
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000124 assert header == "# ninja log v5\n", ("unrecognized ninja log version %r" %
Takuto Ikuta0b98e7c2023-11-16 06:56:28 +0000125 header)
Bruce Dawson6be8afd2018-06-11 20:00:05 +0000126 targets_dict = {}
127 last_end_seen = 0.0
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800128 for line in log:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000129 parts = line.strip().split("\t")
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800130 if len(parts) != 5:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000131 # If ninja.exe is rudely halted then the .ninja_log file may be
132 # corrupt. Silently continue.
133 continue
134 start, end, _, name, cmdhash = parts # Ignore restat.
Bruce Dawson6be8afd2018-06-11 20:00:05 +0000135 # Convert from integral milliseconds to float seconds.
136 start = int(start) / 1000.0
137 end = int(end) / 1000.0
138 if not show_all and end < last_end_seen:
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800139 # An earlier time stamp means that this step is the first in a new
140 # build, possibly an incremental build. Throw away the previous
141 # data so that this new build will be displayed independently.
Bruce Dawson6be8afd2018-06-11 20:00:05 +0000142 # This has to be done by comparing end times because records are
143 # written to the .ninja_log file when commands complete, so end
144 # times are guaranteed to be in order, but start times are not.
145 targets_dict = {}
146 target = None
147 if cmdhash in targets_dict:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000148 target = targets_dict[cmdhash]
149 if not show_all and (target.start != start or target.end != end):
150 # If several builds in a row just run one or two build steps
151 # then the end times may not go backwards so the last build may
152 # not be detected as such. However in many cases there will be a
153 # build step repeated in the two builds and the changed
154 # start/stop points for that command, identified by the hash,
155 # can be used to detect and reset the target dictionary.
156 targets_dict = {}
157 target = None
Bruce Dawson6be8afd2018-06-11 20:00:05 +0000158 if not target:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000159 targets_dict[cmdhash] = target = Target(start, end)
Bruce Dawson6be8afd2018-06-11 20:00:05 +0000160 last_end_seen = end
161 target.targets.append(name)
Bruce Dawson0081a122020-09-26 19:13:23 +0000162 return list(targets_dict.values())
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800163
164
Bruce Dawson34d90be2020-03-16 23:08:05 +0000165def GetExtension(target, extra_patterns):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000166 """Return the file extension that best represents a target.
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800167
Takuto Ikuta0b98e7c2023-11-16 06:56:28 +0000168 For targets that generate multiple outputs it is important to return a
169 consistent 'canonical' extension. Ultimately the goal is to group build
170 steps by type."""
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000171 for output in target.targets:
172 if extra_patterns:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000173 for fn_pattern in extra_patterns.split(";"):
174 if fnmatch.fnmatch(output, "*" + fn_pattern + "*"):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000175 return fn_pattern
176 # Not a true extension, but a good grouping.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000177 if output.endswith("type_mappings"):
178 extension = "type_mappings"
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000179 break
Peter Wene3a42b22020-04-08 21:39:26 +0000180
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000181 # Capture two extensions if present. For example: file.javac.jar should
182 # be distinguished from file.interface.jar.
183 root, ext1 = os.path.splitext(output)
184 _, ext2 = os.path.splitext(root)
185 extension = ext2 + ext1 # Preserve the order in the file name.
Peter Wene3a42b22020-04-08 21:39:26 +0000186
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000187 if len(extension) == 0:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000188 extension = "(no extension found)"
Peter Wene3a42b22020-04-08 21:39:26 +0000189
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000190 if ext1 in [".pdb", ".dll", ".exe"]:
191 extension = "PEFile (linking)"
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000192 # Make sure that .dll and .exe are grouped together and that the
193 # .dll.lib files don't cause these to be listed as libraries
194 break
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000195 if ext1 in [".so", ".TOC"]:
196 extension = ".so (linking)"
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000197 # Attempt to identify linking, avoid identifying as '.TOC'
198 break
199 # Make sure .obj files don't get categorized as mojo files
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000200 if ext1 in [".obj", ".o"]:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000201 break
202 # Jars are the canonical output of java targets.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000203 if ext1 == ".jar":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000204 break
205 # Normalize all mojo related outputs to 'mojo'.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000206 if output.count(".mojom") > 0:
207 extension = "mojo"
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000208 break
209 return extension
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800210
211
Bruce Dawson58da0c82023-02-23 19:40:12 +0000212def SummarizeEntries(entries, extra_step_types, elapsed_time_sorting):
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800213 """Print a summary of the passed in list of Target objects."""
214
215 # Create a list that is in order by time stamp and has entries for the
216 # beginning and ending of each build step (one time stamp may have multiple
217 # entries due to multiple steps starting/stopping at exactly the same time).
218 # Iterate through this list, keeping track of which tasks are running at all
219 # times. At each time step calculate a running total for weighted time so
220 # that when each task ends its own weighted time can easily be calculated.
221 task_start_stop_times = []
222
223 earliest = -1
224 latest = 0
225 total_cpu_time = 0
226 for target in entries:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000227 if earliest < 0 or target.start < earliest:
228 earliest = target.start
229 if target.end > latest:
230 latest = target.end
231 total_cpu_time += target.Duration()
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000232 task_start_stop_times.append((target.start, "start", target))
233 task_start_stop_times.append((target.end, "stop", target))
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800234 length = latest - earliest
235 weighted_total = 0.0
236
Bruce Dawson0081a122020-09-26 19:13:23 +0000237 # Sort by the time/type records and ignore |target|
238 task_start_stop_times.sort(key=lambda times: times[:2])
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800239 # Now we have all task start/stop times sorted by when they happen. If a
240 # task starts and stops on the same time stamp then the start will come
241 # first because of the alphabet, which is important for making this work
242 # correctly.
243 # Track the tasks which are currently running.
244 running_tasks = {}
245 # Record the time we have processed up to so we know how to calculate time
246 # deltas.
247 last_time = task_start_stop_times[0][0]
248 # Track the accumulated weighted time so that it can efficiently be added
249 # to individual tasks.
250 last_weighted_time = 0.0
251 # Scan all start/stop events.
252 for event in task_start_stop_times:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000253 time, action_name, target = event
254 # Accumulate weighted time up to now.
255 num_running = len(running_tasks)
256 if num_running > 0:
257 # Update the total weighted time up to this moment.
258 last_weighted_time += (time - last_time) / float(num_running)
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000259 if action_name == "start":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000260 # Record the total weighted task time when this task starts.
261 running_tasks[target] = last_weighted_time
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000262 if action_name == "stop":
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000263 # Record the change in the total weighted task time while this task
264 # ran.
265 weighted_duration = last_weighted_time - running_tasks[target]
266 target.SetWeightedDuration(weighted_duration)
267 weighted_total += weighted_duration
268 del running_tasks[target]
269 last_time = time
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000270 assert len(running_tasks) == 0
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800271
272 # Warn if the sum of weighted times is off by more than half a second.
273 if abs(length - weighted_total) > 500:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000274 print("Warning: Possible corrupt ninja log, results may be "
275 "untrustworthy. Length = %.3f, weighted total = %.3f" %
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000276 (length, weighted_total))
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800277
Bruce Dawson58da0c82023-02-23 19:40:12 +0000278 # Print the slowest build steps:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000279 print(" Longest build steps:")
Bruce Dawson58da0c82023-02-23 19:40:12 +0000280 if elapsed_time_sorting:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000281 entries.sort(key=lambda x: x.Duration())
Bruce Dawson58da0c82023-02-23 19:40:12 +0000282 else:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000283 entries.sort(key=lambda x: x.WeightedDuration())
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800284 for target in entries[-long_count:]:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000285 print(" %8.1f weighted s to build %s (%.1f s elapsed time)" % (
286 target.WeightedDuration(),
287 target.DescribeTargets(),
288 target.Duration(),
289 ))
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800290
291 # Sum up the time by file extension/type of the output file
292 count_by_ext = {}
293 time_by_ext = {}
294 weighted_time_by_ext = {}
295 # Scan through all of the targets to build up per-extension statistics.
296 for target in entries:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000297 extension = GetExtension(target, extra_step_types)
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000298 time_by_ext[extension] = (time_by_ext.get(extension, 0) +
299 target.Duration())
300 weighted_time_by_ext[extension] = (
301 weighted_time_by_ext.get(extension, 0) + target.WeightedDuration())
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000302 count_by_ext[extension] = count_by_ext.get(extension, 0) + 1
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800303
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000304 print(" Time by build-step type:")
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800305 # Copy to a list with extension name and total time swapped, to (time, ext)
Bruce Dawson58da0c82023-02-23 19:40:12 +0000306 if elapsed_time_sorting:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000307 weighted_time_by_ext_sorted = sorted(
308 (y, x) for (x, y) in time_by_ext.items())
Bruce Dawson58da0c82023-02-23 19:40:12 +0000309 else:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000310 weighted_time_by_ext_sorted = sorted(
311 (y, x) for (x, y) in weighted_time_by_ext.items())
Bruce Dawson58da0c82023-02-23 19:40:12 +0000312 # Print the slowest build target types:
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800313 for time, extension in weighted_time_by_ext_sorted[-long_ext_count:]:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000314 print(" %8.1f s weighted time to generate %d %s files "
315 "(%1.1f s elapsed time sum)" % (
316 time,
317 count_by_ext[extension],
318 extension,
319 time_by_ext[extension],
320 ))
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800321
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000322 print(" %.1f s weighted time (%.1f s elapsed time sum, %1.1fx "
323 "parallelism)" %
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000324 (length, total_cpu_time, total_cpu_time * 1.0 / length))
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000325 print(" %d build steps completed, average of %1.2f/s" %
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000326 (len(entries), len(entries) / (length)))
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800327
328
Daniel Bratella10370c2018-06-11 07:58:59 +0000329def main():
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000330 log_file = ".ninja_log"
331 metrics_file = "siso_metrics.json"
Daniel Bratella10370c2018-06-11 07:58:59 +0000332 parser = argparse.ArgumentParser()
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000333 parser.add_argument("-C", dest="build_directory", help="Build directory.")
Bruce Dawson34d90be2020-03-16 23:08:05 +0000334 parser.add_argument(
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000335 "-s",
336 "--step-types",
337 help="semicolon separated fnmatch patterns for build-step grouping",
338 )
Bruce Dawson58da0c82023-02-23 19:40:12 +0000339 parser.add_argument(
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000340 "-e",
341 "--elapsed_time_sorting",
Bruce Dawson58da0c82023-02-23 19:40:12 +0000342 default=False,
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000343 action="store_true",
344 help="Sort output by elapsed time instead of weighted time",
345 )
346 parser.add_argument("--log-file",
Daniel Bratella10370c2018-06-11 07:58:59 +0000347 help="specific ninja log file to analyze.")
348 args, _extra_args = parser.parse_known_args()
349 if args.build_directory:
350 log_file = os.path.join(args.build_directory, log_file)
Bruce Dawson8babcb72023-09-11 15:53:27 +0000351 metrics_file = os.path.join(args.build_directory, metrics_file)
Daniel Bratella10370c2018-06-11 07:58:59 +0000352 if args.log_file:
353 log_file = args.log_file
Bruce Dawson34d90be2020-03-16 23:08:05 +0000354 if not args.step_types:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000355 # Offer a convenient way to add extra step types automatically,
356 # including when this script is run by autoninja. get() returns None if
357 # the variable isn't set.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000358 args.step_types = os.environ.get("chromium_step_types")
Bruce Dawson34d90be2020-03-16 23:08:05 +0000359 if args.step_types:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000360 # Make room for the extra build types.
361 global long_ext_count
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000362 long_ext_count += len(args.step_types.split(";"))
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800363
Bruce Dawson8babcb72023-09-11 15:53:27 +0000364 if os.path.exists(metrics_file):
365 # Automatically handle summarizing siso builds.
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000366 cmd = ["siso.bat" if "win32" in sys.platform else "siso"]
367 cmd.extend(["metrics", "summary"])
Bruce Dawson8babcb72023-09-11 15:53:27 +0000368 if args.build_directory:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000369 cmd.extend(["-C", args.build_directory])
Bruce Dawson8babcb72023-09-11 15:53:27 +0000370 if args.step_types:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000371 cmd.extend(["--step_types", args.step_types])
Bruce Dawson8babcb72023-09-11 15:53:27 +0000372 if args.elapsed_time_sorting:
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000373 cmd.append("--elapsed_time_sorting")
Takuto Ikuta8efb05d2023-11-22 07:39:31 +0000374 subprocess.run(cmd, check=True)
375 return 0
376
377 try:
378 with open(log_file, "r") as log:
379 entries = ReadTargets(log, False)
380 if entries:
381 SummarizeEntries(entries, args.step_types,
382 args.elapsed_time_sorting)
383 except IOError:
384 print("Log file %r not found, no build summary created." % log_file)
385 return errno.ENOENT
Bruce Dawsonffc0c7c2018-02-07 18:00:48 -0800386
387
Takuto Ikutadf3e5772023-11-16 07:14:49 +0000388if __name__ == "__main__":
Daniel Bratella10370c2018-06-11 07:58:59 +0000389 sys.exit(main())