Add TOO_MANY_REQUESTS to BinaryUploadService::Result This adds a new enum value to handle 429 errors so they can be used to throttle multi-file uploads. The following changes are made in this CL to achieve this: - BinaryUploadService and MultipartUploadRequest are updated to handle the new result. - ContentAnalysisDelegate is update so no more files are uploaded in a multi-file upload case. - Histograms are updated so data about this new case can be collected. Bug: 1191062 Change-Id: Ie6794d2a48739a9216ca7493e038e714ca70b822 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2785163 Reviewed-by: Steven Holte <holte@chromium.org> Reviewed-by: Roger Tawa <rogerta@chromium.org> Reviewed-by: Daniel Rubery <drubery@chromium.org> Auto-Submit: Dominique Fauteux-Chapleau <domfc@chromium.org> Commit-Queue: Dominique Fauteux-Chapleau <domfc@chromium.org> Cr-Commit-Position: refs/heads/master@{#869534}
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc index b087f138..fe88383 100644 --- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc +++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -236,6 +236,7 @@ case BinaryUploadService::Result::UPLOAD_FAILURE: case BinaryUploadService::Result::TIMEOUT: case BinaryUploadService::Result::FAILED_TO_GET_TOKEN: + case BinaryUploadService::Result::TOO_MANY_REQUESTS: // UNAUTHORIZED allows data usage since it's a result only obtained if the // browser is not authorized to perform deep scanning. It does not make // sense to block data in this situation since no actual scanning of the @@ -447,6 +448,9 @@ base::FilePath path, BinaryUploadService::Result result, enterprise_connectors::ContentAnalysisResponse response) { + if (result == BinaryUploadService::Result::TOO_MANY_REQUESTS) + throttled_ = true; + // Find the path in the set of files that are being scanned. auto it = std::find(data_.paths.begin(), data_.paths.end(), path); DCHECK(it != data_.paths.end()); @@ -611,6 +615,14 @@ return; } + // If |throttled_| is true, then the file shouldn't be upload since the server + // is receiving too many requests. + if (throttled_) { + request->FinishRequest(BinaryUploadService::Result::TOO_MANY_REQUESTS, + enterprise_connectors::ContentAnalysisResponse()); + return; + } + UploadFileForDeepScanning(result, data_.paths[index], std::move(request)); }
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h index fbab5d6..a6b257e 100644 --- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h +++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
@@ -360,6 +360,10 @@ // for every file/text. This is read to ensure |this| isn't deleted too early. bool data_uploaded_ = false; + // This is set to true as soon as a TOO_MANY_REQUESTS response is obtained. No + // more data should be upload for |this| at that point. + bool throttled_ = false; + base::TimeTicks upload_start_time_; base::WeakPtrFactory<ContentAnalysisDelegate> weak_ptr_factory_{this};
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc index cd17970..25ca4be 100644 --- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc +++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_browsertest.cc
@@ -511,6 +511,84 @@ ASSERT_EQ(FakeBinaryUploadServiceStorage()->requests_count(), 2); } +IN_PROC_BROWSER_TEST_P(ContentAnalysisDelegateBrowserTest, Throttled) { + base::ScopedAllowBlockingForTesting allow_blocking; + + // Set up delegate and upload service. + EnableUploadsScanningAndReporting(); + + ContentAnalysisDelegate::SetFactoryForTesting( + base::BindRepeating(&MinimalFakeContentAnalysisDelegate::Create)); + + FakeBinaryUploadServiceStorage()->SetAuthorized(true); + FakeBinaryUploadServiceStorage()->SetShouldAutomaticallyAuthorize(true); + + // Create the files to be opened and scanned. + ContentAnalysisDelegate::Data data; + CreateFilesForTest({"a.exe", "b.exe", "c.exe"}, + {"a content", "b content", "c content"}, &data); + ASSERT_TRUE(ContentAnalysisDelegate::IsEnabled( + browser()->profile(), GURL(kTestUrl), &data, FILE_ATTACHED)); + + // The malware verdict means an event should be reported. + safe_browsing::EventReportValidator validator(client()); + validator.ExpectUnscannedFileEvents( + /*url*/ "about:blank", + { + created_file_paths()[0].AsUTF8Unsafe(), + created_file_paths()[1].AsUTF8Unsafe(), + created_file_paths()[2].AsUTF8Unsafe(), + }, + { + // printf "a content" | sha256sum | tr '[:lower:]' '[:upper:]' + "D2D2ACF640179223BF9E1EB43C5FBF854C4E50FFB6733BC3A9279D3FF7DE9BE1", + // printf "b content" | sha256sum | tr '[:lower:]' '[:upper:]' + "93CB3641ADD6A9A6619D7E2F304EBCF5160B2DB016B27C6E3D641C5306897224", + // printf "c content" | sha256sum | tr '[:lower:]' '[:upper:]' + "2E6D1C4A1F39A02562BF1505AD775C0323D7A04C0C37C9B29D25F532B9972080", + }, + /*trigger*/ SafeBrowsingPrivateEventRouter::kTriggerFileUpload, + // TODO(crbug.com/1191060): Update this string when the event is supported + /*reason*/ "SERVICE_UNAVAILABLE", + /*mimetypes*/ ExeMimeTypes(), + /*size*/ 9, + /*result*/ + safe_browsing::EventResultToString(safe_browsing::EventResult::ALLOWED), + /*username*/ kUserName); + + // Only the first file should be uploaded since the other ones will be + // throttled. + FakeBinaryUploadServiceStorage()->SetResponseForFile( + "a.exe", BinaryUploadService::Result::TOO_MANY_REQUESTS, + ContentAnalysisResponse()); + + bool called = false; + base::RunLoop run_loop; + SetQuitClosure(run_loop.QuitClosure()); + + // Start test. + ContentAnalysisDelegate::CreateForWebContents( + browser()->tab_strip_model()->GetActiveWebContents(), std::move(data), + base::BindLambdaForTesting( + [&called](const ContentAnalysisDelegate::Data& data, + const ContentAnalysisDelegate::Result& result) { + ASSERT_TRUE(result.text_results.empty()); + ASSERT_EQ(result.paths_results.size(), 3u); + for (bool result : result.paths_results) + ASSERT_TRUE(result); + called = true; + }), + safe_browsing::DeepScanAccessPoint::UPLOAD); + + run_loop.Run(); + + EXPECT_TRUE(called); + + // There should have been 1 request for the first file and 1 for + // authentication. + ASSERT_EQ(FakeBinaryUploadServiceStorage()->requests_count(), 2); +} + // This class tests each of the blocking settings used in Connector policies: // - block_until_verdict // - block_password_protected
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc index b46abd7f..0263c51 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
@@ -83,6 +83,8 @@ return "FILE_ENCRYPTED"; case BinaryUploadService::Result::DLP_SCAN_UNSUPPORTED_FILE_TYPE: return "DLP_SCAN_UNSUPPORTED_FILE_TYPE"; + case BinaryUploadService::Result::TOO_MANY_REQUESTS: + return "TOO_MANY_REQUESTS"; } } @@ -364,10 +366,17 @@ void BinaryUploadService::OnUploadComplete(Request* request, bool success, + int http_status, const std::string& response_data) { if (!IsActive(request)) return; + if (http_status == net::HTTP_TOO_MANY_REQUESTS) { + FinishRequest(request, Result::TOO_MANY_REQUESTS, + enterprise_connectors::ContentAnalysisResponse()); + return; + } + if (!success) { FinishRequest(request, Result::UPLOAD_FAILURE, enterprise_connectors::ContentAnalysisResponse());
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h index ac4de13..ef9e17a 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h +++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
@@ -81,7 +81,11 @@ // The file's type is not supported and the file was not uploaded. DLP_SCAN_UNSUPPORTED_FILE_TYPE = 8, - kMaxValue = DLP_SCAN_UNSUPPORTED_FILE_TYPE, + // The server returned a 429 HTTP status indicating too many requests are + // being sent. + TOO_MANY_REQUESTS = 9, + + kMaxValue = TOO_MANY_REQUESTS, }; // Callbacks used to pass along the results of scanning. The response protos @@ -244,6 +248,7 @@ void OnUploadComplete(Request* request, bool success, + int http_status, const std::string& response_data); void OnGetResponse(Request* request,
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc index 33d5c7b..85dea56 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc
@@ -63,7 +63,8 @@ void Start() override { std::string serialized_response; response_.SerializeToString(&serialized_response); - std::move(callback_).Run(should_succeed_, serialized_response); + std::move(callback_).Run(should_succeed_, should_succeed_ ? 200 : 401, + serialized_response); } private: @@ -161,7 +162,7 @@ void ReceiveResponseFromUpload(BinaryUploadService::Request* request, bool success, const std::string& response) { - service_->OnUploadComplete(request, success, response); + service_->OnUploadComplete(request, success, success ? 200 : 401, response); } void ServiceWithNoFCMConnection() {
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc index 76424db..e3a0c8b 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h" +#include "base/containers/flat_map.h" #include "base/json/json_reader.h" #include "base/strings/string_number_conversions.h" #include "base/values.h" @@ -48,8 +49,7 @@ const std::string& expected_username) { event_key_ = SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent; url_ = expected_url; - filename_ = expected_filename; - sha256_ = expected_sha256; + filenames_and_hashes_[expected_filename] = expected_sha256; mimetypes_ = expected_mimetypes; trigger_ = expected_trigger; unscanned_reason_ = expected_reason; @@ -66,6 +66,37 @@ }); } +void EventReportValidator::ExpectUnscannedFileEvents( + const std::string& expected_url, + const std::vector<const std::string>& expected_filenames, + const std::vector<const std::string>& expected_sha256s, + const std::string& expected_trigger, + const std::string& expected_reason, + const std::set<std::string>* expected_mimetypes, + int expected_content_size, + const std::string& expected_result, + const std::string& expected_username) { + DCHECK_EQ(expected_filenames.size(), expected_sha256s.size()); + for (size_t i = 0; i < expected_filenames.size(); ++i) + filenames_and_hashes_[expected_filenames[i]] = expected_sha256s[i]; + + event_key_ = SafeBrowsingPrivateEventRouter::kKeyUnscannedFileEvent; + url_ = expected_url; + mimetypes_ = expected_mimetypes; + trigger_ = expected_trigger; + unscanned_reason_ = expected_reason; + content_size_ = expected_content_size; + result_ = expected_result; + username_ = expected_username; + EXPECT_CALL(*client_, UploadSecurityEventReport_(_, _, _, _)) + .Times(expected_filenames.size()) + .WillRepeatedly([this](content::BrowserContext* context, + bool include_device_info, base::Value& report, + base::OnceCallback<void(bool)>& callback) { + ValidateReport(&report); + }); +} + void EventReportValidator::ExpectDangerousDeepScanningResult( const std::string& expected_url, const std::string& expected_filename, @@ -78,8 +109,7 @@ const std::string& expected_username) { event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent; url_ = expected_url; - filename_ = expected_filename; - sha256_ = expected_sha256; + filenames_and_hashes_[expected_filename] = expected_sha256; threat_type_ = expected_threat_type; mimetypes_ = expected_mimetypes; trigger_ = expected_trigger; @@ -110,8 +140,7 @@ event_key_ = SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent; url_ = expected_url; dlp_verdict_ = expected_dlp_verdict; - filename_ = expected_filename; - sha256_ = expected_sha256; + filenames_and_hashes_[expected_filename] = expected_sha256; mimetypes_ = expected_mimetypes; trigger_ = expected_trigger; content_size_ = expected_content_size; @@ -142,8 +171,7 @@ const std::string& expected_username) { event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent; url_ = expected_url; - filename_ = expected_filename; - sha256_ = expected_sha256; + filenames_and_hashes_[expected_filename] = expected_sha256; threat_type_ = expected_threat_type; trigger_ = expected_trigger; mimetypes_ = expected_mimetypes; @@ -184,8 +212,7 @@ const std::string& expected_username) { event_key_ = SafeBrowsingPrivateEventRouter::kKeySensitiveDataEvent; url_ = expected_url; - filename_ = expected_filename; - sha256_ = expected_sha256; + filenames_and_hashes_[expected_filename] = expected_sha256; trigger_ = expected_trigger; mimetypes_ = expected_mimetypes; content_size_ = expected_content_size; @@ -223,8 +250,7 @@ const std::string& expected_username) { event_key_ = SafeBrowsingPrivateEventRouter::kKeyDangerousDownloadEvent; url_ = expected_url; - filename_ = expected_filename; - sha256_ = expected_sha256; + filenames_and_hashes_[expected_filename] = expected_sha256; threat_type_ = expected_threat_type; mimetypes_ = expected_mimetypes; trigger_ = expected_trigger; @@ -261,9 +287,7 @@ // The event should match the expected values. ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyUrl, url_); - ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyFileName, filename_); - ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyDownloadDigestSha256, - sha256_); + ValidateFilenameAndHash(event); ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyTrigger, trigger_); ValidateField(event, SafeBrowsingPrivateEventRouter::kKeyContentSize, content_size_); @@ -316,6 +340,16 @@ expected_rule.rule_id()); } +void EventReportValidator::ValidateFilenameAndHash(base::Value* value) { + const std::string* filename = + value->FindStringKey(SafeBrowsingPrivateEventRouter::kKeyFileName); + ASSERT_TRUE(filename); + ASSERT_TRUE(filenames_and_hashes_.count(*filename)) + << "Mismatch in field " << SafeBrowsingPrivateEventRouter::kKeyFileName; + ValidateField(value, SafeBrowsingPrivateEventRouter::kKeyDownloadDigestSha256, + filenames_and_hashes_[*filename]); +} + void EventReportValidator::ValidateField( base::Value* value, const std::string& field_key,
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h index 5e1898e..56bfd50 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h +++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_test_utils.h
@@ -9,6 +9,7 @@ #include <string> #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/optional.h" #include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h" #include "components/enterprise/common/proto/connectors.pb.h" @@ -93,6 +94,17 @@ const std::string& expected_result, const std::string& expected_username); + void ExpectUnscannedFileEvents( + const std::string& expected_url, + const std::vector<const std::string>& expected_filenames, + const std::vector<const std::string>& expected_sha256s, + const std::string& expected_trigger, + const std::string& expected_reason, + const std::set<std::string>* expected_mimetypes, + int expected_content_size, + const std::string& expected_result, + const std::string& expected_username); + void ExpectDangerousDownloadEvent( const std::string& expected_url, const std::string& expected_filename, @@ -116,6 +128,7 @@ void ValidateDlpRule(base::Value* value, const enterprise_connectors::ContentAnalysisResponse:: Result::TriggeredRule& expected_rule); + void ValidateFilenameAndHash(base::Value* value); void ValidateField(base::Value* value, const std::string& field_key, const base::Optional<std::string>& expected_value); @@ -130,8 +143,6 @@ std::string event_key_; std::string url_; - std::string filename_; - std::string sha256_; std::string trigger_; base::Optional<enterprise_connectors::ContentAnalysisResponse::Result> dlp_verdict_ = base::nullopt; @@ -142,6 +153,11 @@ base::Optional<std::string> result_ = base::nullopt; std::string username_; + // When multiple files generate events, we don't necessarily know in which + // order they will be reported. As such, we use a map to ensure all of them + // are called as expected. + base::flat_map<std::string, std::string> filenames_and_hashes_; + base::RepeatingClosure done_closure_; };
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc index 7e33556..104a5e5 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.cc
@@ -36,6 +36,8 @@ case BinaryUploadService::Result::UNKNOWN: case BinaryUploadService::Result::UPLOAD_FAILURE: case BinaryUploadService::Result::FAILED_TO_GET_TOKEN: + // TODO(crbug.com/1191060): Update this string when the event is supported. + case BinaryUploadService::Result::TOO_MANY_REQUESTS: unscanned_reason = "SERVICE_UNAVAILABLE"; break; case BinaryUploadService::Result::FILE_ENCRYPTED: @@ -324,6 +326,8 @@ return "FileEncrypted"; case BinaryUploadService::Result::DLP_SCAN_UNSUPPORTED_FILE_TYPE: return "DlpScanUnsupportedFileType"; + case BinaryUploadService::Result::TOO_MANY_REQUESTS: + return "TooManyRequests"; } }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc index a0f4ac7..04bfd04 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.cc
@@ -133,13 +133,15 @@ base::UmaHistogramMediumTimes( "SBMultipartUploader.SuccessfulUploadDuration", base::Time::Now() - start_time_); - std::move(callback_).Run(/*success=*/true, *response_body.get()); + std::move(callback_).Run(/*success=*/true, response_code, + *response_body.get()); } else { if (response_code < 500 || retry_count_ >= kMaxRetryAttempts) { RecordUploadSuccessHistogram(/*success=*/false); base::UmaHistogramMediumTimes("SBMultipartUploader.FailedUploadDuration", base::Time::Now() - start_time_); - std::move(callback_).Run(/*success=*/false, *response_body.get()); + std::move(callback_).Run(/*success=*/false, response_code, + *response_body.get()); } else { content::GetUIThreadTaskRunner({})->PostDelayedTask( FROM_HERE,
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h index b6b9690..d8e3a8c 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h +++ b/chrome/browser/safe_browsing/cloud_content_scanning/multipart_uploader.h
@@ -29,8 +29,8 @@ // multipart protocol. This class is neither movable nor copyable. class MultipartUploadRequest { public: - using Callback = - base::OnceCallback<void(bool success, const std::string& response_data)>; + using Callback = base::OnceCallback< + void(bool success, int http_status, const std::string& response_data)>; // Creates a MultipartUploadRequest, which will upload |data| to the given // |base_url| with |metadata| attached.
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index ebc2ed8..c573b12 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -67848,6 +67848,7 @@ <int value="6" label="Unauthorized"/> <int value="7" label="File encrypted"/> <int value="8" label="Unsupported file type"/> + <int value="9" label="Too many requests"/> </enum> <enum name="SafeBrowsingDelayedWarningEvent">
diff --git a/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml b/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml index ecb7c2f..381277f 100644 --- a/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml +++ b/tools/metrics/histograms/histograms_xml/safe_browsing/histograms.xml
@@ -304,6 +304,17 @@ </summary> </histogram> +<histogram name="SafeBrowsing.DeepScan.Download.TooManyRequests.Duration" + units="ms" expires_after="2022-02-01"> + <owner>domfc@chromium.org</owner> + <owner>webprotect-team@google.com</owner> + <summary> + This records the deep scanning duration of a user download request with a + TooManyRequests result. It is logged once for each binary upload with that + result. + </summary> +</histogram> + <histogram name="SafeBrowsing.DeepScan.Download.Unknown.Duration" units="ms" expires_after="2022-02-01"> <owner>domfc@chromium.org</owner> @@ -425,6 +436,17 @@ </summary> </histogram> +<histogram name="SafeBrowsing.DeepScan.DragAndDrop.TooManyRequests.Duration" + units="ms" expires_after="2022-02-01"> + <owner>domfc@chromium.org</owner> + <owner>webprotect-team@google.com</owner> + <summary> + This records the deep scanning duration of a user "drag and drop" + request with a TooManyRequests result. It is logged once for each binary + upload with that result. + </summary> +</histogram> + <histogram name="SafeBrowsing.DeepScan.DragAndDrop.Unknown.Duration" units="ms" expires_after="2022-02-01"> <owner>domfc@chromium.org</owner> @@ -533,6 +555,17 @@ </summary> </histogram> +<histogram name="SafeBrowsing.DeepScan.Paste.TooManyRequests.Duration" + units="ms" expires_after="2022-02-01"> + <owner>domfc@chromium.org</owner> + <owner>webprotect-team@google.com</owner> + <summary> + This records the deep scanning duration of a user paste request with a + TooManyRequests result. It is logged once for each binary upload with that + result. + </summary> +</histogram> + <histogram name="SafeBrowsing.DeepScan.Paste.Unknown.Duration" units="ms" expires_after="2022-02-01"> <owner>domfc@chromium.org</owner> @@ -650,6 +683,17 @@ </summary> </histogram> +<histogram name="SafeBrowsing.DeepScan.Upload.TooManyRequests.Duration" + units="ms" expires_after="2022-02-01"> + <owner>domfc@chromium.org</owner> + <owner>webprotect-team@google.com</owner> + <summary> + This records the deep scanning duration of a user upload request with a + TooManyRequests result. It is logged once for each binary upload with that + result. + </summary> +</histogram> + <histogram name="SafeBrowsing.DeepScan.Upload.Unknown.Duration" units="ms" expires_after="2022-02-01"> <owner>domfc@chromium.org</owner>