blob: 8f6520543303e714081323b0711d79d4218ccbe7 [file] [log] [blame]
// 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/sync/driver/startup_controller.h"
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/sync/driver/sync_driver_switches.h"
namespace syncer {
namespace {
// The amount of time we'll wait to initialize sync if no data type requests
// immediately initialization.
const int kDefaultDeferredInitDelaySeconds = 10;
base::TimeDelta GetDeferredInitDelay() {
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (cmdline->HasSwitch(switches::kSyncDeferredStartupTimeoutSeconds)) {
int timeout = 0;
if (base::StringToInt(cmdline->GetSwitchValueASCII(
switches::kSyncDeferredStartupTimeoutSeconds),
&timeout)) {
DCHECK_GE(timeout, 0);
DVLOG(2) << "Sync StartupController overriding startup timeout to "
<< timeout << " seconds.";
return base::TimeDelta::FromSeconds(timeout);
}
}
return base::TimeDelta::FromSeconds(kDefaultDeferredInitDelaySeconds);
}
bool IsDeferredStartupEnabled() {
return !base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSyncDisableDeferredStartup);
}
// Enum for UMA defining different events that cause us to exit the "deferred"
// state of initialization and invoke start_engine.
enum DeferredInitTrigger {
// We have received a signal from a SyncableService requesting that sync
// starts as soon as possible.
TRIGGER_DATA_TYPE_REQUEST,
// No data type requested sync to start and our fallback timer expired.
TRIGGER_FALLBACK_TIMER,
MAX_TRIGGER_VALUE
};
} // namespace
StartupController::StartupController(
base::RepeatingCallback<ModelTypeSet()> get_preferred_data_types,
base::RepeatingCallback<bool()> should_start,
base::RepeatingClosure start_engine)
: get_preferred_data_types_callback_(std::move(get_preferred_data_types)),
should_start_callback_(std::move(should_start)),
start_engine_callback_(std::move(start_engine)),
bypass_deferred_startup_(false),
weak_factory_(this) {}
StartupController::~StartupController() {}
void StartupController::Reset() {
bypass_deferred_startup_ = false;
start_up_time_ = base::Time();
start_engine_time_ = base::Time();
// Don't let previous timers affect us post-reset.
weak_factory_.InvalidateWeakPtrs();
}
void StartupController::StartUp(StartUpDeferredOption deferred_option) {
const bool first_start = start_up_time_.is_null();
if (first_start) {
start_up_time_ = base::Time::Now();
}
if (deferred_option == STARTUP_DEFERRED && IsDeferredStartupEnabled() &&
get_preferred_data_types_callback_.Run().Has(SESSIONS)) {
if (first_start) {
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&StartupController::OnFallbackStartupTimerExpired,
weak_factory_.GetWeakPtr()),
GetDeferredInitDelay());
}
return;
}
if (start_engine_time_.is_null()) {
start_engine_time_ = base::Time::Now();
start_engine_callback_.Run();
}
}
void StartupController::TryStart(bool force_immediate) {
if (!should_start_callback_.Run()) {
return;
}
// For performance reasons, defer the heavy lifting for sync init unless:
//
// - a datatype has requested an immediate start of sync, or
// - sync needs to start up the engine immediately to provide control state
// and encryption information to the UI.
// Do not start up the sync engine if setup has not completed and isn't
// in progress, unless told to otherwise.
StartUp((force_immediate || bypass_deferred_startup_) ? STARTUP_IMMEDIATE
: STARTUP_DEFERRED);
}
void StartupController::RecordTimeDeferred() {
DCHECK(!start_up_time_.is_null());
base::TimeDelta time_deferred = base::Time::Now() - start_up_time_;
UMA_HISTOGRAM_CUSTOM_TIMES("Sync.Startup.TimeDeferred2", time_deferred,
base::TimeDelta::FromSeconds(0),
base::TimeDelta::FromMinutes(2), 60);
}
void StartupController::OnFallbackStartupTimerExpired() {
DCHECK(IsDeferredStartupEnabled());
if (!start_engine_time_.is_null())
return;
DVLOG(2) << "Sync deferred init fallback timer expired, starting engine.";
RecordTimeDeferred();
UMA_HISTOGRAM_ENUMERATION("Sync.Startup.DeferredInitTrigger",
TRIGGER_FALLBACK_TIMER, MAX_TRIGGER_VALUE);
// Once the deferred init timer has expired, don't defer startup again (until
// Reset() or browser restart), even if this startup attempt doesn't succeed.
bypass_deferred_startup_ = true;
TryStart(/*force_immediate=*/false);
}
StartupController::State StartupController::GetState() const {
if (!start_engine_time_.is_null())
return State::STARTED;
if (!start_up_time_.is_null())
return State::STARTING_DEFERRED;
return State::NOT_STARTED;
}
void StartupController::OnDataTypeRequestsSyncStartup(ModelType type) {
if (!IsDeferredStartupEnabled()) {
DVLOG(2) << "Ignoring data type request for sync startup: "
<< ModelTypeToString(type);
return;
}
if (!start_engine_time_.is_null())
return;
DVLOG(2) << "Data type requesting sync startup: " << ModelTypeToString(type);
// Measure the time spent waiting for init and the type that triggered it.
// We could measure the time spent deferred on a per-datatype basis, but
// for now this is probably sufficient.
// TODO(wychen): enum uma should be strongly typed. crbug.com/661401
UMA_HISTOGRAM_ENUMERATION("Sync.Startup.TypeTriggeringInit",
ModelTypeToHistogramInt(type),
static_cast<int>(ModelType::NUM_ENTRIES));
if (!start_up_time_.is_null()) {
RecordTimeDeferred();
UMA_HISTOGRAM_ENUMERATION("Sync.Startup.DeferredInitTrigger",
TRIGGER_DATA_TYPE_REQUEST, MAX_TRIGGER_VALUE);
}
bypass_deferred_startup_ = true;
TryStart(/*force_immediate=*/false);
}
} // namespace syncer