blob: 353dcd4a46b60052f66135ea72bf5f64c59ae77e [file] [log] [blame]
Klaus Aehligbffec602016-02-25 09:27:48 +00001// Copyright 2016 The Bazel Authors. All rights reserved.
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.
14package com.google.devtools.build.lib.runtime;
15
Klaus Aehlig59196362016-03-04 11:49:54 +000016import com.google.devtools.build.lib.actions.Action;
Klaus Aehligc211b252016-02-25 12:51:25 +000017import com.google.devtools.build.lib.actions.ActionCompletionEvent;
18import com.google.devtools.build.lib.actions.ActionStartedEvent;
Klaus Aehlig0b26b422016-06-17 14:17:26 +000019import com.google.devtools.build.lib.actions.ActionStatusMessage;
Klaus Aehligc211b252016-02-25 12:51:25 +000020import com.google.devtools.build.lib.analysis.AnalysisPhaseCompleteEvent;
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000021import com.google.devtools.build.lib.analysis.ConfiguredTarget;
Klaus Aehlig59196362016-03-04 11:49:54 +000022import com.google.devtools.build.lib.buildtool.ExecutionProgressReceiver;
Klaus Aehligc211b252016-02-25 12:51:25 +000023import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
24import com.google.devtools.build.lib.buildtool.buildevent.BuildStartingEvent;
Klaus Aehlig59196362016-03-04 11:49:54 +000025import com.google.devtools.build.lib.buildtool.buildevent.ExecutionProgressReceiverAvailableEvent;
Klaus Aehligd232d232016-04-13 09:51:25 +000026import com.google.devtools.build.lib.buildtool.buildevent.TestFilteringCompleteEvent;
Klaus Aehlig6d0876a2016-04-15 14:41:07 +000027import com.google.devtools.build.lib.cmdline.Label;
Klaus Aehligd41a91e2017-03-09 16:42:07 +000028import com.google.devtools.build.lib.events.ExtendedEventHandler.FetchProgress;
Klaus Aehligbffec602016-02-25 09:27:48 +000029import com.google.devtools.build.lib.pkgcache.LoadingPhaseCompleteEvent;
Klaus Aehlig2c94a382016-04-07 13:23:48 +000030import com.google.devtools.build.lib.skyframe.LoadingPhaseStartedEvent;
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +000031import com.google.devtools.build.lib.skyframe.PackageProgressReceiver;
Klaus Aehliga63d9612016-04-04 12:15:23 +000032import com.google.devtools.build.lib.util.Clock;
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +000033import com.google.devtools.build.lib.util.Pair;
Klaus Aehligbffec602016-02-25 09:27:48 +000034import com.google.devtools.build.lib.util.io.AnsiTerminalWriter;
Klaus Aehlig6d0876a2016-04-15 14:41:07 +000035import com.google.devtools.build.lib.util.io.PositionAwareAnsiTerminalWriter;
Klaus Aehligd232d232016-04-13 09:51:25 +000036import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
Klaus Aehligbffec602016-02-25 09:27:48 +000037import java.io.IOException;
Klaus Aehligc211b252016-02-25 12:51:25 +000038import java.util.ArrayDeque;
39import java.util.Deque;
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000040import java.util.LinkedHashSet;
Klaus Aehlig00d479c2016-03-04 12:18:56 +000041import java.util.Map;
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000042import java.util.Set;
Klaus Aehlig00d479c2016-03-04 12:18:56 +000043import java.util.TreeMap;
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000044import java.util.TreeSet;
Klaus Aehligbffec602016-02-25 09:27:48 +000045
46/**
47 * An experimental state tracker for the new experimental UI.
48 */
49class ExperimentalStateTracker {
50
Klaus Aehlige25642a2016-04-04 13:31:28 +000051 static final long SHOW_TIME_THRESHOLD_SECONDS = 3;
Klaus Aehlig6d0876a2016-04-15 14:41:07 +000052 static final String ELLIPSIS = "...";
Klaus Aehlig5c06b432017-03-01 18:00:39 +000053 static final String FETCH_PREFIX = " Fetching ";
54 static final String AND_MORE = " ...";
55
Klaus Aehligc211b252016-02-25 12:51:25 +000056
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000057 static final int NANOS_PER_SECOND = 1000000000;
Klaus Aehlig5c06b432017-03-01 18:00:39 +000058 static final String URL_PROTOCOL_SEP = "://";
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000059
Klaus Aehligcd211ee2016-06-21 08:44:15 +000060 private int sampleSize = 3;
61
Klaus Aehligc211b252016-02-25 12:51:25 +000062 private String status;
63 private String additionalMessage;
Klaus Aehlig00d479c2016-03-04 12:18:56 +000064
Klaus Aehliga63d9612016-04-04 12:15:23 +000065 private final Clock clock;
66
Klaus Aehlig6d0876a2016-04-15 14:41:07 +000067 // Desired maximal width of the progress bar, if positive.
68 // Non-positive values indicate not to aim for a particular width.
69 private final int targetWidth;
70
Klaus Aehlig00d479c2016-03-04 12:18:56 +000071 // currently running actions, using the path of the primary
72 // output as unique identifier.
Klaus Aehligc211b252016-02-25 12:51:25 +000073 private final Deque<String> runningActions;
Klaus Aehlig00d479c2016-03-04 12:18:56 +000074 private final Map<String, Action> actions;
Klaus Aehlige25642a2016-04-04 13:31:28 +000075 private final Map<String, Long> actionNanoStartTimes;
Klaus Aehlig0b26b422016-06-17 14:17:26 +000076 private final Map<String, String> actionStrategy;
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000077
Klaus Aehlig5c06b432017-03-01 18:00:39 +000078 // running downloads are identified by the original URL they were trying to
79 // access.
80 private final Deque<String> runningDownloads;
81 private final Map<String, Long> downloadNanoStartTimes;
Klaus Aehligd41a91e2017-03-09 16:42:07 +000082 private final Map<String, FetchProgress> downloads;
Klaus Aehlig5c06b432017-03-01 18:00:39 +000083
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +000084 // For each test, the list of actions (again identified by the path of the
85 // primary output) currently running for that test (identified by its label),
86 // in order they got started. A key is present in the map if and only if that
87 // was discovered as a test.
88 private final Map<Label, Set<String>> testActions;
89
Klaus Aehlig59196362016-03-04 11:49:54 +000090 private int actionsCompleted;
Klaus Aehligd232d232016-04-13 09:51:25 +000091 private int totalTests;
92 private int completedTests;
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +000093 private TestSummary mostRecentTest;
Klaus Aehligd232d232016-04-13 09:51:25 +000094 private int failedTests;
Klaus Aehligc211b252016-02-25 12:51:25 +000095 private boolean ok;
Klaus Aehligbffec602016-02-25 09:27:48 +000096
Klaus Aehlig59196362016-03-04 11:49:54 +000097 private ExecutionProgressReceiver executionProgressReceiver;
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +000098 private PackageProgressReceiver packageProgressReceiver;
Klaus Aehlig59196362016-03-04 11:49:54 +000099
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000100 ExperimentalStateTracker(Clock clock, int targetWidth) {
Klaus Aehligc211b252016-02-25 12:51:25 +0000101 this.runningActions = new ArrayDeque<>();
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000102 this.actions = new TreeMap<>();
Klaus Aehlige25642a2016-04-04 13:31:28 +0000103 this.actionNanoStartTimes = new TreeMap<>();
Klaus Aehlig0b26b422016-06-17 14:17:26 +0000104 this.actionStrategy = new TreeMap<>();
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000105 this.testActions = new TreeMap<>();
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000106 this.runningDownloads = new ArrayDeque<>();
107 this.downloads = new TreeMap<>();
108 this.downloadNanoStartTimes = new TreeMap<>();
Klaus Aehligc211b252016-02-25 12:51:25 +0000109 this.ok = true;
Klaus Aehliga63d9612016-04-04 12:15:23 +0000110 this.clock = clock;
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000111 this.targetWidth = targetWidth;
112 }
113
114 ExperimentalStateTracker(Clock clock) {
115 this(clock, 0);
Klaus Aehligc211b252016-02-25 12:51:25 +0000116 }
117
Klaus Aehligcd211ee2016-06-21 08:44:15 +0000118 /**
119 * Set the maximal number of actions shown in the progress bar.
120 */
121 void setSampleSize(int sampleSize) {
Klaus Aehlig6c07f622016-06-27 15:15:52 +0000122 if (sampleSize >= 1) {
123 this.sampleSize = sampleSize;
124 } else {
125 this.sampleSize = 1;
126 }
Klaus Aehligcd211ee2016-06-21 08:44:15 +0000127 }
128
Klaus Aehligc211b252016-02-25 12:51:25 +0000129 void buildStarted(BuildStartingEvent event) {
130 status = "Loading";
131 additionalMessage = "";
Klaus Aehligbffec602016-02-25 09:27:48 +0000132 }
133
Klaus Aehlig2c94a382016-04-07 13:23:48 +0000134 void loadingStarted(LoadingPhaseStartedEvent event) {
135 status = null;
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +0000136 packageProgressReceiver = event.getPackageProgressReceiver();
Klaus Aehlig2c94a382016-04-07 13:23:48 +0000137 }
138
Klaus Aehligbffec602016-02-25 09:27:48 +0000139 void loadingComplete(LoadingPhaseCompleteEvent event) {
Klaus Aehligbffec602016-02-25 09:27:48 +0000140 int count = event.getTargets().size();
Klaus Aehligfab39252016-05-09 14:13:20 +0000141 status = "Analyzing";
142 if (count == 1) {
143 additionalMessage = "target " + event.getTargets().asList().get(0).getLabel();
144 } else {
145 additionalMessage = "" + count + " targets";
146 }
Klaus Aehligbffec602016-02-25 09:27:48 +0000147 }
148
Klaus Aehlig0fc2ddf2016-06-23 09:17:13 +0000149 /**
150 * Make the state tracker aware of the fact that the analyis has finished. Return a summary of the
151 * work done in the analysis phase.
152 */
153 synchronized String analysisComplete(AnalysisPhaseCompleteEvent event) {
154 String workDone = "Analysed " + additionalMessage;
155 if (packageProgressReceiver != null) {
156 Pair<String, String> progress = packageProgressReceiver.progressState();
157 workDone += " (" + progress.getFirst() + ")";
158 }
159 workDone += ".";
Klaus Aehligc211b252016-02-25 12:51:25 +0000160 status = null;
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +0000161 packageProgressReceiver = null;
Klaus Aehlig0fc2ddf2016-06-23 09:17:13 +0000162 return workDone;
Klaus Aehligc211b252016-02-25 12:51:25 +0000163 }
164
Klaus Aehlig59196362016-03-04 11:49:54 +0000165 void progressReceiverAvailable(ExecutionProgressReceiverAvailableEvent event) {
166 executionProgressReceiver = event.getExecutionProgressReceiver();
167 }
168
Klaus Aehlig02f23482016-06-29 07:55:29 +0000169 void buildComplete(BuildCompleteEvent event, String additionalInfo) {
Klaus Aehligc211b252016-02-25 12:51:25 +0000170 if (event.getResult().getSuccess()) {
171 status = "INFO";
Klaus Aehlig3165c6c2016-06-29 10:53:11 +0000172 if (failedTests == 0) {
173 additionalMessage =
174 additionalInfo + "Build completed successfully, "
175 + actionsCompleted + " total action" + (actionsCompleted == 1 ? "" : "s");
176 } else {
177 additionalMessage =
178 additionalInfo + "Build completed, "
179 + failedTests + " test" + (failedTests == 1 ? "" : "s") + " FAILED, "
180 + actionsCompleted + " total action" + (actionsCompleted == 1 ? "" : "s");
181 }
Klaus Aehligc211b252016-02-25 12:51:25 +0000182 } else {
183 ok = false;
184 status = "FAILED";
Klaus Aehlig02f23482016-06-29 07:55:29 +0000185 additionalMessage = additionalInfo + "Build did NOT complete successfully";
Klaus Aehligc211b252016-02-25 12:51:25 +0000186 }
187 }
188
Klaus Aehlig02f23482016-06-29 07:55:29 +0000189 void buildComplete(BuildCompleteEvent event) {
190 buildComplete(event, "");
191 }
192
Klaus Aehligd41a91e2017-03-09 16:42:07 +0000193 synchronized void downloadProgress(FetchProgress event) {
194 String url = event.getResourceIdentifier();
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000195 if (event.isFinished()) {
196 // a download is finished, clean it up
197 runningDownloads.remove(url);
198 downloadNanoStartTimes.remove(url);
199 downloads.remove(url);
200 } else if (runningDownloads.contains(url)) {
201 // a new progress update on an already known, still running download
202 downloads.put(url, event);
203 } else {
204 // Start of a new download
205 long nanoTime = clock.nanoTime();
206 runningDownloads.add(url);
207 downloads.put(url, event);
208 downloadNanoStartTimes.put(url, nanoTime);
209 }
210 }
211
Klaus Aehligc211b252016-02-25 12:51:25 +0000212 synchronized void actionStarted(ActionStartedEvent event) {
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000213 Action action = event.getAction();
214 String name = action.getPrimaryOutput().getPath().getPathString();
Klaus Aehlige25642a2016-04-04 13:31:28 +0000215 Long nanoStartTime = event.getNanoTimeStart();
Klaus Aehligc211b252016-02-25 12:51:25 +0000216 runningActions.addLast(name);
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000217 actions.put(name, action);
Klaus Aehlige25642a2016-04-04 13:31:28 +0000218 actionNanoStartTimes.put(name, nanoStartTime);
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000219 if (action.getOwner() != null) {
220 Label owner = action.getOwner().getLabel();
221 if (owner != null) {
222 Set<String> testActionsForOwner = testActions.get(owner);
223 if (testActionsForOwner != null) {
224 testActionsForOwner.add(name);
225 }
226 }
227 }
Klaus Aehligc211b252016-02-25 12:51:25 +0000228 }
229
Klaus Aehlig0b26b422016-06-17 14:17:26 +0000230 void actionStatusMessage(ActionStatusMessage event) {
231 String strategy = event.getStrategy();
232 if (strategy != null) {
233 String name = event.getActionMetadata().getPrimaryOutput().getPath().getPathString();
234 synchronized (this) {
235 actionStrategy.put(name, strategy);
236 }
237 }
238 }
239
Klaus Aehligc211b252016-02-25 12:51:25 +0000240 synchronized void actionCompletion(ActionCompletionEvent event) {
241 actionsCompleted++;
Klaus Aehlig59196362016-03-04 11:49:54 +0000242 Action action = event.getAction();
243 String name = action.getPrimaryOutput().getPath().getPathString();
Klaus Aehligc211b252016-02-25 12:51:25 +0000244 runningActions.remove(name);
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000245 actions.remove(name);
Klaus Aehlige25642a2016-04-04 13:31:28 +0000246 actionNanoStartTimes.remove(name);
Klaus Aehlig0b26b422016-06-17 14:17:26 +0000247 actionStrategy.remove(name);
Klaus Aehlig59196362016-03-04 11:49:54 +0000248
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000249 if (action.getOwner() != null) {
250 Label owner = action.getOwner().getLabel();
251 if (owner != null) {
252 Set<String> testActionsForOwner = testActions.get(owner);
253 if (testActionsForOwner != null) {
254 testActionsForOwner.remove(name);
255 }
256 }
257 }
258
Klaus Aehlig59196362016-03-04 11:49:54 +0000259 // As callers to the experimental state tracker assume we will fully report the new state once
260 // informed of an action completion, we need to make sure the progress receiver is aware of the
261 // completion, even though it might be called later on the event bus.
Klaus Aehlig8cad4bd2016-03-14 11:13:58 +0000262 if (executionProgressReceiver != null) {
263 executionProgressReceiver.actionCompleted(action);
264 }
Klaus Aehligc211b252016-02-25 12:51:25 +0000265 }
266
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000267 /**
268 * From a string, take a suffix of at most the given length.
269 */
Klaus Aehlige52813b2016-08-02 10:06:07 +0000270 static String suffix(String s, int len) {
271 if (len <= 0) {
272 return "";
273 }
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000274 int startPos = s.length() - len;
275 if (startPos <= 0) {
276 return s;
277 }
278 return s.substring(startPos);
279 }
280
281 /**
282 * If possible come up with a human-readable description of the label
283 * that fits within the given width; a non-positive width indicates not
284 * no restriction at all.
285 */
286 private String shortenedLabelString(Label label, int width) {
287 if (width <= 0) {
288 return label.toString();
289 }
290 String name = label.toString();
291 if (name.length() <= width) {
292 return name;
293 }
294 name = suffix(name, width - ELLIPSIS.length());
295 int slashPos = name.indexOf('/');
296 if (slashPos >= 0) {
297 return ELLIPSIS + name.substring(slashPos);
298 }
299 int colonPos = name.indexOf(':');
300 if (slashPos >= 0) {
301 return ELLIPSIS + name.substring(colonPos);
302 }
303 // no reasonable place found to shorten; as last resort, just truncate
304 if (3 * ELLIPSIS.length() <= width) {
305 return ELLIPSIS + suffix(label.toString(), width - ELLIPSIS.length());
306 }
307 return label.toString();
308 }
309
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000310 // Describe a group of actions running for the same test.
311 private String describeTestGroup(
312 Label owner, long nanoTime, int desiredWidth, Set<String> allActions) {
313 String prefix = "Testing ";
314 String labelSep = " [";
315 String postfix = " (" + allActions.size() + " actions)]";
316 // Leave enough room for at least 3 samples of run times, each 4 characters
317 // (a digit, 's', comma, and space).
318 int labelWidth = desiredWidth - prefix.length() - labelSep.length() - postfix.length() - 12;
319 StringBuffer message =
320 new StringBuffer(prefix).append(shortenedLabelString(owner, labelWidth)).append(labelSep);
321
322 // Compute the remaining width for the sample times, but if the desired width is too small
323 // anyway, then show at least one sample.
324 int remainingWidth = desiredWidth - message.length() - postfix.length();
325 if (remainingWidth < 0) {
326 remainingWidth = 5;
327 }
328
329 String sep = "";
330 int count = 0;
331 for (String action : allActions) {
332 long nanoRuntime = nanoTime - actionNanoStartTimes.get(action);
333 long runtimeSeconds = nanoRuntime / NANOS_PER_SECOND;
334 String text = sep + runtimeSeconds + "s";
335 if (remainingWidth < text.length()) {
336 break;
337 }
338 message.append(text);
339 remainingWidth -= text.length();
340 count++;
341 sep = ", ";
342 }
343 if (count == allActions.size()) {
344 postfix = "]";
345 }
346 return message.append(postfix).toString();
347 }
348
349 // Describe an action by a string of the desired length; if describing that action includes
350 // describing other actions, add those to the to set of actions to skip in further samples of
351 // actions.
352 private String describeAction(String name, long nanoTime, int desiredWidth, Set<String> toSkip) {
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000353 Action action = actions.get(name);
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000354 if (action.getOwner() != null) {
355 Label owner = action.getOwner().getLabel();
356 if (owner != null) {
357 Set<String> allRelatedActions = testActions.get(owner);
358 if (allRelatedActions != null && allRelatedActions.size() > 1) {
359 if (toSkip != null) {
360 toSkip.addAll(allRelatedActions);
361 }
362 return describeTestGroup(owner, nanoTime, desiredWidth, allRelatedActions);
363 }
364 }
365 }
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000366
367 String postfix = "";
368 long nanoRuntime = nanoTime - actionNanoStartTimes.get(name);
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000369 long runtimeSeconds = nanoRuntime / NANOS_PER_SECOND;
Klaus Aehlig0b26b422016-06-17 14:17:26 +0000370 String strategy = actionStrategy.get(name);
371 // To keep the UI appearance more stable, always show the elapsed
372 // time if we also show a strategy (otherwise the strategy will jump in
373 // the progress bar).
374 if (strategy != null || runtimeSeconds > SHOW_TIME_THRESHOLD_SECONDS) {
375 postfix = "; " + runtimeSeconds + "s";
376 }
377 if (strategy != null) {
378 postfix += " " + strategy;
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000379 }
380
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000381 String message = action.getProgressMessage();
Klaus Aehlige25642a2016-04-04 13:31:28 +0000382 if (message == null) {
383 message = action.prettyPrint();
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000384 }
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000385
386 if (desiredWidth <= 0) {
387 return message + postfix;
Klaus Aehlige25642a2016-04-04 13:31:28 +0000388 }
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000389 if (message.length() + postfix.length() <= desiredWidth) {
390 return message + postfix;
391 }
Klaus Aehliga4c7d252016-05-20 10:44:21 +0000392
393 // We have to shorten the message to fit into the line.
394
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000395 if (action.getOwner() != null) {
396 if (action.getOwner().getLabel() != null) {
Klaus Aehliga4c7d252016-05-20 10:44:21 +0000397 // First attempt is to shorten the package path string in the messge, if it occurs there
398 String pathString = action.getOwner().getLabel().getPackageFragment().toString();
399 int pathIndex = message.indexOf(pathString);
400 if (pathIndex >= 0) {
401 String start = message.substring(0, pathIndex);
402 String end = message.substring(pathIndex + pathString.length());
403 int pathTargetLength = desiredWidth - start.length() - end.length() - postfix.length();
404 // This attempt of shortening is reasonable if what is left from the label
405 // is significantly longer (twice as long) as the ellipsis symbols introduced.
406 if (pathTargetLength >= 3 * ELLIPSIS.length()) {
407 String shortPath = suffix(pathString, pathTargetLength - ELLIPSIS.length());
408 int slashPos = shortPath.indexOf('/');
409 if (slashPos >= 0) {
410 return start + ELLIPSIS + shortPath.substring(slashPos) + end + postfix;
411 }
412 }
413 }
414
415 // Second attempt: just take a shortened version of the label.
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000416 String shortLabel =
417 shortenedLabelString(action.getOwner().getLabel(), desiredWidth - postfix.length());
418 if (shortLabel.length() + postfix.length() <= desiredWidth) {
419 return shortLabel + postfix;
420 }
421 }
422 }
Klaus Aehliga4c7d252016-05-20 10:44:21 +0000423 if (3 * ELLIPSIS.length() + postfix.length() <= desiredWidth) {
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000424 message = ELLIPSIS + suffix(message, desiredWidth - ELLIPSIS.length() - postfix.length());
425 }
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000426
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000427 return message + postfix;
Klaus Aehlig00d479c2016-03-04 12:18:56 +0000428 }
429
Klaus Aehligc211b252016-02-25 12:51:25 +0000430 private void sampleOldestActions(AnsiTerminalWriter terminalWriter) throws IOException {
431 int count = 0;
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000432 int totalCount = 0;
Klaus Aehlige25642a2016-04-04 13:31:28 +0000433 long nanoTime = clock.nanoTime();
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000434 int actionCount = runningActions.size();
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000435 Set<String> toSkip = new TreeSet<>();
Klaus Aehligc211b252016-02-25 12:51:25 +0000436 for (String action : runningActions) {
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000437 totalCount++;
438 if (toSkip.contains(action)) {
439 continue;
440 }
Klaus Aehligc211b252016-02-25 12:51:25 +0000441 count++;
Klaus Aehligcd211ee2016-06-21 08:44:15 +0000442 if (count > sampleSize) {
443 totalCount--;
Klaus Aehligc211b252016-02-25 12:51:25 +0000444 break;
445 }
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000446 int width =
447 targetWidth - 4 - ((count >= sampleSize && count < actionCount) ? AND_MORE.length() : 0);
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000448 terminalWriter.newline().append(" " + describeAction(action, nanoTime, width, toSkip));
Klaus Aehligc211b252016-02-25 12:51:25 +0000449 }
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000450 if (totalCount < actionCount) {
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000451 terminalWriter.append(AND_MORE);
Klaus Aehligc211b252016-02-25 12:51:25 +0000452 }
453 }
454
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000455 public synchronized void testFilteringComplete(TestFilteringCompleteEvent event) {
Klaus Aehligd232d232016-04-13 09:51:25 +0000456 if (event.getTestTargets() != null) {
457 totalTests = event.getTestTargets().size();
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000458 for (ConfiguredTarget target : event.getTestTargets()) {
459 if (target.getLabel() != null) {
460 testActions.put(target.getLabel(), new LinkedHashSet<String>());
461 }
462 }
Klaus Aehligd232d232016-04-13 09:51:25 +0000463 }
464 }
465
466 public synchronized void testSummary(TestSummary summary) {
467 completedTests++;
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000468 mostRecentTest = summary;
Klaus Aehligd232d232016-04-13 09:51:25 +0000469 if (summary.getStatus() != BlazeTestStatus.PASSED) {
470 failedTests++;
471 }
472 }
473
Klaus Aehlig2ce24d42016-04-04 15:54:58 +0000474 /***
475 * Predicate indicating whether the contents of the progress bar can change, if the
476 * only thing that happens is that time passes; this is the case, e.g., if the progress
477 * bar shows time information relative to the current time.
478 */
479 boolean progressBarTimeDependent() {
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +0000480 if (packageProgressReceiver != null) {
Klaus Aehlig4cf6b6c2016-05-19 14:10:50 +0000481 return true;
482 }
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000483 if (runningDownloads.size() >= 1) {
484 return true;
485 }
Klaus Aehlig2ce24d42016-04-04 15:54:58 +0000486 if (status != null) {
487 return false;
488 }
489 if (runningActions.size() >= 1) {
490 return true;
491 }
Klaus Aehlig2ce24d42016-04-04 15:54:58 +0000492 return false;
493 }
494
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000495 /**
496 * Maybe add a note about the last test that passed. Return true, if the note was added (and
497 * hence a line break is appropriate if more data is to come. If a null value is provided for
498 * the terminal writer, only return wether a note would be added.
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000499 *
500 * The width parameter gives advice on to which length the the description of the test should
501 * the shortened to, if possible.
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000502 */
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000503 private boolean maybeShowRecentTest(
504 AnsiTerminalWriter terminalWriter, boolean shortVersion, int width) throws IOException {
505 final String prefix = "; last test: ";
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000506 if (!shortVersion && mostRecentTest != null) {
507 if (terminalWriter != null) {
Klaus Aehlig07122cf2016-06-02 16:04:26 +0000508 terminalWriter.normal().append(prefix);
509 if (mostRecentTest.getStatus() == BlazeTestStatus.PASSED) {
510 terminalWriter.okStatus();
511 } else {
512 terminalWriter.failStatus();
513 }
514 terminalWriter.append(
515 shortenedLabelString(mostRecentTest.getTarget().getLabel(), width - prefix.length()));
516 terminalWriter.normal();
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000517 }
518 return true;
519 } else {
520 return false;
521 }
522 }
523
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000524 private String shortenUrl(String url, int width) {
525
526 if (url.length() < width) {
527 return url;
528 }
529
530 // Try to shorten to the form prot://host/.../rest/path/filename
531 String prefix = "";
532 int protocolIndex = url.indexOf(URL_PROTOCOL_SEP);
533 if (protocolIndex > 0) {
534 prefix = url.substring(0, protocolIndex + URL_PROTOCOL_SEP.length() + 1);
535 url = url.substring(protocolIndex + URL_PROTOCOL_SEP.length() + 1);
536 int hostIndex = url.indexOf("/");
537 if (hostIndex > 0) {
538 prefix = prefix + url.substring(0, hostIndex + 1);
539 url = url.substring(hostIndex + 1);
540 int targetLength = width - prefix.length();
541 // accept this form of shortening, if what is left from the filename is
542 // significantly longer (twice as long) as the ellipsis symbol introduced
543 if (targetLength > 3 * ELLIPSIS.length()) {
544 String shortPath = suffix(url, targetLength - ELLIPSIS.length());
545 int slashPos = shortPath.indexOf("/");
546 if (slashPos >= 0) {
547 return prefix + ELLIPSIS + shortPath.substring(slashPos);
548 } else {
549 return prefix + ELLIPSIS + shortPath;
550 }
551 }
552 }
553 }
554
555 // Last resort: just take a suffix
556 if (width <= ELLIPSIS.length()) {
557 // No chance to shorten anyway
558 return "";
559 }
560 return ELLIPSIS + suffix(url, width - ELLIPSIS.length());
561 }
562
563 private void reportOnOneDownload(
564 String url, long nanoTime, int width, AnsiTerminalWriter terminalWriter) throws IOException {
565
566 String postfix = "";
567
Klaus Aehligd41a91e2017-03-09 16:42:07 +0000568 FetchProgress download = downloads.get(url);
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000569 long nanoDownloadTime = nanoTime - downloadNanoStartTimes.get(url);
570 long downloadSeconds = nanoDownloadTime / NANOS_PER_SECOND;
571
Klaus Aehligd41a91e2017-03-09 16:42:07 +0000572 String progress = download.getProgress();
573 if (progress.length() > 0) {
574 postfix = postfix + " " + progress;
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000575 }
576 if (downloadSeconds > SHOW_TIME_THRESHOLD_SECONDS) {
577 postfix = postfix + " " + downloadSeconds + "s";
578 }
579 if (postfix.length() > 0) {
580 postfix = ";" + postfix;
581 }
582 url = shortenUrl(url, width - postfix.length());
583 terminalWriter.append(url + postfix);
584 }
585
586 private void reportOnDownloads(AnsiTerminalWriter terminalWriter) throws IOException {
587 int count = 0;
588 long nanoTime = clock.nanoTime();
589 int downloadCount = runningDownloads.size();
590 for (String url : runningDownloads) {
591 if (count >= sampleSize) {
592 break;
593 }
594 count++;
595 terminalWriter.newline().append(FETCH_PREFIX);
596 reportOnOneDownload(
597 url,
598 nanoTime,
599 targetWidth
600 - FETCH_PREFIX.length()
601 - ((count >= sampleSize && count < downloadCount) ? AND_MORE.length() : 0),
602 terminalWriter);
603 }
604 if (count < downloadCount) {
605 terminalWriter.append(AND_MORE);
606 }
607 }
608
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000609 synchronized void writeProgressBar(AnsiTerminalWriter rawTerminalWriter, boolean shortVersion)
Klaus Aehligc0c88842016-04-11 12:07:38 +0000610 throws IOException {
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000611 PositionAwareAnsiTerminalWriter terminalWriter =
612 new PositionAwareAnsiTerminalWriter(rawTerminalWriter);
Klaus Aehligc211b252016-02-25 12:51:25 +0000613 if (status != null) {
614 if (ok) {
615 terminalWriter.okStatus();
616 } else {
617 terminalWriter.failStatus();
618 }
619 terminalWriter.append(status + ":").normal().append(" " + additionalMessage);
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +0000620 if (packageProgressReceiver != null) {
621 Pair<String, String> progress = packageProgressReceiver.progressState();
622 terminalWriter.append(" (" + progress.getFirst() + ")");
Klaus Aehlig818bdd82016-06-28 10:50:25 +0000623 if (progress.getSecond().length() > 0 && !shortVersion) {
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +0000624 terminalWriter.newline().append(" " + progress.getSecond());
625 }
Klaus Aehlig4cf6b6c2016-05-19 14:10:50 +0000626 }
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000627 if (!shortVersion) {
628 reportOnDownloads(terminalWriter);
629 }
Klaus Aehligc211b252016-02-25 12:51:25 +0000630 return;
631 }
Klaus Aehligc6fd6bb2016-05-27 11:42:32 +0000632 if (packageProgressReceiver != null) {
633 Pair<String, String> progress = packageProgressReceiver.progressState();
634 terminalWriter.okStatus().append("Loading:").normal().append(" " + progress.getFirst());
635 if (progress.getSecond().length() > 0) {
636 terminalWriter.newline().append(" " + progress.getSecond());
637 }
Klaus Aehlig5c06b432017-03-01 18:00:39 +0000638 if (!shortVersion) {
639 reportOnDownloads(terminalWriter);
640 }
Klaus Aehlig2c94a382016-04-07 13:23:48 +0000641 return;
642 }
Klaus Aehlig59196362016-03-04 11:49:54 +0000643 if (executionProgressReceiver != null) {
644 terminalWriter.okStatus().append(executionProgressReceiver.getProgressString());
645 } else {
646 terminalWriter.okStatus().append("Building:");
647 }
Klaus Aehligd232d232016-04-13 09:51:25 +0000648 if (completedTests > 0) {
649 terminalWriter.normal().append(" " + completedTests + " / " + totalTests + " tests");
650 if (failedTests > 0) {
651 terminalWriter.append(", ").failStatus().append("" + failedTests + " failed").normal();
652 }
653 terminalWriter.append(";");
654 }
Klaus Aehligc0c88842016-04-11 12:07:38 +0000655 if (runningActions.size() == 0) {
Klaus Aehlig38ebf2a2016-06-27 10:54:56 +0000656 terminalWriter.normal().append(" no action running");
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000657 maybeShowRecentTest(terminalWriter, shortVersion, targetWidth - terminalWriter.getPosition());
Klaus Aehligc0c88842016-04-11 12:07:38 +0000658 } else if (runningActions.size() == 1) {
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000659 if (maybeShowRecentTest(null, shortVersion, targetWidth - terminalWriter.getPosition())) {
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000660 // As we will break lines anyway, also show the number of running actions, to keep
661 // things stay roughly in the same place (also compensating for the missing plural-s
662 // in the word action).
Klaus Aehlig38ebf2a2016-06-27 10:54:56 +0000663 terminalWriter.normal().append(" 1 action running");
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000664 maybeShowRecentTest(
665 terminalWriter, shortVersion, targetWidth - terminalWriter.getPosition());
666 String statusMessage =
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000667 describeAction(runningActions.peekFirst(), clock.nanoTime(), targetWidth - 4, null);
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000668 terminalWriter.normal().newline().append(" " + statusMessage);
669 } else {
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000670 String statusMessage =
671 describeAction(
672 runningActions.peekFirst(),
673 clock.nanoTime(),
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000674 targetWidth - terminalWriter.getPosition() - 1,
675 null);
Klaus Aehligb8d0c6b2016-04-13 11:06:41 +0000676 terminalWriter.normal().append(" " + statusMessage);
677 }
Klaus Aehlig59196362016-03-04 11:49:54 +0000678 } else {
Klaus Aehligc0c88842016-04-11 12:07:38 +0000679 if (shortVersion) {
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000680 String statusMessage =
681 describeAction(
682 runningActions.peekFirst(),
683 clock.nanoTime(),
Klaus Aehligc6d3ccc2016-05-24 12:58:09 +0000684 targetWidth - terminalWriter.getPosition(),
685 null);
Klaus Aehlig38ebf2a2016-06-27 10:54:56 +0000686 statusMessage += " ... (" + runningActions.size() + " actions running)";
Klaus Aehligc0c88842016-04-11 12:07:38 +0000687 terminalWriter.normal().append(" " + statusMessage);
688 } else {
Klaus Aehlig38ebf2a2016-06-27 10:54:56 +0000689 String statusMessage = "" + runningActions.size() + " actions running";
Klaus Aehligc0c88842016-04-11 12:07:38 +0000690 terminalWriter.normal().append(" " + statusMessage);
Klaus Aehlig6d0876a2016-04-15 14:41:07 +0000691 maybeShowRecentTest(
692 terminalWriter, shortVersion, targetWidth - terminalWriter.getPosition());
Klaus Aehligc0c88842016-04-11 12:07:38 +0000693 sampleOldestActions(terminalWriter);
694 }
Klaus Aehlig59196362016-03-04 11:49:54 +0000695 }
Klaus Aehligbffec602016-02-25 09:27:48 +0000696 }
Klaus Aehligc0c88842016-04-11 12:07:38 +0000697
698 void writeProgressBar(AnsiTerminalWriter terminalWriter) throws IOException {
699 writeProgressBar(terminalWriter, false);
700 }
Klaus Aehligbffec602016-02-25 09:27:48 +0000701}