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