mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-01 01:36:09 +03:00
289 lines
9.9 KiB
C++
289 lines
9.9 KiB
C++
// Copyright 2017 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/trace_event/memory_peak_detector.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "base/bind.h"
|
|
#include "base/logging.h"
|
|
#include "base/sys_info.h"
|
|
#include "base/threading/sequenced_task_runner_handle.h"
|
|
#include "base/time/time.h"
|
|
#include "base/trace_event/memory_dump_manager.h"
|
|
#include "base/trace_event/memory_dump_provider_info.h"
|
|
#include "base/trace_event/trace_event.h"
|
|
#include "build/build_config.h"
|
|
|
|
namespace base {
|
|
namespace trace_event {
|
|
|
|
// static
|
|
MemoryPeakDetector* MemoryPeakDetector::GetInstance() {
|
|
static MemoryPeakDetector* instance = new MemoryPeakDetector();
|
|
return instance;
|
|
}
|
|
|
|
MemoryPeakDetector::MemoryPeakDetector()
|
|
: generation_(0),
|
|
state_(NOT_INITIALIZED),
|
|
poll_tasks_count_for_testing_(0) {}
|
|
|
|
MemoryPeakDetector::~MemoryPeakDetector() {
|
|
// This is hit only in tests, in which case the test is expected to TearDown()
|
|
// cleanly and not leave the peak detector running.
|
|
DCHECK_EQ(NOT_INITIALIZED, state_);
|
|
}
|
|
|
|
void MemoryPeakDetector::Setup(
|
|
const GetDumpProvidersFunction& get_dump_providers_function,
|
|
const scoped_refptr<SequencedTaskRunner>& task_runner,
|
|
const OnPeakDetectedCallback& on_peak_detected_callback) {
|
|
DCHECK(!get_dump_providers_function.is_null());
|
|
DCHECK(task_runner);
|
|
DCHECK(!on_peak_detected_callback.is_null());
|
|
DCHECK(state_ == NOT_INITIALIZED || state_ == DISABLED);
|
|
DCHECK(dump_providers_.empty());
|
|
get_dump_providers_function_ = get_dump_providers_function;
|
|
task_runner_ = task_runner;
|
|
on_peak_detected_callback_ = on_peak_detected_callback;
|
|
state_ = DISABLED;
|
|
config_ = {};
|
|
ResetPollHistory();
|
|
|
|
static_threshold_bytes_ = 0;
|
|
#if !defined(OS_NACL)
|
|
// Set threshold to 1% of total system memory.
|
|
static_threshold_bytes_ =
|
|
static_cast<uint64_t>(SysInfo::AmountOfPhysicalMemory()) / 100;
|
|
#endif
|
|
// Fallback, mostly for test environments where AmountOfPhysicalMemory() is
|
|
// broken.
|
|
static_threshold_bytes_ =
|
|
std::max(static_threshold_bytes_, static_cast<uint64_t>(5 * 1024 * 1024));
|
|
}
|
|
|
|
void MemoryPeakDetector::TearDown() {
|
|
if (task_runner_) {
|
|
task_runner_->PostTask(
|
|
FROM_HERE,
|
|
BindOnce(&MemoryPeakDetector::TearDownInternal, Unretained(this)));
|
|
}
|
|
task_runner_ = nullptr;
|
|
}
|
|
|
|
void MemoryPeakDetector::Start(MemoryPeakDetector::Config config) {
|
|
if (!config.polling_interval_ms) {
|
|
NOTREACHED();
|
|
return;
|
|
}
|
|
task_runner_->PostTask(FROM_HERE, BindOnce(&MemoryPeakDetector::StartInternal,
|
|
Unretained(this), config));
|
|
}
|
|
|
|
void MemoryPeakDetector::Stop() {
|
|
task_runner_->PostTask(
|
|
FROM_HERE, BindOnce(&MemoryPeakDetector::StopInternal, Unretained(this)));
|
|
}
|
|
|
|
void MemoryPeakDetector::Throttle() {
|
|
if (!task_runner_)
|
|
return; // Can be called before Setup().
|
|
task_runner_->PostTask(
|
|
FROM_HERE, BindOnce(&MemoryPeakDetector::ResetPollHistory,
|
|
Unretained(this), true /* keep_last_sample */));
|
|
}
|
|
|
|
void MemoryPeakDetector::NotifyMemoryDumpProvidersChanged() {
|
|
if (!task_runner_)
|
|
return; // Can be called before Setup().
|
|
task_runner_->PostTask(
|
|
FROM_HERE,
|
|
BindOnce(&MemoryPeakDetector::ReloadDumpProvidersAndStartPollingIfNeeded,
|
|
Unretained(this)));
|
|
}
|
|
|
|
void MemoryPeakDetector::StartInternal(MemoryPeakDetector::Config config) {
|
|
DCHECK_EQ(DISABLED, state_);
|
|
state_ = ENABLED;
|
|
config_ = config;
|
|
ResetPollHistory();
|
|
|
|
// If there are any dump providers available,
|
|
// NotifyMemoryDumpProvidersChanged will fetch them and start the polling.
|
|
// Otherwise this will remain in the ENABLED state and the actual polling
|
|
// will start on the next call to
|
|
// ReloadDumpProvidersAndStartPollingIfNeeded().
|
|
// Depending on the sandbox model, it is possible that no polling-capable
|
|
// dump providers will be ever available.
|
|
ReloadDumpProvidersAndStartPollingIfNeeded();
|
|
}
|
|
|
|
void MemoryPeakDetector::StopInternal() {
|
|
DCHECK_NE(NOT_INITIALIZED, state_);
|
|
state_ = DISABLED;
|
|
++generation_;
|
|
for (const scoped_refptr<MemoryDumpProviderInfo>& mdp_info : dump_providers_)
|
|
mdp_info->dump_provider->SuspendFastMemoryPolling();
|
|
dump_providers_.clear();
|
|
}
|
|
|
|
void MemoryPeakDetector::TearDownInternal() {
|
|
StopInternal();
|
|
get_dump_providers_function_.Reset();
|
|
on_peak_detected_callback_.Reset();
|
|
state_ = NOT_INITIALIZED;
|
|
}
|
|
|
|
void MemoryPeakDetector::ReloadDumpProvidersAndStartPollingIfNeeded() {
|
|
if (state_ == DISABLED || state_ == NOT_INITIALIZED)
|
|
return; // Start() will re-fetch the MDP list later.
|
|
|
|
DCHECK((state_ == RUNNING && !dump_providers_.empty()) ||
|
|
(state_ == ENABLED && dump_providers_.empty()));
|
|
|
|
dump_providers_.clear();
|
|
|
|
// This is really MemoryDumpManager::GetDumpProvidersForPolling, % testing.
|
|
get_dump_providers_function_.Run(&dump_providers_);
|
|
|
|
if (state_ == ENABLED && !dump_providers_.empty()) {
|
|
// It's now time to start polling for realz.
|
|
state_ = RUNNING;
|
|
task_runner_->PostTask(
|
|
FROM_HERE, BindOnce(&MemoryPeakDetector::PollMemoryAndDetectPeak,
|
|
Unretained(this), ++generation_));
|
|
} else if (state_ == RUNNING && dump_providers_.empty()) {
|
|
// Will cause the next PollMemoryAndDetectPeak() task to early return.
|
|
state_ = ENABLED;
|
|
++generation_;
|
|
}
|
|
}
|
|
|
|
void MemoryPeakDetector::PollMemoryAndDetectPeak(uint32_t expected_generation) {
|
|
if (state_ != RUNNING || generation_ != expected_generation)
|
|
return;
|
|
|
|
// We should never end up in a situation where state_ == RUNNING but all dump
|
|
// providers are gone.
|
|
DCHECK(!dump_providers_.empty());
|
|
|
|
poll_tasks_count_for_testing_++;
|
|
uint64_t polled_mem_bytes = 0;
|
|
for (const scoped_refptr<MemoryDumpProviderInfo>& mdp_info :
|
|
dump_providers_) {
|
|
DCHECK(mdp_info->options.is_fast_polling_supported);
|
|
uint64_t value = 0;
|
|
mdp_info->dump_provider->PollFastMemoryTotal(&value);
|
|
polled_mem_bytes += value;
|
|
}
|
|
if (config_.enable_verbose_poll_tracing) {
|
|
TRACE_COUNTER1(MemoryDumpManager::kTraceCategory, "PolledMemoryMB",
|
|
polled_mem_bytes / 1024 / 1024);
|
|
}
|
|
|
|
// Peak detection logic. Design doc: https://goo.gl/0kOU4A .
|
|
bool is_peak = false;
|
|
if (skip_polls_ > 0) {
|
|
skip_polls_--;
|
|
} else if (last_dump_memory_total_ == 0) {
|
|
last_dump_memory_total_ = polled_mem_bytes;
|
|
} else if (polled_mem_bytes > 0) {
|
|
int64_t diff_from_last_dump = polled_mem_bytes - last_dump_memory_total_;
|
|
|
|
DCHECK_GT(static_threshold_bytes_, 0u);
|
|
is_peak =
|
|
diff_from_last_dump > static_cast<int64_t>(static_threshold_bytes_);
|
|
|
|
if (!is_peak)
|
|
is_peak = DetectPeakUsingSlidingWindowStddev(polled_mem_bytes);
|
|
}
|
|
|
|
DCHECK_GT(config_.polling_interval_ms, 0u);
|
|
SequencedTaskRunnerHandle::Get()->PostDelayedTask(
|
|
FROM_HERE,
|
|
BindOnce(&MemoryPeakDetector::PollMemoryAndDetectPeak, Unretained(this),
|
|
expected_generation),
|
|
TimeDelta::FromMilliseconds(config_.polling_interval_ms));
|
|
|
|
if (!is_peak)
|
|
return;
|
|
TRACE_EVENT_INSTANT1(MemoryDumpManager::kTraceCategory,
|
|
"Peak memory detected", TRACE_EVENT_SCOPE_PROCESS,
|
|
"PolledMemoryMB", polled_mem_bytes / 1024 / 1024);
|
|
ResetPollHistory(true /* keep_last_sample */);
|
|
last_dump_memory_total_ = polled_mem_bytes;
|
|
on_peak_detected_callback_.Run();
|
|
}
|
|
|
|
bool MemoryPeakDetector::DetectPeakUsingSlidingWindowStddev(
|
|
uint64_t polled_mem_bytes) {
|
|
DCHECK(polled_mem_bytes);
|
|
samples_bytes_[samples_index_] = polled_mem_bytes;
|
|
samples_index_ = (samples_index_ + 1) % kSlidingWindowNumSamples;
|
|
float mean = 0;
|
|
for (uint32_t i = 0; i < kSlidingWindowNumSamples; ++i) {
|
|
if (samples_bytes_[i] == 0)
|
|
return false; // Not enough samples to detect peaks.
|
|
mean += samples_bytes_[i];
|
|
}
|
|
mean /= kSlidingWindowNumSamples;
|
|
float variance = 0;
|
|
for (uint32_t i = 0; i < kSlidingWindowNumSamples; ++i) {
|
|
const float deviation = samples_bytes_[i] - mean;
|
|
variance += deviation * deviation;
|
|
}
|
|
variance /= kSlidingWindowNumSamples;
|
|
|
|
// If stddev is less than 0.2% then we consider that the process is inactive.
|
|
if (variance < (mean / 500) * (mean / 500))
|
|
return false;
|
|
|
|
// (mean + 3.69 * stddev) corresponds to a value that is higher than current
|
|
// sample with 99.99% probability.
|
|
const float cur_sample_deviation = polled_mem_bytes - mean;
|
|
return cur_sample_deviation * cur_sample_deviation > (3.69 * 3.69 * variance);
|
|
}
|
|
|
|
void MemoryPeakDetector::ResetPollHistory(bool keep_last_sample) {
|
|
// TODO(primiano,ssid): this logic should probably be revisited. In the case
|
|
// of Android, the browser process sees the total of all processes memory in
|
|
// the same peak detector instance. Perhaps the best thing to do here is to
|
|
// keep the window of samples around and just bump the skip_polls_.
|
|
last_dump_memory_total_ = 0;
|
|
if (keep_last_sample) {
|
|
const uint32_t prev_index =
|
|
samples_index_ > 0 ? samples_index_ - 1 : kSlidingWindowNumSamples - 1;
|
|
last_dump_memory_total_ = samples_bytes_[prev_index];
|
|
}
|
|
memset(samples_bytes_, 0, sizeof(samples_bytes_));
|
|
samples_index_ = 0;
|
|
skip_polls_ = 0;
|
|
if (config_.polling_interval_ms > 0) {
|
|
skip_polls_ =
|
|
(config_.min_time_between_peaks_ms + config_.polling_interval_ms - 1) /
|
|
config_.polling_interval_ms;
|
|
}
|
|
}
|
|
|
|
void MemoryPeakDetector::SetStaticThresholdForTesting(
|
|
uint64_t static_threshold_bytes) {
|
|
DCHECK_EQ(DISABLED, state_);
|
|
static_threshold_bytes_ = static_threshold_bytes;
|
|
}
|
|
|
|
MemoryPeakDetector::MemoryPeakDetector::Config::Config()
|
|
: Config(0, 0, false) {}
|
|
|
|
MemoryPeakDetector::MemoryPeakDetector::Config::Config(
|
|
uint32_t polling_interval_ms,
|
|
uint32_t min_time_between_peaks_ms,
|
|
bool enable_verbose_poll_tracing)
|
|
: polling_interval_ms(polling_interval_ms),
|
|
min_time_between_peaks_ms(min_time_between_peaks_ms),
|
|
enable_verbose_poll_tracing(enable_verbose_poll_tracing) {}
|
|
|
|
} // namespace trace_event
|
|
} // namespace base
|