Skip to content

Commit 68a1f76

Browse files
committed
Merge pull request square#385 from square/dimitris/download-stats
Add download stats.
2 parents 7cfc9f2 + 46c4e72 commit 68a1f76

File tree

12 files changed

+150
-19
lines changed

12 files changed

+150
-19
lines changed

picasso/src/main/java/com/squareup/picasso/Downloader.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,35 +47,66 @@ class Response {
4747
final InputStream stream;
4848
final Bitmap bitmap;
4949
final boolean cached;
50+
final long contentLength;
5051

5152
/**
5253
* Response image and info.
5354
*
5455
* @param bitmap Image.
5556
* @param loadedFromCache {@code true} if the source of the image is from a local disk cache.
57+
* @deprecated Use {@link Response#Response(android.graphics.Bitmap, boolean, long)} instead.
5658
*/
59+
@Deprecated @SuppressWarnings("UnusedDeclaration")
5760
public Response(Bitmap bitmap, boolean loadedFromCache) {
61+
this(bitmap, loadedFromCache, 0);
62+
}
63+
64+
/**
65+
* Response stream and info.
66+
*
67+
* @param stream Image data stream.
68+
* @param loadedFromCache {@code true} if the source of the stream is from a local disk cache.
69+
* @deprecated Use {@link Response#Response(java.io.InputStream, boolean, long)} instead.
70+
*/
71+
@Deprecated @SuppressWarnings("UnusedDeclaration")
72+
public Response(InputStream stream, boolean loadedFromCache) {
73+
this(stream, loadedFromCache, 0);
74+
}
75+
76+
/**
77+
* Response image and info.
78+
*
79+
* @param bitmap Image.
80+
* @param loadedFromCache {@code true} if the source of the image is from a local disk cache.
81+
* @param contentLength The content length of the response, typically derived by the
82+
* {@code Content-Length} HTTP header.
83+
*/
84+
public Response(Bitmap bitmap, boolean loadedFromCache, long contentLength) {
5885
if (bitmap == null) {
5986
throw new IllegalArgumentException("Bitmap may not be null.");
6087
}
6188
this.stream = null;
6289
this.bitmap = bitmap;
6390
this.cached = loadedFromCache;
91+
this.contentLength = contentLength;
6492
}
6593

6694
/**
6795
* Response stream and info.
6896
*
6997
* @param stream Image data stream.
7098
* @param loadedFromCache {@code true} if the source of the stream is from a local disk cache.
99+
* @param contentLength The content length of the response, typically derived by the
100+
* {@code Content-Length} HTTP header.
71101
*/
72-
public Response(InputStream stream, boolean loadedFromCache) {
102+
public Response(InputStream stream, boolean loadedFromCache, long contentLength) {
73103
if (stream == null) {
74104
throw new IllegalArgumentException("Stream may not be null.");
75105
}
76106
this.stream = stream;
77107
this.bitmap = null;
78108
this.cached = loadedFromCache;
109+
this.contentLength = contentLength;
79110
}
80111

81112
/**
@@ -95,5 +126,10 @@ public InputStream getInputStream() {
95126
public Bitmap getBitmap() {
96127
return bitmap;
97128
}
129+
130+
/** Content length of the response. */
131+
public long getContentLength() {
132+
return contentLength;
133+
}
98134
}
99135
}

picasso/src/main/java/com/squareup/picasso/NetworkBitmapHunter.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public NetworkBitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache,
5656
}
5757

5858
InputStream is = response.getInputStream();
59+
if (is == null) {
60+
return null;
61+
}
62+
if (loadedFrom == NETWORK && response.getContentLength() > 0) {
63+
stats.dispatchDownloadFinished(response.getContentLength());
64+
}
5965
try {
6066
return decodeStream(is, data);
6167
} finally {
@@ -73,9 +79,6 @@ public NetworkBitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache,
7379
}
7480

7581
private Bitmap decodeStream(InputStream stream, Request data) throws IOException {
76-
if (stream == null) {
77-
return null;
78-
}
7982
MarkableInputStream markStream = new MarkableInputStream(stream);
8083
stream = markStream;
8184

picasso/src/main/java/com/squareup/picasso/OkHttpDownloader.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ protected OkHttpClient getClient() {
112112
if (responseSource == null) {
113113
responseSource = connection.getHeaderField(RESPONSE_SOURCE_ANDROID);
114114
}
115+
116+
long contentLength = connection.getHeaderFieldInt("Content-Length", 0);
115117
boolean fromCache = parseResponseSourceHeader(responseSource);
116118

117-
return new Response(connection.getInputStream(), fromCache);
119+
return new Response(connection.getInputStream(), fromCache, contentLength);
118120
}
119121
}

