blob: d1f5030a1ad1fdb8b5b147077b9b7c5f55a9165d [file] [log] [blame]
// Copyright 2018 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/metric_collector.h"
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/system/sys_info.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"
namespace metrics {
namespace {
// Name prefix of the histogram that represents the success and various failure
// modes for a collector.
const char kCollectionOutcomeHistogramPrefix[] = "ChromeOS.CWP.Collect";
// Name prefix of the histogram that counts the number of reports uploaded by a
// collector.
const char kUploadCountHistogramPrefix[] = "ChromeOS.CWP.Upload";
// An upper bound on the count of reports expected to be uploaded by an UMA
// callback.
const int kMaxValueUploadReports = 10;
// This is used to space out session restore collections in the face of several
// notifications in a short period of time. There should be no less than this
// much time between collections.
const int kMinIntervalBetweenSessionRestoreCollectionsInSec = 30;
// Returns a random TimeDelta uniformly selected between zero and |max|.
base::TimeDelta RandomTimeDelta(base::TimeDelta max) {
if (max.is_zero())
return max;
return base::TimeDelta::FromMicroseconds(
base::RandGenerator(max.InMicroseconds()));
}
// PerfDataProto is defined elsewhere with more fields than the definition in
// Chromium's copy of perf_data.proto. During deserialization, the protobuf
// data could contain fields that are defined elsewhere but not in
// perf_data.proto, resulting in some data in |unknown_fields| for the message
// types within PerfDataProto.
//
// This function deletes those dangling unknown fields if they are in messages
// containing strings. See comments in perf_data.proto describing the fields
// that have been intentionally left out. Note that all unknown fields will be
// removed from those messages, not just unknown string fields.
void RemoveUnknownFieldsFromMessagesWithStrings(PerfDataProto* proto) {
// Clean up PerfEvent::MMapEvent and PerfEvent::CommEvent.
for (PerfDataProto::PerfEvent& event : *proto->mutable_events()) {
if (event.has_comm_event())
event.mutable_comm_event()->mutable_unknown_fields()->clear();
if (event.has_mmap_event())
event.mutable_mmap_event()->mutable_unknown_fields()->clear();
}
// Clean up PerfBuildID.
for (PerfDataProto::PerfBuildID& build_id : *proto->mutable_build_ids()) {
build_id.mutable_unknown_fields()->clear();
}
// Clean up StringMetadata and StringMetadata::StringAndMd5sumPrefix.
if (proto->has_string_metadata()) {
proto->mutable_string_metadata()->mutable_unknown_fields()->clear();
if (proto->string_metadata().has_perf_command_line_whole()) {
proto->mutable_string_metadata()
->mutable_perf_command_line_whole()
->mutable_unknown_fields()
->clear();
}
}
}
} // namespace
MetricCollector::MetricCollector(const std::string& name)
: MetricCollector(name, CollectionParams()) {}
MetricCollector::MetricCollector(const std::string& name,
const CollectionParams& collection_params)
: collection_params_(collection_params) {
collect_uma_histogram_ =
std::string(kCollectionOutcomeHistogramPrefix) + name;
upload_uma_histogram_ = std::string(kUploadCountHistogramPrefix) + name;
}
MetricCollector::~MetricCollector() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void MetricCollector::AddToUmaHistogram(CollectionAttemptStatus outcome) const {
base::UmaHistogramEnumeration(collect_uma_histogram_, outcome,
CollectionAttemptStatus::NUM_OUTCOMES);
}
bool MetricCollector::GetSampledProfiles(
std::vector<SampledProfile>* sampled_profiles) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ShouldUpload() || cached_profile_data_.empty()) {
base::UmaHistogramExactLinear(upload_uma_histogram_, 0,
kMaxValueUploadReports);
return false;
}
base::UmaHistogramExactLinear(upload_uma_histogram_,
cached_profile_data_.size(),
kMaxValueUploadReports);
sampled_profiles->insert(
sampled_profiles->end(),
std::make_move_iterator(cached_profile_data_.begin()),
std::make_move_iterator(cached_profile_data_.end()));
cached_profile_data_.clear();
return true;
}
bool MetricCollector::ShouldUpload() const {
return true;
}
void MetricCollector::SuspendDone(base::TimeDelta sleep_duration) {
// Collect a profile only 1/|sampling_factor| of the time, to avoid
// collecting too much data. (0 means disable the trigger)
const auto& resume_params = collection_params_.resume_from_suspend;
if (resume_params.sampling_factor == 0 ||
base::RandGenerator(resume_params.sampling_factor) != 0)
return;
// Override any existing profiling.
if (timer_.IsRunning())
timer_.Stop();
// Randomly pick a delay before doing the collection.
base::TimeDelta collection_delay =
RandomTimeDelta(resume_params.max_collection_delay);
timer_.Start(FROM_HERE, collection_delay,
base::BindOnce(&MetricCollector::CollectPerfDataAfterResume,
AsWeakPtr(), sleep_duration, collection_delay));
}
void MetricCollector::CollectPerfDataAfterResume(
base::TimeDelta sleep_duration,
base::TimeDelta time_after_resume) {
// Fill out a SampledProfile protobuf that will contain the collected data.
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::RESUME_FROM_SUSPEND);
sampled_profile->set_suspend_duration_ms(sleep_duration.InMilliseconds());
sampled_profile->set_ms_after_resume(time_after_resume.InMilliseconds());
CollectIfNecessary(std::move(sampled_profile));
}
void MetricCollector::OnSessionRestoreDone(int num_tabs_restored) {
// Collect a profile only 1/|sampling_factor| of the time, to
// avoid collecting too much data and potentially causing UI latency.
// (0 means disable the trigger)
const auto& restore_params = collection_params_.restore_session;
if (restore_params.sampling_factor == 0 ||
base::RandGenerator(restore_params.sampling_factor) != 0) {
return;
}
const auto min_interval = base::TimeDelta::FromSeconds(
kMinIntervalBetweenSessionRestoreCollectionsInSec);
const base::TimeDelta time_since_last_collection =
(base::TimeTicks::Now() - last_session_restore_collection_time_);
// Do not collect if there hasn't been enough elapsed time since the last
// collection.
if (!last_session_restore_collection_time_.is_null() &&
time_since_last_collection < min_interval) {
return;
}
// Stop any existing scheduled collection.
if (timer_.IsRunning())
timer_.Stop();
// Randomly pick a delay before doing the collection.
base::TimeDelta collection_delay =
RandomTimeDelta(restore_params.max_collection_delay);
timer_.Start(
FROM_HERE, collection_delay,
base::BindOnce(&MetricCollector::CollectPerfDataAfterSessionRestore,
AsWeakPtr(), collection_delay, num_tabs_restored));
}
void MetricCollector::CollectPerfDataAfterSessionRestore(
base::TimeDelta time_after_restore,
int num_tabs_restored) {
// Fill out a SampledProfile protobuf that will contain the collected data.
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::RESTORE_SESSION);
sampled_profile->set_ms_after_restore(time_after_restore.InMilliseconds());
sampled_profile->set_num_tabs_restored(num_tabs_restored);
CollectIfNecessary(std::move(sampled_profile));
last_session_restore_collection_time_ = base::TimeTicks::Now();
}
void MetricCollector::ScheduleIntervalCollection() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (timer_.IsRunning())
return;
const base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks interval_end =
next_profiling_interval_start_ + collection_params_.periodic_interval;
if (now > interval_end) {
// We somehow missed at least one window. Start over.
next_profiling_interval_start_ = now;
interval_end = now + collection_params_.periodic_interval;
}
// Pick a random time in the current interval.
base::TimeTicks scheduled_time =
next_profiling_interval_start_ +
RandomTimeDelta(collection_params_.periodic_interval);
// If the scheduled time has already passed in the time it took to make the
// above calculations, trigger the collection event immediately.
if (scheduled_time < now)
scheduled_time = now;
timer_.Start(FROM_HERE, scheduled_time - now, this,
&MetricCollector::DoPeriodicCollection);
// Update the profiling interval tracker to the start of the next interval.
next_profiling_interval_start_ = interval_end;
}
void MetricCollector::DoPeriodicCollection() {
auto sampled_profile = std::make_unique<SampledProfile>();
sampled_profile->set_trigger_event(SampledProfile::PERIODIC_COLLECTION);
CollectIfNecessary(std::move(sampled_profile));
}
void MetricCollector::CollectIfNecessary(
std::unique_ptr<SampledProfile> sampled_profile) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ShouldCollect()) {
// Do the actual profile collection.
CollectProfile(std::move(sampled_profile));
}
// Schedule another interval collection. This call makes sense regardless of
// whether or not the current collection was interval-triggered. If it had
// been another type of trigger event, the interval timer would have been
// halted, so it makes sense to reschedule a new interval collection.
ScheduleIntervalCollection();
}
bool MetricCollector::ShouldCollect() const {
return true;
}
void MetricCollector::OnUserLoggedIn() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const base::TimeTicks now = base::TimeTicks::Now();
login_time_ = now;
next_profiling_interval_start_ = now;
ScheduleIntervalCollection();
}
void MetricCollector::Deactivate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Stop the timer, but leave |cached_profile_data_| intact.
timer_.Stop();
}
void MetricCollector::SaveSerializedPerfProto(
std::unique_ptr<SampledProfile> sampled_profile,
PerfProtoType type,
const std::string& serialized_proto) {
if (serialized_proto.empty()) {
AddToUmaHistogram(CollectionAttemptStatus::ILLEGAL_DATA_RETURNED);
return;
}
switch (type) {
case PerfProtoType::PERF_TYPE_DATA: {
PerfDataProto perf_data_proto;
if (!perf_data_proto.ParseFromString(serialized_proto)) {
AddToUmaHistogram(CollectionAttemptStatus::PROTOBUF_NOT_PARSED);
return;
}
RemoveUnknownFieldsFromMessagesWithStrings(&perf_data_proto);
sampled_profile->mutable_perf_data()->Swap(&perf_data_proto);
break;
}
case PerfProtoType::PERF_TYPE_STAT: {
PerfStatProto perf_stat_proto;
if (!perf_stat_proto.ParseFromString(serialized_proto)) {
AddToUmaHistogram(CollectionAttemptStatus::PROTOBUF_NOT_PARSED);
return;
}
sampled_profile->mutable_perf_stat()->Swap(&perf_stat_proto);
break;
}
case PerfProtoType::PERF_TYPE_UNSUPPORTED:
AddToUmaHistogram(CollectionAttemptStatus::PROTOBUF_NOT_PARSED);
return;
}
sampled_profile->set_ms_after_boot(base::SysInfo::Uptime().InMilliseconds());
DCHECK(!login_time_.is_null());
sampled_profile->set_ms_after_login(
(base::TimeTicks::Now() - login_time_).InMilliseconds());
// Add the collected data to the container of collected SampledProfiles.
cached_profile_data_.resize(cached_profile_data_.size() + 1);
cached_profile_data_.back().Swap(sampled_profile.get());
AddToUmaHistogram(CollectionAttemptStatus::SUCCESS);
}
size_t MetricCollector::cached_profile_data_size() const {
size_t data_size = 0;
for (size_t i = 0; i < cached_profile_data_.size(); ++i) {
data_size += cached_profile_data_[i].ByteSize();
}
return data_size;
}
} // namespace metrics