1515
1616import static com .google .common .util .concurrent .Futures .immediateFuture ;
1717import static com .google .common .util .concurrent .MoreExecutors .directExecutor ;
18+ import static com .google .devtools .build .lib .remote .common .ProgressStatusListener .NO_ACTION ;
19+ import static com .google .devtools .build .lib .remote .util .Utils .bytesCountToDisplayString ;
1820import static com .google .devtools .build .lib .remote .util .Utils .getFromFuture ;
1921
2022import build .bazel .remote .execution .v2 .Action ;
5052import com .google .devtools .build .lib .actions .UserExecException ;
5153import com .google .devtools .build .lib .actions .cache .MetadataInjector ;
5254import com .google .devtools .build .lib .concurrent .ThreadSafety ;
55+ import com .google .devtools .build .lib .exec .SpawnProgressEvent ;
5356import com .google .devtools .build .lib .exec .SpawnRunner .SpawnExecutionContext ;
5457import com .google .devtools .build .lib .profiler .Profiler ;
5558import com .google .devtools .build .lib .profiler .SilentCloseable ;
5861import com .google .devtools .build .lib .remote .RemoteCache .ActionResultMetadata .SymlinkMetadata ;
5962import com .google .devtools .build .lib .remote .common .LazyFileOutputStream ;
6063import com .google .devtools .build .lib .remote .common .OutputDigestMismatchException ;
64+ import com .google .devtools .build .lib .remote .common .ProgressStatusListener ;
6165import com .google .devtools .build .lib .remote .common .RemoteActionExecutionContext ;
6266import com .google .devtools .build .lib .remote .common .RemoteActionFileArtifactValue ;
6367import com .google .devtools .build .lib .remote .common .RemoteCacheClient ;
9195import java .util .List ;
9296import java .util .Map ;
9397import java .util .Map .Entry ;
98+ import java .util .concurrent .atomic .AtomicLong ;
99+ import java .util .regex .Matcher ;
100+ import java .util .regex .Pattern ;
94101import java .util .stream .Collectors ;
95102import java .util .stream .Stream ;
96103import javax .annotation .Nullable ;
@@ -314,6 +321,50 @@ private static Path toTmpDownloadPath(Path actualPath) {
314321 return actualPath .getParentDirectory ().getRelative (actualPath .getBaseName () + ".tmp" );
315322 }
316323
324+ static class DownloadProgressReporter {
325+ private static final Pattern PATTERN = Pattern .compile ("^bazel-out/[^/]+/[^/]+/" );
326+ private final ProgressStatusListener listener ;
327+ private final String id ;
328+ private final String file ;
329+ private final String totalSize ;
330+ private final AtomicLong downloadedBytes = new AtomicLong (0 );
331+
332+ DownloadProgressReporter (ProgressStatusListener listener , String file , long totalSize ) {
333+ this .listener = listener ;
334+ this .id = file ;
335+ this .totalSize = bytesCountToDisplayString (totalSize );
336+
337+ Matcher matcher = PATTERN .matcher (file );
338+ this .file = matcher .replaceFirst ("" );
339+ }
340+
341+ void started () {
342+ reportProgress (false , false );
343+ }
344+
345+ void downloadedBytes (int count ) {
346+ downloadedBytes .addAndGet (count );
347+ reportProgress (true , false );
348+ }
349+
350+ void finished () {
351+ reportProgress (true , true );
352+ }
353+
354+ private void reportProgress (boolean includeBytes , boolean finished ) {
355+ String progress ;
356+ if (includeBytes ) {
357+ progress =
358+ String .format (
359+ "Downloading %s, %s / %s" ,
360+ file , bytesCountToDisplayString (downloadedBytes .get ()), totalSize );
361+ } else {
362+ progress = String .format ("Downloading %s" , file );
363+ }
364+ listener .onProgressStatus (SpawnProgressEvent .create (id , progress , finished ));
365+ }
366+ }
367+
317368 /**
318369 * Download the output files and directory trees of a remotely executed action to the local
319370 * machine, as well stdin / stdout to the given files.
@@ -330,7 +381,8 @@ public void download(
330381 RemotePathResolver remotePathResolver ,
331382 ActionResult result ,
332383 FileOutErr origOutErr ,
333- OutputFilesLocker outputFilesLocker )
384+ OutputFilesLocker outputFilesLocker ,
385+ ProgressStatusListener progressStatusListener )
334386 throws ExecException , IOException , InterruptedException {
335387 ActionResultMetadata metadata = parseActionResultMetadata (context , remotePathResolver , result );
336388
@@ -347,7 +399,11 @@ public void download(
347399 context ,
348400 remotePathResolver .localPathToOutputPath (file .path ()),
349401 toTmpDownloadPath (file .path ()),
350- file .digest ());
402+ file .digest (),
403+ new DownloadProgressReporter (
404+ progressStatusListener ,
405+ remotePathResolver .localPathToOutputPath (file .path ()),
406+ file .digest ().getSizeBytes ()));
351407 return Futures .transform (download , (d ) -> file , directExecutor ());
352408 } catch (IOException e ) {
353409 return Futures .<FileMetadata >immediateFailedFuture (e );
@@ -499,10 +555,14 @@ private void createSymlinks(Iterable<SymlinkMetadata> symlinks) throws IOExcepti
499555 }
500556
501557 public ListenableFuture <Void > downloadFile (
502- RemoteActionExecutionContext context , String outputPath , Path localPath , Digest digest )
558+ RemoteActionExecutionContext context ,
559+ String outputPath ,
560+ Path localPath ,
561+ Digest digest ,
562+ DownloadProgressReporter reporter )
503563 throws IOException {
504564 SettableFuture <Void > outerF = SettableFuture .create ();
505- ListenableFuture <Void > f = downloadFile (context , localPath , digest );
565+ ListenableFuture <Void > f = downloadFile (context , localPath , digest , reporter );
506566 Futures .addCallback (
507567 f ,
508568 new FutureCallback <Void >() {
@@ -529,6 +589,16 @@ public void onFailure(Throwable throwable) {
529589 /** Downloads a file (that is not a directory). The content is fetched from the digest. */
530590 public ListenableFuture <Void > downloadFile (
531591 RemoteActionExecutionContext context , Path path , Digest digest ) throws IOException {
592+ return downloadFile (context , path , digest , new DownloadProgressReporter (NO_ACTION , "" , 0 ));
593+ }
594+
595+ /** Downloads a file (that is not a directory). The content is fetched from the digest. */
596+ public ListenableFuture <Void > downloadFile (
597+ RemoteActionExecutionContext context ,
598+ Path path ,
599+ Digest digest ,
600+ DownloadProgressReporter reporter )
601+ throws IOException {
532602 Preconditions .checkNotNull (path .getParentDirectory ()).createDirectoryAndParents ();
533603 if (digest .getSizeBytes () == 0 ) {
534604 // Handle empty file locally.
@@ -549,7 +619,9 @@ public ListenableFuture<Void> downloadFile(
549619 return COMPLETED_SUCCESS ;
550620 }
551621
552- OutputStream out = new LazyFileOutputStream (path );
622+ reporter .started ();
623+ OutputStream out = new ReportingOutputStream (new LazyFileOutputStream (path ), reporter );
624+
553625 SettableFuture <Void > outerF = SettableFuture .create ();
554626 ListenableFuture <Void > f = cacheProtocol .downloadBlob (context , digest , out );
555627 Futures .addCallback (
@@ -560,6 +632,7 @@ public void onSuccess(Void result) {
560632 try {
561633 out .close ();
562634 outerF .set (null );
635+ reporter .finished ();
563636 } catch (IOException e ) {
564637 outerF .setException (e );
565638 } catch (RuntimeException e ) {
@@ -572,6 +645,7 @@ public void onSuccess(Void result) {
572645 public void onFailure (Throwable t ) {
573646 try {
574647 out .close ();
648+ reporter .finished ();
575649 } catch (IOException e ) {
576650 if (t != e ) {
577651 t .addSuppressed (e );
@@ -1100,6 +1174,49 @@ private static FailureDetail createFailureDetail(String message, Code detailedCo
11001174 .build ();
11011175 }
11021176
1177+ /**
1178+ * An {@link OutputStream} that reports all the write operations with {@link
1179+ * DownloadProgressReporter}.
1180+ */
1181+ private static class ReportingOutputStream extends OutputStream {
1182+
1183+ private final OutputStream out ;
1184+ private final DownloadProgressReporter reporter ;
1185+
1186+ ReportingOutputStream (OutputStream out , DownloadProgressReporter reporter ) {
1187+ this .out = out ;
1188+ this .reporter = reporter ;
1189+ }
1190+
1191+ @ Override
1192+ public void write (byte [] b ) throws IOException {
1193+ out .write (b );
1194+ reporter .downloadedBytes (b .length );
1195+ }
1196+
1197+ @ Override
1198+ public void write (byte [] b , int off , int len ) throws IOException {
1199+ out .write (b , off , len );
1200+ reporter .downloadedBytes (len );
1201+ }
1202+
1203+ @ Override
1204+ public void write (int b ) throws IOException {
1205+ out .write (b );
1206+ reporter .downloadedBytes (1 );
1207+ }
1208+
1209+ @ Override
1210+ public void flush () throws IOException {
1211+ out .flush ();
1212+ }
1213+
1214+ @ Override
1215+ public void close () throws IOException {
1216+ out .close ();
1217+ }
1218+ }
1219+
11031220 /** In-memory representation of action result metadata. */
11041221 static class ActionResultMetadata {
11051222
0 commit comments