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