blob: 2988d6bc1688a506cf782b1dafe444ec25d9140f [file] [log] [blame]
// 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 "components/sync/driver/sync_session_durations_metrics_recorder.h"
#include "base/metrics/histogram_macros.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/engine/cycle/sync_cycle_snapshot.h"
namespace syncer {
namespace {
base::TimeDelta SubtractInactiveTime(base::TimeDelta total_length,
base::TimeDelta inactive_time) {
// Substract any time the user was inactive from our session length. If this
// ends up giving the session negative length, which can happen if the feature
// state changed after the user became inactive, log the length as 0.
base::TimeDelta session_length = total_length - inactive_time;
if (session_length < base::TimeDelta()) {
session_length = base::TimeDelta();
}
return session_length;
}
} // namespace
SyncSessionDurationsMetricsRecorder::SyncSessionDurationsMetricsRecorder(
SyncService* sync_service,
identity::IdentityManager* identity_manager)
: sync_service_(sync_service),
identity_manager_(identity_manager),
sync_observer_(this),
identity_manager_observer_(this) {
// |sync_service| can be null if sync is disabled by a command line flag.
if (sync_service_) {
sync_observer_.Add(sync_service_);
}
identity_manager_observer_.Add(identity_manager_);
// Since this is created after the profile itself is created, we need to
// handle the initial state.
HandleSyncAndAccountChange();
// Check if we already know the signed in cookies. This will trigger a fetch
// if we don't have them yet.
identity::AccountsInCookieJarInfo accounts_in_cookie_jar_info =
identity_manager_->GetAccountsInCookieJar();
if (accounts_in_cookie_jar_info.accounts_are_fresh) {
OnAccountsInCookieUpdated(accounts_in_cookie_jar_info,
GoogleServiceAuthError::AuthErrorNone());
}
DVLOG(1) << "Ready to track Session.TotalDuration metrics";
}
SyncSessionDurationsMetricsRecorder::~SyncSessionDurationsMetricsRecorder() {
DCHECK(!total_session_timer_) << "Missing a call to OnSessionEnded().";
sync_observer_.RemoveAll();
identity_manager_observer_.RemoveAll();
}
void SyncSessionDurationsMetricsRecorder::OnSessionStarted(
base::TimeTicks session_start) {
DVLOG(1) << "Session start";
total_session_timer_ = std::make_unique<base::ElapsedTimer>();
signin_session_timer_ = std::make_unique<base::ElapsedTimer>();
sync_account_session_timer_ = std::make_unique<base::ElapsedTimer>();
}
void SyncSessionDurationsMetricsRecorder::OnSessionEnded(
base::TimeDelta session_length) {
DVLOG(1) << "Session end";
if (!total_session_timer_) {
// If there was no active session, just ignore this call.
return;
}
base::TimeDelta total_session_time = total_session_timer_->Elapsed();
base::TimeDelta signin_session_time = signin_session_timer_->Elapsed();
base::TimeDelta sync_account_session_time_ =
sync_account_session_timer_->Elapsed();
total_session_timer_.reset();
signin_session_timer_.reset();
sync_account_session_timer_.reset();
base::TimeDelta total_inactivity_time = total_session_time - session_length;
LogSigninDuration(
SubtractInactiveTime(signin_session_time, total_inactivity_time));
LogSyncAndAccountDuration(
SubtractInactiveTime(sync_account_session_time_, total_inactivity_time));
}
void SyncSessionDurationsMetricsRecorder::OnAccountsInCookieUpdated(
const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
const GoogleServiceAuthError& error) {
DVLOG(1) << "Cookie state change. accounts: "
<< accounts_in_cookie_jar_info.signed_in_accounts.size()
<< " fresh: " << accounts_in_cookie_jar_info.accounts_are_fresh
<< " err: " << error.ToString();
if (error.state() != GoogleServiceAuthError::NONE) {
// Return early if there's an error. This should only happen if there's an
// actual error getting the account list. If there are any auth errors with
// the tokens, those accounts will be moved to signed_out_accounts instead.
return;
}
DCHECK(accounts_in_cookie_jar_info.accounts_are_fresh);
if (accounts_in_cookie_jar_info.signed_in_accounts.empty()) {
// No signed in account.
if (signin_status_ == FeatureState::ON && signin_session_timer_) {
LogSigninDuration(signin_session_timer_->Elapsed());
signin_session_timer_ = std::make_unique<base::ElapsedTimer>();
}
signin_status_ = FeatureState::OFF;
} else {
// There is a signed in account.
if (signin_status_ == FeatureState::OFF && signin_session_timer_) {
LogSigninDuration(signin_session_timer_->Elapsed());
signin_session_timer_ = std::make_unique<base::ElapsedTimer>();
}
signin_status_ = FeatureState::ON;
}
}
void SyncSessionDurationsMetricsRecorder::OnStateChanged(SyncService* sync) {
DVLOG(1) << "Sync state change";
HandleSyncAndAccountChange();
}
void SyncSessionDurationsMetricsRecorder::OnRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info) {
DVLOG(1) << __func__;
HandleSyncAndAccountChange();
}
void SyncSessionDurationsMetricsRecorder::OnRefreshTokenRemovedForAccount(
const std::string& account_id) {
DVLOG(1) << __func__;
HandleSyncAndAccountChange();
}
void SyncSessionDurationsMetricsRecorder::OnRefreshTokensLoaded() {
DVLOG(1) << __func__;
HandleSyncAndAccountChange();
}
void SyncSessionDurationsMetricsRecorder::
OnErrorStateOfRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info,
const GoogleServiceAuthError& error) {
DVLOG(1) << __func__;
HandleSyncAndAccountChange();
}
bool SyncSessionDurationsMetricsRecorder::ShouldLogUpdate(
FeatureState new_sync_status,
FeatureState new_account_status) {
bool status_change = (new_sync_status != sync_status_ ||
new_account_status != account_status_);
bool was_unknown = sync_status_ == FeatureState::UNKNOWN ||
account_status_ == FeatureState::UNKNOWN;
return sync_account_session_timer_ && status_change && !was_unknown;
}
void SyncSessionDurationsMetricsRecorder::UpdateSyncAndAccountStatus(
FeatureState new_sync_status,
FeatureState new_account_status) {
if (ShouldLogUpdate(new_sync_status, new_account_status)) {
LogSyncAndAccountDuration(sync_account_session_timer_->Elapsed());
sync_account_session_timer_ = std::make_unique<base::ElapsedTimer>();
}
sync_status_ = new_sync_status;
account_status_ = new_account_status;
}
void SyncSessionDurationsMetricsRecorder::HandleSyncAndAccountChange() {
if (!sync_service_ || !sync_service_->CanSyncFeatureStart()) {
// Only the account status needs to be updated when sync is off.
UpdateSyncAndAccountStatus(FeatureState::OFF, DetermineAccountStatus());
return;
}
// Sync has potential to turn on, or get into account error state.
if (sync_service_->GetAuthError().state() ==
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) {
// Sync is enabled, but we have an account issue.
UpdateSyncAndAccountStatus(FeatureState::ON, FeatureState::OFF);
} else if (sync_service_->IsSyncFeatureActive() &&
sync_service_->GetLastCycleSnapshot().is_initialized()) {
// Sync is on and running, we must have an account too.
UpdateSyncAndAccountStatus(FeatureState::ON, FeatureState::ON);
} else {
// We don't know yet if sync is going to work.
// At least update the account status, so that if we never learn what the
// sync state is, we know the signin state.
//
// TODO(msarda): The current code uses the account status for all accounts
// (i.e. it is not scoped to the sync account). Figure out whether this
// should be changed to only capture the status of the sync account when
// the user has opted in to sync.
account_status_ = DetermineAccountStatus();
}
}
void SyncSessionDurationsMetricsRecorder::LogSigninDuration(
base::TimeDelta session_length) {
switch (signin_status_) {
case FeatureState::ON:
DVLOG(1) << "Logging Session.TotalDuration.WithAccount of "
<< session_length;
UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration.WithAccount",
session_length);
break;
case FeatureState::OFF:
// Since the feature wasn't working for the user if we didn't know its
// state, log the status as off.
case FeatureState::UNKNOWN:
DVLOG(1) << "Logging Session.TotalDuration.WithoutAccount of "
<< session_length;
UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration.WithoutAccount",
session_length);
}
}
void SyncSessionDurationsMetricsRecorder::LogSyncAndAccountDuration(
base::TimeDelta session_length) {
if (sync_status_ == FeatureState::ON) {
if (account_status_ == FeatureState::ON) {
DVLOG(1) << "Logging Session.TotalDuration.OptedInToSyncWithAccount of "
<< session_length;
UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration.OptedInToSyncWithAccount",
session_length);
} else {
DVLOG(1)
<< "Logging Session.TotalDuration.OptedInToSyncWithoutAccount of "
<< session_length;
UMA_HISTOGRAM_LONG_TIMES(
"Session.TotalDuration.OptedInToSyncWithoutAccount", session_length);
}
} else {
if (account_status_ == FeatureState::ON) {
DVLOG(1)
<< "Logging Session.TotalDuration.NotOptedInToSyncWithAccount of "
<< session_length;
UMA_HISTOGRAM_LONG_TIMES(
"Session.TotalDuration.NotOptedInToSyncWithAccount", session_length);
} else {
DVLOG(1)
<< "Logging Session.TotalDuration.NotOptedInToSyncWithoutAccount of "
<< session_length;
UMA_HISTOGRAM_LONG_TIMES(
"Session.TotalDuration.NotOptedInToSyncWithoutAccount",
session_length);
}
}
}
SyncSessionDurationsMetricsRecorder::FeatureState
SyncSessionDurationsMetricsRecorder::DetermineAccountStatus() const {
for (const auto& account :
identity_manager_->GetAccountsWithRefreshTokens()) {
if (!identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
account.account_id)) {
return SyncSessionDurationsMetricsRecorder::FeatureState::ON;
}
}
return SyncSessionDurationsMetricsRecorder::FeatureState::OFF;
}
} // namespace syncer