aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionStatusReporter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/actions/ActionExecutionStatusReporter.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/ActionExecutionStatusReporter.java262
1 files changed, 262 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionStatusReporter.java b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionStatusReporter.java
new file mode 100644
index 0000000000..34aadc4a33
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionStatusReporter.java
@@ -0,0 +1,262 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.actions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.util.Clock;
+import com.google.devtools.build.lib.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nullable;
+
+/**
+ * Implements "Still waiting..." message functionality, displaying current status for "in-flight"
+ * actions. Used by the ParallelBuilder.
+ *
+ * TODO(bazel-team): (2010) It would be nice if "duplicated" actions (e.g. test shards and multiple
+ * test runs) were merged into the single line.
+ */
+@ThreadSafe
+public final class ActionExecutionStatusReporter {
+ // Maximum number of lines to output per each status category before truncation.
+ private static final int MAX_LINES = 10;
+
+ private final EventHandler eventHandler;
+ private final Executor executor;
+ private final EventBus eventBus;
+ private final Clock clock;
+
+ /**
+ * The status of each action "in flight", i.e. whose ExecuteBuildAction.call() method is active.
+ * Used for implementing the "still waiting" message.
+ */
+ private final Map<ActionMetadata, Pair<String, Long>> actionStatus =
+ new ConcurrentHashMap<>(100);
+
+ public static ActionExecutionStatusReporter create(EventHandler eventHandler) {
+ return create(eventHandler, null, null);
+ }
+
+ @VisibleForTesting
+ static ActionExecutionStatusReporter create(EventHandler eventHandler, Clock clock) {
+ return create(eventHandler, null, null, clock);
+ }
+
+ public static ActionExecutionStatusReporter create(EventHandler eventHandler,
+ @Nullable Executor executor, @Nullable EventBus eventBus) {
+ return create(eventHandler, executor, eventBus, null);
+ }
+
+ private static ActionExecutionStatusReporter create(EventHandler eventHandler,
+ @Nullable Executor executor, @Nullable EventBus eventBus, @Nullable Clock clock) {
+ ActionExecutionStatusReporter result = new ActionExecutionStatusReporter(eventHandler, executor,
+ eventBus, clock == null ? BlazeClock.instance() : clock);
+ if (eventBus != null) {
+ eventBus.register(result);
+ }
+ return result;
+ }
+
+ private ActionExecutionStatusReporter(EventHandler eventHandler, @Nullable Executor executor,
+ @Nullable EventBus eventBus, Clock clock) {
+ this.eventHandler = Preconditions.checkNotNull(eventHandler);
+ this.executor = executor;
+ this.eventBus = eventBus;
+ this.clock = Preconditions.checkNotNull(clock);
+ }
+
+ public void unregisterFromEventBus() {
+ if (eventBus != null) {
+ eventBus.unregister(this);
+ }
+ }
+
+ private void setStatus(ActionMetadata action, String message) {
+ actionStatus.put(action, Pair.of(message, clock.nanoTime()));
+ }
+
+ /**
+ * Remove action from the list of active actions.
+ */
+ public void remove(Action action) {
+ Preconditions.checkNotNull(actionStatus.remove(action), action);
+ }
+
+ /**
+ * Set "Preparing" status.
+ */
+ public void setPreparing(Action action) {
+ updateStatus(ActionStatusMessage.preparingStrategy(action));
+ }
+
+ public void setRunningFromBuildData(ActionMetadata action) {
+ updateStatus(ActionStatusMessage.runningStrategy(action));
+ }
+
+ @Subscribe
+ public void updateStatus(ActionStatusMessage statusMsg) {
+ String message = statusMsg.getMessage();
+ ActionMetadata action = statusMsg.getActionMetadata();
+ if (statusMsg.needsStrategy()) {
+ String strategy = action.describeStrategy(executor);
+ if (strategy == null) {
+ return;
+ }
+ message = String.format(message, strategy);
+ }
+ setStatus(action, message);
+ }
+
+ public int getCount() {
+ return actionStatus.size();
+ }
+
+ private static void appendGroupStatus(StringBuilder buffer,
+ Map<ActionMetadata, Pair<String, Long>> statusMap, String status, long currentTime) {
+ List<Pair<Long, ActionMetadata>> actions = new ArrayList<>();
+ for (Map.Entry<ActionMetadata, Pair<String, Long>> entry : statusMap.entrySet()) {
+ if (entry.getValue().first.equals(status)) {
+ actions.add(Pair.of(entry.getValue().second, entry.getKey()));
+ }
+ }
+ if (actions.size() == 0) {
+ return;
+ }
+ Collections.sort(actions, Pair.<Long, ActionMetadata>compareByFirst());
+
+ buffer.append("\n " + status + ":");
+
+ boolean truncateList = actions.size() > MAX_LINES;
+ for (Pair<Long, ActionMetadata> entry : actions.subList(0,
+ truncateList ? MAX_LINES - 1 : actions.size())) {
+ String message = entry.second.getProgressMessage();
+ if (message == null) {
+ // Actions will a null progress message should run so
+ // fast we never see them here. In any case...
+ message = entry.second.prettyPrint();
+ }
+ buffer.append("\n ").append(message);
+ long runTime = (currentTime - entry.first) / 1000000000L; // Convert to seconds.
+ buffer.append(", ").append(runTime).append(" s");
+ }
+ if (truncateList) {
+ buffer.append("\n ... ").append(actions.size() - MAX_LINES + 1).append(" more jobs");
+ }
+ }
+
+ /**
+ * Get message showing currently executing actions.
+ */
+ private String getExecutionStatusMessage(Map<ActionMetadata, Pair<String, Long>> statusMap) {
+ int count = statusMap.size();
+ StringBuilder s = count != 1
+ ? new StringBuilder("Still waiting for ").append(count).append(" jobs to complete:")
+ : new StringBuilder("Still waiting for 1 job to complete:");
+
+ long currentTime = clock.nanoTime();
+
+ // A tree is just as fast as HashSet for small data sets.
+ Set<String> statuses = new TreeSet<String>();
+ for (Map.Entry<ActionMetadata, Pair<String, Long>> entry : statusMap.entrySet()) {
+ statuses.add(entry.getValue().first);
+ }
+
+ for (String status : statuses) {
+ appendGroupStatus(s, statusMap, status, currentTime);
+ }
+ return s.toString();
+ }
+
+ /**
+ * Show currently executing actions.
+ */
+ public void showCurrentlyExecutingActions(String progressPercentageMessage) {
+ // Defensive copy to ensure thread safety.
+ Map<ActionMetadata, Pair<String, Long>> statusMap = new HashMap<>(actionStatus);
+ if (statusMap.size() > 0) {
+ eventHandler.handle(
+ Event.progress(progressPercentageMessage + getExecutionStatusMessage(statusMap)));
+ }
+ }
+
+ /**
+ * Warn about actions that are still being executed.
+ * Method is used to produce informative message when build is interrupted.
+ */
+ void warnAboutCurrentlyExecutingActions() {
+ // Defensive copy to ensure thread safety.
+ Map<ActionMetadata, Pair<String, Long>> statusMap = new HashMap<>(actionStatus);
+ if (statusMap.size() == 0) {
+ // There are no tasks in the queue so there is nothing to report.
+ eventHandler.handle(Event.warn("There are no active jobs - stopping the build"));
+ return;
+ }
+ Iterator<ActionMetadata> iterator = statusMap.keySet().iterator();
+ while (iterator.hasNext()) {
+ // Filter out actions that are not executed yet.
+ if (statusMap.get(iterator.next()).first.equals(ActionStatusMessage.PREPARING)) {
+ iterator.remove();
+ }
+ }
+ if (statusMap.size() > 0) {
+ eventHandler.handle(Event.warn(getExecutionStatusMessage(statusMap)
+ + "\nBuild will be stopped after these tasks terminate"));
+ } else {
+ // It is possible that one or more tasks in "Preparing" state just started being executed.
+ // So warn user just in case.
+ eventHandler.handle(Event.warn("Still waiting for unfinished jobs"));
+ }
+ }
+
+ /**
+ * Returns the number of seconds to wait before reporting slow progress again.
+ *
+ * @param userSpecifiedProgressInterval value of the --progress_report_interval flag; 0 means
+ * use default 10, then 30, then 60 seconds wait times
+ * @param previousWaitTime previous value returned by this method
+ */
+ public static int getWaitTime(int userSpecifiedProgressInterval, int previousWaitTime) {
+ if (userSpecifiedProgressInterval > 0) {
+ return userSpecifiedProgressInterval;
+ }
+
+ // Increase waitTime to 10, then to 30 and then to 60 seconds to reduce
+ // spamming during long wait periods. If the user specified a
+ // waitTime directly through progressReportInterval, then use
+ // that value.
+ if (previousWaitTime == 0) {
+ return 10;
+ } else if (previousWaitTime == 10) {
+ return 30;
+ } else {
+ return 60;
+ }
+ }
+}