summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc')
-rw-r--r--chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc383
1 files changed, 383 insertions, 0 deletions
diff --git a/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
new file mode 100644
index 00000000000..17f542efa75
--- /dev/null
+++ b/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
@@ -0,0 +1,383 @@
+// Copyright 2018 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 "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "ui/base/text/bytes_formatting.h"
+
+namespace webrtc_event_logging {
+
+namespace {
+// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
+const char kUploadContentType[] = "multipart/form-data";
+const char kBoundary[] = "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";
+
+constexpr size_t kExpectedMimeOverheadBytes = 1000; // Intentional overshot.
+
+// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
+#if defined(OS_WIN)
+const char kProduct[] = "Chrome";
+#elif defined(OS_MACOSX)
+const char kProduct[] = "Chrome_Mac";
+#elif defined(OS_LINUX)
+const char kProduct[] = "Chrome_Linux";
+#elif defined(OS_ANDROID)
+const char kProduct[] = "Chrome_Android";
+#elif defined(OS_CHROMEOS)
+const char kProduct[] = "Chrome_ChromeOS";
+#else
+#error Platform not supported.
+#endif
+
+constexpr net::NetworkTrafficAnnotationTag
+ kWebrtcEventLogUploaderTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("webrtc_event_log_uploader", R"(
+ semantics {
+ sender: "WebRTC Event Log uploader module"
+ description:
+ "Uploads a WebRTC event log to a server called Crash. These logs "
+ "will not contain private information. They will be used to "
+ "improve WebRTC (fix bugs, tune performance, etc.)."
+ trigger:
+ "A Google service (e.g. Hangouts/Meet) has requested a peer "
+ "connection to be logged, and the resulting event log to be uploaded "
+ "at a time deemed to cause the least interference to the user (i.e., "
+ "when the user is not busy making other VoIP calls)."
+ data:
+ "WebRTC events such as the timing of audio playout (but not the "
+ "content), timing and size of RTP packets sent/received, etc."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "Feature controlled only through Chrome policy; "
+ "no user-facing control surface."
+ chrome_policy {
+ WebRtcEventLogCollectionAllowed {
+ WebRtcEventLogCollectionAllowed: false
+ }
+ }
+ })");
+
+void AddFileContents(const char* filename,
+ const std::string& file_contents,
+ const std::string& content_type,
+ std::string* post_data) {
+ // net::AddMultipartValueForUpload does almost what we want to do here, except
+ // that it does not add the "filename" attribute. We hack it to force it to.
+ std::string mime_value_name =
+ base::StringPrintf("%s\"; filename=\"%s\"", filename, filename);
+ net::AddMultipartValueForUpload(mime_value_name, file_contents, kBoundary,
+ content_type, post_data);
+}
+
+std::string MimeContentType() {
+ const char kBoundaryKeywordAndMisc[] = "; boundary=";
+
+ std::string content_type;
+ content_type.reserve(sizeof(content_type) + sizeof(kBoundaryKeywordAndMisc) +
+ sizeof(kBoundary));
+
+ content_type.append(kUploadContentType);
+ content_type.append(kBoundaryKeywordAndMisc);
+ content_type.append(kBoundary);
+
+ return content_type;
+}
+
+void BindURLLoaderFactoryReceiver(
+ mojo::PendingReceiver<network::mojom::URLLoaderFactory>
+ url_loader_factory_receiver) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory =
+ g_browser_process->shared_url_loader_factory();
+ DCHECK(shared_url_loader_factory);
+ shared_url_loader_factory->Clone(std::move(url_loader_factory_receiver));
+}
+
+void OnURLLoadUploadProgress(uint64_t current, uint64_t total) {
+ ui::DataUnits unit = ui::GetByteDisplayUnits(total);
+ VLOG(1) << "WebRTC event log upload progress: "
+ << FormatBytesWithUnits(current, unit, false) << " / "
+ << FormatBytesWithUnits(total, unit, true) << ".";
+}
+} // namespace
+
+const char WebRtcEventLogUploaderImpl::kUploadURL[] =
+ "https://clients2.google.com/cr/report";
+
+std::unique_ptr<WebRtcEventLogUploader>
+WebRtcEventLogUploaderImpl::Factory::Create(const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback) {
+ return std::make_unique<WebRtcEventLogUploaderImpl>(
+ log_file, std::move(callback), kMaxRemoteLogFileSizeBytes);
+}
+
+std::unique_ptr<WebRtcEventLogUploader>
+WebRtcEventLogUploaderImpl::Factory::CreateWithCustomMaxSizeForTesting(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_log_file_size_bytes) {
+ return std::make_unique<WebRtcEventLogUploaderImpl>(
+ log_file, std::move(callback), max_log_file_size_bytes);
+}
+
+WebRtcEventLogUploaderImpl::WebRtcEventLogUploaderImpl(
+ const WebRtcLogFileInfo& log_file,
+ UploadResultCallback callback,
+ size_t max_log_file_size_bytes)
+ : log_file_(log_file),
+ callback_(std::move(callback)),
+ max_log_file_size_bytes_(max_log_file_size_bytes),
+ io_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
+ history_file_writer_ = WebRtcEventLogHistoryFileWriter::Create(
+ GetWebRtcEventLogHistoryFilePath(log_file_.path));
+ if (!history_file_writer_) {
+ // File either could not be created, or, if a different error occurred,
+ // Create() will have tried to remove the file it has created.
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kHistoryFileCreationError);
+ ReportResult(false);
+ return;
+ }
+
+ const base::Time now = std::max(base::Time::Now(), log_file.last_modified);
+ if (!history_file_writer_->WriteCaptureTime(log_file.last_modified) ||
+ !history_file_writer_->WriteUploadTime(now)) {
+ LOG(ERROR) << "Writing to history file failed.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kHistoryFileWriteError);
+ DeleteHistoryFile(); // Avoid partial, potentially-corrupt history files.
+ ReportResult(false);
+ return;
+ }
+
+ std::string upload_data;
+ if (!PrepareUploadData(&upload_data)) {
+ // History file will reflect a failed upload attempt.
+ ReportResult(false); // UMA recorded by PrepareUploadData().
+ return;
+ }
+
+ StartUpload(upload_data);
+}
+
+WebRtcEventLogUploaderImpl::~WebRtcEventLogUploaderImpl() {
+ // WebRtcEventLogUploaderImpl objects' deletion scenarios:
+ // 1. Upload started and finished - |url_loader_| should have been reset
+ // so that we would be able to DCHECK and demonstrate that the determinant
+ // is maintained.
+ // 2. Upload started and cancelled - behave similarly to a finished upload.
+ // 3. The upload was never started, due to an early failure (e.g. file not
+ // found). In that case, |url_loader_| will not have been set.
+ // 4. Chrome shutdown.
+ if (io_task_runner_->RunsTasksInCurrentSequence()) { // Scenarios 1-3.
+ DCHECK(!url_loader_);
+ } else { // # Scenario #4 - Chrome shutdown.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ bool will_delete =
+ io_task_runner_->DeleteSoon(FROM_HERE, url_loader_.release());
+ DCHECK(!will_delete)
+ << "Task runners must have been stopped by this stage of shutdown.";
+ }
+}
+
+const WebRtcLogFileInfo& WebRtcEventLogUploaderImpl::GetWebRtcLogFileInfo()
+ const {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ return log_file_;
+}
+
+bool WebRtcEventLogUploaderImpl::Cancel() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ // The upload could already have been completed, or maybe was never properly
+ // started (due to a file read failure, etc.).
+ const bool upload_was_active = (url_loader_.get() != nullptr);
+
+ // Note that in this case, it might still be that the last bytes hit the
+ // wire right as we attempt to cancel the upload. OnURLFetchComplete, however,
+ // will not be called.
+ url_loader_.reset();
+
+ DeleteLogFile();
+ DeleteHistoryFile();
+
+ if (upload_was_active) {
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kUploadCancelled);
+ }
+
+ return upload_was_active;
+}
+
+bool WebRtcEventLogUploaderImpl::PrepareUploadData(std::string* upload_data) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ std::string log_file_contents;
+ if (!base::ReadFileToStringWithMaxSize(log_file_.path, &log_file_contents,
+ max_log_file_size_bytes_)) {
+ LOG(WARNING) << "Couldn't read event log file, or max file size exceeded.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileReadError);
+ return false;
+ }
+
+ DCHECK(upload_data->empty());
+ upload_data->reserve(log_file_contents.size() + kExpectedMimeOverheadBytes);
+
+ const std::string filename_str = log_file_.path.BaseName().MaybeAsASCII();
+ if (filename_str.empty()) {
+ LOG(WARNING) << "Log filename is not according to acceptable format.";
+ UmaRecordWebRtcEventLoggingUpload(
+ WebRtcEventLoggingUploadUma::kLogFileNameError);
+ return false;
+ }
+
+ const char* filename = filename_str.c_str();
+
+ net::AddMultipartValueForUpload("prod", kProduct, kBoundary, std::string(),
+ upload_data);
+ net::AddMultipartValueForUpload("ver",
+ version_info::GetVersionNumber() + "-webrtc",
+ kBoundary, std::string(), upload_data);
+ net::AddMultipartValueForUpload("guid", "0", kBoundary, std::string(),
+ upload_data);
+ net::AddMultipartValueForUpload("type", filename, kBoundary, std::string(),
+ upload_data);
+ AddFileContents(filename, log_file_contents, "application/log", upload_data);
+ net::AddMultipartFinalDelimiterForUpload(kBoundary, upload_data);
+
+ return true;
+}
+
+void WebRtcEventLogUploaderImpl::StartUpload(const std::string& upload_data) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = GURL(kUploadURL);
+ resource_request->method = "POST";
+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+
+ // Create a new mojo pipe. It's safe to pass this around and use
+ // immediately, even though it needs to finish initialization on the UI
+ // thread.
+ network::mojom::URLLoaderFactoryPtr url_loader_factory_ptr;
+ base::PostTask(FROM_HERE, {content::BrowserThread::UI},
+ base::BindOnce(BindURLLoaderFactoryReceiver,
+ mojo::MakeRequest(&url_loader_factory_ptr)));
+
+ url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), kWebrtcEventLogUploaderTrafficAnnotation);
+ url_loader_->AttachStringForUpload(upload_data, MimeContentType());
+ url_loader_->SetOnUploadProgressCallback(
+ base::BindRepeating(OnURLLoadUploadProgress));
+
+ // See comment in destructor for an explanation about why using
+ // base::Unretained(this) is safe here.
+ url_loader_->DownloadToString(
+ url_loader_factory_ptr.get(),
+ base::BindOnce(&WebRtcEventLogUploaderImpl::OnURLLoadComplete,
+ base::Unretained(this)),
+ kWebRtcEventLogMaxUploadIdBytes);
+}
+
+void WebRtcEventLogUploaderImpl::OnURLLoadComplete(
+ std::unique_ptr<std::string> response_body) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(url_loader_);
+
+ if (response_body.get() != nullptr && response_body->empty()) {
+ LOG(WARNING) << "SimpleURLLoader reported upload successful, "
+ << "but report ID unknown.";
+ }
+
+ const bool upload_successful =
+ (response_body.get() != nullptr && !response_body->empty());
+
+ // NetError() is 0 when no error occurred.
+ UmaRecordWebRtcEventLoggingNetErrorType(url_loader_->NetError());
+
+ DCHECK(history_file_writer_);
+ if (upload_successful) {
+ if (!history_file_writer_->WriteUploadId(*response_body)) {
+ // Discard the incomplete, potentially now corrupt history file, but the
+ // upload is still considered successful.
+ LOG(ERROR) << "Failed to write upload ID to history file.";
+ DeleteHistoryFile();
+ }
+ } else {
+ LOG(WARNING) << "Upload unsuccessful.";
+ // By not writing an UploadId to the history file, it is inferrable that
+ // the upload was initiated, but did not end successfully.
+ }
+
+ UmaRecordWebRtcEventLoggingUpload(
+ upload_successful ? WebRtcEventLoggingUploadUma::kSuccess
+ : WebRtcEventLoggingUploadUma::kUploadFailure);
+
+ url_loader_.reset(); // Explicitly maintain determinant.
+
+ ReportResult(upload_successful);
+}
+
+void WebRtcEventLogUploaderImpl::ReportResult(bool result) {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+
+ // * If the upload was successful, the file is no longer needed.
+ // * If the upload failed, we don't want to retry, because we run the risk of
+ // uploading significant amounts of data once again, only for the upload to
+ // fail again after (as an example) wasting 50MBs of upload bandwidth.
+ // * If the file was not found, this will simply have no effect (other than
+ // to LOG() an error).
+ // TODO(crbug.com/775415): Provide refined retrial behavior.
+ DeleteLogFile();
+
+ // Release hold of history file, allowing it to be read, moved or deleted.
+ history_file_writer_.reset();
+
+ io_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), log_file_.path, result));
+}
+
+void WebRtcEventLogUploaderImpl::DeleteLogFile() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ const bool deletion_successful =
+ base::DeleteFile(log_file_.path, /*recursive=*/false);
+ if (!deletion_successful) {
+ // This is a somewhat serious (though unlikely) error, because now we'll
+ // try to upload this file again next time Chrome launches.
+ LOG(ERROR) << "Could not delete pending WebRTC event log file.";
+ }
+}
+
+void WebRtcEventLogUploaderImpl::DeleteHistoryFile() {
+ DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
+ if (!history_file_writer_) {
+ LOG(ERROR) << "Deletion of history file attempted after uploader "
+ << "has relinquished ownership of it.";
+ return;
+ }
+ history_file_writer_->Delete();
+ history_file_writer_.reset();
+}
+
+} // namespace webrtc_event_logging