1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/download/download_stats.h"
6
7#include "base/metrics/histogram.h"
8#include "base/strings/string_util.h"
9#include "content/browser/download/download_resource_handler.h"
10#include "content/public/browser/download_interrupt_reasons.h"
11#include "net/http/http_content_disposition.h"
12
13namespace content {
14
15namespace {
16
17// All possible error codes from the network module. Note that the error codes
18// are all positive (since histograms expect positive sample values).
19const int kAllInterruptReasonCodes[] = {
20#define INTERRUPT_REASON(label, value) (value),
21#include "content/public/browser/download_interrupt_reason_values.h"
22#undef INTERRUPT_REASON
23};
24
25// These values are based on net::HttpContentDisposition::ParseResult values.
26// Values other than HEADER_PRESENT and IS_VALID are only measured if |IS_VALID|
27// is true.
28enum ContentDispositionCountTypes {
29  // Count of downloads which had a Content-Disposition headers. The total
30  // number of downloads is measured by UNTHROTTLED_COUNT.
31  CONTENT_DISPOSITION_HEADER_PRESENT = 0,
32
33  // At least one of 'name', 'filename' or 'filenae*' attributes were valid and
34  // yielded a non-empty filename.
35  CONTENT_DISPOSITION_IS_VALID,
36
37  // The following enum values correspond to
38  // net::HttpContentDisposition::ParseResult.
39  CONTENT_DISPOSITION_HAS_DISPOSITION_TYPE,
40  CONTENT_DISPOSITION_HAS_UNKNOWN_TYPE,
41  CONTENT_DISPOSITION_HAS_NAME,
42  CONTENT_DISPOSITION_HAS_FILENAME,
43  CONTENT_DISPOSITION_HAS_EXT_FILENAME,
44  CONTENT_DISPOSITION_HAS_NON_ASCII_STRINGS,
45  CONTENT_DISPOSITION_HAS_PERCENT_ENCODED_STRINGS,
46  CONTENT_DISPOSITION_HAS_RFC2047_ENCODED_STRINGS,
47
48  // Only have the 'name' attribute is present.
49  CONTENT_DISPOSITION_HAS_NAME_ONLY,
50
51  CONTENT_DISPOSITION_LAST_ENTRY
52};
53
54void RecordContentDispositionCount(ContentDispositionCountTypes type,
55                                   bool record) {
56  if (!record)
57    return;
58  UMA_HISTOGRAM_ENUMERATION(
59      "Download.ContentDisposition", type, CONTENT_DISPOSITION_LAST_ENTRY);
60}
61
62void RecordContentDispositionCountFlag(
63    ContentDispositionCountTypes type,
64    int flags_to_test,
65    net::HttpContentDisposition::ParseResultFlags flag) {
66  RecordContentDispositionCount(type, (flags_to_test & flag) == flag);
67}
68
69} // namespace
70
71void RecordDownloadCount(DownloadCountTypes type) {
72  UMA_HISTOGRAM_ENUMERATION(
73      "Download.Counts", type, DOWNLOAD_COUNT_TYPES_LAST_ENTRY);
74}
75
76void RecordDownloadSource(DownloadSource source) {
77  UMA_HISTOGRAM_ENUMERATION(
78      "Download.Sources", source, DOWNLOAD_SOURCE_LAST_ENTRY);
79}
80
81void RecordDownloadCompleted(const base::TimeTicks& start, int64 download_len) {
82  RecordDownloadCount(COMPLETED_COUNT);
83  UMA_HISTOGRAM_LONG_TIMES("Download.Time", (base::TimeTicks::Now() - start));
84  int64 max = 1024 * 1024 * 1024;  // One Terabyte.
85  download_len /= 1024;  // In Kilobytes
86  UMA_HISTOGRAM_CUSTOM_COUNTS("Download.DownloadSize",
87                              download_len,
88                              1,
89                              max,
90                              256);
91}
92
93void RecordDownloadInterrupted(DownloadInterruptReason reason,
94                               int64 received,
95                               int64 total) {
96  RecordDownloadCount(INTERRUPTED_COUNT);
97  UMA_HISTOGRAM_CUSTOM_ENUMERATION(
98      "Download.InterruptedReason",
99      reason,
100      base::CustomHistogram::ArrayToCustomRanges(
101          kAllInterruptReasonCodes, arraysize(kAllInterruptReasonCodes)));
102
103  // The maximum should be 2^kBuckets, to have the logarithmic bucket
104  // boundaries fall on powers of 2.
105  static const int kBuckets = 30;
106  static const int64 kMaxKb = 1 << kBuckets;  // One Terabyte, in Kilobytes.
107  int64 delta_bytes = total - received;
108  bool unknown_size = total <= 0;
109  int64 received_kb = received / 1024;
110  int64 total_kb = total / 1024;
111  UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedReceivedSizeK",
112                              received_kb,
113                              1,
114                              kMaxKb,
115                              kBuckets);
116  if (!unknown_size) {
117    UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedTotalSizeK",
118                                total_kb,
119                                1,
120                                kMaxKb,
121                                kBuckets);
122    if (delta_bytes == 0) {
123      RecordDownloadCount(INTERRUPTED_AT_END_COUNT);
124      UMA_HISTOGRAM_CUSTOM_ENUMERATION(
125          "Download.InterruptedAtEndReason",
126          reason,
127          base::CustomHistogram::ArrayToCustomRanges(
128              kAllInterruptReasonCodes,
129              arraysize(kAllInterruptReasonCodes)));
130    } else if (delta_bytes > 0) {
131      UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedOverrunBytes",
132                                  delta_bytes,
133                                  1,
134                                  kMaxKb,
135                                  kBuckets);
136    } else {
137      UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedUnderrunBytes",
138                                  -delta_bytes,
139                                  1,
140                                  kMaxKb,
141                                  kBuckets);
142    }
143  }
144
145  UMA_HISTOGRAM_BOOLEAN("Download.InterruptedUnknownSize", unknown_size);
146}
147
148void RecordDangerousDownloadAccept(DownloadDangerType danger_type) {
149  UMA_HISTOGRAM_ENUMERATION("Download.DangerousDownloadValidated",
150                            danger_type,
151                            DOWNLOAD_DANGER_TYPE_MAX);
152}
153
154void RecordDangerousDownloadDiscard(DownloadDiscardReason reason,
155                                    DownloadDangerType danger_type) {
156  switch (reason) {
157    case DOWNLOAD_DISCARD_DUE_TO_USER_ACTION:
158      UMA_HISTOGRAM_ENUMERATION(
159          "Download.UserDiscard", danger_type, DOWNLOAD_DANGER_TYPE_MAX);
160      break;
161    case DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN:
162      UMA_HISTOGRAM_ENUMERATION(
163          "Download.Discard", danger_type, DOWNLOAD_DANGER_TYPE_MAX);
164      break;
165    default:
166      NOTREACHED();
167  }
168}
169
170void RecordDownloadWriteSize(size_t data_len) {
171  int max = 1024 * 1024;  // One Megabyte.
172  UMA_HISTOGRAM_CUSTOM_COUNTS("Download.WriteSize", data_len, 1, max, 256);
173}
174
175void RecordDownloadWriteLoopCount(int count) {
176  UMA_HISTOGRAM_ENUMERATION("Download.WriteLoopCount", count, 20);
177}
178
179void RecordAcceptsRanges(const std::string& accepts_ranges,
180                         int64 download_len,
181                         const std::string& etag) {
182  int64 max = 1024 * 1024 * 1024;  // One Terabyte.
183  download_len /= 1024;  // In Kilobytes
184  static const int kBuckets = 50;
185
186  if (LowerCaseEqualsASCII(accepts_ranges, "none")) {
187    UMA_HISTOGRAM_CUSTOM_COUNTS("Download.AcceptRangesNone.KBytes",
188                                download_len,
189                                1,
190                                max,
191                                kBuckets);
192  } else if (LowerCaseEqualsASCII(accepts_ranges, "bytes")) {
193    UMA_HISTOGRAM_CUSTOM_COUNTS("Download.AcceptRangesBytes.KBytes",
194                                download_len,
195                                1,
196                                max,
197                                kBuckets);
198    // ETags that start with "W/" are considered weak ETags which don't imply
199    // byte-wise equality.
200    if (!StartsWithASCII(etag, "w/", false))
201      RecordDownloadCount(STRONG_ETAG_AND_ACCEPTS_RANGES);
202  } else {
203    UMA_HISTOGRAM_CUSTOM_COUNTS("Download.AcceptRangesMissingOrInvalid.KBytes",
204                                download_len,
205                                1,
206                                max,
207                                kBuckets);
208  }
209}
210
211namespace {
212
213enum DownloadContent {
214  DOWNLOAD_CONTENT_UNRECOGNIZED = 0,
215  DOWNLOAD_CONTENT_TEXT = 1,
216  DOWNLOAD_CONTENT_IMAGE = 2,
217  DOWNLOAD_CONTENT_AUDIO = 3,
218  DOWNLOAD_CONTENT_VIDEO = 4,
219  DOWNLOAD_CONTENT_OCTET_STREAM = 5,
220  DOWNLOAD_CONTENT_PDF = 6,
221  DOWNLOAD_CONTENT_DOC = 7,
222  DOWNLOAD_CONTENT_XLS = 8,
223  DOWNLOAD_CONTENT_PPT = 9,
224  DOWNLOAD_CONTENT_ARCHIVE = 10,
225  DOWNLOAD_CONTENT_EXE = 11,
226  DOWNLOAD_CONTENT_DMG = 12,
227  DOWNLOAD_CONTENT_CRX = 13,
228  DOWNLOAD_CONTENT_MAX = 14,
229};
230
231struct MimeTypeToDownloadContent {
232  const char* mime_type;
233  DownloadContent download_content;
234};
235
236static MimeTypeToDownloadContent kMapMimeTypeToDownloadContent[] = {
237  {"application/octet-stream", DOWNLOAD_CONTENT_OCTET_STREAM},
238  {"binary/octet-stream", DOWNLOAD_CONTENT_OCTET_STREAM},
239  {"application/pdf", DOWNLOAD_CONTENT_PDF},
240  {"application/msword", DOWNLOAD_CONTENT_DOC},
241  {"application/vnd.ms-excel", DOWNLOAD_CONTENT_XLS},
242  {"application/vns.ms-powerpoint", DOWNLOAD_CONTENT_PPT},
243  {"application/zip", DOWNLOAD_CONTENT_ARCHIVE},
244  {"application/x-gzip", DOWNLOAD_CONTENT_ARCHIVE},
245  {"application/x-rar-compressed", DOWNLOAD_CONTENT_ARCHIVE},
246  {"application/x-tar", DOWNLOAD_CONTENT_ARCHIVE},
247  {"application/x-bzip", DOWNLOAD_CONTENT_ARCHIVE},
248  {"application/x-exe", DOWNLOAD_CONTENT_EXE},
249  {"application/x-apple-diskimage", DOWNLOAD_CONTENT_DMG},
250  {"application/x-chrome-extension", DOWNLOAD_CONTENT_CRX},
251};
252
253enum DownloadImage {
254  DOWNLOAD_IMAGE_UNRECOGNIZED = 0,
255  DOWNLOAD_IMAGE_GIF = 1,
256  DOWNLOAD_IMAGE_JPEG = 2,
257  DOWNLOAD_IMAGE_PNG = 3,
258  DOWNLOAD_IMAGE_TIFF = 4,
259  DOWNLOAD_IMAGE_ICON = 5,
260  DOWNLOAD_IMAGE_WEBP = 6,
261  DOWNLOAD_IMAGE_MAX = 7,
262};
263
264struct MimeTypeToDownloadImage {
265  const char* mime_type;
266  DownloadImage download_image;
267};
268
269static MimeTypeToDownloadImage kMapMimeTypeToDownloadImage[] = {
270  {"image/gif", DOWNLOAD_IMAGE_GIF},
271  {"image/jpeg", DOWNLOAD_IMAGE_JPEG},
272  {"image/png", DOWNLOAD_IMAGE_PNG},
273  {"image/tiff", DOWNLOAD_IMAGE_TIFF},
274  {"image/vnd.microsoft.icon", DOWNLOAD_IMAGE_ICON},
275  {"image/webp", DOWNLOAD_IMAGE_WEBP},
276};
277
278void RecordDownloadImageType(const std::string& mime_type_string) {
279  DownloadImage download_image = DOWNLOAD_IMAGE_UNRECOGNIZED;
280
281  // Look up exact matches.
282  for (size_t i = 0; i < arraysize(kMapMimeTypeToDownloadImage); ++i) {
283    const MimeTypeToDownloadImage& entry = kMapMimeTypeToDownloadImage[i];
284    if (mime_type_string == entry.mime_type) {
285      download_image = entry.download_image;
286      break;
287    }
288  }
289
290  UMA_HISTOGRAM_ENUMERATION("Download.ContentImageType",
291                            download_image,
292                            DOWNLOAD_IMAGE_MAX);
293}
294
295}  // namespace
296
297void RecordDownloadMimeType(const std::string& mime_type_string) {
298  DownloadContent download_content = DOWNLOAD_CONTENT_UNRECOGNIZED;
299
300  // Look up exact matches.
301  for (size_t i = 0; i < arraysize(kMapMimeTypeToDownloadContent); ++i) {
302    const MimeTypeToDownloadContent& entry = kMapMimeTypeToDownloadContent[i];
303    if (mime_type_string == entry.mime_type) {
304      download_content = entry.download_content;
305      break;
306    }
307  }
308
309  // Do partial matches.
310  if (download_content == DOWNLOAD_CONTENT_UNRECOGNIZED) {
311    if (StartsWithASCII(mime_type_string, "text/", true)) {
312      download_content = DOWNLOAD_CONTENT_TEXT;
313    } else if (StartsWithASCII(mime_type_string, "image/", true)) {
314      download_content = DOWNLOAD_CONTENT_IMAGE;
315      RecordDownloadImageType(mime_type_string);
316    } else if (StartsWithASCII(mime_type_string, "audio/", true)) {
317      download_content = DOWNLOAD_CONTENT_AUDIO;
318    } else if (StartsWithASCII(mime_type_string, "video/", true)) {
319      download_content = DOWNLOAD_CONTENT_VIDEO;
320    }
321  }
322
323  // Record the value.
324  UMA_HISTOGRAM_ENUMERATION("Download.ContentType",
325                            download_content,
326                            DOWNLOAD_CONTENT_MAX);
327}
328
329void RecordDownloadContentDisposition(
330    const std::string& content_disposition_string) {
331  if (content_disposition_string.empty())
332    return;
333  net::HttpContentDisposition content_disposition(content_disposition_string,
334                                                  std::string());
335  int result = content_disposition.parse_result_flags();
336
337  bool is_valid = !content_disposition.filename().empty();
338  RecordContentDispositionCount(CONTENT_DISPOSITION_HEADER_PRESENT, true);
339  RecordContentDispositionCount(CONTENT_DISPOSITION_IS_VALID, is_valid);
340  if (!is_valid)
341    return;
342
343  RecordContentDispositionCountFlag(
344      CONTENT_DISPOSITION_HAS_DISPOSITION_TYPE, result,
345      net::HttpContentDisposition::HAS_DISPOSITION_TYPE);
346  RecordContentDispositionCountFlag(
347      CONTENT_DISPOSITION_HAS_UNKNOWN_TYPE, result,
348      net::HttpContentDisposition::HAS_UNKNOWN_DISPOSITION_TYPE);
349  RecordContentDispositionCountFlag(
350      CONTENT_DISPOSITION_HAS_NAME, result,
351      net::HttpContentDisposition::HAS_NAME);
352  RecordContentDispositionCountFlag(
353      CONTENT_DISPOSITION_HAS_FILENAME, result,
354      net::HttpContentDisposition::HAS_FILENAME);
355  RecordContentDispositionCountFlag(
356      CONTENT_DISPOSITION_HAS_EXT_FILENAME, result,
357      net::HttpContentDisposition::HAS_EXT_FILENAME);
358  RecordContentDispositionCountFlag(
359      CONTENT_DISPOSITION_HAS_NON_ASCII_STRINGS, result,
360      net::HttpContentDisposition::HAS_NON_ASCII_STRINGS);
361  RecordContentDispositionCountFlag(
362      CONTENT_DISPOSITION_HAS_PERCENT_ENCODED_STRINGS, result,
363      net::HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS);
364  RecordContentDispositionCountFlag(
365      CONTENT_DISPOSITION_HAS_RFC2047_ENCODED_STRINGS, result,
366      net::HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS);
367
368  RecordContentDispositionCount(
369      CONTENT_DISPOSITION_HAS_NAME_ONLY,
370      (result & (net::HttpContentDisposition::HAS_NAME |
371                 net::HttpContentDisposition::HAS_FILENAME |
372                 net::HttpContentDisposition::HAS_EXT_FILENAME)) ==
373      net::HttpContentDisposition::HAS_NAME);
374}
375
376void RecordFileThreadReceiveBuffers(size_t num_buffers) {
377    UMA_HISTOGRAM_CUSTOM_COUNTS(
378      "Download.FileThreadReceiveBuffers", num_buffers, 1,
379      100, 100);
380}
381
382void RecordBandwidth(double actual_bandwidth, double potential_bandwidth) {
383  UMA_HISTOGRAM_CUSTOM_COUNTS(
384      "Download.ActualBandwidth", actual_bandwidth, 1, 1000000000, 50);
385  UMA_HISTOGRAM_CUSTOM_COUNTS(
386      "Download.PotentialBandwidth", potential_bandwidth, 1, 1000000000, 50);
387  UMA_HISTOGRAM_PERCENTAGE(
388      "Download.BandwidthUsed",
389      (int) ((actual_bandwidth * 100)/ potential_bandwidth));
390}
391
392void RecordOpen(const base::Time& end, bool first) {
393  if (!end.is_null()) {
394    UMA_HISTOGRAM_LONG_TIMES("Download.OpenTime", (base::Time::Now() - end));
395    if (first) {
396      UMA_HISTOGRAM_LONG_TIMES("Download.FirstOpenTime",
397                              (base::Time::Now() - end));
398    }
399  }
400}
401
402void RecordClearAllSize(int size) {
403  UMA_HISTOGRAM_CUSTOM_COUNTS("Download.ClearAllSize",
404                              size,
405                              0/*min*/,
406                              (1 << 10)/*max*/,
407                              32/*num_buckets*/);
408}
409
410void RecordOpensOutstanding(int size) {
411  UMA_HISTOGRAM_CUSTOM_COUNTS("Download.OpensOutstanding",
412                              size,
413                              0/*min*/,
414                              (1 << 10)/*max*/,
415                              64/*num_buckets*/);
416}
417
418void RecordContiguousWriteTime(base::TimeDelta time_blocked) {
419  UMA_HISTOGRAM_TIMES("Download.FileThreadBlockedTime", time_blocked);
420}
421
422// Record what percentage of the time we have the network flow controlled.
423void RecordNetworkBlockage(base::TimeDelta resource_handler_lifetime,
424                           base::TimeDelta resource_handler_blocked_time) {
425  int percentage = 0;
426  // Avoid division by zero errors.
427  if (resource_handler_blocked_time != base::TimeDelta()) {
428    percentage =
429        resource_handler_blocked_time * 100 / resource_handler_lifetime;
430  }
431
432  UMA_HISTOGRAM_COUNTS_100("Download.ResourceHandlerBlockedPercentage",
433                           percentage);
434}
435
436void RecordFileBandwidth(size_t length,
437                         base::TimeDelta disk_write_time,
438                         base::TimeDelta elapsed_time) {
439  size_t elapsed_time_ms = elapsed_time.InMilliseconds();
440  if (0u == elapsed_time_ms)
441    elapsed_time_ms = 1;
442  size_t disk_write_time_ms = disk_write_time.InMilliseconds();
443  if (0u == disk_write_time_ms)
444    disk_write_time_ms = 1;
445
446  UMA_HISTOGRAM_CUSTOM_COUNTS(
447      "Download.BandwidthOverallBytesPerSecond",
448      (1000 * length / elapsed_time_ms), 1, 50000000, 50);
449  UMA_HISTOGRAM_CUSTOM_COUNTS(
450      "Download.BandwidthDiskBytesPerSecond",
451      (1000 * length / disk_write_time_ms), 1, 50000000, 50);
452  UMA_HISTOGRAM_COUNTS_100("Download.DiskBandwidthUsedPercentage",
453                           disk_write_time_ms * 100 / elapsed_time_ms);
454}
455
456void RecordSavePackageEvent(SavePackageEvent event) {
457  UMA_HISTOGRAM_ENUMERATION("Download.SavePackage",
458                            event,
459                            SAVE_PACKAGE_LAST_ENTRY);
460}
461
462}  // namespace content
463