blob: 2e1bc70b2f5f14eff05b276af240d09d09fbcea0 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/profiles/profile_activity_metrics_recorder.h"
#include <string>
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
namespace {
ProfileActivityMetricsRecorder* g_profile_activity_metrics_recorder = nullptr;
// The maximum number of profiles that are recorded. This means that all
// profiles with bucket index greater than |kMaxProfileBucket| won't be included
// in the metrics.
constexpr int kMaxProfileBucket = 100;
// Long time of inactivity that is treated as if user starts the browser anew.
constexpr base::TimeDelta kLongTimeOfInactivity = base::Minutes(30);
int GetMetricsBucketIndex(const Profile* profile) {
if (profile->IsGuestSession())
return 0;
if (!g_browser_process->profile_manager()) {
VLOG(1) << "Failed to read profile bucket index because profile manager "
"doesn't exist.";
return -1;
}
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
if (!entry) {
// This can happen if the profile is deleted.
VLOG(1) << "Failed to read profile bucket index because attributes entry "
"doesn't exist.";
return -1;
}
return entry->GetMetricsBucketIndex();
}
void RecordProfileSessionDuration(const Profile* profile,
base::TimeDelta session_length) {
if (!profile || session_length.InMinutes() <= 0)
return;
int profile_bucket = GetMetricsBucketIndex(profile);
if (0 <= profile_bucket && profile_bucket <= kMaxProfileBucket) {
base::Histogram::FactoryGet("Profile.SessionDuration.PerProfile", 0,
kMaxProfileBucket, kMaxProfileBucket + 1,
base::HistogramBase::kUmaTargetedHistogramFlag)
->AddCount(profile_bucket, session_length.InMinutes());
}
}
void RecordBrowserActivation(const Profile* profile) {
DCHECK(profile);
int profile_bucket = GetMetricsBucketIndex(profile);
if (0 <= profile_bucket && profile_bucket <= kMaxProfileBucket) {
UMA_HISTOGRAM_EXACT_LINEAR("Profile.BrowserActive.PerProfile",
profile_bucket, kMaxProfileBucket);
}
}
void RecordProfileSwitch() {
int profiles_count =
g_browser_process->profile_manager()->GetNumberOfProfiles();
UMA_HISTOGRAM_COUNTS_100("Profile.NumberOfProfilesAtProfileSwitch",
profiles_count);
}
void RecordUserAction(const Profile* profile) {
if (!profile)
return;
int profile_bucket = GetMetricsBucketIndex(profile);
if (0 <= profile_bucket && profile_bucket <= kMaxProfileBucket) {
UMA_HISTOGRAM_EXACT_LINEAR("Profile.UserAction.PerProfile", profile_bucket,
kMaxProfileBucket);
}
}
void RecordProfilesState() {
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.RecordProfilesState();
}
void RecordAccountMetrics(const Profile* profile) {
DCHECK(profile);
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
if (!entry) {
// This can happen if the profile is deleted / for guest profile.
return;
}
entry->RecordAccountMetrics();
}
} // namespace
// static
void ProfileActivityMetricsRecorder::Initialize() {
DCHECK(!g_profile_activity_metrics_recorder);
g_profile_activity_metrics_recorder = new ProfileActivityMetricsRecorder();
}
// static
void ProfileActivityMetricsRecorder::CleanupForTesting() {
DCHECK(g_profile_activity_metrics_recorder);
delete g_profile_activity_metrics_recorder;
g_profile_activity_metrics_recorder = nullptr;
}
void ProfileActivityMetricsRecorder::OnBrowserSetLastActive(Browser* browser) {
Profile* active_profile = browser->profile()->GetOriginalProfile();
RecordBrowserActivation(active_profile);
RecordAccountMetrics(active_profile);
if (running_session_profile_ != active_profile) {
// No-op, if starting a new session (|running_session_profile_| is nullptr).
RecordProfileSessionDuration(
running_session_profile_,
base::TimeTicks::Now() - running_session_start_);
running_session_profile_ = active_profile;
running_session_start_ = base::TimeTicks::Now();
profile_observation_.Reset();
profile_observation_.Observe(running_session_profile_.get());
// Record state at startup (when |last_session_end_| is 0) and whenever the
// user starts browsing after a longer time of inactivity. Do it
// asynchronously because active_time of the just activated profile is also
// updated from OnBrowserSetLastActive() in another BrowserListObserver and
// we have no guarantee if this happens before or after this function call.
if (last_session_end_.is_null() ||
(running_session_start_ - last_session_end_ > kLongTimeOfInactivity)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&RecordProfilesState));
}
}
if (last_active_profile_ != active_profile) {
if (last_active_profile_ != nullptr)
RecordProfileSwitch();
last_active_profile_ = active_profile;
}
// This browsing session is still lasting.
last_session_end_ = base::TimeTicks::Now();
}
void ProfileActivityMetricsRecorder::OnSessionEnded(
base::TimeDelta session_length,
base::TimeTicks session_end) {
// If this call is emitted after OnProfileWillBeDestroyed, return
// early. We already logged the session duration there.
if (!running_session_profile_)
return;
// |session_length| can't be used here because it was measured across all
// profiles.
RecordProfileSessionDuration(running_session_profile_,
session_end - running_session_start_);
DCHECK(
profile_observation_.IsObservingSource(running_session_profile_.get()));
profile_observation_.Reset();
running_session_profile_ = nullptr;
last_session_end_ = base::TimeTicks::Now();
}
void ProfileActivityMetricsRecorder::OnProfileWillBeDestroyed(
Profile* profile) {
DCHECK_EQ(profile, running_session_profile_);
// The profile may be deleted without an OnSessionEnded call if, for
// example, the browser shuts down.
//
// TODO(crbug.com/1096145): explore having
// DesktopSessionDurationTracker call OnSessionEnded() when the
// profile is destroyed. Remove this workaround if this is done.
DCHECK(
profile_observation_.IsObservingSource(running_session_profile_.get()));
profile_observation_.Reset();
running_session_profile_ = nullptr;
last_active_profile_ = nullptr;
last_session_end_ = base::TimeTicks::Now();
}
ProfileActivityMetricsRecorder::ProfileActivityMetricsRecorder() {
BrowserList::AddObserver(this);
metrics::DesktopSessionDurationTracker::Get()->AddObserver(this);
action_callback_ = base::BindRepeating(
&ProfileActivityMetricsRecorder::OnUserAction, base::Unretained(this));
base::AddActionCallback(action_callback_);
}
ProfileActivityMetricsRecorder::~ProfileActivityMetricsRecorder() {
BrowserList::RemoveObserver(this);
metrics::DesktopSessionDurationTracker::Get()->RemoveObserver(this);
base::RemoveActionCallback(action_callback_);
}
void ProfileActivityMetricsRecorder::OnUserAction(const std::string& action,
base::TimeTicks action_time) {
RecordUserAction(running_session_profile_);
}