1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/sync/driver/sync_session_durations_metrics_recorder.h"
6 
7 #include "base/metrics/histogram_macros.h"
8 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
9 #include "components/sync/engine/cycle/sync_cycle_snapshot.h"
10 
11 namespace syncer {
12 
13 namespace {
SubtractInactiveTime(base::TimeDelta total_length,base::TimeDelta inactive_time)14 base::TimeDelta SubtractInactiveTime(base::TimeDelta total_length,
15                                      base::TimeDelta inactive_time) {
16   // Substract any time the user was inactive from our session length. If this
17   // ends up giving the session negative length, which can happen if the feature
18   // state changed after the user became inactive, log the length as 0.
19   base::TimeDelta session_length = total_length - inactive_time;
20   if (session_length < base::TimeDelta()) {
21     session_length = base::TimeDelta();
22   }
23   return session_length;
24 }
25 }  // namespace
26 
SyncSessionDurationsMetricsRecorder(SyncService * sync_service,signin::IdentityManager * identity_manager)27 SyncSessionDurationsMetricsRecorder::SyncSessionDurationsMetricsRecorder(
28     SyncService* sync_service,
29     signin::IdentityManager* identity_manager)
30     : sync_service_(sync_service), identity_manager_(identity_manager) {
31   // |sync_service| can be null if sync is disabled by a command line flag.
32   if (sync_service_) {
33     sync_observer_.Add(sync_service_);
34   }
35   identity_manager_observer_.Add(identity_manager_);
36 
37   // Since this is created after the profile itself is created, we need to
38   // handle the initial state.
39   HandleSyncAndAccountChange();
40 
41   // Check if we already know the signed in cookies. This will trigger a fetch
42   // if we don't have them yet.
43   signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info =
44       identity_manager_->GetAccountsInCookieJar();
45   if (accounts_in_cookie_jar_info.accounts_are_fresh) {
46     OnAccountsInCookieUpdated(accounts_in_cookie_jar_info,
47                               GoogleServiceAuthError::AuthErrorNone());
48   }
49 
50   DVLOG(1) << "Ready to track Session.TotalDuration metrics";
51 }
52 
~SyncSessionDurationsMetricsRecorder()53 SyncSessionDurationsMetricsRecorder::~SyncSessionDurationsMetricsRecorder() {
54   DCHECK(!total_session_timer_) << "Missing a call to OnSessionEnded().";
55   sync_observer_.RemoveAll();
56   identity_manager_observer_.RemoveAll();
57 }
58 
OnSessionStarted(base::TimeTicks session_start)59 void SyncSessionDurationsMetricsRecorder::OnSessionStarted(
60     base::TimeTicks session_start) {
61   DVLOG(1) << "Session start";
62   total_session_timer_ = std::make_unique<base::ElapsedTimer>();
63   signin_session_timer_ = std::make_unique<base::ElapsedTimer>();
64   sync_account_session_timer_ = std::make_unique<base::ElapsedTimer>();
65 }
66 
OnSessionEnded(base::TimeDelta session_length)67 void SyncSessionDurationsMetricsRecorder::OnSessionEnded(
68     base::TimeDelta session_length) {
69   DVLOG(1) << "Session end";
70 
71   if (!total_session_timer_) {
72     // If there was no active session, just ignore this call.
73     return;
74   }
75 
76   if (session_length.is_zero()) {
77     // During Profile teardown, this method is called with a |session_length|
78     // of zero.
79     session_length = total_session_timer_->Elapsed();
80   }
81 
82   base::TimeDelta total_session_time = total_session_timer_->Elapsed();
83   base::TimeDelta signin_session_time = signin_session_timer_->Elapsed();
84   base::TimeDelta sync_account_session_time_ =
85       sync_account_session_timer_->Elapsed();
86   total_session_timer_.reset();
87   signin_session_timer_.reset();
88   sync_account_session_timer_.reset();
89 
90   base::TimeDelta total_inactivity_time = total_session_time - session_length;
91   LogSigninDuration(
92       SubtractInactiveTime(signin_session_time, total_inactivity_time));
93   LogSyncAndAccountDuration(
94       SubtractInactiveTime(sync_account_session_time_, total_inactivity_time));
95 }
96 
OnAccountsInCookieUpdated(const signin::AccountsInCookieJarInfo & accounts_in_cookie_jar_info,const GoogleServiceAuthError & error)97 void SyncSessionDurationsMetricsRecorder::OnAccountsInCookieUpdated(
98     const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
99     const GoogleServiceAuthError& error) {
100   DVLOG(1) << "Cookie state change. accounts: "
101            << accounts_in_cookie_jar_info.signed_in_accounts.size()
102            << " fresh: " << accounts_in_cookie_jar_info.accounts_are_fresh
103            << " err: " << error.ToString();
104 
105   if (error.state() != GoogleServiceAuthError::NONE) {
106     // Return early if there's an error. This should only happen if there's an
107     // actual error getting the account list. If there are any auth errors with
108     // the tokens, those accounts will be moved to signed_out_accounts instead.
109     return;
110   }
111 
112   DCHECK(accounts_in_cookie_jar_info.accounts_are_fresh);
113   if (accounts_in_cookie_jar_info.signed_in_accounts.empty()) {
114     // No signed in account.
115     if (signin_status_ == FeatureState::ON && signin_session_timer_) {
116       LogSigninDuration(signin_session_timer_->Elapsed());
117       signin_session_timer_ = std::make_unique<base::ElapsedTimer>();
118     }
119     signin_status_ = FeatureState::OFF;
120   } else {
121     // There is a signed in account.
122     if (signin_status_ == FeatureState::OFF && signin_session_timer_) {
123       LogSigninDuration(signin_session_timer_->Elapsed());
124       signin_session_timer_ = std::make_unique<base::ElapsedTimer>();
125     }
126     signin_status_ = FeatureState::ON;
127   }
128 }
129 
OnStateChanged(SyncService * sync)130 void SyncSessionDurationsMetricsRecorder::OnStateChanged(SyncService* sync) {
131   DVLOG(1) << "Sync state change";
132   HandleSyncAndAccountChange();
133 }
134 
OnRefreshTokenUpdatedForAccount(const CoreAccountInfo & account_info)135 void SyncSessionDurationsMetricsRecorder::OnRefreshTokenUpdatedForAccount(
136     const CoreAccountInfo& account_info) {
137   DVLOG(1) << __func__;
138   HandleSyncAndAccountChange();
139 }
140 
OnRefreshTokenRemovedForAccount(const CoreAccountId & account_id)141 void SyncSessionDurationsMetricsRecorder::OnRefreshTokenRemovedForAccount(
142     const CoreAccountId& account_id) {
143   DVLOG(1) << __func__;
144   HandleSyncAndAccountChange();
145 }
146 
OnRefreshTokensLoaded()147 void SyncSessionDurationsMetricsRecorder::OnRefreshTokensLoaded() {
148   DVLOG(1) << __func__;
149   HandleSyncAndAccountChange();
150 }
151 
152 void SyncSessionDurationsMetricsRecorder::
OnErrorStateOfRefreshTokenUpdatedForAccount(const CoreAccountInfo & account_info,const GoogleServiceAuthError & error)153     OnErrorStateOfRefreshTokenUpdatedForAccount(
154         const CoreAccountInfo& account_info,
155         const GoogleServiceAuthError& error) {
156   DVLOG(1) << __func__;
157   HandleSyncAndAccountChange();
158 }
159 
ShouldLogUpdate(FeatureState new_sync_status,FeatureState new_account_status)160 bool SyncSessionDurationsMetricsRecorder::ShouldLogUpdate(
161     FeatureState new_sync_status,
162     FeatureState new_account_status) {
163   bool status_change = (new_sync_status != sync_status_ ||
164                         new_account_status != account_status_);
165   bool was_unknown = sync_status_ == FeatureState::UNKNOWN ||
166                      account_status_ == FeatureState::UNKNOWN;
167   return sync_account_session_timer_ && status_change && !was_unknown;
168 }
169 
UpdateSyncAndAccountStatus(FeatureState new_sync_status,FeatureState new_account_status)170 void SyncSessionDurationsMetricsRecorder::UpdateSyncAndAccountStatus(
171     FeatureState new_sync_status,
172     FeatureState new_account_status) {
173   if (ShouldLogUpdate(new_sync_status, new_account_status)) {
174     LogSyncAndAccountDuration(sync_account_session_timer_->Elapsed());
175     sync_account_session_timer_ = std::make_unique<base::ElapsedTimer>();
176   }
177   sync_status_ = new_sync_status;
178   account_status_ = new_account_status;
179 }
180 
HandleSyncAndAccountChange()181 void SyncSessionDurationsMetricsRecorder::HandleSyncAndAccountChange() {
182   if (!sync_service_ || !sync_service_->CanSyncFeatureStart()) {
183     // Only the account status needs to be updated when sync is off.
184     UpdateSyncAndAccountStatus(FeatureState::OFF, DetermineAccountStatus());
185     return;
186   }
187 
188   // Sync has potential to turn on, or get into account error state.
189   if (sync_service_->GetAuthError().state() ==
190       GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS) {
191     // Sync is enabled, but we have an account issue.
192     UpdateSyncAndAccountStatus(FeatureState::ON, FeatureState::OFF);
193   } else if (sync_service_->IsSyncFeatureActive() &&
194              sync_service_->HasCompletedSyncCycle()) {
195     // Sync is on and running, we must have an account too.
196     UpdateSyncAndAccountStatus(FeatureState::ON, FeatureState::ON);
197   } else {
198     // We don't know yet if sync is going to work.
199     // At least update the account status, so that if we never learn what the
200     // sync state is, we know the signin state.
201     //
202     // TODO(msarda): The current code uses the account status for all accounts
203     // (i.e. it is not scoped to the sync account). Figure out whether this
204     // should be changed to only capture the status of the sync account when
205     // the user has opted in to sync.
206     account_status_ = DetermineAccountStatus();
207   }
208 }
209 
LogSigninDuration(base::TimeDelta session_length)210 void SyncSessionDurationsMetricsRecorder::LogSigninDuration(
211     base::TimeDelta session_length) {
212   switch (signin_status_) {
213     case FeatureState::ON:
214       DVLOG(1) << "Logging Session.TotalDuration.WithAccount of "
215                << session_length;
216       UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration.WithAccount",
217                                session_length);
218       break;
219     case FeatureState::OFF:
220     // Since the feature wasn't working for the user if we didn't know its
221     // state, log the status as off.
222     case FeatureState::UNKNOWN:
223       DVLOG(1) << "Logging Session.TotalDuration.WithoutAccount of "
224                << session_length;
225       UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration.WithoutAccount",
226                                session_length);
227   }
228 }
229 
LogSyncAndAccountDuration(base::TimeDelta session_length)230 void SyncSessionDurationsMetricsRecorder::LogSyncAndAccountDuration(
231     base::TimeDelta session_length) {
232   if (sync_status_ == FeatureState::ON) {
233     if (account_status_ == FeatureState::ON) {
234       DVLOG(1) << "Logging Session.TotalDuration.OptedInToSyncWithAccount of "
235                << session_length;
236       UMA_HISTOGRAM_LONG_TIMES("Session.TotalDuration.OptedInToSyncWithAccount",
237                                session_length);
238     } else {
239       DVLOG(1)
240           << "Logging Session.TotalDuration.OptedInToSyncWithoutAccount of "
241           << session_length;
242       UMA_HISTOGRAM_LONG_TIMES(
243           "Session.TotalDuration.OptedInToSyncWithoutAccount", session_length);
244     }
245   } else {
246     if (account_status_ == FeatureState::ON) {
247       DVLOG(1)
248           << "Logging Session.TotalDuration.NotOptedInToSyncWithAccount of "
249           << session_length;
250       UMA_HISTOGRAM_LONG_TIMES(
251           "Session.TotalDuration.NotOptedInToSyncWithAccount", session_length);
252     } else {
253       DVLOG(1)
254           << "Logging Session.TotalDuration.NotOptedInToSyncWithoutAccount of "
255           << session_length;
256       UMA_HISTOGRAM_LONG_TIMES(
257           "Session.TotalDuration.NotOptedInToSyncWithoutAccount",
258           session_length);
259     }
260   }
261 }
262 
263 SyncSessionDurationsMetricsRecorder::FeatureState
DetermineAccountStatus() const264 SyncSessionDurationsMetricsRecorder::DetermineAccountStatus() const {
265   for (const auto& account :
266        identity_manager_->GetAccountsWithRefreshTokens()) {
267     if (!identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
268             account.account_id)) {
269       return SyncSessionDurationsMetricsRecorder::FeatureState::ON;
270     }
271   }
272   return SyncSessionDurationsMetricsRecorder::FeatureState::OFF;
273 }
274 
275 }  // namespace syncer
276