blob: 442257147f21b43eb2973acadd25872041b751d8 [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/metrics/perf/heap_collector.h"
#include <memory>
#include <string>
#include <utility>
#include "base/allocator/allocator_extension.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/process/process_handle.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"
namespace metrics {
// Feature for controlling the heap collection parameters.
const base::Feature kCWPHeapCollection{"CWPHeapCollection",
base::FEATURE_DISABLED_BY_DEFAULT};
namespace {
// Name of the heap collector. It is appended to the UMA metric names for
// reporting collection and upload status.
const char kHeapCollectorName[] = "Heap";
// The approximate gap in bytes between sampling actions. Heap allocations are
// sampled using a geometric distribution with the specified mean.
const size_t kHeapSamplingIntervalBytes = 1 * 1024 * 1024;
// Feature parameters that control the behavior of the heap collector.
constexpr base::FeatureParam<int> kSamplingIntervalBytes{
&kCWPHeapCollection, "SamplingIntervalBytes", kHeapSamplingIntervalBytes};
constexpr base::FeatureParam<int> kPeriodicCollectionIntervalMs{
&kCWPHeapCollection, "PeriodicCollectionIntervalMs",
3 * 3600 * 1000}; // 3h
constexpr base::FeatureParam<int> kResumeFromSuspendSamplingFactor{
&kCWPHeapCollection, "ResumeFromSuspend::SamplingFactor", 10};
constexpr base::FeatureParam<int> kResumeFromSuspendMaxDelaySec{
&kCWPHeapCollection, "ResumeFromSuspend::MaxDelaySec", 5};
constexpr base::FeatureParam<int> kRestoreSessionSamplingFactor{
&kCWPHeapCollection, "RestoreSession::SamplingFactor", 10};
constexpr base::FeatureParam<int> kRestoreSessionMaxDelaySec{
&kCWPHeapCollection, "RestoreSession::MaxDelaySec", 10};
// Limit the total size of protobufs that can be cached, so they don't take up
// too much memory. If the size of cached protobufs exceeds this value, stop
// collecting further perf data. The current value is 2 MB.
const size_t kCachedHeapDataProtobufSizeThreshold = 2 * 1024 * 1024;
// Location of quipper on ChromeOS.
const char kQuipperLocation[] = "/usr/bin/quipper";
// Quipper arguments for passing in a profile and the process PID.
const char kQuipperHeapProfile[] = "input_heap_profile";
const char kQuipperProcessPid[] = "pid";
// Deletes the temp file when the object goes out of scope.
class FileDeleter {
public:
explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
~FileDeleter();
private:
const base::FilePath temp_dir_;
DISALLOW_COPY_AND_ASSIGN(FileDeleter);
};
FileDeleter::~FileDeleter() {
base::PostTaskWithTraits(FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(base::IgnoreResult(&base::DeleteFile),
std::move(temp_dir_), false));
}
void SetHeapSamplingPeriod(size_t sampling_period) {
bool res = base::allocator::SetNumericProperty(
"tcmalloc.sampling_period_bytes", sampling_period);
DCHECK(res);
}
} // namespace
HeapCollector::HeapCollector()
: MetricCollector(kHeapCollectorName),
sampling_period_bytes_(kHeapSamplingIntervalBytes) {
BrowserList::AddObserver(this);
}
HeapCollector::~HeapCollector() {
// Disable heap sampling when the collector exits.
SetHeapSamplingPeriod(0);
BrowserList::RemoveObserver(this);
}
void HeapCollector::Init() {
if (base::FeatureList::IsEnabled(kCWPHeapCollection))
SetCollectionParamsFromFeatureParams();
size_t sampling_period = 0;
// Enable sampling if no incognito session is active.
if (!BrowserList::IsIncognitoSessionActive()) {
sampling_period = sampling_period_bytes_;
}
SetHeapSamplingPeriod(sampling_period);
MetricCollector::Init();
}
void HeapCollector::OnBrowserAdded(Browser* browser) {
// Pause heap sampling when an incognito session is opened.
if (browser->profile()->IsOffTheRecord()) {
SetHeapSamplingPeriod(0);
}
}
void HeapCollector::OnBrowserRemoved(Browser* browser) {
// Resume heap sampling if no incognito sessions are active.
if (!BrowserList::IsIncognitoSessionActive()) {
SetHeapSamplingPeriod(sampling_period_bytes_);
}
}
void HeapCollector::SetCollectionParamsFromFeatureParams() {
sampling_period_bytes_ = kSamplingIntervalBytes.Get();
collection_params_.periodic_interval =
base::TimeDelta::FromMilliseconds(kPeriodicCollectionIntervalMs.Get());
collection_params_.resume_from_suspend.sampling_factor =
kResumeFromSuspendSamplingFactor.Get();
collection_params_.resume_from_suspend.max_collection_delay =
base::TimeDelta::FromSeconds(kResumeFromSuspendMaxDelaySec.Get());
collection_params_.restore_session.sampling_factor =
kRestoreSessionSamplingFactor.Get();
collection_params_.restore_session.max_collection_delay =
base::TimeDelta::FromSeconds(kRestoreSessionMaxDelaySec.Get());
}
bool HeapCollector::ShouldCollect() const {
// Do not collect further data if we've already collected a substantial amount
// of data, as indicated by |kCachedHeapDataProtobufSizeThreshold|.
if (cached_profile_data_size() >= kCachedHeapDataProtobufSizeThreshold) {
AddToUmaHistogram(CollectionAttemptStatus::NOT_READY_TO_COLLECT);
return false;
}
return true;
}
void HeapCollector::CollectProfile(
std::unique_ptr<SampledProfile> sampled_profile) {
base::Optional<base::FilePath> temp_file = DumpProfileToTempFile();
if (!temp_file)
return;
base::CommandLine quipper = MakeQuipperCommand(temp_file.value());
ParseAndSaveProfile(quipper, temp_file.value(), std::move(sampled_profile));
}
base::Optional<base::FilePath> HeapCollector::DumpProfileToTempFile() {
base::FilePath temp_path;
if (!base::CreateTemporaryFile(&temp_path)) {
AddToUmaHistogram(CollectionAttemptStatus::UNABLE_TO_COLLECT);
return base::nullopt;
}
std::string writer;
base::allocator::GetHeapSample(&writer);
base::File temp(temp_path, base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_READ |
base::File::FLAG_WRITE);
DCHECK(temp.created());
DCHECK(temp.IsValid());
int res = temp.WriteAtCurrentPos(writer.c_str(), writer.length());
DCHECK_EQ(res, static_cast<int>(writer.length()));
temp.Close();
return base::make_optional<base::FilePath>(temp_path);
}
base::CommandLine HeapCollector::MakeQuipperCommand(
const base::FilePath& profile_path) {
base::CommandLine quipper{base::FilePath(kQuipperLocation)};
quipper.AppendSwitchPath(kQuipperHeapProfile, profile_path);
quipper.AppendSwitchASCII(kQuipperProcessPid,
std::to_string(base::GetCurrentProcId()));
return quipper;
}
void HeapCollector::ParseAndSaveProfile(
const base::CommandLine& parser,
const base::FilePath& profile_path,
std::unique_ptr<SampledProfile> sampled_profile) {
// We may exit due to parsing errors, so use a FileDeleter to remove the
// temporary profile data on all paths.
FileDeleter file_deleter(profile_path);
// Run the parser command on the profile file.
std::string output;
if (!base::GetAppOutput(parser, &output)) {
AddToUmaHistogram(CollectionAttemptStatus::ILLEGAL_DATA_RETURNED);
return;
}
SaveSerializedPerfProto(std::move(sampled_profile),
PerfProtoType::PERF_TYPE_DATA, output);
}
} // namespace metrics