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 13 namespace content { 14 15 namespace { 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). 19 const 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. 28 enum 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 54 void 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 62 void 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 71 void RecordDownloadCount(DownloadCountTypes type) { 72 UMA_HISTOGRAM_ENUMERATION( 73 "Download.Counts", type, DOWNLOAD_COUNT_TYPES_LAST_ENTRY); 74 } 75 76 void RecordDownloadSource(DownloadSource source) { 77 UMA_HISTOGRAM_ENUMERATION( 78 "Download.Sources", source, DOWNLOAD_SOURCE_LAST_ENTRY); 79 } 80 81 void 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 93 void 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 148 void RecordDangerousDownloadAccept(DownloadDangerType danger_type) { 149 UMA_HISTOGRAM_ENUMERATION("Download.DangerousDownloadValidated", 150 danger_type, 151 DOWNLOAD_DANGER_TYPE_MAX); 152 } 153 154 void 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 170 void 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 175 void RecordDownloadWriteLoopCount(int count) { 176 UMA_HISTOGRAM_ENUMERATION("Download.WriteLoopCount", count, 20); 177 } 178 179 void 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 211 namespace { 212 213 enum 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 231 struct MimeTypeToDownloadContent { 232 const char* mime_type; 233 DownloadContent download_content; 234 }; 235 236 static 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 253 enum 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 264 struct MimeTypeToDownloadImage { 265 const char* mime_type; 266 DownloadImage download_image; 267 }; 268 269 static 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 278 void 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 297 void 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 329 void 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 376 void RecordFileThreadReceiveBuffers(size_t num_buffers) { 377 UMA_HISTOGRAM_CUSTOM_COUNTS( 378 "Download.FileThreadReceiveBuffers", num_buffers, 1, 379 100, 100); 380 } 381 382 void 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 392 void 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 402 void 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 410 void 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 418 void 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. 423 void 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 436 void 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 456 void RecordSavePackageEvent(SavePackageEvent event) { 457 UMA_HISTOGRAM_ENUMERATION("Download.SavePackage", 458 event, 459 SAVE_PACKAGE_LAST_ENTRY); 460 } 461 462 } // namespace content 463