| // 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 <utility> |
| |
| #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 internal { |
| |
| namespace { |
| |
| // Name prefix of the histogram that represents the success and various failure |
| // modes for a collector. |
| const char kCollectionOutcomeHistogramPrefix[] = "ChromeOS.CWP.Collect"; |
| |
| // 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, |
| const CollectionParams& collection_params) |
| : collection_params_(collection_params), |
| collect_uma_histogram_(std::string(kCollectionOutcomeHistogramPrefix) + |
| name) { |
| // Allow rebinding |sequence_checker_| to the sequence the collector runs on. |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| MetricCollector::~MetricCollector() = default; |
| |
| void MetricCollector::Init() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| SetUp(); |
| } |
| |
| void MetricCollector::AddCachedDataDelta(size_t delta) { |
| cached_data_size_ += delta; |
| } |
| |
| void MetricCollector::ResetCachedDataSize() { |
| cached_data_size_ = 0; |
| } |
| |
| void MetricCollector::RecordUserLogin(base::TimeTicks login_time) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| login_time_ = login_time; |
| next_profiling_interval_start_ = login_time; |
| ScheduleIntervalCollection(); |
| } |
| void MetricCollector::StopTimer() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| timer_.AbandonAndStop(); |
| } |
| |
| void MetricCollector::ScheduleSuspendDoneCollection( |
| base::TimeDelta sleep_duration) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // 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, |
| GetWeakPtr(), sleep_duration, collection_delay)); |
| } |
| |
| void MetricCollector::OnJankStarted() { |
| // Fill out a SampledProfile protobuf that will contain the collected data. |
| auto sampled_profile = std::make_unique<SampledProfile>(); |
| sampled_profile->set_trigger_event(SampledProfile::JANKY_TASK); |
| |
| CollectIfNecessary(std::move(sampled_profile)); |
| } |
| |
| void MetricCollector::OnJankStopped() { |
| StopCollection(); |
| } |
| |
| void MetricCollector::ScheduleSessionRestoreCollection(int num_tabs_restored) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Collect a profile only 1/|sampling_factor| of the time, to avoid |
| // collecting too much data. (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, |
| GetWeakPtr(), collection_delay, num_tabs_restored)); |
| } |
| |
| void MetricCollector::AddToUmaHistogram(CollectionAttemptStatus outcome) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::UmaHistogramEnumeration(collect_uma_histogram_, outcome, |
| CollectionAttemptStatus::NUM_OUTCOMES); |
| } |
| |
| 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::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; |
| // Schedule periodic collection only if periodic_interval is non-zero. A value |
| // of zero is the escape hatch for turning periodic collection off via Finch. |
| if (collection_params_.periodic_interval.is_zero()) |
| 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, |
| base::BindOnce(&MetricCollector::DoPeriodicCollection, GetWeakPtr())); |
| |
| // 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::SaveSerializedPerfProto( |
| std::unique_ptr<SampledProfile> sampled_profile, |
| std::string serialized_proto) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (serialized_proto.empty()) { |
| AddToUmaHistogram(CollectionAttemptStatus::ILLEGAL_DATA_RETURNED); |
| return; |
| } |
| |
| 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); |
| |
| 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()); |
| |
| // Run |profile_done_callback_| on success. |
| AddToUmaHistogram(CollectionAttemptStatus::SUCCESS); |
| profile_done_callback_.Run(std::move(sampled_profile)); |
| } |
| |
| } // namespace internal |
| |
| } // namespace metrics |