123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/feedback/feedback_uploader.h"
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/command_line.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/task/task_traits.h"
- #include "base/task/thread_pool.h"
- #include "components/feedback/feedback_report.h"
- #include "components/feedback/feedback_switches.h"
- #include "components/variations/net/variations_http_headers.h"
- #include "net/base/load_flags.h"
- #include "services/network/public/cpp/resource_request.h"
- #include "services/network/public/cpp/simple_url_loader.h"
- #include "services/network/public/mojom/url_response_head.mojom.h"
- namespace feedback {
- namespace {
- constexpr char kReportSendingResultHistogramName[] =
- "Feedback.ReportSending.Result";
- // These values are persisted to logs. Entries should not be renumbered and
- // numeric values should never be reused.
- enum class FeedbackReportSendingResult {
- kSuccessAtFirstTry = 0, // The report was uploaded successfully without retry
- kSuccessAfterRetry = 1, // The report was uploaded successfully after retry
- kDropped = 2, // The report is corrupt or invalid and was dropped
- kMaxValue = kDropped,
- };
- constexpr base::FilePath::CharType kFeedbackReportPath[] =
- FILE_PATH_LITERAL("Feedback Reports");
- constexpr char kFeedbackPostUrl[] =
- "https://www.google.com/tools/feedback/chrome/__submit";
- constexpr char kProtoBufMimeType[] = "application/x-protobuf";
- constexpr int kHttpPostSuccessNoContent = 204;
- constexpr int kHttpPostFailNoConnection = -1;
- constexpr int kHttpPostFailClientError = 400;
- constexpr int kHttpPostFailServerError = 500;
- // The minimum time to wait before uploading reports are retried. Exponential
- // backoff delay is applied on successive failures.
- // This value can be overriden by tests by calling
- // FeedbackUploader::SetMinimumRetryDelayForTesting().
- base::TimeDelta g_minimum_retry_delay = base::Minutes(60);
- // If a new report is queued to be dispatched immediately while another is being
- // dispatched, this is the time to wait for the on-going dispatching to finish.
- base::TimeDelta g_dispatching_wait_delay = base::Seconds(4);
- GURL GetFeedbackPostGURL() {
- const base::CommandLine& command_line =
- *base::CommandLine::ForCurrentProcess();
- return GURL(command_line.HasSwitch(switches::kFeedbackServer)
- ? command_line.GetSwitchValueASCII(switches::kFeedbackServer)
- : kFeedbackPostUrl);
- }
- // Creates a new SingleThreadTaskRunner that is used to run feedback blocking
- // background work.
- scoped_refptr<base::SingleThreadTaskRunner> CreateUploaderTaskRunner() {
- // Uses a BLOCK_SHUTDOWN file task runner to prevent losing reports or
- // corrupting report's files.
- return base::ThreadPool::CreateSingleThreadTaskRunner(
- {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
- base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
- }
- } // namespace
- FeedbackUploader::FeedbackUploader(
- bool is_off_the_record,
- const base::FilePath& state_path,
- SharedURLLoaderFactoryGetter shared_url_loader_factory_getter)
- : FeedbackUploader(is_off_the_record,
- state_path,
- std::move(shared_url_loader_factory_getter),
- nullptr) {}
- FeedbackUploader::FeedbackUploader(
- bool is_off_the_record,
- const base::FilePath& state_path,
- scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory)
- : FeedbackUploader(is_off_the_record,
- state_path,
- SharedURLLoaderFactoryGetter(),
- shared_url_loader_factory) {}
- FeedbackUploader::~FeedbackUploader() {}
- // static
- void FeedbackUploader::SetMinimumRetryDelayForTesting(base::TimeDelta delay) {
- g_minimum_retry_delay = delay;
- }
- void FeedbackUploader::QueueReport(std::unique_ptr<std::string> data,
- bool has_email) {
- reports_queue_.emplace(base::MakeRefCounted<FeedbackReport>(
- feedback_reports_path_, base::Time::Now(), std::move(data), task_runner_,
- has_email));
- UpdateUploadTimer();
- }
- void FeedbackUploader::RequeueReport(scoped_refptr<FeedbackReport> report) {
- DCHECK_EQ(task_runner_, report->reports_task_runner());
- report->set_upload_at(base::Time::Now());
- reports_queue_.emplace(std::move(report));
- UpdateUploadTimer();
- }
- void FeedbackUploader::StartDispatchingReport() {
- DispatchReport();
- }
- void FeedbackUploader::OnReportUploadSuccess() {
- if (retry_delay_ == g_minimum_retry_delay) {
- UMA_HISTOGRAM_ENUMERATION(kReportSendingResultHistogramName,
- FeedbackReportSendingResult::kSuccessAtFirstTry);
- } else {
- UMA_HISTOGRAM_ENUMERATION(kReportSendingResultHistogramName,
- FeedbackReportSendingResult::kSuccessAfterRetry);
- }
- retry_delay_ = g_minimum_retry_delay;
- is_dispatching_ = false;
- // Explicitly release the successfully dispatched report.
- report_being_dispatched_->DeleteReportOnDisk();
- report_being_dispatched_ = nullptr;
- UpdateUploadTimer();
- }
- void FeedbackUploader::OnReportUploadFailure(bool should_retry) {
- if (should_retry) {
- // Implement a backoff delay by doubling the retry delay on each failure.
- retry_delay_ *= 2;
- report_being_dispatched_->set_upload_at(retry_delay_ + base::Time::Now());
- reports_queue_.emplace(report_being_dispatched_);
- } else {
- // The report won't be retried, hence explicitly delete its file on disk.
- report_being_dispatched_->DeleteReportOnDisk();
- UMA_HISTOGRAM_ENUMERATION(kReportSendingResultHistogramName,
- FeedbackReportSendingResult::kDropped);
- }
- // The report dispatching failed, and should either be retried or not. In all
- // cases, we need to release |report_being_dispatched_|. If it was up for
- // retry, then it has already been re-enqueued and will be kept alive.
- // Otherwise we're done with it and it should destruct.
- report_being_dispatched_ = nullptr;
- is_dispatching_ = false;
- UpdateUploadTimer();
- }
- bool FeedbackUploader::ReportsUploadTimeComparator::operator()(
- const scoped_refptr<FeedbackReport>& a,
- const scoped_refptr<FeedbackReport>& b) const {
- return a->upload_at() > b->upload_at();
- }
- FeedbackUploader::FeedbackUploader(
- bool is_off_the_record,
- const base::FilePath& state_path,
- SharedURLLoaderFactoryGetter url_loader_factory_getter,
- scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
- : url_loader_factory_getter_(std::move(url_loader_factory_getter)),
- url_loader_factory_(url_loader_factory),
- feedback_reports_path_(state_path.Append(kFeedbackReportPath)),
- task_runner_(CreateUploaderTaskRunner()),
- feedback_post_url_(GetFeedbackPostGURL()),
- retry_delay_(g_minimum_retry_delay),
- is_off_the_record_(is_off_the_record) {
- DCHECK(!!url_loader_factory_getter_ != !!url_loader_factory_);
- }
- void FeedbackUploader::AppendExtraHeadersToUploadRequest(
- network::ResourceRequest* resource_request) {}
- void FeedbackUploader::DispatchReport() {
- net::NetworkTrafficAnnotationTag traffic_annotation =
- net::DefineNetworkTrafficAnnotation("chrome_feedback_report_app", R"(
- semantics {
- sender: "Chrome Feedback Report App"
- description:
- "Users can press Alt+Shift+i to report a bug or a feedback in "
- "general. Along with the free-form text they entered, system logs "
- "that helps in diagnosis of the issue are sent to Google. This "
- "service uploads the report to Google Feedback server."
- trigger:
- "When user chooses to send a feedback to Google."
- data:
- "The free-form text that user has entered and useful debugging "
- "logs (UI logs, Chrome logs, kernel logs, auto update engine logs, "
- "ARC++ logs, etc.). The logs are redacted to remove any "
- "user-private data. The user can view the system information "
- "before sending, and choose to send the feedback report without "
- "system information and the logs (unchecking 'Send system "
- "information' prevents sending logs as well), the screenshot, or "
- "even his/her email address."
- destination: GOOGLE_OWNED_SERVICE
- }
- policy {
- cookies_allowed: NO
- setting:
- "This feature cannot be disabled by settings and is only activated "
- "by direct user request."
- chrome_policy {
- UserFeedbackAllowed {
- UserFeedbackAllowed: false
- }
- }
- })");
- auto resource_request = std::make_unique<network::ResourceRequest>();
- resource_request->url = feedback_post_url_;
- resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
- resource_request->method = "POST";
- // Tell feedback server about the variation state of this install.
- variations::AppendVariationsHeaderUnknownSignedIn(
- feedback_post_url_,
- is_off_the_record_ ? variations::InIncognito::kYes
- : variations::InIncognito::kNo,
- resource_request.get());
- if (report_being_dispatched_->has_email()) {
- AppendExtraHeadersToUploadRequest(resource_request.get());
- }
- std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
- network::SimpleURLLoader::Create(std::move(resource_request),
- traffic_annotation);
- network::SimpleURLLoader* simple_url_loader_ptr = simple_url_loader.get();
- simple_url_loader->AttachStringForUpload(report_being_dispatched_->data(),
- kProtoBufMimeType);
- auto it = uploads_in_progress_.insert(uploads_in_progress_.begin(),
- std::move(simple_url_loader));
- if (!url_loader_factory_) {
- // Lazily create the URLLoaderFactory.
- url_loader_factory_ = std::move(url_loader_factory_getter_).Run();
- DCHECK(url_loader_factory_);
- }
- simple_url_loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
- url_loader_factory_.get(),
- base::BindOnce(&FeedbackUploader::OnDispatchComplete,
- base::Unretained(this), std::move(it)));
- }
- void FeedbackUploader::OnDispatchComplete(
- UrlLoaderList::iterator it,
- std::unique_ptr<std::string> response_body) {
- std::stringstream error_stream;
- network::SimpleURLLoader* simple_url_loader = it->get();
- int response_code = kHttpPostFailNoConnection;
- if (simple_url_loader->ResponseInfo() &&
- simple_url_loader->ResponseInfo()->headers) {
- response_code = simple_url_loader->ResponseInfo()->headers->response_code();
- }
- if (response_code == kHttpPostSuccessNoContent) {
- error_stream << "Success";
- OnReportUploadSuccess();
- } else {
- bool should_retry = true;
- // Process the error for debug output
- if (response_code == kHttpPostFailNoConnection) {
- error_stream << "No connection to server.";
- } else if ((response_code >= kHttpPostFailClientError) &&
- (response_code < kHttpPostFailServerError)) {
- // Client errors mean that the server failed to parse the proto that was
- // sent, or that some requirements weren't met by the server side
- // validation, and hence we should NOT retry sending this corrupt report
- // and give up.
- should_retry = false;
- error_stream << "Client error: HTTP response code " << response_code;
- } else if (response_code >= kHttpPostFailServerError) {
- error_stream << "Server error: HTTP response code " << response_code;
- } else {
- error_stream << "Unknown error: HTTP response code " << response_code;
- }
- OnReportUploadFailure(should_retry);
- }
- LOG(WARNING) << "FEEDBACK: Submission to feedback server ("
- << simple_url_loader->GetFinalURL()
- << ") status: " << error_stream.str();
- uploads_in_progress_.erase(it);
- }
- void FeedbackUploader::UpdateUploadTimer() {
- if (reports_queue_.empty())
- return;
- scoped_refptr<FeedbackReport> report = reports_queue_.top();
- const base::Time now = base::Time::Now();
- if (report->upload_at() <= now && !is_dispatching_) {
- reports_queue_.pop();
- is_dispatching_ = true;
- report_being_dispatched_ = report;
- StartDispatchingReport();
- } else {
- // Stop the old timer and start an updated one.
- const base::TimeDelta delay = (is_dispatching_ || now > report->upload_at())
- ? g_dispatching_wait_delay
- : report->upload_at() - now;
- upload_timer_.Stop();
- upload_timer_.Start(FROM_HERE, delay, this,
- &FeedbackUploader::UpdateUploadTimer);
- }
- }
- } // namespace feedback
|