Skip to content

Commit 6ffff17

Browse files
fmeumcopybara-github
authored andcommitted
Report percentual download progress in repository rules
If the HTTP response to a download request contains a `Content-Length` header, download progress is now reported as `10.1 MiB (20.2%)` instead of `10.1 MiB (10,590,000B)`. Closes #18450. PiperOrigin-RevId: 534035444 Change-Id: I1c5144555eda1890652b4d3f62b414292ba909d5
1 parent f934019 commit 6ffff17

File tree

5 files changed

+81
-18
lines changed

5 files changed

+81
-18
lines changed

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/DownloadProgressEvent.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
import com.google.devtools.build.lib.events.ExtendedEventHandler;
1818
import com.google.devtools.build.lib.remote.util.Utils;
1919
import java.net.URL;
20+
import java.text.DecimalFormat;
21+
import java.text.DecimalFormatSymbols;
22+
import java.util.Locale;
23+
import java.util.OptionalLong;
2024

2125
/**
2226
* Postable event reporting on progress made downloading an URL. It can be used to report the URL
@@ -26,17 +30,20 @@ public class DownloadProgressEvent implements ExtendedEventHandler.FetchProgress
2630
private final URL originalUrl;
2731
private final URL actualUrl;
2832
private final long bytesRead;
33+
private final OptionalLong totalBytes;
2934
private final boolean downloadFinished;
3035

31-
public DownloadProgressEvent(URL originalUrl, URL actualUrl, long bytesRead, boolean finished) {
36+
public DownloadProgressEvent(
37+
URL originalUrl, URL actualUrl, long bytesRead, OptionalLong totalBytes, boolean finished) {
3238
this.originalUrl = originalUrl;
3339
this.actualUrl = actualUrl;
3440
this.bytesRead = bytesRead;
41+
this.totalBytes = totalBytes;
3542
this.downloadFinished = finished;
3643
}
3744

3845
public DownloadProgressEvent(URL originalUrl, long bytesRead, boolean finished) {
39-
this(originalUrl, null, bytesRead, finished);
46+
this(originalUrl, null, bytesRead, OptionalLong.empty(), finished);
4047
}
4148

4249
public DownloadProgressEvent(URL url, long bytesRead) {
@@ -69,10 +76,22 @@ public long getBytesRead() {
6976
return bytesRead;
7077
}
7178

79+
private static final DecimalFormat PERCENTAGE_FORMAT =
80+
new DecimalFormat("0.0%", new DecimalFormatSymbols(Locale.US));
81+
7282
@Override
7383
public String getProgress() {
7484
if (bytesRead > 0) {
75-
return String.format("%s (%,dB)", Utils.bytesCountToDisplayString(bytesRead), bytesRead);
85+
if (totalBytes.isPresent()) {
86+
double totalBytesDouble = this.totalBytes.getAsLong();
87+
double ratio = totalBytesDouble != 0 ? bytesRead / totalBytesDouble : 1;
88+
// 10.1 MiB (20.2%)
89+
return String.format(
90+
"%s (%s)", Utils.bytesCountToDisplayString(bytesRead), PERCENTAGE_FORMAT.format(ratio));
91+
} else {
92+
// 10.1 MiB (10,590,000B)
93+
return String.format("%s (%,dB)", Utils.bytesCountToDisplayString(bytesRead), bytesRead);
94+
}
7695
} else {
7796
return "";
7897
}

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpStream.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.io.SequenceInputStream;
3131
import java.net.URL;
3232
import java.net.URLConnection;
33+
import java.util.OptionalLong;
3334
import java.util.zip.GZIPInputStream;
3435
import javax.annotation.WillCloseWhenClosed;
3536

@@ -87,17 +88,19 @@ HttpStream create(
8788
stream = retrier;
8889
}
8990

91+
OptionalLong totalBytes = OptionalLong.empty();
9092
try {
9193
String contentLength = connection.getHeaderField("Content-Length");
9294
if (contentLength != null) {
93-
long expectedSize = Long.parseLong(contentLength);
94-
stream = new CheckContentLengthInputStream(stream, expectedSize);
95+
totalBytes = OptionalLong.of(Long.parseUnsignedLong(contentLength));
96+
stream = new CheckContentLengthInputStream(stream, totalBytes.getAsLong());
9597
}
9698
} catch (NumberFormatException ignored) {
9799
// ignored
98100
}
99101

100-
stream = progressInputStreamFactory.create(stream, connection.getURL(), originalUrl);
102+
stream =
103+
progressInputStreamFactory.create(stream, connection.getURL(), originalUrl, totalBytes);
101104

102105
// Determine if we need to transparently gunzip. See RFC2616 § 3.5 and § 14.11. Please note
103106
// that some web servers will send Content-Encoding: gzip even when we didn't request it if

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/ProgressInputStream.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.io.InputStream;
2525
import java.net.URL;
2626
import java.util.Locale;
27+
import java.util.OptionalLong;
2728
import java.util.concurrent.atomic.AtomicLong;
2829
import javax.annotation.WillCloseWhenClosed;
2930

@@ -50,9 +51,20 @@ static class Factory {
5051
this.eventHandler = eventHandler;
5152
}
5253

53-
InputStream create(@WillCloseWhenClosed InputStream delegate, URL url, URL originalUrl) {
54+
InputStream create(
55+
@WillCloseWhenClosed InputStream delegate,
56+
URL url,
57+
URL originalUrl,
58+
OptionalLong totalBytes) {
5459
return new ProgressInputStream(
55-
locale, clock, eventHandler, PROGRESS_INTERVAL_MS, delegate, url, originalUrl);
60+
locale,
61+
clock,
62+
eventHandler,
63+
PROGRESS_INTERVAL_MS,
64+
delegate,
65+
url,
66+
originalUrl,
67+
totalBytes);
5668
}
5769
}
5870

@@ -63,6 +75,7 @@ InputStream create(@WillCloseWhenClosed InputStream delegate, URL url, URL origi
6375
private final long intervalMs;
6476
private final URL url;
6577
private final URL originalUrl;
78+
private final OptionalLong totalBytes;
6679
private final AtomicLong toto = new AtomicLong();
6780
private final AtomicLong nextEvent;
6881

@@ -73,7 +86,8 @@ InputStream create(@WillCloseWhenClosed InputStream delegate, URL url, URL origi
7386
long intervalMs,
7487
InputStream delegate,
7588
URL url,
76-
URL originalUrl) {
89+
URL originalUrl,
90+
OptionalLong totalBytes) {
7791
Preconditions.checkArgument(intervalMs >= 0);
7892
this.locale = locale;
7993
this.clock = clock;
@@ -82,8 +96,9 @@ InputStream create(@WillCloseWhenClosed InputStream delegate, URL url, URL origi
8296
this.delegate = delegate;
8397
this.url = url;
8498
this.originalUrl = originalUrl;
99+
this.totalBytes = totalBytes;
85100
this.nextEvent = new AtomicLong(clock.currentTimeMillis() + intervalMs);
86-
eventHandler.post(new DownloadProgressEvent(originalUrl, url, 0, false));
101+
eventHandler.post(new DownloadProgressEvent(originalUrl, url, 0, totalBytes, false));
87102
}
88103

89104
@Override
@@ -112,7 +127,7 @@ public int available() throws IOException {
112127
@Override
113128
public void close() throws IOException {
114129
delegate.close();
115-
eventHandler.post(new DownloadProgressEvent(originalUrl, url, toto.get(), true));
130+
eventHandler.post(new DownloadProgressEvent(originalUrl, url, toto.get(), totalBytes, true));
116131
}
117132

118133
private void reportProgress(long bytesRead) {
@@ -124,7 +139,7 @@ private void reportProgress(long bytesRead) {
124139
if (!url.getHost().equals(originalUrl.getHost())) {
125140
via = " via " + url.getHost();
126141
}
127-
eventHandler.post(new DownloadProgressEvent(originalUrl, url, bytesRead, false));
142+
eventHandler.post(new DownloadProgressEvent(originalUrl, url, bytesRead, totalBytes, false));
128143
eventHandler.handle(
129144
Event.progress(
130145
String.format(locale, "Downloading %s%s: %,d bytes", originalUrl, via, bytesRead)));

src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpStreamTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void before() throws Exception {
9393
nRetries = 0;
9494

9595
when(connection.getInputStream()).thenReturn(new ByteArrayInputStream(data));
96-
when(progress.create(any(InputStream.class), any(), any(URL.class)))
96+
when(progress.create(any(InputStream.class), any(), any(URL.class), any()))
9797
.thenAnswer(
9898
new Answer<InputStream>() {
9999
@Override

src/test/java/com/google/devtools/build/lib/bazel/repository/downloader/ProgressInputStreamTest.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.io.InputStream;
3838
import java.net.URL;
3939
import java.util.Locale;
40+
import java.util.OptionalLong;
4041
import org.junit.After;
4142
import org.junit.Test;
4243
import org.junit.runner.RunWith;
@@ -53,7 +54,8 @@ public class ProgressInputStreamTest {
5354
private final InputStream delegate = mock(InputStream.class);
5455
private final URL url = makeUrl("http://lol.example");
5556
private ProgressInputStream stream =
56-
new ProgressInputStream(Locale.US, clock, extendedEventHandler, 1, delegate, url, url);
57+
new ProgressInputStream(
58+
Locale.US, clock, extendedEventHandler, 1, delegate, url, url, OptionalLong.empty());
5759

5860
@After
5961
public void after() throws Exception {
@@ -126,7 +128,15 @@ public void bufferReadsAfterInterval_emitsProgressOnce() throws Exception {
126128
@Test
127129
public void bufferReadsAfterIntervalInGermany_usesPeriodAsSeparator() throws Exception {
128130
stream =
129-
new ProgressInputStream(Locale.GERMANY, clock, extendedEventHandler, 1, delegate, url, url);
131+
new ProgressInputStream(
132+
Locale.GERMANY,
133+
clock,
134+
extendedEventHandler,
135+
1,
136+
delegate,
137+
url,
138+
url,
139+
OptionalLong.empty());
130140
byte[] buffer = new byte[1024];
131141
when(delegate.read(any(byte[].class), anyInt(), anyInt())).thenReturn(1024);
132142
clock.advanceMillis(1);
@@ -145,14 +155,30 @@ public void redirectedToDifferentServer_showsOriginalUrlWithVia() throws Excepti
145155
1,
146156
delegate,
147157
new URL("http://cdn.example/foo"),
148-
url);
158+
url,
159+
OptionalLong.empty());
149160
when(delegate.read()).thenReturn(42);
150161
assertThat(stream.read()).isEqualTo(42);
151162
clock.advanceMillis(1);
152163
assertThat(stream.read()).isEqualTo(42);
153164
assertThat(stream.read()).isEqualTo(42);
154165
verify(delegate, times(3)).read();
155-
verify(eventHandler).handle(
156-
Event.progress("Downloading http://lol.example via cdn.example: 2 bytes"));
166+
verify(eventHandler)
167+
.handle(Event.progress("Downloading http://lol.example via cdn.example: 2 bytes"));
168+
}
169+
170+
@Test
171+
public void percentualProgress() {
172+
DownloadProgressEvent event =
173+
new DownloadProgressEvent(
174+
url, url, 25 * 1024 * 1024, OptionalLong.of(100 * 1024 * 1024), false);
175+
assertThat(event.getProgress()).isEqualTo("25.0 MiB (25.0%)");
176+
}
177+
178+
@Test
179+
public void percentualProgress_zeroTotalBytes() {
180+
DownloadProgressEvent event =
181+
new DownloadProgressEvent(url, url, 25 * 1024 * 1024, OptionalLong.of(0), false);
182+
assertThat(event.getProgress()).isEqualTo("25.0 MiB (100.0%)");
157183
}
158184
}

0 commit comments

Comments
 (0)