Takuto Ikuta | 13466d0 | 2021-01-19 00:19:20 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 2 | # 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 Cheung | 9f3b33a | 2023-10-29 05:43:58 +0000 | [diff] [blame] | 5 | r"""Summarize the last ninja build, invoked with ninja's -C syntax. |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 6 | |
| 7 | This script is designed to be automatically run after each ninja build in |
| 8 | order to summarize the build's performance. Making build performance information |
| 9 | more visible should make it easier to notice anomalies and opportunities. To use |
Bruce Dawson | e186e50 | 2018-02-12 15:41:11 -0800 | [diff] [blame] | 10 | this script on Windows just set NINJA_SUMMARIZE_BUILD=1 and run autoninja.bat. |
| 11 | |
| 12 | On Linux you can get autoninja to invoke this script using this syntax: |
| 13 | |
| 14 | $ NINJA_SUMMARIZE_BUILD=1 autoninja -C out/Default/ chrome |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 15 | |
| 16 | You can also call this script directly using ninja's syntax to specify the |
| 17 | output directory of interest: |
| 18 | |
Takuto Ikuta | 13466d0 | 2021-01-19 00:19:20 +0000 | [diff] [blame] | 19 | > python3 post_build_ninja_summary.py -C out/Default |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 20 | |
| 21 | Typical output looks like this: |
| 22 | |
| 23 | >ninja -C out\debug_component base |
| 24 | ninja.exe -C out\debug_component base -j 960 -l 48 -d keeprsp |
| 25 | ninja: Entering directory `out\debug_component' |
| 26 | [1 processes, 1/1 @ 0.3/s : 3.092s ] Regenerating ninja files |
Bruce Dawson | 0e9afd2 | 2019-11-08 18:57:08 +0000 | [diff] [blame] | 27 | Longest 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) |
| 32 | Time 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 |
| 37 | time sum) |
| 38 | 23.9 s weighted time to generate 770 .obj files (974.8 s elapsed time sum) |
| 39 | 26.1 s weighted time (982.9 s elapsed time sum, 37.7x parallelism) |
| 40 | 839 build steps completed, average of 32.17/s |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 41 | |
| 42 | If no gn clean has been done then results will be for the last non-NULL |
| 43 | invocation of ninja. Ideas for future statistics, and implementations are |
| 44 | appreciated. |
| 45 | |
| 46 | The "weighted" time is the elapsed time of each build step divided by the number |
| 47 | of tasks that were running in parallel. This makes it an excellent approximation |
| 48 | of how "important" a slow step was. A link that is entirely or mostly serialized |
| 49 | will have a weighted time that is the same or similar to its elapsed time. A |
| 50 | compile that runs in parallel with 999 other compiles will have a weighted time |
| 51 | that is tiny.""" |
| 52 | |
Daniel Bratell | a10370c | 2018-06-11 07:58:59 +0000 | [diff] [blame] | 53 | import argparse |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 54 | import errno |
Bruce Dawson | 34d90be | 2020-03-16 23:08:05 +0000 | [diff] [blame] | 55 | import fnmatch |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 56 | import os |
Bruce Dawson | 8babcb7 | 2023-09-11 15:53:27 +0000 | [diff] [blame] | 57 | import subprocess |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 58 | import sys |
| 59 | |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 60 | # The number of long build times to report: |
| 61 | long_count = 10 |
| 62 | # The number of long times by extension to report |
Peter Wen | e3a42b2 | 2020-04-08 21:39:26 +0000 | [diff] [blame] | 63 | long_ext_count = 10 |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 64 | |
| 65 | |
| 66 | class Target: |
| 67 | """Represents a single line read for a .ninja_log file.""" |
| 68 | def __init__(self, start, end): |
Bruce Dawson | 6be8afd | 2018-06-11 20:00:05 +0000 | [diff] [blame] | 69 | """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 Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 73 | # 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 Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 97 | 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 Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 100 | return self.weighted_duration |
| 101 | |
| 102 | def DescribeTargets(self): |
| 103 | """Returns a printable string that summarizes the targets.""" |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 104 | # Some build steps generate dozens of outputs - handle them sanely. |
Bruce Dawson | 0081a12 | 2020-09-26 19:13:23 +0000 | [diff] [blame] | 105 | # The max_length was chosen so that it can fit most of the long |
| 106 | # single-target names, while minimizing word wrapping. |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 107 | result = ", ".join(self.targets) |
Bruce Dawson | 0081a12 | 2020-09-26 19:13:23 +0000 | [diff] [blame] | 108 | max_length = 65 |
| 109 | if len(result) > max_length: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 110 | result = result[:max_length] + "..." |
Bruce Dawson | 0081a12 | 2020-09-26 19:13:23 +0000 | [diff] [blame] | 111 | return result |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 112 | |
| 113 | |
| 114 | # Copied with some modifications from ninjatracing |
| 115 | def 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 Watanabe | c518299 | 2023-10-13 04:52:02 +0000 | [diff] [blame] | 120 | # Handle empty ninja_log gracefully by silently returning an empty list of |
| 121 | # targets. |
Philipp Wollermann | c3d210d | 2023-09-07 16:11:00 +0000 | [diff] [blame] | 122 | if not header: |
| 123 | return [] |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 124 | assert header == "# ninja log v5\n", ("unrecognized ninja log version %r" % |
Takuto Ikuta | 0b98e7c | 2023-11-16 06:56:28 +0000 | [diff] [blame] | 125 | header) |
Bruce Dawson | 6be8afd | 2018-06-11 20:00:05 +0000 | [diff] [blame] | 126 | targets_dict = {} |
| 127 | last_end_seen = 0.0 |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 128 | for line in log: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 129 | parts = line.strip().split("\t") |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 130 | if len(parts) != 5: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 131 | # 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 Dawson | 6be8afd | 2018-06-11 20:00:05 +0000 | [diff] [blame] | 135 | # 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 Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 139 | # 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 Dawson | 6be8afd | 2018-06-11 20:00:05 +0000 | [diff] [blame] | 142 | # 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 Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 148 | 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 Dawson | 6be8afd | 2018-06-11 20:00:05 +0000 | [diff] [blame] | 158 | if not target: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 159 | targets_dict[cmdhash] = target = Target(start, end) |
Bruce Dawson | 6be8afd | 2018-06-11 20:00:05 +0000 | [diff] [blame] | 160 | last_end_seen = end |
| 161 | target.targets.append(name) |
Bruce Dawson | 0081a12 | 2020-09-26 19:13:23 +0000 | [diff] [blame] | 162 | return list(targets_dict.values()) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 163 | |
| 164 | |
Bruce Dawson | 34d90be | 2020-03-16 23:08:05 +0000 | [diff] [blame] | 165 | def GetExtension(target, extra_patterns): |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 166 | """Return the file extension that best represents a target. |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 167 | |
Takuto Ikuta | 0b98e7c | 2023-11-16 06:56:28 +0000 | [diff] [blame] | 168 | 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 Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 171 | for output in target.targets: |
| 172 | if extra_patterns: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 173 | for fn_pattern in extra_patterns.split(";"): |
| 174 | if fnmatch.fnmatch(output, "*" + fn_pattern + "*"): |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 175 | return fn_pattern |
| 176 | # Not a true extension, but a good grouping. |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 177 | if output.endswith("type_mappings"): |
| 178 | extension = "type_mappings" |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 179 | break |
Peter Wen | e3a42b2 | 2020-04-08 21:39:26 +0000 | [diff] [blame] | 180 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 181 | # 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 Wen | e3a42b2 | 2020-04-08 21:39:26 +0000 | [diff] [blame] | 186 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 187 | if len(extension) == 0: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 188 | extension = "(no extension found)" |
Peter Wen | e3a42b2 | 2020-04-08 21:39:26 +0000 | [diff] [blame] | 189 | |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 190 | if ext1 in [".pdb", ".dll", ".exe"]: |
| 191 | extension = "PEFile (linking)" |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 192 | # 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 Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 195 | if ext1 in [".so", ".TOC"]: |
| 196 | extension = ".so (linking)" |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 197 | # Attempt to identify linking, avoid identifying as '.TOC' |
| 198 | break |
| 199 | # Make sure .obj files don't get categorized as mojo files |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 200 | if ext1 in [".obj", ".o"]: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 201 | break |
| 202 | # Jars are the canonical output of java targets. |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 203 | if ext1 == ".jar": |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 204 | break |
| 205 | # Normalize all mojo related outputs to 'mojo'. |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 206 | if output.count(".mojom") > 0: |
| 207 | extension = "mojo" |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 208 | break |
| 209 | return extension |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 210 | |
| 211 | |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 212 | def SummarizeEntries(entries, extra_step_types, elapsed_time_sorting): |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 213 | """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 Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 227 | 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 Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 232 | task_start_stop_times.append((target.start, "start", target)) |
| 233 | task_start_stop_times.append((target.end, "stop", target)) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 234 | length = latest - earliest |
| 235 | weighted_total = 0.0 |
| 236 | |
Bruce Dawson | 0081a12 | 2020-09-26 19:13:23 +0000 | [diff] [blame] | 237 | # Sort by the time/type records and ignore |target| |
| 238 | task_start_stop_times.sort(key=lambda times: times[:2]) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 239 | # 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 Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 253 | 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 Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 259 | if action_name == "start": |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 260 | # Record the total weighted task time when this task starts. |
| 261 | running_tasks[target] = last_weighted_time |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 262 | if action_name == "stop": |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 263 | # 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 Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 270 | assert len(running_tasks) == 0 |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 271 | |
| 272 | # Warn if the sum of weighted times is off by more than half a second. |
| 273 | if abs(length - weighted_total) > 500: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 274 | print("Warning: Possible corrupt ninja log, results may be " |
| 275 | "untrustworthy. Length = %.3f, weighted total = %.3f" % |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 276 | (length, weighted_total)) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 277 | |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 278 | # Print the slowest build steps: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 279 | print(" Longest build steps:") |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 280 | if elapsed_time_sorting: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 281 | entries.sort(key=lambda x: x.Duration()) |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 282 | else: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 283 | entries.sort(key=lambda x: x.WeightedDuration()) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 284 | for target in entries[-long_count:]: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 285 | print(" %8.1f weighted s to build %s (%.1f s elapsed time)" % ( |
| 286 | target.WeightedDuration(), |
| 287 | target.DescribeTargets(), |
| 288 | target.Duration(), |
| 289 | )) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 290 | |
| 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 Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 297 | extension = GetExtension(target, extra_step_types) |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 298 | 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 Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 302 | count_by_ext[extension] = count_by_ext.get(extension, 0) + 1 |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 303 | |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 304 | print(" Time by build-step type:") |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 305 | # Copy to a list with extension name and total time swapped, to (time, ext) |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 306 | if elapsed_time_sorting: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 307 | weighted_time_by_ext_sorted = sorted( |
| 308 | (y, x) for (x, y) in time_by_ext.items()) |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 309 | else: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 310 | weighted_time_by_ext_sorted = sorted( |
| 311 | (y, x) for (x, y) in weighted_time_by_ext.items()) |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 312 | # Print the slowest build target types: |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 313 | for time, extension in weighted_time_by_ext_sorted[-long_ext_count:]: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 314 | 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 Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 321 | |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 322 | print(" %.1f s weighted time (%.1f s elapsed time sum, %1.1fx " |
| 323 | "parallelism)" % |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 324 | (length, total_cpu_time, total_cpu_time * 1.0 / length)) |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 325 | print(" %d build steps completed, average of %1.2f/s" % |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 326 | (len(entries), len(entries) / (length))) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 327 | |
| 328 | |
Daniel Bratell | a10370c | 2018-06-11 07:58:59 +0000 | [diff] [blame] | 329 | def main(): |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 330 | log_file = ".ninja_log" |
| 331 | metrics_file = "siso_metrics.json" |
Daniel Bratell | a10370c | 2018-06-11 07:58:59 +0000 | [diff] [blame] | 332 | parser = argparse.ArgumentParser() |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 333 | parser.add_argument("-C", dest="build_directory", help="Build directory.") |
Bruce Dawson | 34d90be | 2020-03-16 23:08:05 +0000 | [diff] [blame] | 334 | parser.add_argument( |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 335 | "-s", |
| 336 | "--step-types", |
| 337 | help="semicolon separated fnmatch patterns for build-step grouping", |
| 338 | ) |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 339 | parser.add_argument( |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 340 | "-e", |
| 341 | "--elapsed_time_sorting", |
Bruce Dawson | 58da0c8 | 2023-02-23 19:40:12 +0000 | [diff] [blame] | 342 | default=False, |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 343 | action="store_true", |
| 344 | help="Sort output by elapsed time instead of weighted time", |
| 345 | ) |
| 346 | parser.add_argument("--log-file", |
Daniel Bratell | a10370c | 2018-06-11 07:58:59 +0000 | [diff] [blame] | 347 | 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 Dawson | 8babcb7 | 2023-09-11 15:53:27 +0000 | [diff] [blame] | 351 | metrics_file = os.path.join(args.build_directory, metrics_file) |
Daniel Bratell | a10370c | 2018-06-11 07:58:59 +0000 | [diff] [blame] | 352 | if args.log_file: |
| 353 | log_file = args.log_file |
Bruce Dawson | 34d90be | 2020-03-16 23:08:05 +0000 | [diff] [blame] | 354 | if not args.step_types: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 355 | # 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 Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 358 | args.step_types = os.environ.get("chromium_step_types") |
Bruce Dawson | 34d90be | 2020-03-16 23:08:05 +0000 | [diff] [blame] | 359 | if args.step_types: |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame] | 360 | # Make room for the extra build types. |
| 361 | global long_ext_count |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 362 | long_ext_count += len(args.step_types.split(";")) |
Bruce Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 363 | |
Bruce Dawson | 8babcb7 | 2023-09-11 15:53:27 +0000 | [diff] [blame] | 364 | if os.path.exists(metrics_file): |
| 365 | # Automatically handle summarizing siso builds. |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 366 | cmd = ["siso.bat" if "win32" in sys.platform else "siso"] |
| 367 | cmd.extend(["metrics", "summary"]) |
Bruce Dawson | 8babcb7 | 2023-09-11 15:53:27 +0000 | [diff] [blame] | 368 | if args.build_directory: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 369 | cmd.extend(["-C", args.build_directory]) |
Bruce Dawson | 8babcb7 | 2023-09-11 15:53:27 +0000 | [diff] [blame] | 370 | if args.step_types: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 371 | cmd.extend(["--step_types", args.step_types]) |
Bruce Dawson | 8babcb7 | 2023-09-11 15:53:27 +0000 | [diff] [blame] | 372 | if args.elapsed_time_sorting: |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 373 | cmd.append("--elapsed_time_sorting") |
Takuto Ikuta | 8efb05d | 2023-11-22 07:39:31 +0000 | [diff] [blame] | 374 | 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 Dawson | ffc0c7c | 2018-02-07 18:00:48 -0800 | [diff] [blame] | 386 | |
| 387 | |
Takuto Ikuta | df3e577 | 2023-11-16 07:14:49 +0000 | [diff] [blame] | 388 | if __name__ == "__main__": |
Daniel Bratell | a10370c | 2018-06-11 07:58:59 +0000 | [diff] [blame] | 389 | sys.exit(main()) |