// Copyright 2022 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/memory/oom_kills_monitor.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/process/process_metrics.h"
#include "chrome/common/pref_names.h"
#include "components/metrics/daily_event.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
namespace memory {
const char OOMKillsMonitor::kOOMKillsCountHistogramName[] =
"Memory.OOMKills.Count";
const char OOMKillsMonitor::kOOMKillsDailyHistogramName[] =
"Memory.OOMKills.Daily";
namespace {
void UmaHistogramOOMKills(int oom_kills) {
base::UmaHistogramCustomCounts(OOMKillsMonitor::kOOMKillsCountHistogramName,
oom_kills, 1, 1000, 1001);
}
// The interval at which the DailyEvent::CheckInterval function should be
// called.
constexpr base::TimeDelta kDailyEventIntervalTimeDelta = base::Minutes(30);
// The name of the histogram used to report that the OOM Kills daily event
// happened.
const char kOOMKillsDailyEventHistogramName[] =
"Memory.OOMKills.DailyEventInterval";
} // namespace
// This shim class is needed since metrics::DailyEvent requires taking ownership
// of its observers. It just forwards events to OOMKillsMonitor.
class OOMKillsMonitor::DailyEventObserver
: public ::metrics::DailyEvent::Observer {
public:
explicit DailyEventObserver(OOMKillsMonitor* reporter)
: reporter_(reporter) {}
DailyEventObserver(const DailyEventObserver&) = delete;
DailyEventObserver& operator=(const DailyEventObserver&) = delete;
~DailyEventObserver() override = default;
void OnDailyEvent(::metrics::DailyEvent::IntervalType type) override {
reporter_->ReportDailyMetrics(type);
}
private:
raw_ptr<OOMKillsMonitor> reporter_;
};
OOMKillsMonitor::OOMKillsMonitor() = default;
OOMKillsMonitor::~OOMKillsMonitor() = default;
// static
OOMKillsMonitor& OOMKillsMonitor::GetInstance() {
static base::NoDestructor<OOMKillsMonitor> instance;
return *instance;
}
void OOMKillsMonitor::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterIntegerPref(::prefs::kOOMKillsDailyCount, 0);
::metrics::DailyEvent::RegisterPref(registry, ::prefs::kOOMKillsDailySample);
}
void OOMKillsMonitor::Initialize(PrefService* pref_service) {
VLOG(2) << "Starting OOM kills monitor from thread "
<< base::PlatformThread::CurrentId();
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (monitoring_started_) {
NOTREACHED_IN_MIGRATION()
<< "OOM kiils monitor should only be initialized once";
return;
}
monitoring_started_ = true;
// Insert a zero kill record at the beginning for easy comparison to those
// with non-zero kill sessions.
UmaHistogramOOMKills(0);
base::VmStatInfo vmstat;
if (base::GetVmStatInfo(&vmstat)) {
last_oom_kills_count_ = vmstat.oom_kill;
} else {
last_oom_kills_count_ = 0;
}
checking_timer_.Start(FROM_HERE, base::Minutes(1),
base::BindRepeating(&OOMKillsMonitor::CheckOOMKill,
base::Unretained(this)));
pref_service_ = pref_service;
oom_kills_daily_count_ =
pref_service_->GetInteger(::prefs::kOOMKillsDailyCount);
daily_event_ = std::make_unique<::metrics::DailyEvent>(
pref_service, ::prefs::kOOMKillsDailySample,
kOOMKillsDailyEventHistogramName);
daily_event_->AddObserver(std::make_unique<DailyEventObserver>(this));
daily_event_->CheckInterval();
daily_event_timer_.Start(FROM_HERE, kDailyEventIntervalTimeDelta,
daily_event_.get(),
&::metrics::DailyEvent::CheckInterval);
}
// Both host and guest(ARCVM) oom kills are logged to the same histogram
// Memory.OOMKills.Count.
//
// TODO(vovoy): Log ARCVM oom kills to a new histogram.
void OOMKillsMonitor::LogArcOOMKill(unsigned long current_oom_kills) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(monitoring_started_);
unsigned long oom_kills_delta = current_oom_kills - last_arc_oom_kills_count_;
if (oom_kills_delta == 0)
return;
VLOG(1) << "ARC_OOM_KILLS " << oom_kills_delta << " times";
ReportOOMKills(oom_kills_delta);
last_arc_oom_kills_count_ = current_oom_kills;
}
void OOMKillsMonitor::StopTimersForTesting() {
checking_timer_.Stop();
daily_event_timer_.Stop();
}
void OOMKillsMonitor::TriggerDailyEventForTesting() {
ReportDailyMetrics(::metrics::DailyEvent::IntervalType::DAY_ELAPSED);
}
void OOMKillsMonitor::CheckOOMKill() {
base::VmStatInfo vmstat;
if (base::GetVmStatInfo(&vmstat)) {
CheckOOMKillImpl(vmstat.oom_kill);
}
}
void OOMKillsMonitor::CheckOOMKillImpl(unsigned long current_oom_kills) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(monitoring_started_);
unsigned long oom_kills_delta = current_oom_kills - last_oom_kills_count_;
if (oom_kills_delta == 0)
return;
VLOG(1) << "OOM_KILLS " << oom_kills_delta << " times";
ReportOOMKills(oom_kills_delta);
last_oom_kills_count_ = current_oom_kills;
}
void OOMKillsMonitor::ReportOOMKills(unsigned long oom_kills_delta) {
for (size_t i = 0; i < oom_kills_delta; ++i) {
++oom_kills_count_;
// Report the cumulative count of killed process. For example if there are
// 3 processes killed, it would report 1 for the first kill, 2 for the
// second kill, then 3 for the final kill.
UmaHistogramOOMKills(oom_kills_count_);
}
oom_kills_daily_count_ += oom_kills_delta;
pref_service_->SetInteger(::prefs::kOOMKillsDailyCount,
oom_kills_daily_count_);
}
void OOMKillsMonitor::ReportDailyMetrics(
::metrics::DailyEvent::IntervalType type) {
if (type == ::metrics::DailyEvent::IntervalType::DAY_ELAPSED) {
base::UmaHistogramCounts10000(kOOMKillsDailyHistogramName,
oom_kills_daily_count_);
}
// There are 3 interval types: FIRST_RUN, DAY_ELAPSED, CLOCK_CHANGED. The
// counter should be reset in all 3 cases.
oom_kills_daily_count_ = 0;
pref_service_->SetInteger(::prefs::kOOMKillsDailyCount, 0);
}
} // namespace memory