picasso/src/main/java/com/squareup/picasso/Picasso.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,11 @@ public RequestCreator load(int resourceId) {
227227
this.debugging = debugging;
228228
}
229229

230-
/** Creates a {@link StatsSnapshot} of the current stats for this instance. */
230+
/**
231+
* Creates a {@link StatsSnapshot} of the current stats for this instance.
232+
* <b>NOTE:</b> The snapshot may not always be completely up-to-date if requests are still in
233+
* progress.
234+
*/
231235
@SuppressWarnings("UnusedDeclaration") public StatsSnapshot getSnapshot() {
232236
return stats.createSnapshot();
233237
}

picasso/src/main/java/com/squareup/picasso/Stats.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Stats {
2828
private static final int CACHE_MISS = 1;
2929
private static final int BITMAP_DECODE_FINISHED = 2;
3030
private static final int BITMAP_TRANSFORMED_FINISHED = 3;
31+
private static final int DOWNLOAD_FINISHED = 4;
3132

3233
private static final String STATS_THREAD_NAME = Utils.THREAD_PREFIX + "Stats";
3334

@@ -37,10 +38,13 @@ class Stats {
3738

3839
long cacheHits;
3940
long cacheMisses;
41+
long totalDownloadSize;
4042
long totalOriginalBitmapSize;
4143
long totalTransformedBitmapSize;
44+
long averageDownloadSize;
4245
long averageOriginalBitmapSize;
4346
long averageTransformedBitmapSize;
47+
int downloadCount;
4448
int originalBitmapCount;
4549
int transformedBitmapCount;
4650

@@ -59,6 +63,10 @@ void dispatchBitmapTransformed(Bitmap bitmap) {
5963
processBitmap(bitmap, BITMAP_TRANSFORMED_FINISHED);
6064
}
6165

66+
void dispatchDownloadFinished(long size) {
67+
handler.sendMessage(handler.obtainMessage(DOWNLOAD_FINISHED, size));
68+
}
69+
6270
void dispatchCacheHit() {
6371
handler.sendEmptyMessage(CACHE_HIT);
6472
}
@@ -79,6 +87,12 @@ void performCacheMiss() {
7987
cacheMisses++;
8088
}
8189

90+
void performDownloadFinished(Long size) {
91+
downloadCount++;
92+
totalDownloadSize += size;
93+
averageDownloadSize = getAverage(downloadCount, totalDownloadSize);
94+
}
95+
8296
void performBitmapDecoded(long size) {
8397
originalBitmapCount++;
8498
totalOriginalBitmapSize += size;
@@ -91,11 +105,11 @@ void performBitmapTransformed(long size) {
91105
averageTransformedBitmapSize = getAverage(originalBitmapCount, totalTransformedBitmapSize);
92106
}
93107

94-
synchronized StatsSnapshot createSnapshot() {
108+
StatsSnapshot createSnapshot() {
95109
return new StatsSnapshot(cache.maxSize(), cache.size(), cacheHits, cacheMisses,
96-
totalOriginalBitmapSize, totalTransformedBitmapSize, averageOriginalBitmapSize,
97-
averageTransformedBitmapSize, originalBitmapCount, transformedBitmapCount,
98-
System.currentTimeMillis());
110+
totalDownloadSize, totalOriginalBitmapSize, totalTransformedBitmapSize, averageDownloadSize,
111+
averageOriginalBitmapSize, averageTransformedBitmapSize, downloadCount, originalBitmapCount,
112+
transformedBitmapCount, System.currentTimeMillis());
99113
}
100114

101115
private void processBitmap(Bitmap bitmap, int what) {
@@ -131,6 +145,9 @@ public StatsHandler(Looper looper, Stats stats) {
131145
case BITMAP_TRANSFORMED_FINISHED:
132146
stats.performBitmapTransformed(msg.arg1);
133147
break;
148+
case DOWNLOAD_FINISHED:
149+
stats.performDownloadFinished((Long) msg.obj);
150+
break;
134151
default:
135152
Picasso.HANDLER.post(new Runnable() {
136153
@Override public void run() {

picasso/src/main/java/com/squareup/picasso/StatsSnapshot.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,40 @@ public class StatsSnapshot {
2727
public final int size;
2828
public final long cacheHits;
2929
public final long cacheMisses;
30+
public final long totalDownloadSize;
3031
public final long totalOriginalBitmapSize;
3132
public final long totalTransformedBitmapSize;
33+
public final long averageDownloadSize;
3234
public final long averageOriginalBitmapSize;
3335
public final long averageTransformedBitmapSize;
36+
public final int downloadCount;
3437
public final int originalBitmapCount;
3538
public final int transformedBitmapCount;
3639

3740
public final long timeStamp;
3841

3942
public StatsSnapshot(int maxSize, int size, long cacheHits, long cacheMisses,
40-
long totalOriginalBitmapSize, long totalTransformedBitmapSize, long averageOriginalBitmapSize,
41-
long averageTransformedBitmapSize, int originalBitmapCount, int transformedBitmapCount,
42-
long timeStamp) {
43+
long totalDownloadSize, long totalOriginalBitmapSize, long totalTransformedBitmapSize,
44+
long averageDownloadSize, long averageOriginalBitmapSize, long averageTransformedBitmapSize,
45+
int downloadCount, int originalBitmapCount, int transformedBitmapCount, long timeStamp) {
4346
this.maxSize = maxSize;
4447
this.size = size;
4548
this.cacheHits = cacheHits;
4649
this.cacheMisses = cacheMisses;
50+
this.totalDownloadSize = totalDownloadSize;
4751
this.totalOriginalBitmapSize = totalOriginalBitmapSize;
4852
this.totalTransformedBitmapSize = totalTransformedBitmapSize;
53+
this.averageDownloadSize = averageDownloadSize;
4954
this.averageOriginalBitmapSize = averageOriginalBitmapSize;
5055
this.averageTransformedBitmapSize = averageTransformedBitmapSize;
56+
this.downloadCount = downloadCount;
5157
this.originalBitmapCount = originalBitmapCount;
5258
this.transformedBitmapCount = transformedBitmapCount;
5359
this.timeStamp = timeStamp;
5460
}
5561

5662
/** Prints out this {@link StatsSnapshot} into log. */
57-
public void dump() {
63+
@SuppressWarnings("UnusedDeclaration") public void dump() {
5864
StringWriter logWriter = new StringWriter();
5965
dump(new PrintWriter(logWriter));
6066
Log.i(TAG, logWriter.toString());
@@ -74,6 +80,13 @@ public void dump(PrintWriter writer) {
7480
writer.println(cacheHits);
7581
writer.print(" Cache Misses: ");
7682
writer.println(cacheMisses);
83+
writer.println("Network Stats");
84+
writer.print(" Download Count: ");
85+
writer.println(downloadCount);
86+
writer.print(" Total Download Size: ");
87+
writer.println(totalDownloadSize);
88+
writer.print(" Average Download Size: ");
89+
writer.println(averageDownloadSize);
7790
writer.println("Bitmap Stats");
7891
writer.print(" Total Bitmaps Decoded: ");
7992
writer.println(originalBitmapCount);
@@ -101,6 +114,12 @@ public void dump(PrintWriter writer) {
101114
+ cacheHits
102115
+ ", cacheMisses="
103116
+ cacheMisses
117+
+ ", downloadCount="
118+
+ downloadCount
119+
+ ", totalDownloadSize="
120+
+ totalDownloadSize
121+
+ ", averageDownloadSize="
122+
+ averageDownloadSize
104123
+ ", totalOriginalBitmapSize="
105124
+ totalOriginalBitmapSize
106125
+ ", totalTransformedBitmapSize="

picasso/src/main/java/com/squareup/picasso/UrlConnectionDownloader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ protected HttpURLConnection openConnection(Uri path) throws IOException {
6767
throw new ResponseException(responseCode + " " + connection.getResponseMessage());
6868
}
6969

70+
long contentLength = connection.getHeaderFieldInt("Content-Length", 0);
7071
boolean fromCache = parseResponseSourceHeader(connection.getHeaderField(RESPONSE_SOURCE));
7172

72-
return new Response(connection.getInputStream(), fromCache);
73+
return new Response(connection.getInputStream(), fromCache, contentLength);
7374
}
7475

7576
private static void installCacheIfNeeded(Context context) {

picasso/src/test/java/com/squareup/picasso/NetworkBitmapHunterTest.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
import static android.graphics.Bitmap.Config.ARGB_8888;
3131
import static com.squareup.picasso.TestUtils.URI_1;
3232
import static com.squareup.picasso.TestUtils.URI_KEY_1;
33+
import static com.squareup.picasso.TestUtils.mockInputStream;
3334
import static com.squareup.picasso.TestUtils.mockNetworkInfo;
3435
import static org.fest.assertions.api.Assertions.assertThat;
3536
import static org.mockito.Matchers.any;
3637
import static org.mockito.Matchers.anyBoolean;
3738
import static org.mockito.Mockito.mock;
3839
import static org.mockito.Mockito.verify;
40+
import static org.mockito.Mockito.verifyZeroInteractions;
3941
import static org.mockito.Mockito.when;
4042
import static org.mockito.MockitoAnnotations.initMocks;
4143

@@ -109,11 +111,41 @@ public class NetworkBitmapHunterTest {
109111
assertThat(hunter.shouldRetry(true, info)).isFalse();
110112
}
111113

114+
@Test public void noCacheAndKnownContentLengthDispatchToStats() throws Exception {
115+
Downloader.Response response = new Downloader.Response(mockInputStream(), false, 1024);
116+
when(downloader.load(any(Uri.class), anyBoolean())).thenReturn(response);
117+
Action action = TestUtils.mockAction(URI_KEY_1, URI_1);
118+
NetworkBitmapHunter hunter =
119+
new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader);
120+
hunter.decode(action.getData());
121+
verify(stats).dispatchDownloadFinished(response.contentLength);
122+
}
123+
124+
@Test public void unknownContentLengthDoesNotDispatchToStats() throws Exception {
125+
Downloader.Response response = new Downloader.Response(mockInputStream(), false, 0);
126+
when(downloader.load(any(Uri.class), anyBoolean())).thenReturn(response);
127+
Action action = TestUtils.mockAction(URI_KEY_1, URI_1);
128+
NetworkBitmapHunter hunter =
129+
new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader);
130+
hunter.decode(action.getData());
131+
verifyZeroInteractions(stats);
132+
}
133+
134+
@Test public void cachedResponseDoesNotDispatchToStats() throws Exception {
135+
Downloader.Response response = new Downloader.Response(mockInputStream(), true, 1024);
136+
when(downloader.load(any(Uri.class), anyBoolean())).thenReturn(response);
137+
Action action = TestUtils.mockAction(URI_KEY_1, URI_1);
138+
NetworkBitmapHunter hunter =
139+
new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader);
140+
hunter.decode(action.getData());
141+
verifyZeroInteractions(stats);
142+
}
143+
112144
@Test public void downloaderCanReturnBitmapDirectly() throws Exception {
113145
final Bitmap expected = Bitmap.createBitmap(10, 10, ARGB_8888);
114146
Downloader bitmapDownloader = new Downloader() {
115147
@Override public Response load(Uri uri, boolean localCacheOnly) throws IOException {
116-
return new Response(expected, false);
148+
return new Response(expected, false, 0);
117149
}
118150
};
119151
Action action = TestUtils.mockAction(URI_KEY_1, URI_1);

picasso/src/test/java/com/squareup/picasso/OkHttpDownloaderTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ public class OkHttpDownloaderTest {
8686
assertThat(response3.cached).isTrue();
8787
}
8888

89+
@Test public void readsContentLengthHeader() throws Exception {
90+
server.enqueue(new MockResponse().addHeader("Content-Length", 1024));
91+
Downloader.Response response = loader.load(URL, true);
92+
assertThat(response.contentLength).isEqualTo(1024);
93+
}
94+
8995
@Test public void throwsResponseException() throws Exception {
9096
server.enqueue(new MockResponse().setStatus("HTTP/1.1 401 Not Authorized"));
9197
try {

picasso/src/test/java/com/squareup/picasso/TestUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import android.view.ViewTreeObserver;
2525
import android.widget.ImageView;
2626
import java.io.File;
27+
import java.io.IOException;
28+
import java.io.InputStream;
2729
import org.mockito.invocation.InvocationOnMock;
2830
import org.mockito.stubbing.Answer;
2931

@@ -150,6 +152,10 @@ static NetworkInfo mockNetworkInfo() {
150152
return mock(NetworkInfo.class);
151153
}
152154

155+
static InputStream mockInputStream() throws IOException {
156+
return mock(InputStream.class);
157+
}
158+
153159
static BitmapHunter mockHunter(String key, Bitmap result, boolean skipCache) {
154160
Request data = new Request.Builder(URI_1).build();
155161
BitmapHunter hunter = mock(BitmapHunter.class);

picasso/src/test/java/com/squareup/picasso/UrlConnectionDownloaderTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ public class UrlConnectionDownloaderTest {
109109
assertThat(response2.cached).isTrue();
110110
}
111111

112+
@Test public void readsContentLengthHeader() throws Exception {
113+
server.enqueue(new MockResponse().addHeader("Content-Length", 1024));
114+
Downloader.Response response = loader.load(URL, true);
115+
assertThat(response.contentLength).isEqualTo(1024);
116+
}
117+
112118
@Test public void throwsResponseException() throws Exception {
113119
server.enqueue(new MockResponse().setStatus("HTTP/1.1 401 Not Authorized"));
114120
try {

0 commit comments

Comments
 (0)