1 // Copyright 2019 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 "chrome/browser/profiles/profile_activity_metrics_recorder.h"
6 
7 #include <string>
8 
9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/profiles/profile_attributes_entry.h"
15 #include "chrome/browser/profiles/profile_attributes_storage.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_list.h"
19 
20 namespace {
21 
22 ProfileActivityMetricsRecorder* g_profile_activity_metrics_recorder = nullptr;
23 
24 // The maximum number of profiles that are recorded. This means that all
25 // profiles with bucket index greater than |kMaxProfileBucket| won't be included
26 // in the metrics.
27 constexpr int kMaxProfileBucket = 100;
28 
29 // Long time of inactivity that is treated as if user starts the browser anew.
30 constexpr base::TimeDelta kLongTimeOfInactivity =
31     base::TimeDelta::FromMinutes(30);
32 
GetMetricsBucketIndex(const Profile * profile)33 int GetMetricsBucketIndex(const Profile* profile) {
34   if (profile->IsGuestSession() || profile->IsEphemeralGuestProfile())
35     return 0;
36 
37   ProfileAttributesEntry* entry;
38   if (!g_browser_process->profile_manager()
39            ->GetProfileAttributesStorage()
40            .GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
41     // This can happen if the profile is deleted.
42     VLOG(1) << "Failed to read profile bucket index because attributes entry "
43                "doesn't exist.";
44     return -1;
45   }
46   return entry->GetMetricsBucketIndex();
47 }
48 
RecordProfileSessionDuration(const Profile * profile,base::TimeDelta session_length)49 void RecordProfileSessionDuration(const Profile* profile,
50                                   base::TimeDelta session_length) {
51   if (!profile || session_length.InMinutes() <= 0)
52     return;
53 
54   int profile_bucket = GetMetricsBucketIndex(profile);
55 
56   if (0 <= profile_bucket && profile_bucket <= kMaxProfileBucket) {
57     base::Histogram::FactoryGet("Profile.SessionDuration.PerProfile", 0,
58                                 kMaxProfileBucket, kMaxProfileBucket + 1,
59                                 base::HistogramBase::kUmaTargetedHistogramFlag)
60         ->AddCount(profile_bucket, session_length.InMinutes());
61   }
62 }
63 
RecordBrowserActivation(const Profile * profile)64 void RecordBrowserActivation(const Profile* profile) {
65   DCHECK(profile);
66   int profile_bucket = GetMetricsBucketIndex(profile);
67 
68   if (0 <= profile_bucket && profile_bucket <= kMaxProfileBucket) {
69     UMA_HISTOGRAM_EXACT_LINEAR("Profile.BrowserActive.PerProfile",
70                                profile_bucket, kMaxProfileBucket);
71   }
72 }
73 
RecordProfileSwitch()74 void RecordProfileSwitch() {
75   int profiles_count =
76       g_browser_process->profile_manager()->GetNumberOfProfiles();
77   UMA_HISTOGRAM_COUNTS_100("Profile.NumberOfProfilesAtProfileSwitch",
78                            profiles_count);
79 }
80 
RecordUserAction(const Profile * profile)81 void RecordUserAction(const Profile* profile) {
82   if (!profile)
83     return;
84 
85   int profile_bucket = GetMetricsBucketIndex(profile);
86 
87   if (0 <= profile_bucket && profile_bucket <= kMaxProfileBucket) {
88     UMA_HISTOGRAM_EXACT_LINEAR("Profile.UserAction.PerProfile", profile_bucket,
89                                kMaxProfileBucket);
90   }
91 }
92 
RecordProfilesState()93 void RecordProfilesState() {
94   g_browser_process->profile_manager()
95       ->GetProfileAttributesStorage()
96       .RecordProfilesState();
97 }
98 
RecordAccountMetrics(const Profile * profile)99 void RecordAccountMetrics(const Profile* profile) {
100   DCHECK(profile);
101 
102   ProfileAttributesEntry* entry;
103   if (!g_browser_process->profile_manager()
104            ->GetProfileAttributesStorage()
105            .GetProfileAttributesWithPath(profile->GetPath(), &entry)) {
106     // This can happen if the profile is deleted / for guest profile.
107     return;
108   }
109 
110   entry->RecordAccountMetrics();
111 }
112 
113 }  // namespace
114 
115 // static
Initialize()116 void ProfileActivityMetricsRecorder::Initialize() {
117   DCHECK(!g_profile_activity_metrics_recorder);
118   g_profile_activity_metrics_recorder = new ProfileActivityMetricsRecorder();
119 }
120 
121 // static
CleanupForTesting()122 void ProfileActivityMetricsRecorder::CleanupForTesting() {
123   DCHECK(g_profile_activity_metrics_recorder);
124   delete g_profile_activity_metrics_recorder;
125   g_profile_activity_metrics_recorder = nullptr;
126 }
127 
OnBrowserSetLastActive(Browser * browser)128 void ProfileActivityMetricsRecorder::OnBrowserSetLastActive(Browser* browser) {
129   Profile* active_profile = browser->profile()->GetOriginalProfile();
130 
131   RecordBrowserActivation(active_profile);
132   RecordAccountMetrics(active_profile);
133 
134   if (running_session_profile_ != active_profile) {
135     // No-op, if starting a new session (|running_session_profile_| is nullptr).
136     RecordProfileSessionDuration(
137         running_session_profile_,
138         base::TimeTicks::Now() - running_session_start_);
139 
140     running_session_profile_ = active_profile;
141     running_session_start_ = base::TimeTicks::Now();
142     profile_observer_.RemoveAll();
143     profile_observer_.Add(running_session_profile_);
144 
145     // Record state at startup (when |last_session_end_| is 0) and whenever the
146     // user starts browsing after a longer time of inactivity. Do it
147     // asynchronously because active_time of the just activated profile is also
148     // updated from OnBrowserSetLastActive() in another BrowserListObserver and
149     // we have no guarantee if this happens before or after this function call.
150     if (last_session_end_.is_null() ||
151         (running_session_start_ - last_session_end_ > kLongTimeOfInactivity)) {
152       base::SequencedTaskRunnerHandle::Get()->PostTask(
153           FROM_HERE, base::BindOnce(&RecordProfilesState));
154     }
155   }
156 
157   if (last_active_profile_ != active_profile) {
158     if (last_active_profile_ != nullptr)
159       RecordProfileSwitch();
160     last_active_profile_ = active_profile;
161   }
162 
163   // This browsing session is still lasting.
164   last_session_end_ = base::TimeTicks::Now();
165 }
166 
OnSessionEnded(base::TimeDelta session_length,base::TimeTicks session_end)167 void ProfileActivityMetricsRecorder::OnSessionEnded(
168     base::TimeDelta session_length,
169     base::TimeTicks session_end) {
170   // If this call is emitted after OnProfileWillBeDestroyed, return
171   // early. We already logged the session duration there.
172   if (!running_session_profile_)
173     return;
174 
175   // |session_length| can't be used here because it was measured across all
176   // profiles.
177   RecordProfileSessionDuration(running_session_profile_,
178                                session_end - running_session_start_);
179   profile_observer_.Remove(running_session_profile_);
180   running_session_profile_ = nullptr;
181   last_session_end_ = base::TimeTicks::Now();
182 }
183 
OnProfileWillBeDestroyed(Profile * profile)184 void ProfileActivityMetricsRecorder::OnProfileWillBeDestroyed(
185     Profile* profile) {
186   DCHECK_EQ(profile, running_session_profile_);
187 
188   // The profile may be deleted without an OnSessionEnded call if, for
189   // example, the browser shuts down.
190   //
191   // TODO(crbug.com/1096145): explore having
192   // DesktopSessionDurationTracker call OnSessionEnded() when the
193   // profile is destroyed. Remove this workaround if this is done.
194   profile_observer_.Remove(running_session_profile_);
195   running_session_profile_ = nullptr;
196   last_active_profile_ = nullptr;
197   last_session_end_ = base::TimeTicks::Now();
198 }
199 
ProfileActivityMetricsRecorder()200 ProfileActivityMetricsRecorder::ProfileActivityMetricsRecorder() {
201   BrowserList::AddObserver(this);
202   metrics::DesktopSessionDurationTracker::Get()->AddObserver(this);
203   action_callback_ = base::Bind(&ProfileActivityMetricsRecorder::OnUserAction,
204                                 base::Unretained(this));
205   base::AddActionCallback(action_callback_);
206 }
207 
~ProfileActivityMetricsRecorder()208 ProfileActivityMetricsRecorder::~ProfileActivityMetricsRecorder() {
209   BrowserList::RemoveObserver(this);
210   metrics::DesktopSessionDurationTracker::Get()->RemoveObserver(this);
211   base::RemoveActionCallback(action_callback_);
212 }
213 
OnUserAction(const std::string & action,base::TimeTicks action_time)214 void ProfileActivityMetricsRecorder::OnUserAction(const std::string& action,
215                                                   base::TimeTicks action_time) {
216   RecordUserAction(running_session_profile_);
217 }
218