Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package com.google.devtools.build.lib.runtime; |
| 16 | |
tomlu | a155b53 | 2017-11-08 20:12:47 +0100 | [diff] [blame] | 17 | import com.google.common.base.Preconditions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 18 | import com.google.common.collect.ImmutableList; |
| 19 | import com.google.common.collect.Maps; |
nharmata | be6a401 | 2017-08-23 21:09:11 +0200 | [diff] [blame] | 20 | import com.google.common.eventbus.AllowConcurrentEvents; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 21 | import com.google.common.eventbus.Subscribe; |
| 22 | import com.google.devtools.build.lib.actions.Action; |
| 23 | import com.google.devtools.build.lib.actions.ActionCompletionEvent; |
tomlu | 3d1a194 | 2017-11-29 14:01:21 -0800 | [diff] [blame] | 24 | import com.google.devtools.build.lib.actions.ActionKeyContext; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 25 | import com.google.devtools.build.lib.actions.ActionMiddlemanEvent; |
| 26 | import com.google.devtools.build.lib.actions.ActionStartedEvent; |
| 27 | import com.google.devtools.build.lib.actions.Actions; |
| 28 | import com.google.devtools.build.lib.actions.Artifact; |
| 29 | import com.google.devtools.build.lib.actions.CachedActionEvent; |
philwo | 3bcb9f6 | 2017-09-06 12:52:21 +0200 | [diff] [blame] | 30 | import com.google.devtools.build.lib.clock.Clock; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 31 | import java.util.ArrayList; |
| 32 | import java.util.Collections; |
laszlocsomor | 356e224 | 2017-06-30 11:30:38 +0200 | [diff] [blame] | 33 | import java.util.Comparator; |
Janak Ramakrishnan | 68e5551 | 2016-09-28 00:06:31 +0000 | [diff] [blame] | 34 | import java.util.Objects; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 35 | import java.util.PriorityQueue; |
| 36 | import java.util.concurrent.ConcurrentMap; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 37 | import javax.annotation.concurrent.ThreadSafe; |
| 38 | |
| 39 | /** |
| 40 | * Computes the critical path in the action graph based on events published to the event bus. |
| 41 | * |
| 42 | * <p>After instantiation, this object needs to be registered on the event bus to work. |
| 43 | */ |
| 44 | @ThreadSafe |
| 45 | public abstract class CriticalPathComputer<C extends AbstractCriticalPathComponent<C>, |
| 46 | A extends AggregatedCriticalPath<C>> { |
| 47 | |
| 48 | /** Number of top actions to record. */ |
| 49 | static final int SLOWEST_COMPONENTS_SIZE = 30; |
| 50 | // outputArtifactToComponent is accessed from multiple event handlers. |
| 51 | protected final ConcurrentMap<Artifact, C> outputArtifactToComponent = Maps.newConcurrentMap(); |
tomlu | 3d1a194 | 2017-11-29 14:01:21 -0800 | [diff] [blame] | 52 | private final ActionKeyContext actionKeyContext; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 53 | |
| 54 | /** Maximum critical path found. */ |
| 55 | private C maxCriticalPath; |
| 56 | private final Clock clock; |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 57 | protected final boolean discardActions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 58 | |
| 59 | /** |
| 60 | * The list of slowest individual components, ignoring the time to build dependencies. |
| 61 | * |
| 62 | * <p>This data is a useful metric when running non highly incremental builds, where multiple |
| 63 | * tasks could run un parallel and critical path would only record the longest path. |
| 64 | */ |
laszlocsomor | 356e224 | 2017-06-30 11:30:38 +0200 | [diff] [blame] | 65 | private final PriorityQueue<C> slowestComponents = new PriorityQueue<>(SLOWEST_COMPONENTS_SIZE, |
| 66 | new Comparator<C>() { |
| 67 | @Override |
| 68 | public int compare(C o1, C o2) { |
| 69 | return Long.compare(o1.getElapsedTimeNanos(), o2.getElapsedTimeNanos()); |
| 70 | } |
| 71 | } |
| 72 | ); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 73 | |
| 74 | private final Object lock = new Object(); |
| 75 | |
tomlu | 3d1a194 | 2017-11-29 14:01:21 -0800 | [diff] [blame] | 76 | protected CriticalPathComputer( |
| 77 | ActionKeyContext actionKeyContext, Clock clock, boolean discardActions) { |
| 78 | this.actionKeyContext = actionKeyContext; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 79 | this.clock = clock; |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 80 | this.discardActions = discardActions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 81 | maxCriticalPath = null; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Creates a critical path component for an action. |
| 86 | * @param action the action for the critical path component |
Miguel Alcon Pinto | c5102fc | 2015-02-25 00:10:14 +0000 | [diff] [blame] | 87 | * @param relativeStartNanos time when the action started to run in nanos. Only mean to be used |
| 88 | * for computing time differences. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 89 | */ |
Miguel Alcon Pinto | c5102fc | 2015-02-25 00:10:14 +0000 | [diff] [blame] | 90 | protected abstract C createComponent(Action action, long relativeStartNanos); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 91 | |
| 92 | /** |
| 93 | * Return the critical path stats for the current command execution. |
| 94 | * |
| 95 | * <p>This method allows us to calculate lazily the aggregate statistics of the critical path, |
| 96 | * avoiding the memory and cpu penalty for doing it for all the actions executed. |
| 97 | */ |
| 98 | public abstract A aggregate(); |
| 99 | |
| 100 | /** |
| 101 | * Record an action that has started to run. |
| 102 | * |
| 103 | * @param event information about the started action |
| 104 | */ |
| 105 | @Subscribe |
nharmata | be6a401 | 2017-08-23 21:09:11 +0200 | [diff] [blame] | 106 | @AllowConcurrentEvents |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 107 | public void actionStarted(ActionStartedEvent event) { |
| 108 | Action action = event.getAction(); |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 109 | tryAddComponent(createComponent(action, event.getNanoTimeStart())); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Record a middleman action execution. Even if middleman are almost instant, we record them |
| 114 | * because they depend on other actions and we need them for constructing the critical path. |
| 115 | * |
| 116 | * <p>For some rules with incorrect configuration transitions we might get notified several times |
| 117 | * for the same middleman. This should only happen if the actions are shared. |
| 118 | */ |
| 119 | @Subscribe |
nharmata | be6a401 | 2017-08-23 21:09:11 +0200 | [diff] [blame] | 120 | @AllowConcurrentEvents |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 121 | public void middlemanAction(ActionMiddlemanEvent event) { |
| 122 | Action action = event.getAction(); |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 123 | C component = tryAddComponent(createComponent(action, event.getNanoTimeStart())); |
| 124 | finalizeActionStat(event.getNanoTimeStart(), action, component); |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Try to add the component to the map of critical path components. If there is an existing |
| 129 | * component for its primary output it uses that to update the rest of the outputs. |
| 130 | * |
| 131 | * @return The component to be used for updating the time stats. |
| 132 | */ |
| 133 | private C tryAddComponent(C newComponent) { |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 134 | Action newAction = Preconditions.checkNotNull(newComponent.maybeGetAction(), newComponent); |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 135 | Artifact primaryOutput = newAction.getPrimaryOutput(); |
| 136 | C storedComponent = outputArtifactToComponent.putIfAbsent(primaryOutput, newComponent); |
| 137 | |
| 138 | if (storedComponent != null) { |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 139 | Action oldAction = storedComponent.maybeGetAction(); |
| 140 | if (oldAction != null) { |
tomlu | 3d1a194 | 2017-11-29 14:01:21 -0800 | [diff] [blame] | 141 | if (!Actions.canBeShared(actionKeyContext, newAction, oldAction)) { |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 142 | throw new IllegalStateException( |
| 143 | "Duplicate output artifact found for unsharable actions." |
| 144 | + "This can happen if a previous event registered the action.\n" |
| 145 | + "Old action: " |
| 146 | + oldAction |
| 147 | + "\n\nNew action: " |
| 148 | + newAction |
| 149 | + "\n\nArtifact: " |
| 150 | + primaryOutput |
| 151 | + "\n"); |
| 152 | } |
| 153 | } else { |
| 154 | String mnemonic = storedComponent.getMnemonic(); |
| 155 | String prettyPrint = storedComponent.prettyPrintAction(); |
| 156 | if (!newAction.getMnemonic().equals(mnemonic) |
| 157 | || !newAction.prettyPrint().equals(prettyPrint)) { |
| 158 | throw new IllegalStateException( |
| 159 | "Duplicate output artifact found for unsharable actions." |
| 160 | + "This can happen if a previous event registered the action.\n" |
| 161 | + "Old action mnemonic and prettyPrint: " |
| 162 | + mnemonic |
| 163 | + ", " |
| 164 | + prettyPrint |
| 165 | + "\n\nNew action: " |
| 166 | + newAction |
| 167 | + "\n\nArtifact: " |
| 168 | + primaryOutput |
| 169 | + "\n"); |
| 170 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 171 | } |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 172 | } else { |
| 173 | storedComponent = newComponent; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 174 | } |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 175 | // Try to insert the existing component for the rest of the outputs even if we failed to be |
| 176 | // the ones inserting the component so that at the end of this method we guarantee that all the |
| 177 | // outputs have a component. |
| 178 | for (Artifact output : newAction.getOutputs()) { |
| 179 | if (output == primaryOutput) { |
| 180 | continue; |
| 181 | } |
| 182 | C old = outputArtifactToComponent.putIfAbsent(output, storedComponent); |
| 183 | // If two actions run concurrently maybe we find a component by primary output but we are |
| 184 | // the first updating the rest of the outputs. |
| 185 | Preconditions.checkState(old == null || old == storedComponent, |
| 186 | "Inconsistent state for %s", newAction); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 187 | } |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 188 | return storedComponent; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Record an action that was not executed because it was in the (disk) cache. This is needed so |
| 193 | * that we can calculate correctly the dependencies tree if we have some cached actions in the |
| 194 | * middle of the critical path. |
| 195 | */ |
| 196 | @Subscribe |
nharmata | be6a401 | 2017-08-23 21:09:11 +0200 | [diff] [blame] | 197 | @AllowConcurrentEvents |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 198 | public void actionCached(CachedActionEvent event) { |
| 199 | Action action = event.getAction(); |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 200 | C component = tryAddComponent(createComponent(action, event.getNanoTimeStart())); |
| 201 | finalizeActionStat(event.getNanoTimeStart(), action, component); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 202 | } |
| 203 | |
| 204 | /** |
| 205 | * Records the elapsed time stats for the action. For each input artifact, it finds the real |
| 206 | * dependent artifacts and records the critical path stats. |
| 207 | */ |
| 208 | @Subscribe |
nharmata | be6a401 | 2017-08-23 21:09:11 +0200 | [diff] [blame] | 209 | @AllowConcurrentEvents |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 210 | public void actionComplete(ActionCompletionEvent event) { |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 211 | Action action = event.getAction(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 212 | C component = Preconditions.checkNotNull( |
| 213 | outputArtifactToComponent.get(action.getPrimaryOutput())); |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 214 | finalizeActionStat(event.getRelativeActionStartTime(), action, component); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 215 | } |
| 216 | |
| 217 | /** Maximum critical path component found during the build. */ |
| 218 | protected C getMaxCriticalPath() { |
| 219 | synchronized (lock) { |
| 220 | return maxCriticalPath; |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * The list of slowest individual components, ignoring the time to build dependencies. |
| 226 | */ |
| 227 | public ImmutableList<C> getSlowestComponents() { |
| 228 | ArrayList<C> list; |
| 229 | synchronized (lock) { |
| 230 | list = new ArrayList<>(slowestComponents); |
| 231 | Collections.sort(list, slowestComponents.comparator()); |
| 232 | } |
| 233 | return ImmutableList.copyOf(list).reverse(); |
| 234 | } |
| 235 | |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 236 | private void finalizeActionStat(long startTimeNanos, Action action, C component) { |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 237 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 238 | for (Artifact input : action.getInputs()) { |
| 239 | addArtifactDependency(component, input); |
| 240 | } |
| 241 | |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 242 | boolean updated = component.finishActionExecution(startTimeNanos, clock.nanoTime()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 243 | synchronized (lock) { |
| 244 | if (isBiggestCriticalPath(component)) { |
| 245 | maxCriticalPath = component; |
| 246 | } |
| 247 | |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 248 | // We do not want to fill slow components list with the same component. |
| 249 | // |
| 250 | // This might still insert a second copy of the component but only if the new self elapsed |
| 251 | // time is greater than the old time. That said, in practice this is not important, since |
| 252 | // this would happen when we have two concurrent shared actions and one is a cache hit |
| 253 | // because of the other one. In this case, the cache hit would not appear in the 30 slowest |
| 254 | // actions or we had a very fast build, so we do not care :). |
| 255 | if (updated) { |
| 256 | if (slowestComponents.size() == SLOWEST_COMPONENTS_SIZE) { |
| 257 | // The new component is faster than any of the slow components, avoid insertion. |
| 258 | if (slowestComponents.peek().getElapsedTimeNanos() >= component.getElapsedTimeNanos()) { |
| 259 | return; |
| 260 | } |
| 261 | // Remove the head element to make space (The fastest component in the queue). |
| 262 | slowestComponents.remove(); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 263 | } |
Miguel Alcon Pinto | 4a2b1b3 | 2015-03-20 17:18:01 +0000 | [diff] [blame] | 264 | slowestComponents.add(component); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 265 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 266 | } |
| 267 | } |
| 268 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 269 | private boolean isBiggestCriticalPath(C newCriticalPath) { |
| 270 | synchronized (lock) { |
| 271 | return maxCriticalPath == null |
ccalvarin | af79eb4 | 2018-02-22 12:41:44 -0800 | [diff] [blame] | 272 | || maxCriticalPath |
| 273 | .getAggregatedElapsedTime() |
| 274 | .compareTo(newCriticalPath.getAggregatedElapsedTime()) |
| 275 | < 0; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 276 | } |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * If "input" is a generated artifact, link its critical path to the one we're building. |
| 281 | */ |
| 282 | private void addArtifactDependency(C actionStats, Artifact input) { |
| 283 | C depComponent = outputArtifactToComponent.get(input); |
| 284 | if (depComponent != null) { |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 285 | Action action = depComponent.maybeGetAction(); |
| 286 | if (depComponent.isRunning && action != null) { |
Janak Ramakrishnan | 68e5551 | 2016-09-28 00:06:31 +0000 | [diff] [blame] | 287 | // Rare case that an action depending on a previously-cached shared action sees a different |
| 288 | // shared action that is in the midst of being an action cache hit. |
Janak Ramakrishnan | 68e5551 | 2016-09-28 00:06:31 +0000 | [diff] [blame] | 289 | for (Artifact actionOutput : action.getOutputs()) { |
| 290 | if (input.equals(actionOutput) |
| 291 | && Objects.equals(input.getArtifactOwner(), actionOutput.getArtifactOwner())) { |
| 292 | // As far as we can tell, this (currently running) action is the same action that |
| 293 | // produced input, not another shared action. This should be impossible. |
| 294 | throw new IllegalStateException( |
| 295 | String.format( |
| 296 | "Cannot add critical path stats when the action is not finished. %s. %s. %s", |
janakr | 95d4280 | 2017-04-06 00:33:39 +0000 | [diff] [blame] | 297 | input, actionStats.prettyPrintAction(), action)); |
Janak Ramakrishnan | 68e5551 | 2016-09-28 00:06:31 +0000 | [diff] [blame] | 298 | } |
| 299 | } |
| 300 | return; |
| 301 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 302 | actionStats.addDepInfo(depComponent); |
| 303 | } |
| 304 | } |
| 305 | } |
| 306 | |