blob: 968a4412c912ea9da3161f9bc954c28134755431 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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
15package com.google.devtools.build.lib.runtime;
16
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.collect.ImmutableList;
19import com.google.common.collect.Maps;
nharmatabe6a4012017-08-23 21:09:11 +020020import com.google.common.eventbus.AllowConcurrentEvents;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.eventbus.Subscribe;
22import com.google.devtools.build.lib.actions.Action;
23import com.google.devtools.build.lib.actions.ActionCompletionEvent;
tomlu3d1a1942017-11-29 14:01:21 -080024import com.google.devtools.build.lib.actions.ActionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.devtools.build.lib.actions.ActionMiddlemanEvent;
26import com.google.devtools.build.lib.actions.ActionStartedEvent;
27import com.google.devtools.build.lib.actions.Actions;
28import com.google.devtools.build.lib.actions.Artifact;
29import com.google.devtools.build.lib.actions.CachedActionEvent;
philwo3bcb9f62017-09-06 12:52:21 +020030import com.google.devtools.build.lib.clock.Clock;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import java.util.ArrayList;
32import java.util.Collections;
laszlocsomor356e2242017-06-30 11:30:38 +020033import java.util.Comparator;
Janak Ramakrishnan68e55512016-09-28 00:06:31 +000034import java.util.Objects;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.util.PriorityQueue;
36import java.util.concurrent.ConcurrentMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import 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
45public 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();
tomlu3d1a1942017-11-29 14:01:21 -080052 private final ActionKeyContext actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053
54 /** Maximum critical path found. */
55 private C maxCriticalPath;
56 private final Clock clock;
janakr95d42802017-04-06 00:33:39 +000057 protected final boolean discardActions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058
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 */
laszlocsomor356e2242017-06-30 11:30:38 +020065 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 Nienhuysd08b27f2015-02-25 16:45:20 +010073
74 private final Object lock = new Object();
75
tomlu3d1a1942017-11-29 14:01:21 -080076 protected CriticalPathComputer(
77 ActionKeyContext actionKeyContext, Clock clock, boolean discardActions) {
78 this.actionKeyContext = actionKeyContext;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 this.clock = clock;
janakr95d42802017-04-06 00:33:39 +000080 this.discardActions = discardActions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010081 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 Pintoc5102fc2015-02-25 00:10:14 +000087 * @param relativeStartNanos time when the action started to run in nanos. Only mean to be used
88 * for computing time differences.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089 */
Miguel Alcon Pintoc5102fc2015-02-25 00:10:14 +000090 protected abstract C createComponent(Action action, long relativeStartNanos);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091
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
nharmatabe6a4012017-08-23 21:09:11 +0200106 @AllowConcurrentEvents
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100107 public void actionStarted(ActionStartedEvent event) {
108 Action action = event.getAction();
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000109 tryAddComponent(createComponent(action, event.getNanoTimeStart()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100110 }
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
nharmatabe6a4012017-08-23 21:09:11 +0200120 @AllowConcurrentEvents
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100121 public void middlemanAction(ActionMiddlemanEvent event) {
122 Action action = event.getAction();
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000123 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) {
janakr95d42802017-04-06 00:33:39 +0000134 Action newAction = Preconditions.checkNotNull(newComponent.maybeGetAction(), newComponent);
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000135 Artifact primaryOutput = newAction.getPrimaryOutput();
136 C storedComponent = outputArtifactToComponent.putIfAbsent(primaryOutput, newComponent);
137
138 if (storedComponent != null) {
janakr95d42802017-04-06 00:33:39 +0000139 Action oldAction = storedComponent.maybeGetAction();
140 if (oldAction != null) {
tomlu3d1a1942017-11-29 14:01:21 -0800141 if (!Actions.canBeShared(actionKeyContext, newAction, oldAction)) {
janakr95d42802017-04-06 00:33:39 +0000142 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100171 }
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000172 } else {
173 storedComponent = newComponent;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100174 }
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000175 // 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100187 }
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000188 return storedComponent;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100189 }
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
nharmatabe6a4012017-08-23 21:09:11 +0200197 @AllowConcurrentEvents
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100198 public void actionCached(CachedActionEvent event) {
199 Action action = event.getAction();
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000200 C component = tryAddComponent(createComponent(action, event.getNanoTimeStart()));
201 finalizeActionStat(event.getNanoTimeStart(), action, component);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100202 }
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
nharmatabe6a4012017-08-23 21:09:11 +0200209 @AllowConcurrentEvents
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100210 public void actionComplete(ActionCompletionEvent event) {
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000211 Action action = event.getAction();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100212 C component = Preconditions.checkNotNull(
213 outputArtifactToComponent.get(action.getPrimaryOutput()));
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000214 finalizeActionStat(event.getRelativeActionStartTime(), action, component);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100215 }
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 Pinto4a2b1b32015-03-20 17:18:01 +0000236 private void finalizeActionStat(long startTimeNanos, Action action, C component) {
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000237
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100238 for (Artifact input : action.getInputs()) {
239 addArtifactDependency(component, input);
240 }
241
janakr95d42802017-04-06 00:33:39 +0000242 boolean updated = component.finishActionExecution(startTimeNanos, clock.nanoTime());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100243 synchronized (lock) {
244 if (isBiggestCriticalPath(component)) {
245 maxCriticalPath = component;
246 }
247
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000248 // 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 Nienhuysd08b27f2015-02-25 16:45:20 +0100263 }
Miguel Alcon Pinto4a2b1b32015-03-20 17:18:01 +0000264 slowestComponents.add(component);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100265 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100266 }
267 }
268
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100269 private boolean isBiggestCriticalPath(C newCriticalPath) {
270 synchronized (lock) {
271 return maxCriticalPath == null
ccalvarinaf79eb42018-02-22 12:41:44 -0800272 || maxCriticalPath
273 .getAggregatedElapsedTime()
274 .compareTo(newCriticalPath.getAggregatedElapsedTime())
275 < 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 }
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) {
janakr95d42802017-04-06 00:33:39 +0000285 Action action = depComponent.maybeGetAction();
286 if (depComponent.isRunning && action != null) {
Janak Ramakrishnan68e55512016-09-28 00:06:31 +0000287 // 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 Ramakrishnan68e55512016-09-28 00:06:31 +0000289 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",
janakr95d42802017-04-06 00:33:39 +0000297 input, actionStats.prettyPrintAction(), action));
Janak Ramakrishnan68e55512016-09-28 00:06:31 +0000298 }
299 }
300 return;
301 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100302 actionStats.addDepInfo(depComponent);
303 }
304 }
305}
306