Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(253)

Unified Diff: handler/mac/crash_report_upload_thread.cc

Issue 982613002: handler: Add report upload (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Address review feedback Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « handler/mac/crash_report_upload_thread.h ('k') | handler/mac/main.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: handler/mac/crash_report_upload_thread.cc
diff --git a/handler/mac/crash_report_upload_thread.cc b/handler/mac/crash_report_upload_thread.cc
index 55e2d291dfba647a6e623a32ff258ab6c672d711..a5ae5d05d7444637293db6814c1b58794a7e7c54 100644
--- a/handler/mac/crash_report_upload_thread.cc
+++ b/handler/mac/crash_report_upload_thread.cc
@@ -16,14 +16,119 @@
#include <errno.h>
+#include <map>
#include <vector>
+#include <utility>
#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "snapshot/minidump/process_snapshot_minidump.h"
+#include "snapshot/module_snapshot.h"
+#include "util/file/file_reader.h"
+#include "util/net/http_body.h"
+#include "util/net/http_multipart_builder.h"
+#include "util/net/http_transport.h"
namespace crashpad {
-CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database)
- : database_(database),
+namespace {
+
+// Given a minidump file readable by |minidump_file_reader|, returns a map of
+// key-value pairs to use as HTTP form parameters for upload to a Breakpad
+// server. The map is built by combining the process simple annotations map with
+// each module’s simple annotations map. In the case of duplicate keys, the map
+// will retain the first value found for any key, and will log a warning about
+// discarded values. Each module’s annotations vector is also examined and built
+// into a single string value, with distinct elements separated by newlines, and
+// stored at the key named “list_annotations”, which supersedes any other key
+// found by that name.
+//
+// In the event of an error reading the minidump file, a message will be logged.
+std::map<std::string, std::string> BreakpadHTTPFormParametersFromMinidump(
+ FileReader* minidump_file_reader) {
+ ProcessSnapshotMinidump minidump_process_snapshot;
+ if (!minidump_process_snapshot.Initialize(minidump_file_reader)) {
+ return std::map<std::string, std::string>();
+ }
+
+ std::map<std::string, std::string> parameters =
+ minidump_process_snapshot.AnnotationsSimpleMap();
+
+ std::string list_annotations;
+ for (const ModuleSnapshot* module : minidump_process_snapshot.Modules()) {
+ for (const auto& kv : module->AnnotationsSimpleMap()) {
+ if (parameters.find(kv.first) != parameters.end()) {
+ LOG(WARNING) << "duplicate key " << kv.first << ", discarding value "
+ << kv.second;
+ } else {
+ parameters.insert(kv);
+ }
+ }
+
+ for (std::string annotation : module->AnnotationsVector()) {
+ list_annotations.append(annotation);
+ list_annotations.append("\n");
+ }
+ }
+
+ if (!list_annotations.empty()) {
+ // Remove the final newline character.
+ list_annotations.pop_back();
+
+ const char kListAnnotationsKey[] = "list_annotations";
+ auto it = parameters.find(kListAnnotationsKey);
+ if (it != parameters.end()) {
+ LOG(WARNING) << "duplicate key " << kListAnnotationsKey
+ << ", discarding value " << it->second;
+ it->second = list_annotations;
+ } else {
+ parameters.insert(std::make_pair(kListAnnotationsKey, list_annotations));
+ }
+ }
+
+ return parameters;
+}
+
+// Calls CrashReportDatabase::RecordUploadAttempt() with |successful| set to
+// false upon destruction unless disarmed by calling Fire() or Disarm(). Fire()
+// triggers an immediate call. Armed upon construction.
+class CallRecordUploadAttempt {
+ public:
+ CallRecordUploadAttempt(CrashReportDatabase* database,
+ const CrashReportDatabase::Report* report)
+ : database_(database),
+ report_(report) {
+ }
+
+ ~CallRecordUploadAttempt() {
+ Fire();
+ }
+
+ void Fire() {
+ if (report_) {
+ database_->RecordUploadAttempt(report_, false, std::string());
+ }
+
+ Disarm();
+ }
+
+ void Disarm() {
+ report_ = nullptr;
+ }
+
+ private:
+ CrashReportDatabase* database_; // weak
+ const CrashReportDatabase::Report* report_; // weak
+
+ DISALLOW_COPY_AND_ASSIGN(CallRecordUploadAttempt);
+};
+
+} // namespace
+
+CrashReportUploadThread::CrashReportUploadThread(CrashReportDatabase* database,
+ const std::string& url)
+ : url_(url),
+ database_(database),
semaphore_(0),
thread_(0),
running_(false) {
@@ -104,8 +209,99 @@ void CrashReportUploadThread::ProcessPendingReports() {
void CrashReportUploadThread::ProcessPendingReport(
const CrashReportDatabase::Report& report) {
- // TODO(mark): Actually upload the report, if uploads are enabled.
- database_->SkipReportUpload(report.uuid);
+ // TODO(mark): Allow uploads to be disabled.
+ // TODO(mark): Rate-limit uploads.
+
+ const CrashReportDatabase::Report* upload_report;
+ CrashReportDatabase::OperationStatus status =
+ database_->GetReportForUploading(report.uuid, &upload_report);
+ switch (status) {
+ case CrashReportDatabase::kNoError:
+ break;
+
+ case CrashReportDatabase::kBusyError:
+ return;
+
+ case CrashReportDatabase::kReportNotFound:
+ case CrashReportDatabase::kFileSystemError:
+ case CrashReportDatabase::kDatabaseError:
+ // In these cases, SkipReportUpload() might not work either, but it’s best
+ // to at least try to get the report out of the way.
+ database_->SkipReportUpload(report.uuid);
+ return;
+ }
+
+ CallRecordUploadAttempt call_record_upload_attempt(database_, upload_report);
+
+ std::string response_body;
+ UploadResult upload_result = UploadReport(upload_report, &response_body);
+ switch (upload_result) {
+ case UploadResult::kSuccess:
+ call_record_upload_attempt.Disarm();
+ database_->RecordUploadAttempt(upload_report, true, response_body);
+ break;
+ case UploadResult::kPermanentFailure:
+ case UploadResult::kRetry:
+ call_record_upload_attempt.Fire();
+
+ // TODO(mark): Deal with retries properly: don’t call SkipReportUplaod()
+ // if the result was kRetry and the report hasn’t already been retried
+ // too many times.
+ database_->SkipReportUpload(report.uuid);
+ break;
+ }
+}
+
+CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport(
+ const CrashReportDatabase::Report* report,
+ std::string* response_body) {
+ std::map<std::string, std::string> parameters;
+
+ {
+ FileReader minidump_file_reader;
+ if (!minidump_file_reader.Open(report->file_path)) {
+ // If the minidump file can’t be opened, all hope is lost.
+ return UploadResult::kPermanentFailure;
+ }
+
+ // If the minidump file could be opened, ignore any errors that might occur
+ // when attempting to interpret it. This may result in its being uploaded
+ // with few or no parameters, but as long as there’s a dump file, the server
+ // can decide what to do with it.
+ parameters = BreakpadHTTPFormParametersFromMinidump(&minidump_file_reader);
+ }
+
+ HTTPMultipartBuilder http_multipart_builder;
+
+ const char kMinidumpKey[] = "upload_file_minidump";
+
+ for (const auto& kv : parameters) {
+ if (kv.first == kMinidumpKey) {
+ LOG(WARNING) << "reserved key " << kv.first << ", discarding value "
+ << kv.second;
+ } else {
+ http_multipart_builder.SetFormData(kv.first, kv.second);
+ }
+ }
+
+ http_multipart_builder.SetFileAttachment(kMinidumpKey,
+ report->file_path.BaseName().value(),
+ report->file_path,
+ "application/octet-stream");
+
+ // TODO(mark): There should be a timeout option for upload.
+ scoped_ptr<HTTPTransport> http_transport(HTTPTransport::Create());
+ http_transport->SetURL(url_);
+ HTTPHeaders::value_type content_type =
+ http_multipart_builder.GetContentType();
+ http_transport->SetHeader(content_type.first, content_type.second);
+ http_transport->SetBodyStream(http_multipart_builder.GetBodyStream().Pass());
+
+ if (!http_transport->ExecuteSynchronously(response_body)) {
+ return UploadResult::kRetry;
+ }
+
+ return UploadResult::kSuccess;
}
// static
« no previous file with comments | « handler/mac/crash_report_upload_thread.h ('k') | handler/mac/main.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698