Skip to content

Commit

Permalink
Manage crash reporting using a CrashReportingContext
Browse files Browse the repository at this point in the history
Bug: b/270571190
Change-Id: I703291c6a9a4699425c985be5406d5898d775e53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4335176
Reviewed-by: Dominique Fauteux-Chapleau <domfc@chromium.org>
Reviewed-by: Roger Tawa <rogerta@chromium.org>
Commit-Queue: Nasser Al-shawwa <alshawwa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1117636}
  • Loading branch information
Nasser Al-shawwa authored and Chromium LUCI CQ committed Mar 15, 2023
1 parent fe67680 commit 4107741
Show file tree
Hide file tree
Showing 10 changed files with 527 additions and 265 deletions.
2 changes: 2 additions & 0 deletions chrome/browser/BUILD.gn
Expand Up @@ -3674,6 +3674,8 @@ static_library("browser") {
"enterprise/connectors/enterprise_connectors_policy_handler.h",
"enterprise/connectors/reporting/browser_crash_event_router.cc",
"enterprise/connectors/reporting/browser_crash_event_router.h",
"enterprise/connectors/reporting/crash_reporting_context.cc",
"enterprise/connectors/reporting/crash_reporting_context.h",
"enterprise/connectors/reporting/extension_install_event_router.cc",
"enterprise/connectors/reporting/extension_install_event_router.h",
"enterprise/connectors/reporting/realtime_reporting_client.cc",
Expand Down
Expand Up @@ -4,241 +4,34 @@

#include "chrome/browser/enterprise/connectors/reporting/browser_crash_event_router.h"

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/enterprise/connectors/connectors_prefs.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client_factory.h"
#include "chrome/browser/enterprise/connectors/reporting/crash_reporting_context.h"
#include "chrome/browser/enterprise/connectors/reporting/reporting_service_settings.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_paths.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"

#if !BUILDFLAG(IS_FUCHSIA)
#include "components/crash/core/app/crashpad.h"
#include "third_party/crashpad/crashpad/client/crash_report_database.h"
#endif // !BUILDFLAG(IS_FUCHSIA)

namespace enterprise_connectors {

#if !BUILDFLAG(IS_FUCHSIA)
namespace {
// key names used when building the dictionary to pass to the real-time
// reporting API
constexpr char kKeyChannel[] = "channel";
constexpr char kKeyVersion[] = "version";
constexpr char kKeyReportId[] = "reportId";
constexpr char kKeyPlatform[] = "platform";
constexpr char kKeyProfileUserName[] = "profileUserName";

constexpr char kCrashpadPollingIntervalFlag[] = "crashpad-polling-interval";
constexpr int kDefaultCrashpadPollingIntervalSeconds = 3600;

void SetLatestCrashReportTime(time_t timestamp) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetInt64(enterprise_connectors::kLatestCrashReportCreationTime,
timestamp);
}

time_t GetLatestCrashReportTime() {
PrefService* local_state = g_browser_process->local_state();
time_t timestamp = local_state->GetInt64(
enterprise_connectors::kLatestCrashReportCreationTime);
VLOG(1) << "enterprise.crash_reporting: latest crash report time: "
<< base::Time::FromTimeT(timestamp);
return timestamp;
}

// Get polling interval for crashpad database. This is factored into a
// function to allow for a dev-only command-line option for ease of
// manual testing
base::TimeDelta GetCrashpadPollingInterval() {
base::TimeDelta result =
base::Seconds(kDefaultCrashpadPollingIntervalSeconds);
if (g_browser_process && g_browser_process->browser_policy_connector()
->IsCommandLineSwitchSupported()) {
base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
if (cmd->HasSwitch(kCrashpadPollingIntervalFlag)) {
int crashpad_polling_interval_seconds = 0;
if (base::StringToInt(
cmd->GetSwitchValueASCII(kCrashpadPollingIntervalFlag),
&crashpad_polling_interval_seconds) &&
crashpad_polling_interval_seconds > 0) {
result = base::Seconds(crashpad_polling_interval_seconds);
}
}
}
VLOG(1) << "enterprise.crash_reporting: crashpad polling interval set to "
<< result;
return result;
}

// Copy new reports (i.e. reports that have not been sent to the
// reporting server) from `reports_to_be_copied` to `reports`
// based on the `latest_creation_time`.
void CopyNewReports(
const std::vector<crashpad::CrashReportDatabase::Report>&
reports_to_be_copied,
int64_t latest_creation_time,
std::vector<crashpad::CrashReportDatabase::Report>& reports) {
for (const crashpad::CrashReportDatabase::Report& report :
reports_to_be_copied) {
if (report.creation_time <= latest_creation_time) {
continue;
}
reports.push_back(report);
}
}

bool GetReportsFromDatabase(
std::vector<crashpad::CrashReportDatabase::Report>& pending_reports,
std::vector<crashpad::CrashReportDatabase::Report>& completed_reports) {
std::unique_ptr<crashpad::CrashReportDatabase> database =
crashpad::CrashReportDatabase::InitializeWithoutCreating(
crash_reporter::GetCrashpadDatabasePath());
// `database` could be null if it has not been initialized yet.
if (!database) {
VLOG(1) << "enterprise.crash_reporting: failed to fetch crashpad db";
return false;
}

crashpad::CrashReportDatabase::OperationStatus status;
status = database->GetPendingReports(&pending_reports);
if (status != crashpad::CrashReportDatabase::kNoError) {
return false;
}
status = database->GetCompletedReports(&completed_reports);
if (status != crashpad::CrashReportDatabase::kNoError) {
return false;
}
return true;
}

// Return a list of new Reports that are ready to be to sent to the
// reporting server. It returns an empty list if any operation fails or
// there is no new report.
std::vector<crashpad::CrashReportDatabase::Report> GetNewReports(
time_t latest_creation_time) {
// Get the creation time of the latest report that was sent to the reporting
// server last time.
// latest_crash_report is the filepath where stores the latest report info
std::vector<crashpad::CrashReportDatabase::Report> reports;
std::vector<crashpad::CrashReportDatabase::Report> pending_reports;
std::vector<crashpad::CrashReportDatabase::Report> completed_reports;
if (!GetReportsFromDatabase(pending_reports, completed_reports)) {
return reports;
}
// Get reports that have not been sent (<= latest_creation_time)
CopyNewReports(pending_reports, latest_creation_time, reports);
CopyNewReports(completed_reports, latest_creation_time, reports);

return reports;
}

} // namespace

// TODO(b/238427470): unit testing this function
void BrowserCrashEventRouter::UploadToReportingServer(
RealtimeReportingClient* reporting_client,
ReportingSettings settings,
std::vector<crashpad::CrashReportDatabase::Report> reports) {
DCHECK(reporting_client);
if (reports.empty()) {
VLOG(1) << "enterprise.crash_reporting: no new crashes";
return;
}
VLOG(1) << "enterprise.crash_reporting: " << reports.size()
<< " new crashes to report";
const std::string version = version_info::GetVersionNumber();
const std::string channel =
version_info::GetChannelString(chrome::GetChannel());
const std::string platform = version_info::GetOSType();

int64_t latest_creation_time = -1;

for (const auto& report : reports) {
base::Value::Dict event;
event.Set(kKeyChannel, channel);
event.Set(kKeyVersion, version);
event.Set(kKeyReportId, report.id);
event.Set(kKeyPlatform, platform);
event.Set(kKeyProfileUserName, reporting_client->GetProfileUserName());
reporting_client->ReportPastEvent(
ReportingServiceSettings::kBrowserCrashEvent, settings,
std::move(event), base::Time::FromTimeT(report.creation_time));
if (report.creation_time > latest_creation_time) {
latest_creation_time = report.creation_time;
}
}
SetLatestCrashReportTime(latest_creation_time);
}

void BrowserCrashEventRouter::ReportCrashes() {
VLOG(1) << "enterprise.crash_reporting: checking for unreported crashes";
DCHECK(reporting_client_);
const absl::optional<ReportingSettings> settings =
reporting_client_->GetReportingSettings();
bool isBrowserCrashReportingEnabled =
settings.has_value() &&
settings->enabled_event_names.count(
ReportingServiceSettings::kBrowserCrashEvent) != 0;
VLOG(1) << "enterprise.crash_reporting: crash reporting enabled: "
<< isBrowserCrashReportingEnabled;
if (!isBrowserCrashReportingEnabled) {
g_browser_process->local_state()->ClearPref(
enterprise_connectors::kLatestCrashReportCreationTime);
return;
}
time_t latest_creation_time = GetLatestCrashReportTime();
if (latest_creation_time == 0) {
latest_creation_time = base::Time::Now().ToTimeT();
SetLatestCrashReportTime(latest_creation_time);
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&GetNewReports, latest_creation_time),
base::BindOnce(&BrowserCrashEventRouter::UploadToReportingServer,
AsWeakPtr(), reporting_client_, std::move(*settings)));
}

void BrowserCrashEventRouter::OnCloudReportingLaunched(
enterprise_reporting::ReportScheduler* report_scheduler) {
VLOG(1) << "enterprise.crash_reporting: crash event reporting initializing";
// An initial call to ReportCrashes() is required because the first call
// in the repeating callback happens after the delay.
ReportCrashes();
repeating_crash_report_.Start(FROM_HERE, GetCrashpadPollingInterval(), this,
&BrowserCrashEventRouter::ReportCrashes);
}
#endif // !BUILDFLAG(IS_FUCHSIA)

BrowserCrashEventRouter::BrowserCrashEventRouter(
content::BrowserContext* context) {
reporting_client_ = RealtimeReportingClientFactory::GetForProfile(context);
if (base::FeatureList::IsEnabled(kBrowserCrashEventsEnabled)) {
#if !BUILDFLAG(IS_CHROMEOS_ASH)
controller_ = g_browser_process->browser_policy_connector()
->chrome_browser_cloud_management_controller();
controller_->AddObserver(this);
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
if (!base::FeatureList::IsEnabled(kBrowserCrashEventsEnabled)) {
return;
}
#if !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_CHROMEOS_ASH)
CrashReportingContext* crash_reporting_context =
CrashReportingContext::GetInstance();
Profile* profile = Profile::FromBrowserContext(context);
crash_reporting_context->AddProfile(this, profile);

#endif
}

BrowserCrashEventRouter::~BrowserCrashEventRouter() {
if (controller_) {
#if !BUILDFLAG(IS_CHROMEOS_ASH)
controller_->RemoveObserver(this);
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
if (!base::FeatureList::IsEnabled(kBrowserCrashEventsEnabled)) {
return;
}
#if !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_CHROMEOS_ASH)
CrashReportingContext* crash_reporting_context =
CrashReportingContext::GetInstance();
crash_reporting_context->RemoveProfile(this);
#endif
}

} // namespace enterprise_connectors
Expand Up @@ -5,54 +5,23 @@
#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_REPORTING_BROWSER_CRASH_EVENT_ROUTER_H_
#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_REPORTING_BROWSER_CRASH_EVENT_ROUTER_H_

#include "base/memory/weak_ptr.h"
#include "chrome/browser/enterprise/connectors/reporting/realtime_reporting_client.h"
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#include "content/public/browser/browser_context.h"

#if !BUILDFLAG(IS_FUCHSIA)
#include "third_party/crashpad/crashpad/client/crash_report_database.h"
#endif // !BUILDFLAG(IS_FUCHSIA)
namespace enterprise_connectors {
// This class collects crash reports from the crashpad database
// and then sends generated crash events to the reporting server.
class BrowserCrashEventRouter
: public policy::ChromeBrowserCloudManagementController::Observer,
public base::SupportsWeakPtr<BrowserCrashEventRouter> {

// An instance of class is owned by the ConnectorsManager, we use its lifetime
// to manage which profiles are observed for the purposes of crash reporting.
// Its constructor and destructor add and remove profiles to the
// CrashReportingContext, respectively, if they are valid for crash reporting.
class BrowserCrashEventRouter {
public:
// BrowserCrashEventRouter adds itself to
// ChromeBrowserCloudManagementController::observers in the constructor, so
// that once the browser launches, OnCloudReportingLaunched() will be called,
// where we can call ReportCrashes() to report crashes.
explicit BrowserCrashEventRouter(content::BrowserContext* context);

BrowserCrashEventRouter(const BrowserCrashEventRouter&) = delete;
BrowserCrashEventRouter& operator=(const BrowserCrashEventRouter&) = delete;
BrowserCrashEventRouter(BrowserCrashEventRouter&&) = delete;
BrowserCrashEventRouter& operator=(BrowserCrashEventRouter&&) = delete;
~BrowserCrashEventRouter() override;

#if !BUILDFLAG(IS_FUCHSIA)
void OnCloudReportingLaunched(
enterprise_reporting::ReportScheduler* report_scheduler) override;
void UploadToReportingServer(
RealtimeReportingClient* reporting_client,
ReportingSettings settings,
std::vector<crashpad::CrashReportDatabase::Report> reports);
#endif // !BUILDFLAG(IS_FUCHSIA)

private:
raw_ptr<enterprise_connectors::RealtimeReportingClient, DanglingUntriaged>
reporting_client_ = nullptr;
raw_ptr<policy::ChromeBrowserCloudManagementController, DanglingUntriaged>
controller_ = nullptr;

#if !BUILDFLAG(IS_FUCHSIA)
base::RepeatingTimer repeating_crash_report_;
// ReportCrashes() checks the enterprise policy settings, retrieves crash
// reports from the crashpad local database and sends reports that have not
// been sent to the reporting server.
void ReportCrashes();
#endif // !BUILDFLAG(IS_FUCHSIA)
~BrowserCrashEventRouter();
};

} // namespace enterprise_connectors
Expand Down

0 comments on commit 4107741

Please sign in to comment.