diff options
Diffstat (limited to 'chromium/components/metrics')
156 files changed, 21066 insertions, 0 deletions
diff --git a/chromium/components/metrics/BUILD.gn b/chromium/components/metrics/BUILD.gn new file mode 100644 index 00000000000..224070db0d4 --- /dev/null +++ b/chromium/components/metrics/BUILD.gn @@ -0,0 +1,367 @@ +# Copyright 2014 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. + +declare_args() { + # Overrides os name in uma metrics log to "Blimp". + metrics_use_blimp = false +} + +# GYP version: components/metrics.gypi:metrics +source_set("metrics") { + sources = [ + "call_stack_profile_metrics_provider.cc", + "call_stack_profile_metrics_provider.h", + "clean_exit_beacon.cc", + "clean_exit_beacon.h", + "client_info.cc", + "client_info.h", + "cloned_install_detector.cc", + "cloned_install_detector.h", + "daily_event.cc", + "daily_event.h", + "data_use_tracker.cc", + "data_use_tracker.h", + "drive_metrics_provider.cc", + "drive_metrics_provider.h", + "drive_metrics_provider_android.cc", + "drive_metrics_provider_ios.mm", + "drive_metrics_provider_linux.cc", + "drive_metrics_provider_mac.mm", + "drive_metrics_provider_win.cc", + "file_metrics_provider.cc", + "file_metrics_provider.h", + "histogram_encoder.cc", + "histogram_encoder.h", + "machine_id_provider.h", + "machine_id_provider_stub.cc", + "machine_id_provider_win.cc", + "metrics_log.cc", + "metrics_log.h", + "metrics_log_manager.cc", + "metrics_log_manager.h", + "metrics_log_uploader.cc", + "metrics_log_uploader.h", + "metrics_pref_names.cc", + "metrics_pref_names.h", + "metrics_provider.cc", + "metrics_provider.h", + "metrics_reporting_scheduler.cc", + "metrics_reporting_scheduler.h", + "metrics_service.cc", + "metrics_service.h", + "metrics_service_accessor.cc", + "metrics_service_accessor.h", + "metrics_service_client.cc", + "metrics_service_client.h", + "metrics_state_manager.cc", + "metrics_state_manager.h", + "metrics_switches.cc", + "metrics_switches.h", + "persisted_logs.cc", + "persisted_logs.h", + "stability_metrics_helper.cc", + "stability_metrics_helper.h", + "system_memory_stats_recorder.h", + "system_memory_stats_recorder_linux.cc", + "system_memory_stats_recorder_win.cc", + "url_constants.cc", + "url_constants.h", + ] + + public_deps = [ + "//components/metrics/proto", + ] + deps = [ + "//base", + "//base:base_static", + "//components/prefs", + "//components/variations", + "//third_party/zlib:compression_utils", + ] + + if (is_chromeos) { + deps += [ ":serialization" ] + } + + if (is_mac) { + libs = [ + # The below are all needed for drive_metrics_provider_mac.mm. + "CoreFoundation.framework", + "DiskArbitration.framework", + "Foundation.framework", + "IOKit.framework", + ] + } + + if (is_win) { + sources -= [ "machine_id_provider_stub.cc" ] + } +} + +if (!is_ios) { + # GYP version: components/metrics.gypi:metrics_gpu + source_set("gpu") { + sources = [ + "gpu/gpu_metrics_provider.cc", + "gpu/gpu_metrics_provider.h", + ] + + public_deps = [ + ":metrics", + ] + deps = [ + "//base", + "//content/public/browser", + "//gpu/config", + ] + } +} + +if (is_chromeos) { + # GYP version: components/metrics.gypi:metrics_leak_detector + source_set("leak_detector") { + sources = [ + "leak_detector/call_stack_manager.cc", + "leak_detector/call_stack_manager.h", + "leak_detector/call_stack_table.cc", + "leak_detector/call_stack_table.h", + "leak_detector/custom_allocator.cc", + "leak_detector/custom_allocator.h", + "leak_detector/leak_analyzer.cc", + "leak_detector/leak_analyzer.h", + "leak_detector/leak_detector.cc", + "leak_detector/leak_detector.h", + "leak_detector/leak_detector_impl.cc", + "leak_detector/leak_detector_impl.h", + "leak_detector/leak_detector_value_type.cc", + "leak_detector/leak_detector_value_type.h", + "leak_detector/ranked_set.cc", + "leak_detector/ranked_set.h", + ] + + deps = [ + "//base", + "//content/public/browser", + ] + } +} + +# GYP version: components/metrics.gypi:metrics_net +static_library("net") { + sources = [ + "net/net_metrics_log_uploader.cc", + "net/net_metrics_log_uploader.h", + "net/network_metrics_provider.cc", + "net/network_metrics_provider.h", + "net/version_utils.cc", + "net/version_utils.h", + "net/wifi_access_point_info_provider.cc", + "net/wifi_access_point_info_provider.h", + ] + + public_deps = [ + ":metrics", + ] + allow_circular_includes_from = [ ":metrics" ] + + deps = [ + "//base", + "//components/data_use_measurement/core", + "//components/version_info", + "//net", + "//url", + ] + + if (is_chromeos) { + sources += [ + "net/wifi_access_point_info_provider_chromeos.cc", + "net/wifi_access_point_info_provider_chromeos.h", + ] + deps += [ "//chromeos" ] + } +} + +# GYP version: components/metrics.gypi:metrics_profiler +source_set("profiler") { + sources = [ + "profiler/profiler_metrics_provider.cc", + "profiler/profiler_metrics_provider.h", + "profiler/tracking_synchronizer.cc", + "profiler/tracking_synchronizer.h", + "profiler/tracking_synchronizer_delegate.h", + "profiler/tracking_synchronizer_observer.cc", + "profiler/tracking_synchronizer_observer.h", + ] + + public_deps = [ + ":metrics", + ] + deps = [ + "//base", + "//components/variations", + ] +} + +# GYP version: components/metrics.gypi:metrics_ui +source_set("ui") { + sources = [ + "ui/screen_info_metrics_provider.cc", + "ui/screen_info_metrics_provider.h", + ] + + public_deps = [ + ":metrics", + ] + deps = [ + "//base", + "//ui/gfx", + "//ui/gfx/geometry", + ] +} + +if (!is_ios) { + # GYP version: components/metrics.gypi:metrics_profiler_content + source_set("profiler_content") { + sources = [ + "profiler/content/content_tracking_synchronizer_delegate.cc", + "profiler/content/content_tracking_synchronizer_delegate.h", + ] + + public_deps = [ + ":profiler", + ] + deps = [ + "//base", + "//components/nacl/common:process_type", + "//content/public/browser", + "//content/public/common", + ] + } +} else { + # GYP version: components/metrics.gypi:metrics_profiler_ios + source_set("profiler_ios") { + sources = [ + "profiler/ios/ios_tracking_synchronizer_delegate.cc", + "profiler/ios/ios_tracking_synchronizer_delegate.h", + ] + + public_deps = [ + ":profiler", + ] + deps = [ + "//base", + ] + } +} + +# GYP version: components/metrics.gypi:metrics_test_support +source_set("test_support") { + sources = [ + "test_metrics_provider.cc", + "test_metrics_provider.h", + "test_metrics_service_client.cc", + "test_metrics_service_client.h", + ] + + public_deps = [ + ":metrics", + ] + deps = [ + "//base", + ] +} + +if (is_linux) { + # GYP version: components/metrics.gypi:metrics_serialization + source_set("serialization") { + sources = [ + "serialization/metric_sample.cc", + "serialization/metric_sample.h", + "serialization/serialization_utils.cc", + "serialization/serialization_utils.h", + ] + deps = [ + "//base", + ] + } +} + +if (is_chromeos) { + source_set("leak_detector_unit_tests") { + testonly = true + sources = [ + "leak_detector/call_stack_manager_unittest.cc", + "leak_detector/call_stack_table_unittest.cc", + "leak_detector/leak_analyzer_unittest.cc", + "leak_detector/leak_detector_impl_unittest.cc", + "leak_detector/leak_detector_unittest.cc", + "leak_detector/ranked_set_unittest.cc", + ] + + deps = [ + ":leak_detector", + "//base", + "//content/test:test_support", + "//testing/gtest", + ] + } +} + +source_set("unit_tests") { + testonly = true + sources = [ + "call_stack_profile_metrics_provider_unittest.cc", + "cloned_install_detector_unittest.cc", + "daily_event_unittest.cc", + "data_use_tracker_unittest.cc", + "drive_metrics_provider_unittest.cc", + "histogram_encoder_unittest.cc", + "machine_id_provider_win_unittest.cc", + "metrics_log_manager_unittest.cc", + "metrics_log_unittest.cc", + "metrics_reporting_scheduler_unittest.cc", + "metrics_service_unittest.cc", + "metrics_state_manager_unittest.cc", + "net/net_metrics_log_uploader_unittest.cc", + "persisted_logs_unittest.cc", + "profiler/profiler_metrics_provider_unittest.cc", + "profiler/tracking_synchronizer_unittest.cc", + "stability_metrics_helper_unittest.cc", + "ui/screen_info_metrics_provider_unittest.cc", + ] + + deps = [ + ":metrics", + ":net", + ":profiler", + ":test_support", + ":ui", + "//base/test:test_support", + "//components/prefs:test_support", + "//components/variations", + "//net:test_support", + "//testing/gtest", + "//third_party/zlib:compression_utils", + "//ui/gfx/geometry", + ] + + if (is_linux) { + sources += [ "serialization/serialization_utils_unittest.cc" ] + deps += [ ":serialization" ] + } + + if (is_chromeos) { + deps += [ ":leak_detector_unit_tests" ] + } + + # These are only used by the blimp team, which has entirely migrated to gn, + # so this logic is not replicated in the gyp file. + if (metrics_use_blimp) { + defines = [ + "OVERRIDE_OS_NAME_TO_BLIMP", + "ENABLE_REPORTING_BLIMP", + ] + } +} +# TODO(GYP): metrics_chromeos diff --git a/chromium/components/metrics/DEPS b/chromium/components/metrics/DEPS new file mode 100644 index 00000000000..8960a59060b --- /dev/null +++ b/chromium/components/metrics/DEPS @@ -0,0 +1,14 @@ +# This component is shared with the Chrome OS build, so it's important to limit +# dependencies to a minimal set. +include_rules = [ + "-components", + "+components/compression", + "+components/metrics", + "+components/prefs", + "+components/variations", + "+components/version_info", + "+content/public/browser", + "+content/public/test", + "+third_party/zlib/google", + "-net", +] diff --git a/chromium/components/metrics/OWNERS b/chromium/components/metrics/OWNERS new file mode 100644 index 00000000000..e17cf1464a7 --- /dev/null +++ b/chromium/components/metrics/OWNERS @@ -0,0 +1,6 @@ +asvitkine@chromium.org +holte@chromium.org +isherman@chromium.org +jar@chromium.org +mpearson@chromium.org +rkaplow@chromium.org diff --git a/chromium/components/metrics/README b/chromium/components/metrics/README new file mode 100644 index 00000000000..5a2abbb2f69 --- /dev/null +++ b/chromium/components/metrics/README @@ -0,0 +1,23 @@ +This component contains the base classes for the metrics service and only +depends on //base. It is used by ChromeOS as the base for a standalone service +that will upload the metrics when ChromeOS is not installed (headless install). + +This is the first step towards the componentization of metrics that will happen +later this spring. + +A proposed structure for the metrics component is: +//components/metrics/base, + Depends on base only. Contains the protobuf definitions. +//components/metrics/core + Depends on everything iOS depends on +//components/metrics/content + Depends on content + +Ideally, the component would abstract the network stack and have a clean +separation between the metrics upload logic (protbuf generation, retry, etc...), +the chrome part (gathering histogram from all the threads, populating the +log with hardware characteristics, plugin state, etc.). + +It is a plus if the code currently in the component (i.e., the code that can +depend only on //base) stays in a single directory as it would be easier +for ChromeOS to pull it :). diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.cc b/chromium/components/metrics/call_stack_profile_metrics_provider.cc new file mode 100644 index 00000000000..7ad942d3fca --- /dev/null +++ b/chromium/components/metrics/call_stack_profile_metrics_provider.cc @@ -0,0 +1,396 @@ +// Copyright 2015 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 "components/metrics/call_stack_profile_metrics_provider.h" + +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> +#include <cstring> +#include <map> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/metrics_hashes.h" +#include "base/profiler/stack_sampling_profiler.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +using base::StackSamplingProfiler; + +namespace metrics { + +namespace { + +// ProfilesState -------------------------------------------------------------- + +// A set of profiles and the CallStackProfileMetricsProvider state associated +// with them. +struct ProfilesState { + ProfilesState(const CallStackProfileMetricsProvider::Params& params, + const base::StackSamplingProfiler::CallStackProfiles& profiles, + base::TimeTicks start_timestamp); + + // The metrics-related parameters provided to + // CallStackProfileMetricsProvider::GetProfilerCallback(). + CallStackProfileMetricsProvider::Params params; + + // The call stack profiles collected by the profiler. + base::StackSamplingProfiler::CallStackProfiles profiles; + + // The time at which the CallStackProfileMetricsProvider became aware of the + // request for profiling. In particular, this is when callback was requested + // via CallStackProfileMetricsProvider::GetProfilerCallback(). Used to + // determine if collection was disabled during the collection of the profile. + base::TimeTicks start_timestamp; +}; + +ProfilesState::ProfilesState( + const CallStackProfileMetricsProvider::Params& params, + const base::StackSamplingProfiler::CallStackProfiles& profiles, + base::TimeTicks start_timestamp) + : params(params), + profiles(profiles), + start_timestamp(start_timestamp) { +} + +// PendingProfiles ------------------------------------------------------------ + +// Singleton class responsible for retaining profiles received via the callback +// created by CallStackProfileMetricsProvider::GetProfilerCallback(). These are +// then sent to UMA on the invocation of +// CallStackProfileMetricsProvider::ProvideGeneralMetrics(). We need to store +// the profiles outside of a CallStackProfileMetricsProvider instance since +// callers may start profiling before the CallStackProfileMetricsProvider is +// created. +// +// Member functions on this class may be called on any thread. +class PendingProfiles { + public: + static PendingProfiles* GetInstance(); + + void Clear(); + void Swap(std::vector<ProfilesState>* profiles); + + // Enables the collection of profiles by CollectProfilesIfCollectionEnabled if + // |enabled| is true. Otherwise, clears current profiles and ignores profiles + // provided to future invocations of CollectProfilesIfCollectionEnabled. + void SetCollectionEnabled(bool enabled); + + // True if profiles are being collected. + bool IsCollectionEnabled() const; + + // Adds |profile| to the list of profiles if collection is enabled. + void CollectProfilesIfCollectionEnabled(const ProfilesState& profiles); + + // Allows testing against the initial state multiple times. + void ResetToDefaultStateForTesting(); + + private: + friend struct base::DefaultSingletonTraits<PendingProfiles>; + + PendingProfiles(); + ~PendingProfiles(); + + mutable base::Lock lock_; + + // If true, profiles provided to CollectProfilesIfCollectionEnabled should be + // collected. Otherwise they will be ignored. + bool collection_enabled_; + + // The last time collection was disabled. Used to determine if collection was + // disabled at any point since a profile was started. + base::TimeTicks last_collection_disable_time_; + + // The set of completed profiles that should be reported. + std::vector<ProfilesState> profiles_; + + DISALLOW_COPY_AND_ASSIGN(PendingProfiles); +}; + +// static +PendingProfiles* PendingProfiles::GetInstance() { + // Leaky for performance rather than correctness reasons. + return base::Singleton<PendingProfiles, + base::LeakySingletonTraits<PendingProfiles>>::get(); +} + +void PendingProfiles::Clear() { + base::AutoLock scoped_lock(lock_); + profiles_.clear(); +} + +void PendingProfiles::Swap(std::vector<ProfilesState>* profiles) { + base::AutoLock scoped_lock(lock_); + profiles_.swap(*profiles); +} + +void PendingProfiles::SetCollectionEnabled(bool enabled) { + base::AutoLock scoped_lock(lock_); + + collection_enabled_ = enabled; + + if (!collection_enabled_) { + profiles_.clear(); + last_collection_disable_time_ = base::TimeTicks::Now(); + } +} + +bool PendingProfiles::IsCollectionEnabled() const { + base::AutoLock scoped_lock(lock_); + return collection_enabled_; +} + +void PendingProfiles::CollectProfilesIfCollectionEnabled( + const ProfilesState& profiles) { + base::AutoLock scoped_lock(lock_); + + // Only collect if collection is not disabled and hasn't been disabled + // since the start of collection for this profile. + if (!collection_enabled_ || + (!last_collection_disable_time_.is_null() && + last_collection_disable_time_ >= profiles.start_timestamp)) { + return; + } + + profiles_.push_back(profiles); +} + +void PendingProfiles::ResetToDefaultStateForTesting() { + base::AutoLock scoped_lock(lock_); + + collection_enabled_ = true; + last_collection_disable_time_ = base::TimeTicks(); + profiles_.clear(); +} + +// |collection_enabled_| is initialized to true to collect any profiles that are +// generated prior to creation of the CallStackProfileMetricsProvider. The +// ultimate disposition of these pre-creation collected profiles will be +// determined by the initial recording state provided to +// CallStackProfileMetricsProvider. +PendingProfiles::PendingProfiles() : collection_enabled_(true) {} + +PendingProfiles::~PendingProfiles() {} + +// Functions to process completed profiles ------------------------------------ + +// Invoked on the profiler's thread. Provides the profiles to PendingProfiles to +// append, if the collecting state allows. +void ReceiveCompletedProfiles( + const CallStackProfileMetricsProvider::Params& params, + base::TimeTicks start_timestamp, + const StackSamplingProfiler::CallStackProfiles& profiles) { + PendingProfiles::GetInstance()->CollectProfilesIfCollectionEnabled( + ProfilesState(params, profiles, start_timestamp)); +} + +// Invoked on an arbitrary thread. Ignores the provided profiles. +void IgnoreCompletedProfiles( + const StackSamplingProfiler::CallStackProfiles& profiles) { +} + +// Functions to encode protobufs ---------------------------------------------- + +// The protobuf expects the MD5 checksum prefix of the module name. +uint64_t HashModuleFilename(const base::FilePath& filename) { + const base::FilePath::StringType basename = filename.BaseName().value(); + // Copy the bytes in basename into a string buffer. + size_t basename_length_in_bytes = + basename.size() * sizeof(base::FilePath::CharType); + std::string name_bytes(basename_length_in_bytes, '\0'); + memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes); + return base::HashMetricName(name_bytes); +} + +// Transcode |sample| into |proto_sample|, using base addresses in |modules| to +// compute module instruction pointer offsets. +void CopySampleToProto( + const StackSamplingProfiler::Sample& sample, + const std::vector<StackSamplingProfiler::Module>& modules, + CallStackProfile::Sample* proto_sample) { + for (const StackSamplingProfiler::Frame& frame : sample) { + CallStackProfile::Entry* entry = proto_sample->add_entry(); + // A frame may not have a valid module. If so, we can't compute the + // instruction pointer offset, and we don't want to send bare pointers, so + // leave call_stack_entry empty. + if (frame.module_index == StackSamplingProfiler::Frame::kUnknownModuleIndex) + continue; + int64_t module_offset = + reinterpret_cast<const char*>(frame.instruction_pointer) - + reinterpret_cast<const char*>(modules[frame.module_index].base_address); + DCHECK_GE(module_offset, 0); + entry->set_address(static_cast<uint64_t>(module_offset)); + entry->set_module_id_index(frame.module_index); + } +} + +// Transcode |profile| into |proto_profile|. +void CopyProfileToProto( + const StackSamplingProfiler::CallStackProfile& profile, + bool preserve_sample_ordering, + CallStackProfile* proto_profile) { + if (profile.samples.empty()) + return; + + if (preserve_sample_ordering) { + // Collapse only consecutive repeated samples together. + CallStackProfile::Sample* current_sample_proto = nullptr; + for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) { + if (!current_sample_proto || *it != *(it - 1)) { + current_sample_proto = proto_profile->add_sample(); + CopySampleToProto(*it, profile.modules, current_sample_proto); + current_sample_proto->set_count(1); + } else { + current_sample_proto->set_count(current_sample_proto->count() + 1); + } + } + } else { + // Collapse all repeated samples together. + std::map<StackSamplingProfiler::Sample, int> sample_index; + for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) { + auto location = sample_index.find(*it); + if (location == sample_index.end()) { + CallStackProfile::Sample* sample_proto = proto_profile->add_sample(); + CopySampleToProto(*it, profile.modules, sample_proto); + sample_proto->set_count(1); + sample_index.insert( + std::make_pair( + *it, static_cast<int>(proto_profile->sample().size()) - 1)); + } else { + CallStackProfile::Sample* sample_proto = + proto_profile->mutable_sample()->Mutable(location->second); + sample_proto->set_count(sample_proto->count() + 1); + } + } + } + + for (const StackSamplingProfiler::Module& module : profile.modules) { + CallStackProfile::ModuleIdentifier* module_id = + proto_profile->add_module_id(); + module_id->set_build_id(module.id); + module_id->set_name_md5_prefix(HashModuleFilename(module.filename)); + } + + proto_profile->set_profile_duration_ms( + profile.profile_duration.InMilliseconds()); + proto_profile->set_sampling_period_ms( + profile.sampling_period.InMilliseconds()); +} + +// Translates CallStackProfileMetricsProvider's trigger to the corresponding +// SampledProfile TriggerEvent. +SampledProfile::TriggerEvent ToSampledProfileTriggerEvent( + CallStackProfileMetricsProvider::Trigger trigger) { + switch (trigger) { + case CallStackProfileMetricsProvider::UNKNOWN: + return SampledProfile::UNKNOWN_TRIGGER_EVENT; + break; + case CallStackProfileMetricsProvider::PROCESS_STARTUP: + return SampledProfile::PROCESS_STARTUP; + break; + case CallStackProfileMetricsProvider::JANKY_TASK: + return SampledProfile::JANKY_TASK; + break; + case CallStackProfileMetricsProvider::THREAD_HUNG: + return SampledProfile::THREAD_HUNG; + break; + } + NOTREACHED(); + return SampledProfile::UNKNOWN_TRIGGER_EVENT; +} + +} // namespace + +// CallStackProfileMetricsProvider::Params ------------------------------------ + +CallStackProfileMetricsProvider::Params::Params( + CallStackProfileMetricsProvider::Trigger trigger) + : Params(trigger, false) { +} + +CallStackProfileMetricsProvider::Params::Params( + CallStackProfileMetricsProvider::Trigger trigger, + bool preserve_sample_ordering) + : trigger(trigger), + preserve_sample_ordering(preserve_sample_ordering) { +} + +// CallStackProfileMetricsProvider -------------------------------------------- + +const char CallStackProfileMetricsProvider::kFieldTrialName[] = + "StackProfiling"; +const char CallStackProfileMetricsProvider::kReportProfilesGroupName[] = + "Report profiles"; + +CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() { +} + +CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() { +} + +// This function can be invoked on an abitrary thread. +base::StackSamplingProfiler::CompletedCallback +CallStackProfileMetricsProvider::GetProfilerCallback(const Params& params) { + // Ignore the profiles if the collection is disabled. If the collection state + // changes while collecting, this will be detected by the callback and + // profiles will be ignored at that point. + if (!PendingProfiles::GetInstance()->IsCollectionEnabled()) + return base::Bind(&IgnoreCompletedProfiles); + + return base::Bind(&ReceiveCompletedProfiles, params, base::TimeTicks::Now()); +} + +void CallStackProfileMetricsProvider::OnRecordingEnabled() { + PendingProfiles::GetInstance()->SetCollectionEnabled(true); +} + +void CallStackProfileMetricsProvider::OnRecordingDisabled() { + PendingProfiles::GetInstance()->SetCollectionEnabled(false); +} + +void CallStackProfileMetricsProvider::ProvideGeneralMetrics( + ChromeUserMetricsExtension* uma_proto) { + std::vector<ProfilesState> pending_profiles; + PendingProfiles::GetInstance()->Swap(&pending_profiles); + + DCHECK(IsReportingEnabledByFieldTrial() || pending_profiles.empty()); + + for (const ProfilesState& profiles_state : pending_profiles) { + for (const StackSamplingProfiler::CallStackProfile& profile : + profiles_state.profiles) { + SampledProfile* sampled_profile = uma_proto->add_sampled_profile(); + sampled_profile->set_trigger_event(ToSampledProfileTriggerEvent( + profiles_state.params.trigger)); + CopyProfileToProto(profile, + profiles_state.params.preserve_sample_ordering, + sampled_profile->mutable_call_stack_profile()); + } + } +} + +// static +void CallStackProfileMetricsProvider::ResetStaticStateForTesting() { + PendingProfiles::GetInstance()->ResetToDefaultStateForTesting(); +} + +// static +bool CallStackProfileMetricsProvider::IsReportingEnabledByFieldTrial() { + const std::string group_name = base::FieldTrialList::FindFullName( + CallStackProfileMetricsProvider::kFieldTrialName); + return group_name == + CallStackProfileMetricsProvider::kReportProfilesGroupName; +} + +} // namespace metrics diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.h b/chromium/components/metrics/call_stack_profile_metrics_provider.h new file mode 100644 index 00000000000..746b82928ed --- /dev/null +++ b/chromium/components/metrics/call_stack_profile_metrics_provider.h @@ -0,0 +1,83 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/profiler/stack_sampling_profiler.h" +#include "components/metrics/metrics_provider.h" + +namespace metrics { +class ChromeUserMetricsExtension; + +// Performs metrics logging for the stack sampling profiler. +class CallStackProfileMetricsProvider : public MetricsProvider { + public: + // The event that triggered the profile collection. + // This enum should be kept in sync with content/common/profiled_stack_state.h + enum Trigger { + UNKNOWN, + PROCESS_STARTUP, + JANKY_TASK, + THREAD_HUNG, + TRIGGER_LAST = THREAD_HUNG + }; + + // Parameters to pass back to the metrics provider. + struct Params { + explicit Params(Trigger trigger); + Params(Trigger trigger, bool preserve_sample_ordering); + + // The triggering event. + Trigger trigger; + + // True if sample ordering is important and should be preserved when the + // associated profiles are compressed. This should only be set to true if + // the intended use of the requires that the sequence of call stacks within + // a particular profile be preserved. The default value of false provides + // better compression of the encoded profile and is sufficient for the + // typical use case of recording profiles for stack frequency analysis in + // aggregate. + bool preserve_sample_ordering; + }; + + CallStackProfileMetricsProvider(); + ~CallStackProfileMetricsProvider() override; + + // Get a callback for use with StackSamplingProfiler that provides completed + // profiles to this object. The callback should be immediately passed to the + // StackSamplingProfiler, and should not be reused between + // StackSamplingProfilers. This function may be called on any thread. + static base::StackSamplingProfiler::CompletedCallback GetProfilerCallback( + const Params& params); + + // MetricsProvider: + void OnRecordingEnabled() override; + void OnRecordingDisabled() override; + void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override; + + protected: + // Finch field trial and group for reporting profiles. Provided here for test + // use. + static const char kFieldTrialName[]; + static const char kReportProfilesGroupName[]; + + // Reset the static state to the defaults after startup. + static void ResetStaticStateForTesting(); + + private: + // Returns true if reporting of profiles is enabled according to the + // controlling Finch field trial. + static bool IsReportingEnabledByFieldTrial(); + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc new file mode 100644 index 00000000000..e361137e168 --- /dev/null +++ b/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc @@ -0,0 +1,619 @@ +// Copyright 2015 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 "components/metrics/call_stack_profile_metrics_provider.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "base/metrics/field_trial.h" +#include "base/profiler/stack_sampling_profiler.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" +#include "components/variations/entropy_provider.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StackSamplingProfiler; +using Frame = StackSamplingProfiler::Frame; +using Module = StackSamplingProfiler::Module; +using Profile = StackSamplingProfiler::CallStackProfile; +using Profiles = StackSamplingProfiler::CallStackProfiles; +using Sample = StackSamplingProfiler::Sample; + +namespace metrics { + +using Params = CallStackProfileMetricsProvider::Params; + +// This test fixture enables the field trial that +// CallStackProfileMetricsProvider depends on to report profiles. +class CallStackProfileMetricsProviderTest : public testing::Test { + public: + CallStackProfileMetricsProviderTest() + : field_trial_list_(nullptr) { + base::FieldTrialList::CreateFieldTrial( + TestState::kFieldTrialName, + TestState::kReportProfilesGroupName); + TestState::ResetStaticStateForTesting(); + } + + ~CallStackProfileMetricsProviderTest() override {} + + // Utility function to append profiles to the metrics provider. + void AppendProfiles(const Params& params, const Profiles& profiles) { + CallStackProfileMetricsProvider::GetProfilerCallback(params).Run(profiles); + } + + private: + // Exposes field trial/group names from the CallStackProfileMetricsProvider. + class TestState : public CallStackProfileMetricsProvider { + public: + using CallStackProfileMetricsProvider::kFieldTrialName; + using CallStackProfileMetricsProvider::kReportProfilesGroupName; + using CallStackProfileMetricsProvider::ResetStaticStateForTesting; + }; + + base::FieldTrialList field_trial_list_; + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProviderTest); +}; + +// Checks that all properties from multiple profiles are filled as expected. +TEST_F(CallStackProfileMetricsProviderTest, MultipleProfiles) { + const uintptr_t module1_base_address = 0x1000; + const uintptr_t module2_base_address = 0x2000; + const uintptr_t module3_base_address = 0x3000; + + const Module profile_modules[][2] = { + { + Module( + module1_base_address, + "ABCD", +#if defined(OS_WIN) + base::FilePath(L"c:\\some\\path\\to\\chrome.exe") +#else + base::FilePath("/some/path/to/chrome") +#endif + ), + Module( + module2_base_address, + "EFGH", +#if defined(OS_WIN) + base::FilePath(L"c:\\some\\path\\to\\third_party.dll") +#else + base::FilePath("/some/path/to/third_party.so") +#endif + ), + }, + { + Module( + module3_base_address, + "MNOP", +#if defined(OS_WIN) + base::FilePath(L"c:\\some\\path\\to\\third_party2.dll") +#else + base::FilePath("/some/path/to/third_party2.so") +#endif + ), + Module( // Repeated from the first profile. + module1_base_address, + "ABCD", +#if defined(OS_WIN) + base::FilePath(L"c:\\some\\path\\to\\chrome.exe") +#else + base::FilePath("/some/path/to/chrome") +#endif + ) + } + }; + + // Values for Windows generated with: + // perl -MDigest::MD5=md5 -MEncode=encode + // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 encode "UTF-16LE", $_}' + // chrome.exe third_party.dll third_party2.dll + // + // Values for Linux generated with: + // perl -MDigest::MD5=md5 + // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 $_}' + // chrome third_party.so third_party2.so + const uint64_t profile_expected_name_md5_prefixes[][2] = { + { +#if defined(OS_WIN) + 0x46c3e4166659ac02ULL, 0x7e2b8bfddeae1abaULL +#else + 0x554838a8451ac36cUL, 0x843661148659c9f8UL +#endif + }, + { +#if defined(OS_WIN) + 0x87b66f4573a4d5caULL, 0x46c3e4166659ac02ULL +#else + 0xb4647e539fa6ec9eUL, 0x554838a8451ac36cUL +#endif + }}; + + // Represents two stack samples for each of two profiles, where each stack + // contains three frames. Each frame contains an instruction pointer and a + // module index corresponding to the module for the profile in + // profile_modules. + // + // So, the first stack sample below has its top frame in module 0 at an offset + // of 0x10 from the module's base address, the next-to-top frame in module 1 + // at an offset of 0x20 from the module's base address, and the bottom frame + // in module 0 at an offset of 0x30 from the module's base address + const Frame profile_sample_frames[][2][3] = { + { + { + Frame(module1_base_address + 0x10, 0), + Frame(module2_base_address + 0x20, 1), + Frame(module1_base_address + 0x30, 0) + }, + { + Frame(module2_base_address + 0x10, 1), + Frame(module1_base_address + 0x20, 0), + Frame(module2_base_address + 0x30, 1) + } + }, + { + { + Frame(module3_base_address + 0x10, 0), + Frame(module1_base_address + 0x20, 1), + Frame(module3_base_address + 0x30, 0) + }, + { + Frame(module1_base_address + 0x10, 1), + Frame(module3_base_address + 0x20, 0), + Frame(module1_base_address + 0x30, 1) + } + } + }; + + base::TimeDelta profile_durations[2] = { + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMilliseconds(200) + }; + + base::TimeDelta profile_sampling_periods[2] = { + base::TimeDelta::FromMilliseconds(10), + base::TimeDelta::FromMilliseconds(20) + }; + + std::vector<Profile> profiles; + for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) { + Profile profile; + profile.modules.insert( + profile.modules.end(), &profile_modules[i][0], + &profile_modules[i][0] + arraysize(profile_modules[i])); + + for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) { + profile.samples.push_back(Sample()); + Sample& sample = profile.samples.back(); + sample.insert(sample.end(), &profile_sample_frames[i][j][0], + &profile_sample_frames[i][j][0] + + arraysize(profile_sample_frames[i][j])); + } + + profile.profile_duration = profile_durations[i]; + profile.sampling_period = profile_sampling_periods[i]; + + profiles.push_back(profile); + } + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + AppendProfiles( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false), + profiles); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames)), + uma_proto.sampled_profile().size()); + for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) { + SCOPED_TRACE("profile " + base::SizeTToString(i)); + const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(i); + ASSERT_TRUE(sampled_profile.has_call_stack_profile()); + const CallStackProfile& call_stack_profile = + sampled_profile.call_stack_profile(); + + ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i])), + call_stack_profile.sample().size()); + for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) { + SCOPED_TRACE("sample " + base::SizeTToString(j)); + const CallStackProfile::Sample& proto_sample = + call_stack_profile.sample().Get(j); + ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i][j])), + proto_sample.entry().size()); + ASSERT_TRUE(proto_sample.has_count()); + EXPECT_EQ(1u, proto_sample.count()); + for (size_t k = 0; k < arraysize(profile_sample_frames[i][j]); ++k) { + SCOPED_TRACE("frame " + base::SizeTToString(k)); + const CallStackProfile::Entry& entry = proto_sample.entry().Get(k); + ASSERT_TRUE(entry.has_address()); + const char* instruction_pointer = reinterpret_cast<const char*>( + profile_sample_frames[i][j][k].instruction_pointer); + const char* module_base_address = reinterpret_cast<const char*>( + profile_modules[i][profile_sample_frames[i][j][k].module_index] + .base_address); + EXPECT_EQ( + static_cast<uint64_t>(instruction_pointer - module_base_address), + entry.address()); + ASSERT_TRUE(entry.has_module_id_index()); + EXPECT_EQ(profile_sample_frames[i][j][k].module_index, + static_cast<size_t>(entry.module_id_index())); + } + } + + ASSERT_EQ(static_cast<int>(arraysize(profile_modules[i])), + call_stack_profile.module_id().size()); + for (size_t j = 0; j < arraysize(profile_modules[i]); ++j) { + SCOPED_TRACE("module " + base::SizeTToString(j)); + const CallStackProfile::ModuleIdentifier& module_identifier = + call_stack_profile.module_id().Get(j); + ASSERT_TRUE(module_identifier.has_build_id()); + EXPECT_EQ(profile_modules[i][j].id, module_identifier.build_id()); + ASSERT_TRUE(module_identifier.has_name_md5_prefix()); + EXPECT_EQ(profile_expected_name_md5_prefixes[i][j], + module_identifier.name_md5_prefix()); + } + + ASSERT_TRUE(call_stack_profile.has_profile_duration_ms()); + EXPECT_EQ(profile_durations[i].InMilliseconds(), + call_stack_profile.profile_duration_ms()); + ASSERT_TRUE(call_stack_profile.has_sampling_period_ms()); + EXPECT_EQ(profile_sampling_periods[i].InMilliseconds(), + call_stack_profile.sampling_period_ms()); + ASSERT_TRUE(sampled_profile.has_trigger_event()); + EXPECT_EQ(SampledProfile::PROCESS_STARTUP, sampled_profile.trigger_event()); + } +} + +// Checks that all duplicate samples are collapsed with +// preserve_sample_ordering = false. +TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) { + const uintptr_t module_base_address = 0x1000; + + const Module modules[] = { + Module( + module_base_address, + "ABCD", +#if defined(OS_WIN) + base::FilePath(L"c:\\some\\path\\to\\chrome.exe") +#else + base::FilePath("/some/path/to/chrome") +#endif + ) + }; + + // Duplicate samples in slots 0, 2, and 3. + const Frame sample_frames[][1] = { + { Frame(module_base_address + 0x10, 0), }, + { Frame(module_base_address + 0x20, 0), }, + { Frame(module_base_address + 0x10, 0), }, + { Frame(module_base_address + 0x10, 0) } + }; + + Profile profile; + profile.modules.insert(profile.modules.end(), &modules[0], + &modules[0] + arraysize(modules)); + + for (size_t i = 0; i < arraysize(sample_frames); ++i) { + profile.samples.push_back(Sample()); + Sample& sample = profile.samples.back(); + sample.insert(sample.end(), &sample_frames[i][0], + &sample_frames[i][0] + arraysize(sample_frames[i])); + } + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + AppendProfiles( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false), + std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + ASSERT_EQ(1, uma_proto.sampled_profile().size()); + const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); + ASSERT_TRUE(sampled_profile.has_call_stack_profile()); + const CallStackProfile& call_stack_profile = + sampled_profile.call_stack_profile(); + + ASSERT_EQ(2, call_stack_profile.sample().size()); + for (int i = 0; i < 2; ++i) { + SCOPED_TRACE("sample " + base::IntToString(i)); + const CallStackProfile::Sample& proto_sample = + call_stack_profile.sample().Get(i); + ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])), + proto_sample.entry().size()); + ASSERT_TRUE(proto_sample.has_count()); + EXPECT_EQ(i == 0 ? 3u : 1u, proto_sample.count()); + for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) { + SCOPED_TRACE("frame " + base::SizeTToString(j)); + const CallStackProfile::Entry& entry = proto_sample.entry().Get(j); + ASSERT_TRUE(entry.has_address()); + const char* instruction_pointer = reinterpret_cast<const char*>( + sample_frames[i][j].instruction_pointer); + const char* module_base_address = reinterpret_cast<const char*>( + modules[sample_frames[i][j].module_index].base_address); + EXPECT_EQ( + static_cast<uint64_t>(instruction_pointer - module_base_address), + entry.address()); + ASSERT_TRUE(entry.has_module_id_index()); + EXPECT_EQ(sample_frames[i][j].module_index, + static_cast<size_t>(entry.module_id_index())); + } + } +} + +// Checks that only contiguous duplicate samples are collapsed with +// preserve_sample_ordering = true. +TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) { + const uintptr_t module_base_address = 0x1000; + + const Module modules[] = { + Module( + module_base_address, + "ABCD", +#if defined(OS_WIN) + base::FilePath(L"c:\\some\\path\\to\\chrome.exe") +#else + base::FilePath("/some/path/to/chrome") +#endif + ) + }; + + // Duplicate samples in slots 0, 2, and 3. + const Frame sample_frames[][1] = { + { Frame(module_base_address + 0x10, 0), }, + { Frame(module_base_address + 0x20, 0), }, + { Frame(module_base_address + 0x10, 0), }, + { Frame(module_base_address + 0x10, 0) } + }; + + Profile profile; + profile.modules.insert(profile.modules.end(), &modules[0], + &modules[0] + arraysize(modules)); + + for (size_t i = 0; i < arraysize(sample_frames); ++i) { + profile.samples.push_back(Sample()); + Sample& sample = profile.samples.back(); + sample.insert(sample.end(), &sample_frames[i][0], + &sample_frames[i][0] + arraysize(sample_frames[i])); + } + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + AppendProfiles(Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, true), + std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + ASSERT_EQ(1, uma_proto.sampled_profile().size()); + const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); + ASSERT_TRUE(sampled_profile.has_call_stack_profile()); + const CallStackProfile& call_stack_profile = + sampled_profile.call_stack_profile(); + + ASSERT_EQ(3, call_stack_profile.sample().size()); + for (int i = 0; i < 3; ++i) { + SCOPED_TRACE("sample " + base::IntToString(i)); + const CallStackProfile::Sample& proto_sample = + call_stack_profile.sample().Get(i); + ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])), + proto_sample.entry().size()); + ASSERT_TRUE(proto_sample.has_count()); + EXPECT_EQ(i == 2 ? 2u : 1u, proto_sample.count()); + for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) { + SCOPED_TRACE("frame " + base::SizeTToString(j)); + const CallStackProfile::Entry& entry = proto_sample.entry().Get(j); + ASSERT_TRUE(entry.has_address()); + const char* instruction_pointer = reinterpret_cast<const char*>( + sample_frames[i][j].instruction_pointer); + const char* module_base_address = reinterpret_cast<const char*>( + modules[sample_frames[i][j].module_index].base_address); + EXPECT_EQ( + static_cast<uint64_t>(instruction_pointer - module_base_address), + entry.address()); + ASSERT_TRUE(entry.has_module_id_index()); + EXPECT_EQ(sample_frames[i][j].module_index, + static_cast<size_t>(entry.module_id_index())); + } + } +} + +// Checks that unknown modules produce an empty Entry. +TEST_F(CallStackProfileMetricsProviderTest, UnknownModule) { + const Frame frame(0x1000, Frame::kUnknownModuleIndex); + + Profile profile; + + profile.samples.push_back(Sample(1, frame)); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + AppendProfiles( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false), + std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + ASSERT_EQ(1, uma_proto.sampled_profile().size()); + const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); + ASSERT_TRUE(sampled_profile.has_call_stack_profile()); + const CallStackProfile& call_stack_profile = + sampled_profile.call_stack_profile(); + + ASSERT_EQ(1, call_stack_profile.sample().size()); + const CallStackProfile::Sample& proto_sample = + call_stack_profile.sample().Get(0); + ASSERT_EQ(1, proto_sample.entry().size()); + ASSERT_TRUE(proto_sample.has_count()); + EXPECT_EQ(1u, proto_sample.count()); + const CallStackProfile::Entry& entry = proto_sample.entry().Get(0); + EXPECT_FALSE(entry.has_address()); + EXPECT_FALSE(entry.has_module_id_index()); +} + +// Checks that pending profiles are only passed back to ProvideGeneralMetrics +// once. +TEST_F(CallStackProfileMetricsProviderTest, ProfilesProvidedOnlyOnce) { + CallStackProfileMetricsProvider provider; + for (int i = 0; i < 2; ++i) { + Profile profile; + profile.samples.push_back( + Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex))); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + // Use the sampling period to distinguish the two profiles. + profile.sampling_period = base::TimeDelta::FromMilliseconds(i); + + provider.OnRecordingEnabled(); + AppendProfiles( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false), + std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + ASSERT_EQ(1, uma_proto.sampled_profile().size()); + const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0); + ASSERT_TRUE(sampled_profile.has_call_stack_profile()); + const CallStackProfile& call_stack_profile = + sampled_profile.call_stack_profile(); + ASSERT_TRUE(call_stack_profile.has_sampling_period_ms()); + EXPECT_EQ(i, call_stack_profile.sampling_period_ms()); + } +} + +// Checks that pending profiles are provided to ProvideGeneralMetrics +// when collected before CallStackProfileMetricsProvider is instantiated. +TEST_F(CallStackProfileMetricsProviderTest, + ProfilesProvidedWhenCollectedBeforeInstantiation) { + Profile profile; + profile.samples.push_back( + Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex))); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + AppendProfiles( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false), + std::vector<Profile>(1, profile)); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + EXPECT_EQ(1, uma_proto.sampled_profile_size()); +} + +// Checks that pending profiles are not provided to ProvideGeneralMetrics +// while recording is disabled. +TEST_F(CallStackProfileMetricsProviderTest, ProfilesNotProvidedWhileDisabled) { + Profile profile; + profile.samples.push_back( + Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex))); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingDisabled(); + AppendProfiles( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false), + std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +// Checks that pending profiles are not provided to ProvideGeneralMetrics +// if recording is disabled while profiling. +TEST_F(CallStackProfileMetricsProviderTest, + ProfilesNotProvidedAfterChangeToDisabled) { + Profile profile; + profile.samples.push_back( + Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex))); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + base::StackSamplingProfiler::CompletedCallback callback = + CallStackProfileMetricsProvider::GetProfilerCallback( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false)); + + provider.OnRecordingDisabled(); + callback.Run(std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +// Checks that pending profiles are not provided to ProvideGeneralMetrics if +// recording is enabled, but then disabled and reenabled while profiling. +TEST_F(CallStackProfileMetricsProviderTest, + ProfilesNotProvidedAfterChangeToDisabledThenEnabled) { + Profile profile; + profile.samples.push_back( + Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex))); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + base::StackSamplingProfiler::CompletedCallback callback = + CallStackProfileMetricsProvider::GetProfilerCallback( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false)); + + provider.OnRecordingDisabled(); + provider.OnRecordingEnabled(); + callback.Run(std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +// Checks that pending profiles are not provided to ProvideGeneralMetrics +// if recording is disabled, but then enabled while profiling. +TEST_F(CallStackProfileMetricsProviderTest, + ProfilesNotProvidedAfterChangeFromDisabled) { + Profile profile; + profile.samples.push_back( + Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex))); + + profile.profile_duration = base::TimeDelta::FromMilliseconds(100); + profile.sampling_period = base::TimeDelta::FromMilliseconds(10); + + CallStackProfileMetricsProvider provider; + provider.OnRecordingDisabled(); + base::StackSamplingProfiler::CompletedCallback callback = + CallStackProfileMetricsProvider::GetProfilerCallback( + Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false)); + + provider.OnRecordingEnabled(); + callback.Run(std::vector<Profile>(1, profile)); + ChromeUserMetricsExtension uma_proto; + provider.ProvideGeneralMetrics(&uma_proto); + + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/clean_exit_beacon.cc b/chromium/components/metrics/clean_exit_beacon.cc new file mode 100644 index 00000000000..f90f2d00626 --- /dev/null +++ b/chromium/components/metrics/clean_exit_beacon.cc @@ -0,0 +1,80 @@ +// Copyright 2014 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 "components/metrics/clean_exit_beacon.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_service.h" + +#if defined(OS_WIN) +#include "base/metrics/histogram.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/registry.h" +#endif + +namespace metrics { + +CleanExitBeacon::CleanExitBeacon(const base::string16& backup_registry_key, + PrefService* local_state) + : local_state_(local_state), + initial_value_(local_state->GetBoolean(prefs::kStabilityExitedCleanly)), + backup_registry_key_(backup_registry_key) { + DCHECK_NE(PrefService::INITIALIZATION_STATUS_WAITING, + local_state_->GetInitializationStatus()); + +#if defined(OS_WIN) + // An enumeration of all possible permutations of the the beacon state in the + // registry and in Local State. + enum { + DIRTY_DIRTY, + DIRTY_CLEAN, + CLEAN_DIRTY, + CLEAN_CLEAN, + MISSING_DIRTY, + MISSING_CLEAN, + NUM_CONSISTENCY_ENUMS + } consistency = DIRTY_DIRTY; + + base::win::RegKey regkey; + DWORD value = 0u; + if (regkey.Open(HKEY_CURRENT_USER, + backup_registry_key_.c_str(), + KEY_ALL_ACCESS) == ERROR_SUCCESS && + regkey.ReadValueDW( + base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(), &value) == + ERROR_SUCCESS) { + if (value) + consistency = initial_value_ ? CLEAN_CLEAN : CLEAN_DIRTY; + else + consistency = initial_value_ ? DIRTY_CLEAN : DIRTY_DIRTY; + } else { + consistency = initial_value_ ? MISSING_CLEAN : MISSING_DIRTY; + } + + UMA_HISTOGRAM_ENUMERATION( + "UMA.CleanExitBeaconConsistency", consistency, NUM_CONSISTENCY_ENUMS); +#endif +} + +CleanExitBeacon::~CleanExitBeacon() { +} + +void CleanExitBeacon::WriteBeaconValue(bool value) { + local_state_->SetBoolean(prefs::kStabilityExitedCleanly, value); + +#if defined(OS_WIN) + base::win::RegKey regkey; + if (regkey.Create(HKEY_CURRENT_USER, + backup_registry_key_.c_str(), + KEY_ALL_ACCESS) == ERROR_SUCCESS) { + regkey.WriteValue( + base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(), + value ? 1u : 0u); + } +#endif +} + +} // namespace metrics diff --git a/chromium/components/metrics/clean_exit_beacon.h b/chromium/components/metrics/clean_exit_beacon.h new file mode 100644 index 00000000000..6b896fa1784 --- /dev/null +++ b/chromium/components/metrics/clean_exit_beacon.h @@ -0,0 +1,45 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_ +#define COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_ + +#include "base/macros.h" +#include "base/strings/string16.h" + +class PrefService; + +namespace metrics { + +// Reads and updates a beacon used to detect whether the previous browser +// process exited cleanly. +class CleanExitBeacon { + public: + // Instantiates a CleanExitBeacon whose value is stored in |local_state|. + // |local_state| must be fully initialized. + // On Windows, |backup_registry_key| is used to store a backup of the beacon. + // It is ignored on other platforms. + CleanExitBeacon( + const base::string16& backup_registry_key, + PrefService* local_state); + + ~CleanExitBeacon(); + + // Returns the original value of the beacon. + bool exited_cleanly() const { return initial_value_; } + + // Writes the provided beacon value. + void WriteBeaconValue(bool exited_cleanly); + + private: + PrefService* const local_state_; + const bool initial_value_; + const base::string16 backup_registry_key_; + + DISALLOW_COPY_AND_ASSIGN(CleanExitBeacon); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_ diff --git a/chromium/components/metrics/client_info.cc b/chromium/components/metrics/client_info.cc new file mode 100644 index 00000000000..57ad3947c1b --- /dev/null +++ b/chromium/components/metrics/client_info.cc @@ -0,0 +1,13 @@ +// Copyright 2014 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 "components/metrics/client_info.h" + +namespace metrics { + +ClientInfo::ClientInfo() : installation_date(0), reporting_enabled_date(0) {} + +ClientInfo::~ClientInfo() {} + +} // namespace metrics diff --git a/chromium/components/metrics/client_info.h b/chromium/components/metrics/client_info.h new file mode 100644 index 00000000000..7dcf5d8de39 --- /dev/null +++ b/chromium/components/metrics/client_info.h @@ -0,0 +1,36 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_CLIENT_INFO_H_ +#define COMPONENTS_METRICS_CLIENT_INFO_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" + +namespace metrics { + +// A data object used to pass data from outside the metrics component into the +// metrics component. +struct ClientInfo { + public: + ClientInfo(); + ~ClientInfo(); + + // The metrics ID of this client: represented as a GUID string. + std::string client_id; + + // The installation date: represented as an epoch time in seconds. + int64_t installation_date; + + // The date on which metrics reporting was enabled: represented as an epoch + // time in seconds. + int64_t reporting_enabled_date; +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CLIENT_INFO_H_ diff --git a/chromium/components/metrics/cloned_install_detector.cc b/chromium/components/metrics/cloned_install_detector.cc new file mode 100644 index 00000000000..7e9be8c3f10 --- /dev/null +++ b/chromium/components/metrics/cloned_install_detector.cc @@ -0,0 +1,101 @@ +// Copyright 2014 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 "components/metrics/cloned_install_detector.h" + +#include <stdint.h> + +#include <string> + +#include "base/bind.h" +#include "base/location.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/metrics_hashes.h" +#include "base/single_thread_task_runner.h" +#include "base/task_runner_util.h" +#include "components/metrics/machine_id_provider.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace metrics { + +namespace { + +uint32_t HashRawId(const std::string& value) { + uint64_t hash = base::HashMetricName(value); + + // Only use 24 bits from the 64-bit hash. + return hash & ((1 << 24) - 1); +} + +// State of the generated machine id in relation to the previously stored value. +// Note: UMA histogram enum - don't re-order or remove entries +enum MachineIdState { + ID_GENERATION_FAILED, + ID_NO_STORED_VALUE, + ID_CHANGED, + ID_UNCHANGED, + ID_ENUM_SIZE +}; + +// Logs the state of generating a machine id and comparing it to a stored value. +void LogMachineIdState(MachineIdState state) { + UMA_HISTOGRAM_ENUMERATION("UMA.MachineIdState", state, ID_ENUM_SIZE); +} + +} // namespace + +ClonedInstallDetector::ClonedInstallDetector(MachineIdProvider* raw_id_provider) + : raw_id_provider_(raw_id_provider), weak_ptr_factory_(this) { +} + +ClonedInstallDetector::~ClonedInstallDetector() { +} + +void ClonedInstallDetector::CheckForClonedInstall( + PrefService* local_state, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + base::PostTaskAndReplyWithResult( + task_runner.get(), + FROM_HERE, + base::Bind(&MachineIdProvider::GetMachineId, raw_id_provider_), + base::Bind(&ClonedInstallDetector::SaveMachineId, + weak_ptr_factory_.GetWeakPtr(), + local_state)); +} + +void ClonedInstallDetector::SaveMachineId(PrefService* local_state, + const std::string& raw_id) { + if (raw_id.empty()) { + LogMachineIdState(ID_GENERATION_FAILED); + local_state->ClearPref(prefs::kMetricsMachineId); + return; + } + + int hashed_id = HashRawId(raw_id); + + MachineIdState id_state = ID_NO_STORED_VALUE; + if (local_state->HasPrefPath(prefs::kMetricsMachineId)) { + if (local_state->GetInteger(prefs::kMetricsMachineId) != hashed_id) { + id_state = ID_CHANGED; + // TODO(jwd): Use a callback to set the reset pref. That way + // ClonedInstallDetector doesn't need to know about this pref. + local_state->SetBoolean(prefs::kMetricsResetIds, true); + } else { + id_state = ID_UNCHANGED; + } + } + + LogMachineIdState(id_state); + + local_state->SetInteger(prefs::kMetricsMachineId, hashed_id); +} + +// static +void ClonedInstallDetector::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(prefs::kMetricsMachineId, 0); +} + +} // namespace metrics diff --git a/chromium/components/metrics/cloned_install_detector.h b/chromium/components/metrics/cloned_install_detector.h new file mode 100644 index 00000000000..0609122cdaf --- /dev/null +++ b/chromium/components/metrics/cloned_install_detector.h @@ -0,0 +1,60 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_ +#define COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_ + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" + +class PrefRegistrySimple; +class PrefService; + +namespace base { +class SingleThreadTaskRunner; +} + +namespace metrics { + +class MachineIdProvider; + +// A class for detecting if an install is cloned. It does this by detecting +// when the hardware running Chrome changes. +class ClonedInstallDetector { + public: + explicit ClonedInstallDetector(MachineIdProvider* raw_id_provider); + virtual ~ClonedInstallDetector(); + + // Posts a task to |task_runner| to generate a machine ID and store it to a + // local state pref. If the newly generated ID is different than the + // previously stored one, then the install is considered cloned. The ID is a + // 24-bit value based off of machine characteristics. This value should never + // be sent over the network. + // TODO(jwd): Implement change detection. + void CheckForClonedInstall( + PrefService* local_state, + scoped_refptr<base::SingleThreadTaskRunner> task_runner); + + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, SaveId); + FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, DetectClone); + + // Converts raw_id into a 24-bit hash and stores the hash in |local_state|. + // |raw_id| is not a const ref because it's passed from a cross-thread post + // task. + void SaveMachineId(PrefService* local_state, const std::string& raw_id); + + scoped_refptr<MachineIdProvider> raw_id_provider_; + base::WeakPtrFactory<ClonedInstallDetector> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ClonedInstallDetector); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_ diff --git a/chromium/components/metrics/cloned_install_detector_unittest.cc b/chromium/components/metrics/cloned_install_detector_unittest.cc new file mode 100644 index 00000000000..91b24e8bf57 --- /dev/null +++ b/chromium/components/metrics/cloned_install_detector_unittest.cc @@ -0,0 +1,53 @@ +// Copyright 2014 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 "components/metrics/cloned_install_detector.h" + +#include "components/metrics/machine_id_provider.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +const std::string kTestRawId = "test"; +// Hashed machine id for |kTestRawId|. +const int kTestHashedId = 2216819; + +} // namespace + +// TODO(jwd): Change these test to test the full flow and histogram outputs. It +// should also remove the need to make the test a friend of +// ClonedInstallDetector. +TEST(ClonedInstallDetectorTest, SaveId) { + TestingPrefServiceSimple prefs; + ClonedInstallDetector::RegisterPrefs(prefs.registry()); + + scoped_ptr<ClonedInstallDetector> detector( + new ClonedInstallDetector(MachineIdProvider::CreateInstance())); + + detector->SaveMachineId(&prefs, kTestRawId); + + EXPECT_EQ(kTestHashedId, prefs.GetInteger(prefs::kMetricsMachineId)); +} + +TEST(ClonedInstallDetectorTest, DetectClone) { + TestingPrefServiceSimple prefs; + MetricsStateManager::RegisterPrefs(prefs.registry()); + + // Save a machine id that will cause a clone to be detected. + prefs.SetInteger(prefs::kMetricsMachineId, kTestHashedId + 1); + + scoped_ptr<ClonedInstallDetector> detector( + new ClonedInstallDetector(MachineIdProvider::CreateInstance())); + + detector->SaveMachineId(&prefs, kTestRawId); + + EXPECT_TRUE(prefs.GetBoolean(prefs::kMetricsResetIds)); +} + +} // namespace metrics diff --git a/chromium/components/metrics/daily_event.cc b/chromium/components/metrics/daily_event.cc new file mode 100644 index 00000000000..2ab54909251 --- /dev/null +++ b/chromium/components/metrics/daily_event.cc @@ -0,0 +1,106 @@ +// Copyright 2014 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 "components/metrics/daily_event.h" + +#include <utility> + +#include "base/metrics/histogram.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace metrics { + +namespace { + +enum IntervalType { + FIRST_RUN, + DAY_ELAPSED, + CLOCK_CHANGED, + NUM_INTERVAL_TYPES +}; + +void RecordIntervalTypeHistogram(const std::string& histogram_name, + IntervalType type) { + base::Histogram::FactoryGet( + histogram_name, + 1, + NUM_INTERVAL_TYPES, + NUM_INTERVAL_TYPES + 1, + base::HistogramBase::kUmaTargetedHistogramFlag)->Add(type); +} + +} // namespace + +DailyEvent::Observer::Observer() { +} + +DailyEvent::Observer::~Observer() { +} + +DailyEvent::DailyEvent(PrefService* pref_service, + const char* pref_name, + const std::string& histogram_name) + : pref_service_(pref_service), + pref_name_(pref_name), + histogram_name_(histogram_name) { +} + +DailyEvent::~DailyEvent() { +} + +// static +void DailyEvent::RegisterPref(PrefRegistrySimple* registry, + const char* pref_name) { + registry->RegisterInt64Pref(pref_name, base::Time().ToInternalValue()); +} + +void DailyEvent::AddObserver(scoped_ptr<DailyEvent::Observer> observer) { + DVLOG(2) << "DailyEvent observer added."; + DCHECK(last_fired_.is_null()); + observers_.push_back(std::move(observer)); +} + +void DailyEvent::CheckInterval() { + base::Time now = base::Time::Now(); + if (last_fired_.is_null()) { + // The first time we call CheckInterval, we read the time stored in prefs. + last_fired_ = base::Time::FromInternalValue( + pref_service_->GetInt64(pref_name_)); + DVLOG(1) << "DailyEvent time loaded: " << last_fired_; + if (last_fired_.is_null()) { + DVLOG(1) << "DailyEvent first run."; + RecordIntervalTypeHistogram(histogram_name_, FIRST_RUN); + OnInterval(now); + return; + } + } + int days_elapsed = (now - last_fired_).InDays(); + if (days_elapsed >= 1) { + DVLOG(1) << "DailyEvent day elapsed."; + RecordIntervalTypeHistogram(histogram_name_, DAY_ELAPSED); + OnInterval(now); + } else if (days_elapsed <= -1) { + // The "last fired" time is more than a day in the future, so the clock + // must have been changed. + DVLOG(1) << "DailyEvent clock change detected."; + RecordIntervalTypeHistogram(histogram_name_, CLOCK_CHANGED); + OnInterval(now); + } +} + +void DailyEvent::OnInterval(base::Time now) { + DCHECK(!now.is_null()); + last_fired_ = now; + pref_service_->SetInt64(pref_name_, last_fired_.ToInternalValue()); + + // Notify all observers + for (ScopedVector<DailyEvent::Observer>::iterator it = observers_.begin(); + it != observers_.end(); + ++it) { + (*it)->OnDailyEvent(); + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/daily_event.h b/chromium/components/metrics/daily_event.h new file mode 100644 index 00000000000..24b31dc58ea --- /dev/null +++ b/chromium/components/metrics/daily_event.h @@ -0,0 +1,92 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_DAILY_EVENT_H_ +#define COMPONENTS_METRICS_DAILY_EVENT_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/time/time.h" + +class PrefRegistrySimple; +class PrefService; + +namespace metrics { + +// DailyEvent is used for throttling an event to about once per day, even if +// the program is restarted more frequently. It is based on local machine +// time, so it could be fired more often if the clock is changed. +// +// The service using the DailyEvent should first provide all of the Observers +// for the interval, and then arrange for CheckInterval() to be called +// periodically to test if the event should be fired. +class DailyEvent { + public: + // Observer receives notifications from a DailyEvent. + // Observers must be added before the DailyEvent begins checking time, + // and will be owned by the DailyEvent. + class Observer { + public: + Observer(); + virtual ~Observer(); + + // Called when the daily event is fired. + virtual void OnDailyEvent() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(Observer); + }; + + // Constructs DailyEvent monitor which stores the time it last fired in the + // preference |pref_name|. |pref_name| should be registered by calling + // RegisterPref before using this object. + // Caller is responsible for ensuring that |pref_service| and |pref_name| + // outlive the DailyEvent. + // |histogram_name| is the name of the UMA metric which record when this + // interval fires, and should be registered in histograms.xml + DailyEvent(PrefService* pref_service, + const char* pref_name, + const std::string& histogram_name); + ~DailyEvent(); + + // Adds a observer to be notified when a day elapses. All observers should + // be registered before the the DailyEvent starts checking time. + void AddObserver(scoped_ptr<Observer> observer); + + // Checks if a day has elapsed. If it has, OnDailyEvent will be called on + // all observers. + void CheckInterval(); + + // Registers the preference used by this interval. + static void RegisterPref(PrefRegistrySimple* registry, const char* pref_name); + + private: + // Handles an interval elapsing. + void OnInterval(base::Time now); + + // A weak pointer to the PrefService object to read and write preferences + // from. Calling code should ensure this object continues to exist for the + // lifetime of the DailyEvent object. + PrefService* pref_service_; + + // The name of the preference to store the last fired time in. + // Calling code should ensure this outlives the DailyEvent. + const char* pref_name_; + + // The name of the histogram to record intervals. + std::string histogram_name_; + + // A list of observers. + ScopedVector<Observer> observers_; + + // The time that the daily event was last fired. + base::Time last_fired_; + + DISALLOW_COPY_AND_ASSIGN(DailyEvent); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_DAILY_EVENT_H_ diff --git a/chromium/components/metrics/daily_event_unittest.cc b/chromium/components/metrics/daily_event_unittest.cc new file mode 100644 index 00000000000..4adbaaf92be --- /dev/null +++ b/chromium/components/metrics/daily_event_unittest.cc @@ -0,0 +1,94 @@ +// Copyright 2014 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 "components/metrics/daily_event.h" + +#include "base/macros.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +const char kTestPrefName[] = "TestPref"; +const char kTestMetricName[] = "TestMetric"; + +class TestDailyObserver : public DailyEvent::Observer { + public: + TestDailyObserver() : fired_(false) {} + + bool fired() const { return fired_; } + + void OnDailyEvent() override { fired_ = true; } + + void Reset() { + fired_ = false; + } + + private: + // True if this event has been observed. + bool fired_; + + DISALLOW_COPY_AND_ASSIGN(TestDailyObserver); +}; + +class DailyEventTest : public testing::Test { + public: + DailyEventTest() : event_(&prefs_, kTestPrefName, kTestMetricName) { + DailyEvent::RegisterPref(prefs_.registry(), kTestPrefName); + observer_ = new TestDailyObserver(); + event_.AddObserver(make_scoped_ptr(observer_)); + } + + protected: + TestingPrefServiceSimple prefs_; + TestDailyObserver* observer_; + DailyEvent event_; + + private: + DISALLOW_COPY_AND_ASSIGN(DailyEventTest); +}; + +} // namespace + +// The event should fire if the preference is not available. +TEST_F(DailyEventTest, TestNewFires) { + event_.CheckInterval(); + EXPECT_TRUE(observer_->fired()); +} + +// The event should fire if the preference is more than a day old. +TEST_F(DailyEventTest, TestOldFires) { + base::Time last_time = base::Time::Now() - base::TimeDelta::FromHours(25); + prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue()); + event_.CheckInterval(); + EXPECT_TRUE(observer_->fired()); +} + +// The event should fire if the preference is more than a day in the future. +TEST_F(DailyEventTest, TestFutureFires) { + base::Time last_time = base::Time::Now() + base::TimeDelta::FromHours(25); + prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue()); + event_.CheckInterval(); + EXPECT_TRUE(observer_->fired()); +} + +// The event should not fire if the preference is more recent than a day. +TEST_F(DailyEventTest, TestRecentNotFired) { + base::Time last_time = base::Time::Now() - base::TimeDelta::FromMinutes(2); + prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue()); + event_.CheckInterval(); + EXPECT_FALSE(observer_->fired()); +} + +// The event should not fire if the preference is less than a day in the future. +TEST_F(DailyEventTest, TestSoonNotFired) { + base::Time last_time = base::Time::Now() + base::TimeDelta::FromMinutes(2); + prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue()); + event_.CheckInterval(); + EXPECT_FALSE(observer_->fired()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/data_use_tracker.cc b/chromium/components/metrics/data_use_tracker.cc new file mode 100644 index 00000000000..a74bdf117eb --- /dev/null +++ b/chromium/components/metrics/data_use_tracker.cc @@ -0,0 +1,205 @@ +// Copyright 2016 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 "components/metrics/data_use_tracker.h" + +#include <string> + +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/variations/variations_associated_data.h" + +namespace metrics { + +namespace { + +// This function is for forwarding metrics usage pref changes to the appropriate +// callback on the appropriate thread. +// TODO(gayane): Reduce the frequency of posting tasks from IO to UI thread. +void UpdateMetricsUsagePrefs( + const UpdateUsagePrefCallbackType& update_on_ui_callback, + scoped_refptr<base::SequencedTaskRunner> ui_task_runner, + const std::string& service_name, + int message_size, + bool is_cellular) { + ui_task_runner->PostTask( + FROM_HERE, base::Bind(update_on_ui_callback, service_name, message_size, + is_cellular)); +} + +} // namespace + +DataUseTracker::DataUseTracker(PrefService* local_state) + : local_state_(local_state), weak_ptr_factory_(this) {} + +DataUseTracker::~DataUseTracker() {} + +// static +scoped_ptr<DataUseTracker> DataUseTracker::Create(PrefService* local_state) { + scoped_ptr<DataUseTracker> data_use_tracker; + if (variations::GetVariationParamValue("UMA_EnableCellularLogUpload", + "Uma_Quota") != "" && + variations::GetVariationParamValue("UMA_EnableCellularLogUpload", + "Uma_Ratio") != "") { + data_use_tracker.reset(new DataUseTracker(local_state)); + } + return data_use_tracker; +} + +// static +void DataUseTracker::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse); + registry->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse); +} + +UpdateUsagePrefCallbackType DataUseTracker::GetDataUseForwardingCallback( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner) { + DCHECK(ui_task_runner->RunsTasksOnCurrentThread()); + + return base::Bind( + &UpdateMetricsUsagePrefs, + base::Bind(&DataUseTracker::UpdateMetricsUsagePrefsOnUIThread, + weak_ptr_factory_.GetWeakPtr()), + ui_task_runner); +} + +bool DataUseTracker::ShouldUploadLogOnCellular(int log_bytes) { + DCHECK(thread_checker_.CalledOnValidThread()); + + RemoveExpiredEntries(); + + int uma_weekly_quota_bytes; + if (!GetUmaWeeklyQuota(&uma_weekly_quota_bytes)) + return true; + + int uma_total_data_use = ComputeTotalDataUse(prefs::kUmaCellDataUse); + int new_uma_total_data_use = log_bytes + uma_total_data_use; + // If the new log doesn't increase the total UMA traffic to be above the + // allowed quota then the log should be uploaded. + if (new_uma_total_data_use <= uma_weekly_quota_bytes) + return true; + + double uma_ratio; + if (!GetUmaRatio(&uma_ratio)) + return true; + + int user_total_data_use = ComputeTotalDataUse(prefs::kUserCellDataUse); + // If after adding the new log the uma ratio is still under the allowed ratio + // then the log should be uploaded and vice versa. + return new_uma_total_data_use / + static_cast<double>(log_bytes + user_total_data_use) <= + uma_ratio; +} + +void DataUseTracker::UpdateMetricsUsagePrefsOnUIThread( + const std::string& service_name, + int message_size, + bool is_celllular) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!is_celllular) + return; + + UpdateUsagePref(prefs::kUserCellDataUse, message_size); + if (service_name == "UMA") + UpdateUsagePref(prefs::kUmaCellDataUse, message_size); +} + +void DataUseTracker::UpdateUsagePref(const std::string& pref_name, + int message_size) { + DCHECK(thread_checker_.CalledOnValidThread()); + + DictionaryPrefUpdate pref_updater(local_state_, pref_name); + int todays_traffic = 0; + std::string todays_key = GetCurrentMeasurementDateAsString(); + + const base::DictionaryValue* user_pref_dict = + local_state_->GetDictionary(pref_name); + user_pref_dict->GetInteger(todays_key, &todays_traffic); + pref_updater->SetInteger(todays_key, todays_traffic + message_size); +} + +void DataUseTracker::RemoveExpiredEntries() { + DCHECK(thread_checker_.CalledOnValidThread()); + RemoveExpiredEntriesForPref(prefs::kUmaCellDataUse); + RemoveExpiredEntriesForPref(prefs::kUserCellDataUse); +} + +void DataUseTracker::RemoveExpiredEntriesForPref(const std::string& pref_name) { + DCHECK(thread_checker_.CalledOnValidThread()); + + const base::DictionaryValue* user_pref_dict = + local_state_->GetDictionary(pref_name); + const base::Time current_date = GetCurrentMeasurementDate(); + const base::Time week_ago = current_date - base::TimeDelta::FromDays(7); + + base::DictionaryValue user_pref_new_dict; + for (base::DictionaryValue::Iterator it(*user_pref_dict); !it.IsAtEnd(); + it.Advance()) { + base::Time key_date; + base::Time::FromUTCString(it.key().c_str(), &key_date); + if (key_date > week_ago) + user_pref_new_dict.Set(it.key(), it.value().CreateDeepCopy()); + } + local_state_->Set(pref_name, user_pref_new_dict); +} + +// Note: We compute total data use regardless of what is the current date. In +// scenario when user travels back in time zone and current date becomes earlier +// than latest registered date in perf, we still count that in total use as user +// actually used that data. +int DataUseTracker::ComputeTotalDataUse(const std::string& pref_name) { + DCHECK(thread_checker_.CalledOnValidThread()); + + int total_data_use = 0; + const base::DictionaryValue* pref_dict = + local_state_->GetDictionary(pref_name); + for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd(); + it.Advance()) { + int value = 0; + it.value().GetAsInteger(&value); + total_data_use += value; + } + return total_data_use; +} + +bool DataUseTracker::GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const { + DCHECK(thread_checker_.CalledOnValidThread()); + + std::string param_value_str = variations::GetVariationParamValue( + "UMA_EnableCellularLogUpload", "Uma_Quota"); + if (param_value_str.empty()) + return false; + + base::StringToInt(param_value_str, uma_weekly_quota_bytes); + return true; +} + +bool DataUseTracker::GetUmaRatio(double* ratio) const { + DCHECK(thread_checker_.CalledOnValidThread()); + + std::string param_value_str = variations::GetVariationParamValue( + "UMA_EnableCellularLogUpload", "Uma_Ratio"); + if (param_value_str.empty()) + return false; + base::StringToDouble(param_value_str, ratio); + return true; +} + +base::Time DataUseTracker::GetCurrentMeasurementDate() const { + return base::Time::Now().LocalMidnight(); +} + +std::string DataUseTracker::GetCurrentMeasurementDateAsString() const { + DCHECK(thread_checker_.CalledOnValidThread()); + + base::Time::Exploded today_exploded; + GetCurrentMeasurementDate().LocalExplode(&today_exploded); + return base::StringPrintf("%04d-%02d-%02d", today_exploded.year, + today_exploded.month, today_exploded.day_of_month); +} + +} // namespace metrics diff --git a/chromium/components/metrics/data_use_tracker.h b/chromium/components/metrics/data_use_tracker.h new file mode 100644 index 00000000000..5682a0c0720 --- /dev/null +++ b/chromium/components/metrics/data_use_tracker.h @@ -0,0 +1,99 @@ +// Copyright 2016 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. + +#ifndef COMPONENTS_METRICS_DATA_USE_TRACKER_H_ +#define COMPONENTS_METRICS_DATA_USE_TRACKER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace metrics { + +typedef base::Callback<void(const std::string&, int, bool)> + UpdateUsagePrefCallbackType; + +// Records the data use of user traffic and UMA traffic in user prefs. Taking +// into account those prefs it can verify whether certain UMA log upload is +// allowed. +class DataUseTracker { + public: + explicit DataUseTracker(PrefService* local_state); + ~DataUseTracker(); + + // Returns an instance of |DataUseTracker| with provided |local_state| if + // users data use should be tracked and null pointer otherwise. + static scoped_ptr<DataUseTracker> Create(PrefService* local_state); + + // Registers data use prefs using provided |registry|. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // Returns a callback to data use pref updating function. Should be called on + // UI thread. + UpdateUsagePrefCallbackType GetDataUseForwardingCallback( + scoped_refptr<base::SequencedTaskRunner> ui_task_runner); + + // Returns whether a log with provided |log_bytes| can be uploaded according + // to data use ratio and UMA quota provided by variations. + bool ShouldUploadLogOnCellular(int log_bytes); + + private: + FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckUpdateUsagePref); + FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckRemoveExpiredEntries); + FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckComputeTotalDataUse); + FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckCanUploadUMALog); + + // Updates data usage prefs on UI thread according to what Prefservice + // expects. + void UpdateMetricsUsagePrefsOnUIThread(const std::string& service_name, + int message_size, + bool is_cellular); + + // Updates provided |pref_name| for a current date with the given message + // size. + void UpdateUsagePref(const std::string& pref_name, int message_size); + + // Removes entries from the all data use prefs. + void RemoveExpiredEntries(); + + // Removes entries from the given |pref_name| if they are more than 7 days + // old. + void RemoveExpiredEntriesForPref(const std::string& pref_name); + + // Computes data usage according to all the entries in the given dictionary + // pref. + int ComputeTotalDataUse(const std::string& pref_name); + + // Returns the weekly allowed quota for UMA data use. + virtual bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const; + + // Returns the allowed ratio for UMA data use over overall data use. + virtual bool GetUmaRatio(double* ratio) const; + + // Returns the current date for measurement. + virtual base::Time GetCurrentMeasurementDate() const; + + // Returns the current date as a string with a proper formatting. + virtual std::string GetCurrentMeasurementDateAsString() const; + + PrefService* local_state_; + + base::ThreadChecker thread_checker_; + + base::WeakPtrFactory<DataUseTracker> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DataUseTracker); +}; + +} // namespace metrics +#endif // COMPONENTS_METRICS_DATA_USE_TRACKER_H_ diff --git a/chromium/components/metrics/data_use_tracker_unittest.cc b/chromium/components/metrics/data_use_tracker_unittest.cc new file mode 100644 index 00000000000..ed32a4a2024 --- /dev/null +++ b/chromium/components/metrics/data_use_tracker_unittest.cc @@ -0,0 +1,206 @@ +// Copyright 2016 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 "components/metrics/data_use_tracker.h" + +#include "base/strings/stringprintf.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +const char kTodayStr[] = "2016-03-16"; +const char kYesterdayStr[] = "2016-03-15"; +const char kExpiredDateStr1[] = "2016-03-09"; +const char kExpiredDateStr2[] = "2016-03-01"; + +class TestDataUsePrefService : public TestingPrefServiceSimple { + public: + TestDataUsePrefService() { + registry()->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse); + registry()->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse); + } + + void ClearDataUsePrefs() { + ClearPref(metrics::prefs::kUserCellDataUse); + ClearPref(metrics::prefs::kUmaCellDataUse); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestDataUsePrefService); +}; + +class FakeDataUseTracker : public DataUseTracker { + public: + FakeDataUseTracker(PrefService* local_state) : DataUseTracker(local_state) {} + + bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const override { + *uma_weekly_quota_bytes = 200; + return true; + } + + bool GetUmaRatio(double* ratio) const override { + *ratio = 0.05; + return true; + } + + base::Time GetCurrentMeasurementDate() const override { + base::Time today_for_test; + base::Time::FromUTCString(kTodayStr, &today_for_test); + return today_for_test; + } + + std::string GetCurrentMeasurementDateAsString() const override { + return kTodayStr; + } + + private: + DISALLOW_COPY_AND_ASSIGN(FakeDataUseTracker); +}; + +// Sets up data usage prefs with mock values so that UMA traffic is above the +// allowed ratio. +void SetPrefTestValuesOverRatio(PrefService* local_state) { + base::DictionaryValue user_pref_dict; + user_pref_dict.SetInteger(kTodayStr, 2 * 100); + user_pref_dict.SetInteger(kYesterdayStr, 2 * 100); + user_pref_dict.SetInteger(kExpiredDateStr1, 2 * 100); + user_pref_dict.SetInteger(kExpiredDateStr2, 2 * 100); + local_state->Set(prefs::kUserCellDataUse, user_pref_dict); + + base::DictionaryValue uma_pref_dict; + uma_pref_dict.SetInteger(kTodayStr, 50); + uma_pref_dict.SetInteger(kYesterdayStr, 50); + uma_pref_dict.SetInteger(kExpiredDateStr1, 50); + uma_pref_dict.SetInteger(kExpiredDateStr2, 50); + local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict); +} + +// Sets up data usage prefs with mock values which can be valid. +void SetPrefTestValuesValidRatio(PrefService* local_state) { + base::DictionaryValue user_pref_dict; + user_pref_dict.SetInteger(kTodayStr, 100 * 100); + user_pref_dict.SetInteger(kYesterdayStr, 100 * 100); + user_pref_dict.SetInteger(kExpiredDateStr1, 100 * 100); + user_pref_dict.SetInteger(kExpiredDateStr2, 100 * 100); + local_state->Set(prefs::kUserCellDataUse, user_pref_dict); + + // Should be 4% of user traffic + base::DictionaryValue uma_pref_dict; + uma_pref_dict.SetInteger(kTodayStr, 4 * 100); + uma_pref_dict.SetInteger(kYesterdayStr, 4 * 100); + uma_pref_dict.SetInteger(kExpiredDateStr1, 4 * 100); + uma_pref_dict.SetInteger(kExpiredDateStr2, 4 * 100); + local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict); +} + +} // namespace + +TEST(DataUseTrackerTest, CheckUpdateUsagePref) { + TestDataUsePrefService local_state; + FakeDataUseTracker data_use_tracker(&local_state); + local_state.ClearDataUsePrefs(); + + int user_pref_value = 0; + int uma_pref_value = 0; + + data_use_tracker.UpdateMetricsUsagePrefsOnUIThread("", 2 * 100, true); + local_state.GetDictionary(prefs::kUserCellDataUse) + ->GetInteger(kTodayStr, &user_pref_value); + EXPECT_EQ(2 * 100, user_pref_value); + local_state.GetDictionary(prefs::kUmaCellDataUse) + ->GetInteger(kTodayStr, &uma_pref_value); + EXPECT_EQ(0, uma_pref_value); + + data_use_tracker.UpdateMetricsUsagePrefsOnUIThread("UMA", 100, true); + local_state.GetDictionary(prefs::kUserCellDataUse) + ->GetInteger(kTodayStr, &user_pref_value); + EXPECT_EQ(3 * 100, user_pref_value); + local_state.GetDictionary(prefs::kUmaCellDataUse) + ->GetInteger(kTodayStr, &uma_pref_value); + EXPECT_EQ(100, uma_pref_value); +} + +TEST(DataUseTrackerTest, CheckRemoveExpiredEntries) { + TestDataUsePrefService local_state; + FakeDataUseTracker data_use_tracker(&local_state); + local_state.ClearDataUsePrefs(); + SetPrefTestValuesOverRatio(&local_state); + data_use_tracker.RemoveExpiredEntries(); + + int user_pref_value = 0; + int uma_pref_value = 0; + + local_state.GetDictionary(prefs::kUserCellDataUse) + ->GetInteger(kExpiredDateStr1, &user_pref_value); + EXPECT_EQ(0, user_pref_value); + local_state.GetDictionary(prefs::kUmaCellDataUse) + ->GetInteger(kExpiredDateStr1, &uma_pref_value); + EXPECT_EQ(0, uma_pref_value); + + local_state.GetDictionary(prefs::kUserCellDataUse) + ->GetInteger(kExpiredDateStr2, &user_pref_value); + EXPECT_EQ(0, user_pref_value); + local_state.GetDictionary(prefs::kUmaCellDataUse) + ->GetInteger(kExpiredDateStr2, &uma_pref_value); + EXPECT_EQ(0, uma_pref_value); + + local_state.GetDictionary(prefs::kUserCellDataUse) + ->GetInteger(kTodayStr, &user_pref_value); + EXPECT_EQ(2 * 100, user_pref_value); + local_state.GetDictionary(prefs::kUmaCellDataUse) + ->GetInteger(kTodayStr, &uma_pref_value); + EXPECT_EQ(50, uma_pref_value); + + local_state.GetDictionary(prefs::kUserCellDataUse) + ->GetInteger(kYesterdayStr, &user_pref_value); + EXPECT_EQ(2 * 100, user_pref_value); + local_state.GetDictionary(prefs::kUmaCellDataUse) + ->GetInteger(kYesterdayStr, &uma_pref_value); + EXPECT_EQ(50, uma_pref_value); +} + +TEST(DataUseTrackerTest, CheckComputeTotalDataUse) { + TestDataUsePrefService local_state; + FakeDataUseTracker data_use_tracker(&local_state); + local_state.ClearDataUsePrefs(); + SetPrefTestValuesOverRatio(&local_state); + + int user_data_use = + data_use_tracker.ComputeTotalDataUse(prefs::kUserCellDataUse); + EXPECT_EQ(8 * 100, user_data_use); + int uma_data_use = + data_use_tracker.ComputeTotalDataUse(prefs::kUmaCellDataUse); + EXPECT_EQ(4 * 50, uma_data_use); +} + +TEST(DataUseTrackerTest, CheckShouldUploadLogOnCellular) { + TestDataUsePrefService local_state; + FakeDataUseTracker data_use_tracker(&local_state); + local_state.ClearDataUsePrefs(); + SetPrefTestValuesOverRatio(&local_state); + + bool can_upload = data_use_tracker.ShouldUploadLogOnCellular(50); + EXPECT_TRUE(can_upload); + can_upload = data_use_tracker.ShouldUploadLogOnCellular(100); + EXPECT_TRUE(can_upload); + can_upload = data_use_tracker.ShouldUploadLogOnCellular(150); + EXPECT_FALSE(can_upload); + + local_state.ClearDataUsePrefs(); + SetPrefTestValuesValidRatio(&local_state); + can_upload = data_use_tracker.ShouldUploadLogOnCellular(100); + EXPECT_TRUE(can_upload); + // this is about 0.49% + can_upload = data_use_tracker.ShouldUploadLogOnCellular(200); + EXPECT_TRUE(can_upload); + can_upload = data_use_tracker.ShouldUploadLogOnCellular(300); + EXPECT_FALSE(can_upload); +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider.cc b/chromium/components/metrics/drive_metrics_provider.cc new file mode 100644 index 00000000000..22ad0891919 --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider.cc @@ -0,0 +1,97 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/path_service.h" +#include "base/task_runner_util.h" +#include "base/time/time.h" + +namespace metrics { + +DriveMetricsProvider::DriveMetricsProvider( + scoped_refptr<base::SequencedTaskRunner> file_thread, + int local_state_path_key) + : file_thread_(file_thread), + local_state_path_key_(local_state_path_key), + weak_ptr_factory_(this) {} + +DriveMetricsProvider::~DriveMetricsProvider() {} + +void DriveMetricsProvider::ProvideSystemProfileMetrics( + metrics::SystemProfileProto* system_profile_proto) { + auto* hardware = system_profile_proto->mutable_hardware(); + FillDriveMetrics(metrics_.app_drive, hardware->mutable_app_drive()); + FillDriveMetrics(metrics_.user_data_drive, + hardware->mutable_user_data_drive()); +} + +void DriveMetricsProvider::GetDriveMetrics(const base::Closure& done_callback) { + base::PostTaskAndReplyWithResult( + file_thread_.get(), FROM_HERE, + base::Bind(&DriveMetricsProvider::GetDriveMetricsOnFileThread, + local_state_path_key_), + base::Bind(&DriveMetricsProvider::GotDriveMetrics, + weak_ptr_factory_.GetWeakPtr(), done_callback)); +} + +DriveMetricsProvider::SeekPenaltyResponse::SeekPenaltyResponse() + : success(false) {} + +// static +DriveMetricsProvider::DriveMetrics +DriveMetricsProvider::GetDriveMetricsOnFileThread(int local_state_path_key) { + DriveMetricsProvider::DriveMetrics metrics; + QuerySeekPenalty(base::FILE_EXE, &metrics.app_drive); + QuerySeekPenalty(local_state_path_key, &metrics.user_data_drive); + return metrics; +} + +// static +void DriveMetricsProvider::QuerySeekPenalty( + int path_service_key, + DriveMetricsProvider::SeekPenaltyResponse* response) { + DCHECK(response); + + base::FilePath path; + if (!PathService::Get(path_service_key, &path)) + return; + + base::TimeTicks start = base::TimeTicks::Now(); + + response->success = HasSeekPenalty(path, &response->has_seek_penalty); + + UMA_HISTOGRAM_TIMES("Hardware.Drive.HasSeekPenalty_Time", + base::TimeTicks::Now() - start); + UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty_Success", + response->success); + if (response->success) { + UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty", + response->has_seek_penalty); + } +} + +void DriveMetricsProvider::GotDriveMetrics( + const base::Closure& done_callback, + const DriveMetricsProvider::DriveMetrics& metrics) { + DCHECK(thread_checker_.CalledOnValidThread()); + metrics_ = metrics; + done_callback.Run(); +} + +void DriveMetricsProvider::FillDriveMetrics( + const DriveMetricsProvider::SeekPenaltyResponse& response, + metrics::SystemProfileProto::Hardware::Drive* drive) { + if (response.success) + drive->set_has_seek_penalty(response.has_seek_penalty); +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider.h b/chromium/components/metrics/drive_metrics_provider.h new file mode 100644 index 00000000000..cab799d40ea --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider.h @@ -0,0 +1,100 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_ + +#include "base/callback_forward.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_checker.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/proto/system_profile.pb.h" + +namespace base { +class FilePath; +} + +namespace metrics { + +// Provides metrics about the local drives on a user's computer. Currently only +// checks to see if they incur a seek-time penalty (e.g. if they're SSDs). +// +// Defers gathering metrics until after "rush hour" (startup) so as to not bog +// down the file thread. +class DriveMetricsProvider : public metrics::MetricsProvider { + public: + DriveMetricsProvider(scoped_refptr<base::SequencedTaskRunner> file_thread, + int local_state_path_key); + ~DriveMetricsProvider() override; + + // metrics::MetricsDataProvider: + void ProvideSystemProfileMetrics( + metrics::SystemProfileProto* system_profile_proto) override; + + // Called to start gathering metrics. + void GetDriveMetrics(const base::Closure& done_callback); + + private: + FRIEND_TEST_ALL_PREFIXES(DriveMetricsProviderTest, HasSeekPenalty); + + // A response to querying a drive as to whether it incurs a seek penalty. + // |has_seek_penalty| is set if |success| is true. + struct SeekPenaltyResponse { + SeekPenaltyResponse(); + bool success; + bool has_seek_penalty; + }; + + struct DriveMetrics { + SeekPenaltyResponse app_drive; + SeekPenaltyResponse user_data_drive; + }; + + // Determine whether the device that services |path| has a seek penalty. + // Returns false if it couldn't be determined (e.g., |path| doesn't exist). + static bool HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty); + + // Gather metrics about various drives on |file_thread_|. + static DriveMetrics GetDriveMetricsOnFileThread(int local_state_path_key); + + // Tries to determine whether there is a penalty for seeking on the drive that + // hosts |path_service_key| (for example: the drive that holds "Local State"). + static void QuerySeekPenalty(int path_service_key, + SeekPenaltyResponse* response); + + // Called when metrics are done being gathered from the FILE thread. + // |done_callback| is the callback that should be called once all metrics are + // gathered. + void GotDriveMetrics(const base::Closure& done_callback, + const DriveMetrics& metrics); + + // Fills |drive| with information from successful |response|s. + void FillDriveMetrics(const SeekPenaltyResponse& response, + metrics::SystemProfileProto::Hardware::Drive* drive); + + // The thread on which file operations are performed (supplied by the + // embedder). + scoped_refptr<base::SequencedTaskRunner> file_thread_; + + // The key to give to base::PathService to obtain the path to local state + // (supplied by the embedder). + int local_state_path_key_; + + // Information gathered about various important drives. + DriveMetrics metrics_; + + base::ThreadChecker thread_checker_; + base::WeakPtrFactory<DriveMetricsProvider> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DriveMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/drive_metrics_provider_android.cc b/chromium/components/metrics/drive_metrics_provider_android.cc new file mode 100644 index 00000000000..a653dd6f3ef --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider_android.cc @@ -0,0 +1,16 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +namespace metrics { + +// static +bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty) { + *has_seek_penalty = false; + return true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider_ios.mm b/chromium/components/metrics/drive_metrics_provider_ios.mm new file mode 100644 index 00000000000..a653dd6f3ef --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider_ios.mm @@ -0,0 +1,16 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +namespace metrics { + +// static +bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty) { + *has_seek_penalty = false; + return true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider_linux.cc b/chromium/components/metrics/drive_metrics_provider_linux.cc new file mode 100644 index 00000000000..35c505a3492 --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider_linux.cc @@ -0,0 +1,65 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +#include <linux/kdev_t.h> // For MAJOR()/MINOR(). +#include <sys/stat.h> +#include <string> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" + +#if defined(OS_CHROMEOS) +#include "base/sys_info.h" +#endif + +namespace metrics { + +namespace { + +// See http://www.kernel.org/doc/Documentation/devices.txt for more info. +const int kFirstScsiMajorNumber = 8; +const int kPartitionsPerScsiDevice = 16; +const char kRotationalFormat[] = "/sys/block/sd%c/queue/rotational"; + +} // namespace + +// static +bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty) { +#if defined(OS_CHROMEOS) + std::string board = base::SysInfo::GetLsbReleaseBoard(); + if (board != "unknown" && board != "parrot") { + // All ChromeOS devices have SSDs. Except some parrots. + *has_seek_penalty = false; + return true; + } +#endif + + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) + return false; + + struct stat path_stat; + int error = fstat(file.GetPlatformFile(), &path_stat); + if (error < 0 || MAJOR(path_stat.st_dev) != kFirstScsiMajorNumber) { + // TODO(dbeam): support more SCSI major numbers (e.g. /dev/sdq+) and LVM? + return false; + } + + char sdX = 'a' + MINOR(path_stat.st_dev) / kPartitionsPerScsiDevice; + std::string rotational_path = base::StringPrintf(kRotationalFormat, sdX); + std::string rotates; + if (!base::ReadFileToString(base::FilePath(rotational_path), &rotates)) + return false; + + *has_seek_penalty = rotates.substr(0, 1) == "1"; + return true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider_mac.mm b/chromium/components/metrics/drive_metrics_provider_mac.mm new file mode 100644 index 00000000000..a6a37614d0c --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider_mac.mm @@ -0,0 +1,76 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <DiskArbitration/DiskArbitration.h> +#include <Foundation/Foundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/storage/IOStorageDeviceCharacteristics.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "base/files/file_path.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_ioobject.h" + +namespace metrics { + +// static +bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty) { + struct stat path_stat; + if (stat(path.value().c_str(), &path_stat) < 0) + return false; + + const char* dev_name = devname(path_stat.st_dev, S_IFBLK); + if (!dev_name) + return false; + + std::string bsd_name("/dev/"); + bsd_name.append(dev_name); + + base::ScopedCFTypeRef<DASessionRef> session( + DASessionCreate(kCFAllocatorDefault)); + if (!session) + return false; + + base::ScopedCFTypeRef<DADiskRef> disk( + DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsd_name.c_str())); + if (!disk) + return false; + + base::mac::ScopedIOObject<io_object_t> io_media(DADiskCopyIOMedia(disk)); + base::ScopedCFTypeRef<CFDictionaryRef> characteristics( + static_cast<CFDictionaryRef>(IORegistryEntrySearchCFProperty( + io_media, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), + kCFAllocatorDefault, + kIORegistryIterateRecursively | kIORegistryIterateParents))); + if (!characteristics) + return false; + + CFStringRef type_ref = base::mac::GetValueFromDictionary<CFStringRef>( + characteristics, CFSTR(kIOPropertyMediumTypeKey)); + if (!type_ref) + return false; + + NSString* type = base::mac::CFToNSCast(type_ref); + if ([type isEqualToString:@kIOPropertyMediumTypeRotationalKey]) { + *has_seek_penalty = true; + return true; + } else if ([type isEqualToString:@kIOPropertyMediumTypeSolidStateKey]) { + *has_seek_penalty = false; + return true; + } + + // TODO(dbeam): should I look for these Rotational/Solid State keys in + // |characteristics|? What if I find device characteristic but there's no + // type? Assume rotational? + return false; +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider_unittest.cc b/chromium/components/metrics/drive_metrics_provider_unittest.cc new file mode 100644 index 00000000000..142faf72be1 --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider_unittest.cc @@ -0,0 +1,20 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +TEST(DriveMetricsProviderTest, HasSeekPenalty) { + base::FilePath tmp_path; + ASSERT_TRUE(base::GetTempDir(&tmp_path)); + bool unused; + DriveMetricsProvider::HasSeekPenalty(tmp_path, &unused); +} + +} // namespace metrics diff --git a/chromium/components/metrics/drive_metrics_provider_win.cc b/chromium/components/metrics/drive_metrics_provider_win.cc new file mode 100644 index 00000000000..360b80e690a --- /dev/null +++ b/chromium/components/metrics/drive_metrics_provider_win.cc @@ -0,0 +1,52 @@ +// Copyright 2015 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 "components/metrics/drive_metrics_provider.h" + +#include <windows.h> +#include <winioctl.h> +#include <vector> + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" + +namespace metrics { + +// static +bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty) { + if (base::win::GetVersion() < base::win::VERSION_WIN7) { + // TODO(dbeam): re-enable XP and Vista detection in a utility process. + return false; + } + + std::vector<base::FilePath::StringType> components; + path.GetComponents(&components); + + base::File volume(base::FilePath(L"\\\\.\\" + components[0]), + base::File::FLAG_OPEN); + if (!volume.IsValid()) + return false; + + STORAGE_PROPERTY_QUERY query = {}; + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageDeviceSeekPenaltyProperty; + + DEVICE_SEEK_PENALTY_DESCRIPTOR result; + DWORD bytes_returned; + + BOOL success = DeviceIoControl( + volume.GetPlatformFile(), IOCTL_STORAGE_QUERY_PROPERTY, &query, + sizeof(query), &result, sizeof(result), &bytes_returned, NULL); + + if (success == FALSE || bytes_returned < sizeof(result)) + return false; + + *has_seek_penalty = result.IncursSeekPenalty != FALSE; + return true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/file_metrics_provider.cc b/chromium/components/metrics/file_metrics_provider.cc new file mode 100644 index 00000000000..78fb90a1211 --- /dev/null +++ b/chromium/components/metrics/file_metrics_provider.cc @@ -0,0 +1,291 @@ +// Copyright 2016 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 "components/metrics/file_metrics_provider.h" + +#include "base/command_line.h" +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/logging.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/persistent_histogram_allocator.h" +#include "base/metrics/persistent_memory_allocator.h" +#include "base/task_runner.h" +#include "base/time/time.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_service.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + + +namespace metrics { + +// This structure stores all the information about the files being monitored +// and their current reporting state. +struct FileMetricsProvider::FileInfo { + FileInfo() {} + ~FileInfo() {} + + // Where on disk the file is located. + base::FilePath path; + + // How to access this file (atomic/active). + FileType type; + + // Name used inside prefs to persistent metadata. + std::string prefs_key; + + // The last-seen time of this file to detect change. + base::Time last_seen; + + // Once a file has been recognized as needing to be read, it is |mapped| + // into memory. If that file is "atomic" then the data from that file + // will be copied to |data| and the mapped file released. If the file is + // "active", it remains mapped and nothing is copied to local memory. + std::vector<uint8_t> data; + scoped_ptr<base::MemoryMappedFile> mapped; + scoped_ptr<base::PersistentHistogramAllocator> allocator; + + private: + DISALLOW_COPY_AND_ASSIGN(FileInfo); +}; + +FileMetricsProvider::FileMetricsProvider( + const scoped_refptr<base::TaskRunner>& task_runner, + PrefService* local_state) + : task_runner_(task_runner), + pref_service_(local_state), + weak_factory_(this) { +} + +FileMetricsProvider::~FileMetricsProvider() {} + +void FileMetricsProvider::RegisterFile(const base::FilePath& path, + FileType type, + const base::StringPiece prefs_key) { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_ptr<FileInfo> file(new FileInfo()); + file->path = path; + file->type = type; + file->prefs_key = prefs_key.as_string(); + + // |prefs_key| may be empty if the caller does not wish to persist the + // state across instances of the program. + if (pref_service_ && !prefs_key.empty()) { + file->last_seen = base::Time::FromInternalValue( + pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix + + file->prefs_key)); + } + + files_to_check_.push_back(std::move(file)); +} + +// static +void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs, + const base::StringPiece prefs_key) { + prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix + + prefs_key.as_string(), 0); +} + +// static +void FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner( + FileMetricsProvider::FileInfoList* files) { + // This method has all state information passed in |files| and is intended + // to run on a worker thread rather than the UI thread. + for (scoped_ptr<FileInfo>& file : *files) { + AccessResult result = CheckAndMapNewMetrics(file.get()); + // Some results are not reported in order to keep the dashboard clean. + if (result != ACCESS_RESULT_DOESNT_EXIST && + result != ACCESS_RESULT_NOT_MODIFIED) { + UMA_HISTOGRAM_ENUMERATION( + "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX); + } + } +} + +// This method has all state information passed in |file| and is intended +// to run on a worker thread rather than the UI thread. +// static +FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapNewMetrics( + FileMetricsProvider::FileInfo* file) { + DCHECK(!file->mapped); + DCHECK(file->data.empty()); + + base::File::Info info; + if (!base::GetFileInfo(file->path, &info)) + return ACCESS_RESULT_DOESNT_EXIST; + + if (info.is_directory || info.size == 0) + return ACCESS_RESULT_INVALID_FILE; + + if (file->last_seen >= info.last_modified) + return ACCESS_RESULT_NOT_MODIFIED; + + // A new file of metrics has been found. Map it into memory. + // TODO(bcwhite): Make this open read/write when supported for "active". + file->mapped.reset(new base::MemoryMappedFile()); + if (!file->mapped->Initialize(file->path)) { + file->mapped.reset(); + return ACCESS_RESULT_SYSTEM_MAP_FAILURE; + } + + // Ensure any problems below don't occur repeatedly. + file->last_seen = info.last_modified; + + // Test the validity of the file contents. + if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*file->mapped)) + return ACCESS_RESULT_INVALID_CONTENTS; + + // For an "atomic" file, immediately copy the data into local memory and + // release the file so that it is not held open. + if (file->type == FILE_HISTOGRAMS_ATOMIC) { + file->data.assign(file->mapped->data(), + file->mapped->data() + file->mapped->length()); + file->mapped.reset(); + } + + return ACCESS_RESULT_SUCCESS; +} + +void FileMetricsProvider::ScheduleFilesCheck() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (files_to_check_.empty()) + return; + + // Create an independent list of files for checking. This will be Owned() + // by the reply call given to the task-runner, to be deleted when that call + // has returned. It is also passed Unretained() to the task itself, safe + // because that must complete before the reply runs. + FileInfoList* check_list = new FileInfoList(); + std::swap(files_to_check_, *check_list); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner, + base::Unretained(check_list)), + base::Bind(&FileMetricsProvider::RecordFilesChecked, + weak_factory_.GetWeakPtr(), base::Owned(check_list))); +} + +void FileMetricsProvider::RecordHistogramSnapshotsFromFile( + base::HistogramSnapshotManager* snapshot_manager, + FileInfo* file) { + DCHECK(thread_checker_.CalledOnValidThread()); + base::PersistentHistogramAllocator::Iterator histogram_iter( + file->allocator.get()); + + int histogram_count = 0; + while (true) { + scoped_ptr<base::HistogramBase> histogram = histogram_iter.GetNext(); + if (!histogram) + break; + if (file->type == FILE_HISTOGRAMS_ATOMIC) + snapshot_manager->PrepareAbsoluteTakingOwnership(std::move(histogram)); + else + snapshot_manager->PrepareDeltaTakingOwnership(std::move(histogram)); + ++histogram_count; + } + + DVLOG(1) << "Reported " << histogram_count << " histograms from " + << file->path.value(); +} + +void FileMetricsProvider::CreateAllocatorForFile(FileInfo* file) { + DCHECK(!file->allocator); + + // File data was validated earlier. Files are not considered "untrusted" + // as some processes might be (e.g. Renderer) so there's no need to check + // again to try to thwart some malicious actor that may have modified the + // data between then and now. + if (file->mapped) { + DCHECK(file->data.empty()); + // TODO(bcwhite): Make this do read/write when supported for "active". + file->allocator.reset(new base::PersistentHistogramAllocator( + make_scoped_ptr(new base::FilePersistentMemoryAllocator( + std::move(file->mapped), 0, "")))); + } else { + DCHECK(!file->mapped); + file->allocator.reset(new base::PersistentHistogramAllocator( + make_scoped_ptr(new base::PersistentMemoryAllocator( + &file->data[0], file->data.size(), 0, 0, "", true)))); + } +} + +void FileMetricsProvider::RecordFilesChecked(FileInfoList* checked) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Move each processed file to either the "to-read" list (for processing) or + // the "to-check" list (for future checking). + for (auto iter = checked->begin(); iter != checked->end();) { + auto temp = iter++; + const FileInfo* file = temp->get(); + if (file->mapped || !file->data.empty()) + files_to_read_.splice(files_to_read_.end(), *checked, temp); + else + files_to_check_.splice(files_to_check_.end(), *checked, temp); + } +} + +void FileMetricsProvider::RecordFileAsSeen(FileInfo* file) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (pref_service_ && !file->prefs_key.empty()) { + pref_service_->SetInt64(metrics::prefs::kMetricsLastSeenPrefix + + file->prefs_key, + file->last_seen.ToInternalValue()); + } +} + +void FileMetricsProvider::OnDidCreateMetricsLog() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Move finished metric files back to list of monitored files. + for (auto iter = files_to_read_.begin(); iter != files_to_read_.end();) { + auto temp = iter++; + const FileInfo* file = temp->get(); + if (!file->allocator && !file->mapped && file->data.empty()) + files_to_check_.splice(files_to_check_.end(), files_to_read_, temp); + } + + // Schedule a check to see if there are new metrics to load. If so, they + // will be reported during the next collection run after this one. The + // check is run off of the worker-pool so as to not cause delays on the + // main UI thread (which is currently where metric collection is done). + ScheduleFilesCheck(); +} + +void FileMetricsProvider::RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { + DCHECK(thread_checker_.CalledOnValidThread()); + + for (scoped_ptr<FileInfo>& file : files_to_read_) { + // If the file is mapped or loaded then it needs to have an allocator + // attached to it in order to read histograms out of it. + if (file->mapped || !file->data.empty()) + CreateAllocatorForFile(file.get()); + + // A file should not be under "files to read" unless it has an allocator + // or is memory-mapped (at which point it will have received an allocator + // above). However, if this method gets called twice before the scheduled- + // files-check has a chance to clean up, this may trigger. This also + // catches the case where creating an allocator from the file has failed. + if (!file->allocator) + continue; + + // Dump all histograms contained within the file to the snapshot-manager. + RecordHistogramSnapshotsFromFile(snapshot_manager, file.get()); + + // Atomic files are read once and then ignored unless they change. + if (file->type == FILE_HISTOGRAMS_ATOMIC) { + DCHECK(!file->mapped); + file->allocator.reset(); + file->data.clear(); + RecordFileAsSeen(file.get()); + } + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/file_metrics_provider.h b/chromium/components/metrics/file_metrics_provider.h new file mode 100644 index 00000000000..88c025f0ec1 --- /dev/null +++ b/chromium/components/metrics/file_metrics_provider.h @@ -0,0 +1,157 @@ +// Copyright 2016 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. + +#ifndef COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_ + +#include <list> +#include <string> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "components/metrics/metrics_provider.h" + +class PrefRegistrySimple; +class PrefService; + +namespace base { +class MemoryMappedFile; +class PersistentMemoryAllocator; +class TaskRunner; +} + +namespace metrics { + +// FileMetricsProvider gathers and logs histograms written to files on disk. +// Any number of files can be registered and will be polled once per upload +// cycle (at startup and periodically thereafter -- about every 30 minutes +// for desktop) for data to send. +class FileMetricsProvider : public metrics::MetricsProvider { + public: + enum FileType { + // "Atomic" files are a collection of histograms that are written + // completely in a single atomic operation (typically a write followed + // by an atomic rename) and the file is never updated again except to + // be replaced by a completely new set of histograms. This is the only + // option that can be used if the file is not writeable by *this* + // process. + FILE_HISTOGRAMS_ATOMIC, + + // "Active" files may be open by one or more other processes and updated + // at any time with new samples or new histograms. Such files may also be + // inactive for any period of time only to be opened again and have new + // data written to them. The file should probably never be deleted because + // there would be no guarantee that the data has been reported. + // TODO(bcwhite): Enable when read/write mem-mapped files are supported. + //FILE_HISTOGRAMS_ACTIVE, + }; + + FileMetricsProvider(const scoped_refptr<base::TaskRunner>& task_runner, + PrefService* local_state); + ~FileMetricsProvider() override; + + // Indicates a file to be monitored and how the file is used. Because some + // metadata may persist across process restarts, preferences entries are + // used based on the |prefs_key| name. Call RegisterPrefs() with the same + // name to create the necessary keys in advance. Set |prefs_key| empty + // if no persistence is required. + void RegisterFile(const base::FilePath& path, + FileType type, + const base::StringPiece prefs_key); + + // Registers all necessary preferences for maintaining persistent state + // about a monitored file across process restarts. The |prefs_key| is + // typically the filename. + static void RegisterPrefs(PrefRegistrySimple* prefs, + const base::StringPiece prefs_key); + + private: + friend class FileMetricsProviderTest; + FRIEND_TEST_ALL_PREFIXES(FileMetricsProviderTest, AccessMetrics); + + // The different results that can occur accessing a file. + enum AccessResult { + // File was successfully mapped. + ACCESS_RESULT_SUCCESS, + + // File does not exist. + ACCESS_RESULT_DOESNT_EXIST, + + // File exists but not modified since last read. + ACCESS_RESULT_NOT_MODIFIED, + + // File is not valid: is a directory or zero-size. + ACCESS_RESULT_INVALID_FILE, + + // System could not map file into memory. + ACCESS_RESULT_SYSTEM_MAP_FAILURE, + + // File had invalid contents. + ACCESS_RESULT_INVALID_CONTENTS, + + ACCESS_RESULT_MAX + }; + + // Information about files being monitored; defined and used exclusively + // inside the .cc file. + struct FileInfo; + using FileInfoList = std::list<scoped_ptr<FileInfo>>; + + // Checks a list of files (on a task-runner allowed to do I/O) to see if + // any should be processed during the next histogram collection. + static void CheckAndMapNewMetricFilesOnTaskRunner(FileInfoList* files); + + // Checks an individual file as part of CheckAndMapNewMetricFilesOnTaskRunner. + static AccessResult CheckAndMapNewMetrics(FileInfo* file); + + // Creates a task to check all monitored files for updates. + void ScheduleFilesCheck(); + + // Creates a PersistentMemoryAllocator for a file that has been marked to + // have its metrics collected. + void CreateAllocatorForFile(FileInfo* file); + + // Records all histograms from a given file via a snapshot-manager. + void RecordHistogramSnapshotsFromFile( + base::HistogramSnapshotManager* snapshot_manager, + FileInfo* file); + + // Takes a list of files checked by an external task and determines what + // to do with each. + void RecordFilesChecked(FileInfoList* checked); + + // Updates the persistent state information to show a file as being read. + void RecordFileAsSeen(FileInfo* file); + + // metrics::MetricsDataProvider: + void OnDidCreateMetricsLog() override; + void RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) override; + + // A task-runner capable of performing I/O. + scoped_refptr<base::TaskRunner> task_runner_; + + // A list of files not currently active that need to be checked for changes. + FileInfoList files_to_check_; + + // A list of files that have data to be read and reported. + FileInfoList files_to_read_; + + // The preferences-service used to store persistent state about files. + PrefService* pref_service_; + + base::ThreadChecker thread_checker_; + base::WeakPtrFactory<FileMetricsProvider> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/file_metrics_provider_unittest.cc b/chromium/components/metrics/file_metrics_provider_unittest.cc new file mode 100644 index 00000000000..0949c2d3690 --- /dev/null +++ b/chromium/components/metrics/file_metrics_provider_unittest.cc @@ -0,0 +1,192 @@ +// Copyright 2016 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 "components/metrics/file_metrics_provider.h" + +#include "base/files/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_snapshot_manager.h" +#include "base/metrics/persistent_histogram_allocator.h" +#include "base/metrics/persistent_memory_allocator.h" +#include "base/metrics/statistics_recorder.h" +#include "base/test/test_simple_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +const char kMetricsName[] = "TestMetrics"; +const char kMetricsFilename[] = "file.metrics"; +} // namespace + +namespace metrics { + +class HistogramFlattenerDeltaRecorder : public base::HistogramFlattener { + public: + HistogramFlattenerDeltaRecorder() {} + + void RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) override { + recorded_delta_histogram_names_.push_back(histogram.histogram_name()); + } + + void InconsistencyDetected(base::HistogramBase::Inconsistency problem) + override { + ASSERT_TRUE(false); + } + + void UniqueInconsistencyDetected( + base::HistogramBase::Inconsistency problem) override { + ASSERT_TRUE(false); + } + + void InconsistencyDetectedInLoggedCount(int amount) override { + ASSERT_TRUE(false); + } + + std::vector<std::string> GetRecordedDeltaHistogramNames() { + return recorded_delta_histogram_names_; + } + + private: + std::vector<std::string> recorded_delta_histogram_names_; + + DISALLOW_COPY_AND_ASSIGN(HistogramFlattenerDeltaRecorder); +}; + +class FileMetricsProviderTest : public testing::Test { + protected: + FileMetricsProviderTest() + : task_runner_(new base::TestSimpleTaskRunner()), + thread_task_runner_handle_(task_runner_), + prefs_(new TestingPrefServiceSimple) { + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + FileMetricsProvider::RegisterPrefs(prefs_->registry(), kMetricsName); + } + + TestingPrefServiceSimple* prefs() { return prefs_.get(); } + base::FilePath temp_dir() { return temp_dir_.path(); } + base::FilePath metrics_file() { + return temp_dir_.path().AppendASCII(kMetricsFilename); + } + + FileMetricsProvider* provider() { + if (!provider_) + provider_.reset(new FileMetricsProvider(task_runner_, prefs())); + return provider_.get(); + } + + void RunTasks() { + task_runner_->RunUntilIdle(); + } + + private: + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; + base::ThreadTaskRunnerHandle thread_task_runner_handle_; + + base::ScopedTempDir temp_dir_; + scoped_ptr<TestingPrefServiceSimple> prefs_; + scoped_ptr<FileMetricsProvider> provider_; + + DISALLOW_COPY_AND_ASSIGN(FileMetricsProviderTest); +}; + + +TEST_F(FileMetricsProviderTest, AccessMetrics) { + ASSERT_FALSE(PathExists(metrics_file())); + + { + // Get this first so it isn't created inside the persistent allocator. + base::GlobalHistogramAllocator::GetCreateHistogramResultHistogram(); + + base::GlobalHistogramAllocator::CreateWithLocalMemory( + 64 << 10, 0, kMetricsName); + base::HistogramBase* foo = + base::Histogram::FactoryGet("foo", 1, 100, 10, 0); + base::HistogramBase* bar = + base::Histogram::FactoryGet("bar", 1, 100, 10, 0); + foo->Add(42); + bar->Add(84); + + scoped_ptr<base::PersistentHistogramAllocator> histogram_allocator = + base::GlobalHistogramAllocator::ReleaseForTesting(); + base::PersistentMemoryAllocator* allocator = + histogram_allocator->memory_allocator(); + base::File writer(metrics_file(), + base::File::FLAG_CREATE | base::File::FLAG_WRITE); + ASSERT_TRUE(writer.IsValid()); + ASSERT_EQ(static_cast<int>(allocator->used()), + writer.Write(0, (const char*)allocator->data(), + allocator->used())); + } + + // Register the file and allow the "checker" task to run. + ASSERT_TRUE(PathExists(metrics_file())); + provider()->RegisterFile(metrics_file(), + FileMetricsProvider::FILE_HISTOGRAMS_ATOMIC, + kMetricsName); + + // Record embedded snapshots via snapshot-manager. + provider()->OnDidCreateMetricsLog(); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + snapshot_manager.StartDeltas(); + provider()->RecordHistogramSnapshots(&snapshot_manager); + snapshot_manager.FinishDeltas(); + EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size()); + } + + // Make sure a second call to the snapshot-recorder doesn't break anything. + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + snapshot_manager.StartDeltas(); + provider()->RecordHistogramSnapshots(&snapshot_manager); + snapshot_manager.FinishDeltas(); + EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size()); + } + + // Second full run on the same file should produce nothing. + provider()->OnDidCreateMetricsLog(); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + snapshot_manager.StartDeltas(); + provider()->RecordHistogramSnapshots(&snapshot_manager); + snapshot_manager.FinishDeltas(); + EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size()); + } + + // Update the time-stamp of the file to indicate that it is "new" and + // must be recorded. + { + base::File touch(metrics_file(), + base::File::FLAG_OPEN | base::File::FLAG_WRITE); + ASSERT_TRUE(touch.IsValid()); + base::Time next = base::Time::Now() + base::TimeDelta::FromSeconds(1); + touch.SetTimes(next, next); + } + + // This run should again have "new" histograms. + provider()->OnDidCreateMetricsLog(); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + snapshot_manager.StartDeltas(); + provider()->RecordHistogramSnapshots(&snapshot_manager); + snapshot_manager.FinishDeltas(); + EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size()); + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/gpu/DEPS b/chromium/components/metrics/gpu/DEPS new file mode 100644 index 00000000000..c2ff8a0af67 --- /dev/null +++ b/chromium/components/metrics/gpu/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/browser", + "+gpu/config", +] diff --git a/chromium/components/metrics/gpu/gpu_metrics_provider.cc b/chromium/components/metrics/gpu/gpu_metrics_provider.cc new file mode 100644 index 00000000000..19cf9b49c8e --- /dev/null +++ b/chromium/components/metrics/gpu/gpu_metrics_provider.cc @@ -0,0 +1,36 @@ +// Copyright 2014 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 "components/metrics/gpu/gpu_metrics_provider.h" + +#include "components/metrics/proto/system_profile.pb.h" +#include "content/public/browser/gpu_data_manager.h" +#include "gpu/config/gpu_info.h" + +namespace metrics { + +GPUMetricsProvider::GPUMetricsProvider() { +} + +GPUMetricsProvider::~GPUMetricsProvider() { +} + +void GPUMetricsProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) { + SystemProfileProto::Hardware* hardware = + system_profile_proto->mutable_hardware(); + + const gpu::GPUInfo& gpu_info = + content::GpuDataManager::GetInstance()->GetGPUInfo(); + SystemProfileProto::Hardware::Graphics* gpu = + hardware->mutable_gpu(); + gpu->set_vendor_id(gpu_info.gpu.vendor_id); + gpu->set_device_id(gpu_info.gpu.device_id); + gpu->set_driver_version(gpu_info.driver_version); + gpu->set_driver_date(gpu_info.driver_date); + gpu->set_gl_vendor(gpu_info.gl_vendor); + gpu->set_gl_renderer(gpu_info.gl_renderer); +} + +} // namespace metrics diff --git a/chromium/components/metrics/gpu/gpu_metrics_provider.h b/chromium/components/metrics/gpu/gpu_metrics_provider.h new file mode 100644 index 00000000000..581c7651ce6 --- /dev/null +++ b/chromium/components/metrics/gpu/gpu_metrics_provider.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_ + +#include "base/macros.h" +#include "components/metrics/metrics_provider.h" + +namespace metrics { + +// GPUMetricsProvider provides GPU-related metrics. +class GPUMetricsProvider : public MetricsProvider { + public: + GPUMetricsProvider(); + ~GPUMetricsProvider() override; + + // MetricsProvider: + void ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) override; + + private: + DISALLOW_COPY_AND_ASSIGN(GPUMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/histogram_encoder.cc b/chromium/components/metrics/histogram_encoder.cc new file mode 100644 index 00000000000..595fb539867 --- /dev/null +++ b/chromium/components/metrics/histogram_encoder.cc @@ -0,0 +1,57 @@ +// Copyright 2014 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 "components/metrics/histogram_encoder.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/metrics_hashes.h" + +using base::SampleCountIterator; + +namespace metrics { + +void EncodeHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot, + ChromeUserMetricsExtension* uma_proto) { + DCHECK_NE(0, snapshot.TotalCount()); + DCHECK(uma_proto); + + // We will ignore the MAX_INT/infinite value in the last element of range[]. + + HistogramEventProto* histogram_proto = uma_proto->add_histogram_event(); + histogram_proto->set_name_hash(base::HashMetricName(histogram_name)); + if (snapshot.sum() != 0) + histogram_proto->set_sum(snapshot.sum()); + + for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done(); + it->Next()) { + base::Histogram::Sample min; + base::Histogram::Sample max; + base::Histogram::Count count; + it->Get(&min, &max, &count); + HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket(); + bucket->set_min(min); + bucket->set_max(max); + // Note: The default for count is 1 in the proto, so omit it in that case. + if (count != 1) + bucket->set_count(count); + } + + // Omit fields to save space (see rules in histogram_event.proto comments). + for (int i = 0; i < histogram_proto->bucket_size(); ++i) { + HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i); + if (i + 1 < histogram_proto->bucket_size() && + bucket->max() == histogram_proto->bucket(i + 1).min()) { + bucket->clear_max(); + } else if (bucket->max() == bucket->min() + 1) { + bucket->clear_min(); + } + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/histogram_encoder.h b/chromium/components/metrics/histogram_encoder.h new file mode 100644 index 00000000000..8ef528de2b7 --- /dev/null +++ b/chromium/components/metrics/histogram_encoder.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +// This file defines an utility function that records any changes in a given +// histogram for transmission. + +#ifndef COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_ +#define COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_ + +#include <string> + +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +namespace base { +class HistogramSamples; +} + +namespace metrics { + +// Record any changes (histogram deltas of counts from |snapshot|) into +// |uma_proto| for the given histogram (|histogram_name|). +void EncodeHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot, + ChromeUserMetricsExtension* uma_proto); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_ diff --git a/chromium/components/metrics/histogram_encoder_unittest.cc b/chromium/components/metrics/histogram_encoder_unittest.cc new file mode 100644 index 00000000000..dfe7f847d5d --- /dev/null +++ b/chromium/components/metrics/histogram_encoder_unittest.cc @@ -0,0 +1,71 @@ +// Copyright 2014 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 "components/metrics/histogram_encoder.h" + +#include <string> + +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/sample_vector.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +TEST(HistogramEncoder, HistogramBucketFields) { + // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12. + base::BucketRanges ranges(8); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 7); + ranges.set_range(3, 8); + ranges.set_range(4, 9); + ranges.set_range(5, 10); + ranges.set_range(6, 11); + ranges.set_range(7, 12); + + base::SampleVector samples(1, &ranges); + samples.Accumulate(3, 1); // Bucket 1-5. + samples.Accumulate(6, 1); // Bucket 5-7. + samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped) + samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped) + samples.Accumulate(11, 1); // Bucket 11-12. + + ChromeUserMetricsExtension uma_proto; + EncodeHistogramDelta("Test", samples, &uma_proto); + + const HistogramEventProto& histogram_proto = + uma_proto.histogram_event(uma_proto.histogram_event_size() - 1); + + // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12. + // Should become: 1-/, 5-7, /-9, 10-/, /-12. + ASSERT_EQ(5, histogram_proto.bucket_size()); + + // 1-5 becomes 1-/ (max is same as next min). + EXPECT_TRUE(histogram_proto.bucket(0).has_min()); + EXPECT_FALSE(histogram_proto.bucket(0).has_max()); + EXPECT_EQ(1, histogram_proto.bucket(0).min()); + + // 5-7 stays 5-7 (no optimization possible). + EXPECT_TRUE(histogram_proto.bucket(1).has_min()); + EXPECT_TRUE(histogram_proto.bucket(1).has_max()); + EXPECT_EQ(5, histogram_proto.bucket(1).min()); + EXPECT_EQ(7, histogram_proto.bucket(1).max()); + + // 8-9 becomes /-9 (min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(2).has_min()); + EXPECT_TRUE(histogram_proto.bucket(2).has_max()); + EXPECT_EQ(9, histogram_proto.bucket(2).max()); + + // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized). + EXPECT_TRUE(histogram_proto.bucket(3).has_min()); + EXPECT_FALSE(histogram_proto.bucket(3).has_max()); + EXPECT_EQ(10, histogram_proto.bucket(3).min()); + + // 11-12 becomes /-12 (last record must keep max, min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(4).has_min()); + EXPECT_TRUE(histogram_proto.bucket(4).has_max()); + EXPECT_EQ(12, histogram_proto.bucket(4).max()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/OWNERS b/chromium/components/metrics/leak_detector/OWNERS new file mode 100644 index 00000000000..d8bfc45aa54 --- /dev/null +++ b/chromium/components/metrics/leak_detector/OWNERS @@ -0,0 +1,2 @@ +sque@chromium.org +wfh@chromium.org diff --git a/chromium/components/metrics/leak_detector/call_stack_manager.cc b/chromium/components/metrics/leak_detector/call_stack_manager.cc new file mode 100644 index 00000000000..dfebd3c7251 --- /dev/null +++ b/chromium/components/metrics/leak_detector/call_stack_manager.cc @@ -0,0 +1,72 @@ +// Copyright 2015 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 "components/metrics/leak_detector/call_stack_manager.h" + +#include <algorithm> // For std::copy. +#include <new> + +#include "base/hash.h" +#include "components/metrics/leak_detector/custom_allocator.h" + +namespace metrics { +namespace leak_detector { + +CallStackManager::CallStackManager() {} + +CallStackManager::~CallStackManager() { + // Free all call stack objects and clear |call_stacks_|. Make sure to save the + // CallStack object pointer and remove it from the container before freeing + // the CallStack memory. + while (!call_stacks_.empty()) { + CallStack* call_stack = *call_stacks_.begin(); + call_stacks_.erase(call_stacks_.begin()); + + CustomAllocator::Free(call_stack->stack, + call_stack->depth * sizeof(*call_stack->stack)); + call_stack->stack = nullptr; + call_stack->depth = 0; + + CustomAllocator::Free(call_stack, sizeof(CallStack)); + } +} + +const CallStack* CallStackManager::GetCallStack(size_t depth, + const void* const stack[]) { + // Temporarily create a call stack object for lookup in |call_stacks_|. + CallStack temp; + temp.depth = depth; + temp.stack = const_cast<const void**>(stack); + // This is the only place where the call stack's hash is computed. This value + // can be reused in the created object to avoid further hash computation. + temp.hash = + base::Hash(reinterpret_cast<const char*>(stack), sizeof(*stack) * depth); + + auto iter = call_stacks_.find(&temp); + if (iter != call_stacks_.end()) + return *iter; + + // Since |call_stacks_| stores CallStack pointers rather than actual objects, + // create new call objects manually here. + CallStack* call_stack = + new (CustomAllocator::Allocate(sizeof(CallStack))) CallStack; + call_stack->depth = depth; + call_stack->hash = temp.hash; // Don't run the hash function again. + call_stack->stack = reinterpret_cast<const void**>( + CustomAllocator::Allocate(sizeof(*stack) * depth)); + std::copy(stack, stack + depth, call_stack->stack); + + call_stacks_.insert(call_stack); + return call_stack; +} + +bool CallStackManager::CallStackPointerEqual::operator()( + const CallStack* c1, + const CallStack* c2) const { + return c1->depth == c2->depth && c1->hash == c2->hash && + std::equal(c1->stack, c1->stack + c1->depth, c2->stack); +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/call_stack_manager.h b/chromium/components/metrics/leak_detector/call_stack_manager.h new file mode 100644 index 00000000000..a411bcab8d9 --- /dev/null +++ b/chromium/components/metrics/leak_detector/call_stack_manager.h @@ -0,0 +1,102 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/stl_allocator.h" + +// Summary of structures: +// +// struct CallStack: +// Represents a unique call stack, defined by its raw call stack (array of +// pointers), and hash value. All CallStack objects are owned by class +// CallStackManager. Other classes may hold pointers to them but should not +// attempt to create or modify any CallStack objects. +// +// class CallStackManager: +// Creates CallStack objects to represent unique call stacks. Owns all +// CallStack objects that it creates, storing them internally. +// +// Returns the call stacks as const pointers because no caller should take +// ownership of them and modify or delete them. The lifetime of all CallStack +// objects is limited to that of the CallStackManager object that created +// them. When the CallStackManager is destroyed, the CallStack objects will be +// invalidated. Hence the caller should make sure that it does not use +// CallStack objects beyond the lifetime of the CallStackManager that created +// them. + +namespace metrics { +namespace leak_detector { + +// Struct to represent a call stack. +struct CallStack { + // Call stack as an array of pointers, |stack|. The array length is stored in + // |depth|. There is no null terminator. + const void** stack; + size_t depth; + + // Hash of call stack. It is cached here so that it doesn not have to be + // recomputed each time. + size_t hash; +}; + +// Maintains and owns all unique call stack objects. +// Not thread-safe. +class CallStackManager { + public: + CallStackManager(); + ~CallStackManager(); + + // Returns a CallStack object for a given raw call stack. The first time a + // particular raw call stack is passed into this function, it will create a + // new CallStack object to hold the raw call stack data, and then return it. + // The CallStack object is stored internally. + // + // On subsequent calls with the same raw call stack, this function will return + // the previously created CallStack object. + const CallStack* GetCallStack(size_t depth, const void* const stack[]); + + size_t size() const { return call_stacks_.size(); } + + private: + // Allocator class for unique call stacks. Uses CustomAllocator to avoid + // recursive malloc hook invocation when analyzing allocs and frees. + using CallStackPointerAllocator = STLAllocator<CallStack*, CustomAllocator>; + + // Hash operator for call stack object given as a pointer. + // Does not actually compute the hash. Instead, returns the already computed + // hash value stored in a CallStack object. + struct CallStackPointerStoredHash { + size_t operator()(const CallStack* call_stack) const { + return call_stack->hash; + } + }; + + // Equality comparator for call stack objects given as pointers. Compares + // their stack trace contents. + struct CallStackPointerEqual { + bool operator()(const CallStack* c1, const CallStack* c2) const; + }; + + // Holds all call stack objects. Each object is allocated elsewhere and stored + // as a pointer because the container may rearrange itself internally. + base::hash_set<CallStack*, + CallStackPointerStoredHash, + CallStackPointerEqual, + CallStackPointerAllocator> call_stacks_; + + DISALLOW_COPY_AND_ASSIGN(CallStackManager); +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_ diff --git a/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc b/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc new file mode 100644 index 00000000000..f71bbfff47d --- /dev/null +++ b/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc @@ -0,0 +1,260 @@ +// Copyright 2015 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 "components/metrics/leak_detector/call_stack_manager.h" + +#include <stdint.h> + +#include <algorithm> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { +namespace leak_detector { + +namespace { + +// Some test call stacks. The addresses are 64-bit but they should automatically +// be truncated to 32 bits on a 32-bit machine. +const void* kRawStack0[] = { + reinterpret_cast<const void*>(0x8899aabbccddeeff), + reinterpret_cast<const void*>(0x0000112233445566), + reinterpret_cast<const void*>(0x5566778899aabbcc), + reinterpret_cast<const void*>(0x9988776655443322), +}; +// This is similar to kRawStack0, differing only in one address by 1. It should +// still produce a distinct CallStack object and hash. +const void* kRawStack1[] = { + kRawStack0[0], kRawStack0[1], + reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(kRawStack0[2]) + + 1), + kRawStack0[3], +}; +const void* kRawStack2[] = { + reinterpret_cast<const void*>(0x900df00dcab58888), + reinterpret_cast<const void*>(0x00001337cafedeed), + reinterpret_cast<const void*>(0x0000deafbabe1234), +}; +const void* kRawStack3[] = { + reinterpret_cast<const void*>(0x0000000012345678), + reinterpret_cast<const void*>(0x00000000abcdef01), + reinterpret_cast<const void*>(0x00000000fdecab98), + reinterpret_cast<const void*>(0x0000deadbeef0001), + reinterpret_cast<const void*>(0x0000900ddeed0002), + reinterpret_cast<const void*>(0x0000f00dcafe0003), + reinterpret_cast<const void*>(0x0000f00d900d0004), + reinterpret_cast<const void*>(0xdeedcafebabe0005), +}; + +// Creates a copy of a call stack as a scoped_ptr to a raw stack. The depth is +// the same as the original stack, but it is not stored in the result. +scoped_ptr<const void* []> CopyStack(const CallStack* stack) { + scoped_ptr<const void* []> stack_copy(new const void*[stack->depth]); + std::copy(stack->stack, stack->stack + stack->depth, stack_copy.get()); + return stack_copy; +} + +} // namespace + +class CallStackManagerTest : public ::testing::Test { + public: + CallStackManagerTest() {} + + void SetUp() override { CustomAllocator::Initialize(); } + void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); } + + private: + DISALLOW_COPY_AND_ASSIGN(CallStackManagerTest); +}; + +TEST_F(CallStackManagerTest, NewStacks) { + CallStackManager manager; + EXPECT_EQ(0U, manager.size()); + + // Request some new stacks and make sure their creation is reflected in the + // size of |manager|. + const CallStack* stack0 = + manager.GetCallStack(arraysize(kRawStack0), kRawStack0); + EXPECT_EQ(arraysize(kRawStack0), stack0->depth); + EXPECT_EQ(1U, manager.size()); + + const CallStack* stack1 = + manager.GetCallStack(arraysize(kRawStack1), kRawStack1); + EXPECT_EQ(arraysize(kRawStack1), stack1->depth); + EXPECT_EQ(2U, manager.size()); + + const CallStack* stack2 = + manager.GetCallStack(arraysize(kRawStack2), kRawStack2); + EXPECT_EQ(arraysize(kRawStack2), stack2->depth); + EXPECT_EQ(3U, manager.size()); + + const CallStack* stack3 = + manager.GetCallStack(arraysize(kRawStack3), kRawStack3); + EXPECT_EQ(arraysize(kRawStack3), stack3->depth); + EXPECT_EQ(4U, manager.size()); + + // Call stack objects should be unique. + EXPECT_NE(stack0, stack1); + EXPECT_NE(stack0, stack2); + EXPECT_NE(stack0, stack3); + EXPECT_NE(stack1, stack2); + EXPECT_NE(stack1, stack3); + EXPECT_NE(stack2, stack3); +} + +TEST_F(CallStackManagerTest, Hashes) { + CallStackManager manager; + + const CallStack* stack0 = + manager.GetCallStack(arraysize(kRawStack0), kRawStack0); + const CallStack* stack1 = + manager.GetCallStack(arraysize(kRawStack1), kRawStack1); + const CallStack* stack2 = + manager.GetCallStack(arraysize(kRawStack2), kRawStack2); + const CallStack* stack3 = + manager.GetCallStack(arraysize(kRawStack3), kRawStack3); + + // Hash values should be unique. This test is not designed to make sure the + // hash function is generating unique hashes, but that CallStackManager is + // properly storing the hashes in CallStack structs. + EXPECT_NE(stack0->hash, stack1->hash); + EXPECT_NE(stack0->hash, stack2->hash); + EXPECT_NE(stack0->hash, stack3->hash); + EXPECT_NE(stack1->hash, stack2->hash); + EXPECT_NE(stack1->hash, stack3->hash); + EXPECT_NE(stack2->hash, stack3->hash); +} + +TEST_F(CallStackManagerTest, MultipleManagersHashes) { + CallStackManager manager1; + const CallStack* stack10 = + manager1.GetCallStack(arraysize(kRawStack0), kRawStack0); + const CallStack* stack11 = + manager1.GetCallStack(arraysize(kRawStack1), kRawStack1); + const CallStack* stack12 = + manager1.GetCallStack(arraysize(kRawStack2), kRawStack2); + const CallStack* stack13 = + manager1.GetCallStack(arraysize(kRawStack3), kRawStack3); + + CallStackManager manager2; + const CallStack* stack20 = + manager2.GetCallStack(arraysize(kRawStack0), kRawStack0); + const CallStack* stack21 = + manager2.GetCallStack(arraysize(kRawStack1), kRawStack1); + const CallStack* stack22 = + manager2.GetCallStack(arraysize(kRawStack2), kRawStack2); + const CallStack* stack23 = + manager2.GetCallStack(arraysize(kRawStack3), kRawStack3); + + // Different CallStackManagers should still generate the same hashes. + EXPECT_EQ(stack10->hash, stack20->hash); + EXPECT_EQ(stack11->hash, stack21->hash); + EXPECT_EQ(stack12->hash, stack22->hash); + EXPECT_EQ(stack13->hash, stack23->hash); +} + +TEST_F(CallStackManagerTest, HashWithReducedDepth) { + CallStackManager manager; + const CallStack* stack = + manager.GetCallStack(arraysize(kRawStack3), kRawStack3); + + // Hash function should only operate on the first |CallStack::depth| elements + // of CallStack::stack. To test this, reduce the depth value of one of the + // stacks and make sure the hash changes. + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 1, stack->stack)->hash); + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 2, stack->stack)->hash); + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 3, stack->stack)->hash); + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 4, stack->stack)->hash); + + // Also try subsets of the stack that don't start from the beginning. + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 1, stack->stack + 1)->hash); + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 2, stack->stack + 2)->hash); + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 3, stack->stack + 3)->hash); + EXPECT_NE(stack->hash, + manager.GetCallStack(stack->depth - 4, stack->stack + 4)->hash); +} + +TEST_F(CallStackManagerTest, DuplicateStacks) { + CallStackManager manager; + EXPECT_EQ(0U, manager.size()); + + // Calling manager.GetCallStack() multiple times with the same raw stack + // arguments will not result in creation of new call stack objects after the + // first call. Instead, the previously created object will be returned, and + // the size of |manager| will remain unchanged. + // + // Thus a call to GetCallStack() will always return the same result, given the + // same inputs. + + // Add stack0. + const CallStack* stack0 = + manager.GetCallStack(arraysize(kRawStack0), kRawStack0); + + scoped_ptr<const void* []> rawstack0_duplicate0 = CopyStack(stack0); + const CallStack* stack0_duplicate0 = + manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate0.get()); + EXPECT_EQ(1U, manager.size()); + EXPECT_EQ(stack0, stack0_duplicate0); + + // Add stack1. + const CallStack* stack1 = + manager.GetCallStack(arraysize(kRawStack1), kRawStack1); + EXPECT_EQ(2U, manager.size()); + + scoped_ptr<const void* []> rawstack0_duplicate1 = CopyStack(stack0); + const CallStack* stack0_duplicate1 = + manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate1.get()); + EXPECT_EQ(2U, manager.size()); + EXPECT_EQ(stack0, stack0_duplicate1); + + scoped_ptr<const void* []> rawstack1_duplicate0 = CopyStack(stack1); + const CallStack* stack1_duplicate0 = + manager.GetCallStack(arraysize(kRawStack1), rawstack1_duplicate0.get()); + EXPECT_EQ(2U, manager.size()); + EXPECT_EQ(stack1, stack1_duplicate0); + + // Add stack2 and stack3. + const CallStack* stack2 = + manager.GetCallStack(arraysize(kRawStack2), kRawStack2); + const CallStack* stack3 = + manager.GetCallStack(arraysize(kRawStack3), kRawStack3); + EXPECT_EQ(4U, manager.size()); + + scoped_ptr<const void* []> rawstack1_duplicate1 = CopyStack(stack1); + const CallStack* stack1_duplicate1 = + manager.GetCallStack(arraysize(kRawStack1), rawstack1_duplicate1.get()); + EXPECT_EQ(4U, manager.size()); + EXPECT_EQ(stack1, stack1_duplicate1); + + scoped_ptr<const void* []> rawstack0_duplicate2 = CopyStack(stack0); + const CallStack* stack0_duplicate2 = + manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate2.get()); + EXPECT_EQ(4U, manager.size()); + EXPECT_EQ(stack0, stack0_duplicate2); + + scoped_ptr<const void* []> rawstack3_duplicate0 = CopyStack(stack3); + const CallStack* stack3_duplicate0 = + manager.GetCallStack(arraysize(kRawStack3), rawstack3_duplicate0.get()); + EXPECT_EQ(4U, manager.size()); + EXPECT_EQ(stack3, stack3_duplicate0); + + scoped_ptr<const void* []> rawstack2_duplicate0 = CopyStack(stack2); + const CallStack* stack2_duplicate0 = + manager.GetCallStack(arraysize(kRawStack2), rawstack2_duplicate0.get()); + EXPECT_EQ(4U, manager.size()); + EXPECT_EQ(stack2, stack2_duplicate0); +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/call_stack_table.cc b/chromium/components/metrics/leak_detector/call_stack_table.cc new file mode 100644 index 00000000000..8f9540bc0d4 --- /dev/null +++ b/chromium/components/metrics/leak_detector/call_stack_table.cc @@ -0,0 +1,77 @@ +// Copyright 2015 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 "components/metrics/leak_detector/call_stack_table.h" + +#include <algorithm> + +#include "components/metrics/leak_detector/call_stack_manager.h" + +namespace metrics { +namespace leak_detector { + +namespace { + +using ValueType = LeakDetectorValueType; + +// During leak analysis, we only want to examine the top +// |kMaxCountOfSuspciousStacks| entries. +const int kMaxCountOfSuspciousStacks = 16; + +const int kInitialHashTableSize = 1999; + +} // namespace + +size_t CallStackTable::StoredHash::operator()( + const CallStack* call_stack) const { + // The call stack object should already have a hash computed when it was + // created. + // + // This is NOT the actual hash computation function for a new call stack. + return call_stack->hash; +} + +CallStackTable::CallStackTable(int call_stack_suspicion_threshold) + : num_allocs_(0), + num_frees_(0), + entry_map_(kInitialHashTableSize), + leak_analyzer_(kMaxCountOfSuspciousStacks, + call_stack_suspicion_threshold) {} + +CallStackTable::~CallStackTable() {} + +void CallStackTable::Add(const CallStack* call_stack) { + ++entry_map_[call_stack]; + ++num_allocs_; +} + +void CallStackTable::Remove(const CallStack* call_stack) { + auto iter = entry_map_.find(call_stack); + if (iter == entry_map_.end()) + return; + uint32_t& count_for_call_stack = iter->second; + --count_for_call_stack; + ++num_frees_; + + // Delete zero-alloc entries to free up space. + if (count_for_call_stack == 0) + entry_map_.erase(iter); +} + +void CallStackTable::TestForLeaks() { + // Add all entries to the ranked list. + RankedSet ranked_entries(kMaxCountOfSuspciousStacks); + GetTopCallStacks(&ranked_entries); + leak_analyzer_.AddSample(std::move(ranked_entries)); +} + +void CallStackTable::GetTopCallStacks(RankedSet* top_entries) const { + for (const auto& call_stack_and_count : entry_map_) { + top_entries->AddCallStack(call_stack_and_count.first, + call_stack_and_count.second); + } +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/call_stack_table.h b/chromium/components/metrics/leak_detector/call_stack_table.h new file mode 100644 index 00000000000..77a4b2a3ed7 --- /dev/null +++ b/chromium/components/metrics/leak_detector/call_stack_table.h @@ -0,0 +1,87 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <functional> // For std::equal_to. + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/leak_analyzer.h" +#include "components/metrics/leak_detector/ranked_set.h" +#include "components/metrics/leak_detector/stl_allocator.h" + +namespace metrics { +namespace leak_detector { + +struct CallStack; + +// Contains a hash table where the key is the call stack and the value is the +// number of allocations from that call stack. +// Not thread-safe. +class CallStackTable { + public: + struct StoredHash { + size_t operator()(const CallStack* call_stack) const; + }; + + explicit CallStackTable(int call_stack_suspicion_threshold); + ~CallStackTable(); + + // Add/Remove an allocation for the given call stack. + // Note that this class does NOT own the CallStack objects. Instead, it + // identifies different CallStacks by their hashes. + void Add(const CallStack* call_stack); + void Remove(const CallStack* call_stack); + + // Check for leak patterns in the allocation data. + void TestForLeaks(); + + // Get the top N entries in the CallStackTable, ranked by net number of + // allocations. N is given by |top_entries->max_size()|, so |*top_entries| + // must already be initialized with that number. + void GetTopCallStacks(RankedSet* top_entries) const; + + const LeakAnalyzer& leak_analyzer() const { return leak_analyzer_; } + + size_t size() const { return entry_map_.size(); } + bool empty() const { return entry_map_.empty(); } + + uint32_t num_allocs() const { return num_allocs_; } + uint32_t num_frees() const { return num_frees_; } + + private: + // Total number of allocs and frees in this table. + uint32_t num_allocs_; + uint32_t num_frees_; + + // Hash table containing entries. Uses CustomAllocator to avoid recursive + // malloc hook invocation when analyzing allocs and frees. + using TableEntryAllocator = + STLAllocator<std::pair<const CallStack* const, uint32_t>, + CustomAllocator>; + + // Stores a mapping of each call stack to the number of recorded allocations + // made from that call site. + base::hash_map<const CallStack*, + uint32_t, + StoredHash, + std::equal_to<const CallStack*>, + TableEntryAllocator> entry_map_; + + // For detecting leak patterns in incoming allocations. + LeakAnalyzer leak_analyzer_; + + DISALLOW_COPY_AND_ASSIGN(CallStackTable); +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_ diff --git a/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc b/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc new file mode 100644 index 00000000000..6600ef714ac --- /dev/null +++ b/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc @@ -0,0 +1,364 @@ +// Copyright 2015 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 "components/metrics/leak_detector/call_stack_table.h" + +#include <set> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/leak_detector/call_stack_manager.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { +namespace leak_detector { + +namespace { + +// Default threshold used for leak analysis. +const int kDefaultLeakThreshold = 5; + +// Some test call stacks. +const void* kRawStack0[] = { + reinterpret_cast<const void*>(0xaabbccdd), + reinterpret_cast<const void*>(0x11223344), + reinterpret_cast<const void*>(0x55667788), + reinterpret_cast<const void*>(0x99887766), +}; +const void* kRawStack1[] = { + reinterpret_cast<const void*>(0xdeadbeef), + reinterpret_cast<const void*>(0x900df00d), + reinterpret_cast<const void*>(0xcafedeed), + reinterpret_cast<const void*>(0xdeafbabe), +}; +const void* kRawStack2[] = { + reinterpret_cast<const void*>(0x12345678), + reinterpret_cast<const void*>(0xabcdef01), + reinterpret_cast<const void*>(0xfdecab98), +}; +const void* kRawStack3[] = { + reinterpret_cast<const void*>(0xdead0001), + reinterpret_cast<const void*>(0xbeef0002), + reinterpret_cast<const void*>(0x900d0003), + reinterpret_cast<const void*>(0xf00d0004), + reinterpret_cast<const void*>(0xcafe0005), + reinterpret_cast<const void*>(0xdeed0006), + reinterpret_cast<const void*>(0xdeaf0007), + reinterpret_cast<const void*>(0xbabe0008), +}; + +} // namespace + +class CallStackTableTest : public ::testing::Test { + public: + CallStackTableTest() + : stack0_(nullptr), + stack1_(nullptr), + stack2_(nullptr), + stack3_(nullptr) {} + + void SetUp() override { + CustomAllocator::Initialize(); + + manager_.reset(new CallStackManager); + + // The unit tests expect a certain order to the call stack pointers. It is + // an important detail when checking the output of LeakAnalyzer's suspected + // leaks, which are ordered by the leak value (call stack pointer). Use a + // set to sort the pointers as they are created. + std::set<const CallStack*> stacks; + stacks.insert(manager_->GetCallStack(arraysize(kRawStack0), kRawStack0)); + stacks.insert(manager_->GetCallStack(arraysize(kRawStack1), kRawStack1)); + stacks.insert(manager_->GetCallStack(arraysize(kRawStack2), kRawStack2)); + stacks.insert(manager_->GetCallStack(arraysize(kRawStack3), kRawStack3)); + ASSERT_EQ(4U, stacks.size()); + + std::set<const CallStack*>::const_iterator iter = stacks.begin(); + stack0_ = *iter++; + stack1_ = *iter++; + stack2_ = *iter++; + stack3_ = *iter++; + } + + void TearDown() override { + // All call stacks generated by |manager_| will be invalidated when it is + // destroyed. + stack0_ = nullptr; + stack1_ = nullptr; + stack2_ = nullptr; + stack3_ = nullptr; + + // Destroy the call stack manager before shutting down the allocator. + manager_.reset(); + + EXPECT_TRUE(CustomAllocator::Shutdown()); + } + + protected: + // Unit tests should directly reference these pointers to CallStack objects. + const CallStack* stack0_; + const CallStack* stack1_; + const CallStack* stack2_; + const CallStack* stack3_; + + private: + scoped_ptr<CallStackManager> manager_; + + DISALLOW_COPY_AND_ASSIGN(CallStackTableTest); +}; + +TEST_F(CallStackTableTest, PointerOrder) { + EXPECT_LT(stack0_, stack1_); + EXPECT_LT(stack1_, stack2_); + EXPECT_LT(stack2_, stack3_); +} + +TEST_F(CallStackTableTest, EmptyTable) { + CallStackTable table(kDefaultLeakThreshold); + EXPECT_TRUE(table.empty()); + + EXPECT_EQ(0U, table.num_allocs()); + EXPECT_EQ(0U, table.num_frees()); + + // The table should be able to gracefully handle an attempt to remove a call + // stack entry when none exists. + table.Remove(stack0_); + table.Remove(stack1_); + table.Remove(stack2_); + table.Remove(stack3_); + + EXPECT_EQ(0U, table.num_allocs()); + EXPECT_EQ(0U, table.num_frees()); +} + +TEST_F(CallStackTableTest, InsertionAndRemoval) { + CallStackTable table(kDefaultLeakThreshold); + + table.Add(stack0_); + EXPECT_EQ(1U, table.size()); + EXPECT_EQ(1U, table.num_allocs()); + table.Add(stack1_); + EXPECT_EQ(2U, table.size()); + EXPECT_EQ(2U, table.num_allocs()); + table.Add(stack2_); + EXPECT_EQ(3U, table.size()); + EXPECT_EQ(3U, table.num_allocs()); + table.Add(stack3_); + EXPECT_EQ(4U, table.size()); + EXPECT_EQ(4U, table.num_allocs()); + + // Add some call stacks that have already been added. There should be no + // change in the number of entries, as they are aggregated by call stack. + table.Add(stack2_); + EXPECT_EQ(4U, table.size()); + EXPECT_EQ(5U, table.num_allocs()); + table.Add(stack3_); + EXPECT_EQ(4U, table.size()); + EXPECT_EQ(6U, table.num_allocs()); + + // Start removing entries. + EXPECT_EQ(0U, table.num_frees()); + + table.Remove(stack0_); + EXPECT_EQ(3U, table.size()); + EXPECT_EQ(1U, table.num_frees()); + table.Remove(stack1_); + EXPECT_EQ(2U, table.size()); + EXPECT_EQ(2U, table.num_frees()); + + // Removing call stacks with multiple counts will not reduce the overall + // number of table entries, until the count reaches 0. + table.Remove(stack2_); + EXPECT_EQ(2U, table.size()); + EXPECT_EQ(3U, table.num_frees()); + table.Remove(stack3_); + EXPECT_EQ(2U, table.size()); + EXPECT_EQ(4U, table.num_frees()); + + table.Remove(stack2_); + EXPECT_EQ(1U, table.size()); + EXPECT_EQ(5U, table.num_frees()); + table.Remove(stack3_); + EXPECT_EQ(0U, table.size()); + EXPECT_EQ(6U, table.num_frees()); + + // Now the table should be empty, but attempt to remove some more and make + // sure nothing breaks. + table.Remove(stack0_); + table.Remove(stack1_); + table.Remove(stack2_); + table.Remove(stack3_); + + EXPECT_TRUE(table.empty()); + EXPECT_EQ(6U, table.num_allocs()); + EXPECT_EQ(6U, table.num_frees()); +} + +TEST_F(CallStackTableTest, MassiveInsertionAndRemoval) { + CallStackTable table(kDefaultLeakThreshold); + + for (int i = 0; i < 100; ++i) + table.Add(stack3_); + EXPECT_EQ(1U, table.size()); + EXPECT_EQ(100U, table.num_allocs()); + + for (int i = 0; i < 100; ++i) + table.Add(stack2_); + EXPECT_EQ(2U, table.size()); + EXPECT_EQ(200U, table.num_allocs()); + + for (int i = 0; i < 100; ++i) + table.Add(stack1_); + EXPECT_EQ(3U, table.size()); + EXPECT_EQ(300U, table.num_allocs()); + + for (int i = 0; i < 100; ++i) + table.Add(stack0_); + EXPECT_EQ(4U, table.size()); + EXPECT_EQ(400U, table.num_allocs()); + + // Remove them in a different order, by removing one of each stack during one + // iteration. The size should not decrease until the last iteration. + EXPECT_EQ(0U, table.num_frees()); + + for (int i = 0; i < 100; ++i) { + table.Remove(stack0_); + EXPECT_EQ(4U * i + 1, table.num_frees()); + + table.Remove(stack1_); + EXPECT_EQ(4U * i + 2, table.num_frees()); + + table.Remove(stack2_); + EXPECT_EQ(4U * i + 3, table.num_frees()); + + table.Remove(stack3_); + EXPECT_EQ(4U * i + 4, table.num_frees()); + } + EXPECT_EQ(400U, table.num_frees()); + EXPECT_TRUE(table.empty()); + + // Try to remove some more from an empty table and make sure nothing breaks. + table.Remove(stack0_); + table.Remove(stack1_); + table.Remove(stack2_); + table.Remove(stack3_); + + EXPECT_TRUE(table.empty()); + EXPECT_EQ(400U, table.num_allocs()); + EXPECT_EQ(400U, table.num_frees()); +} + +TEST_F(CallStackTableTest, DetectLeak) { + CallStackTable table(kDefaultLeakThreshold); + + // Add some base number of entries. + for (int i = 0; i < 60; ++i) + table.Add(stack0_); + for (int i = 0; i < 50; ++i) + table.Add(stack1_); + for (int i = 0; i < 64; ++i) + table.Add(stack2_); + for (int i = 0; i < 72; ++i) + table.Add(stack3_); + + table.TestForLeaks(); + EXPECT_TRUE(table.leak_analyzer().suspected_leaks().empty()); + + // Use the following scheme: + // - stack0_: increase by 4 each time -- leak suspect + // - stack1_: increase by 3 each time -- leak suspect + // - stack2_: increase by 1 each time -- not a suspect + // - stack3_: alternate between increasing and decreasing - not a suspect + bool increase_kstack3 = true; + for (int i = 0; i < kDefaultLeakThreshold; ++i) { + EXPECT_TRUE(table.leak_analyzer().suspected_leaks().empty()); + + for (int j = 0; j < 4; ++j) + table.Add(stack0_); + + for (int j = 0; j < 3; ++j) + table.Add(stack1_); + + table.Add(stack2_); + + // Alternate between adding and removing. + if (increase_kstack3) + table.Add(stack3_); + else + table.Remove(stack3_); + increase_kstack3 = !increase_kstack3; + + table.TestForLeaks(); + } + + // Check that the correct leak values have been detected. + const auto& leaks = table.leak_analyzer().suspected_leaks(); + ASSERT_EQ(2U, leaks.size()); + // Suspected leaks are reported in increasing leak value -- in this case, the + // CallStack object's address. + EXPECT_EQ(stack0_, leaks[0].call_stack()); + EXPECT_EQ(stack1_, leaks[1].call_stack()); +} + +TEST_F(CallStackTableTest, GetTopCallStacks) { + CallStackTable table(kDefaultLeakThreshold); + + // Add a bunch of entries. + for (int i = 0; i < 60; ++i) + table.Add(stack0_); + for (int i = 0; i < 50; ++i) + table.Add(stack1_); + for (int i = 0; i < 64; ++i) + table.Add(stack2_); + for (int i = 0; i < 72; ++i) + table.Add(stack3_); + + // Get the call sites ordered from least to greatest number of entries. + RankedSet top_four(4); + table.GetTopCallStacks(&top_four); + ASSERT_EQ(4U, top_four.size()); + auto iter = top_four.begin(); + EXPECT_EQ(72, iter->count); + EXPECT_EQ(stack3_, iter->value.call_stack()); + ++iter; + EXPECT_EQ(64, iter->count); + EXPECT_EQ(stack2_, iter->value.call_stack()); + ++iter; + EXPECT_EQ(60, iter->count); + EXPECT_EQ(stack0_, iter->value.call_stack()); + ++iter; + EXPECT_EQ(50, iter->count); + EXPECT_EQ(stack1_, iter->value.call_stack()); + + // Get the top three call sites ordered from least to greatest number of + // entries. + RankedSet top_three(3); + table.GetTopCallStacks(&top_three); + ASSERT_EQ(3U, top_three.size()); + iter = top_three.begin(); + EXPECT_EQ(72, iter->count); + EXPECT_EQ(stack3_, iter->value.call_stack()); + ++iter; + EXPECT_EQ(64, iter->count); + EXPECT_EQ(stack2_, iter->value.call_stack()); + ++iter; + EXPECT_EQ(60, iter->count); + EXPECT_EQ(stack0_, iter->value.call_stack()); + + // Get the top two call sites ordered from least to greatest number of + // entries. + RankedSet top_two(2); + table.GetTopCallStacks(&top_two); + ASSERT_EQ(2U, top_two.size()); + iter = top_two.begin(); + EXPECT_EQ(72, iter->count); + EXPECT_EQ(stack3_, iter->value.call_stack()); + ++iter; + EXPECT_EQ(64, iter->count); + EXPECT_EQ(stack2_, iter->value.call_stack()); +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/custom_allocator.cc b/chromium/components/metrics/leak_detector/custom_allocator.cc new file mode 100644 index 00000000000..1f80a9712f8 --- /dev/null +++ b/chromium/components/metrics/leak_detector/custom_allocator.cc @@ -0,0 +1,61 @@ +// Copyright 2015 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 "components/metrics/leak_detector/custom_allocator.h" + +#include <stddef.h> + +namespace metrics { +namespace leak_detector { + +namespace { + +// Wrappers around new and delete. +void* DefaultAlloc(size_t size) { + return new char[size]; +} +void DefaultFree(void* ptr, size_t /* size */) { + delete[] reinterpret_cast<char*>(ptr); +} + +CustomAllocator::AllocFunc g_alloc_func = nullptr; +CustomAllocator::FreeFunc g_free_func = nullptr; + +} // namespace + +// static +void CustomAllocator::Initialize() { + Initialize(&DefaultAlloc, &DefaultFree); +} + +// static +void CustomAllocator::Initialize(AllocFunc alloc_func, FreeFunc free_func) { + g_alloc_func = alloc_func; + g_free_func = free_func; +} + +// static +bool CustomAllocator::Shutdown() { + g_alloc_func = nullptr; + g_free_func = nullptr; + return true; +} + +// static +bool CustomAllocator::IsInitialized() { + return g_alloc_func && g_free_func; +} + +// static +void* CustomAllocator::Allocate(size_t size) { + return g_alloc_func(size); +} + +// static +void CustomAllocator::Free(void* ptr, size_t size) { + g_free_func(ptr, size); +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/custom_allocator.h b/chromium/components/metrics/leak_detector/custom_allocator.h new file mode 100644 index 00000000000..fdbfc779b9f --- /dev/null +++ b/chromium/components/metrics/leak_detector/custom_allocator.h @@ -0,0 +1,50 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_ + +#include <stddef.h> + +#include <type_traits> + +namespace metrics { +namespace leak_detector { + +// Custom allocator class to be passed to STLAllocator as a template argument. +// +// By default, CustomAllocator uses the default allocator (new/delete), but the +// caller of Initialize ()can provide a pair of alternative alloc/ free +// functions to use as an external allocator. +// +// This is a stateless class, but there is static data within the module that +// needs to be created and deleted. +// +// Not thread-safe. +class CustomAllocator { + public: + using AllocFunc = std::add_pointer<void*(size_t)>::type; + using FreeFunc = std::add_pointer<void(void*, size_t)>::type; + + // Initialize CustomAllocator to use the default allocator. + static void Initialize(); + + // Initialize CustomAllocator to use the given alloc/free functions. + static void Initialize(AllocFunc alloc_func, FreeFunc free_func); + + // Performs any cleanup required, e.g. unset custom functions. Returns true + // on success or false if something failed. + static bool Shutdown(); + + static bool IsInitialized(); + + // These functions must match the specifications in STLAllocator. + static void* Allocate(size_t size); + static void Free(void* ptr, size_t size); +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_ diff --git a/chromium/components/metrics/leak_detector/leak_analyzer.cc b/chromium/components/metrics/leak_detector/leak_analyzer.cc new file mode 100644 index 00000000000..54568ac212e --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_analyzer.cc @@ -0,0 +1,139 @@ +// Copyright 2015 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 "components/metrics/leak_detector/leak_analyzer.h" + +#include <set> + +namespace metrics { +namespace leak_detector { + +namespace { + +using RankedEntry = RankedSet::Entry; + +// Increase suspicion scores by this much each time an entry is suspected as +// being a leak. +const int kSuspicionScoreIncrease = 1; + +} // namespace + +LeakAnalyzer::LeakAnalyzer(uint32_t ranking_size, + uint32_t num_suspicions_threshold) + : ranking_size_(ranking_size), + score_threshold_(num_suspicions_threshold), + ranked_entries_(ranking_size), + prev_ranked_entries_(ranking_size) { + suspected_leaks_.reserve(ranking_size); +} + +LeakAnalyzer::~LeakAnalyzer() {} + +void LeakAnalyzer::AddSample(RankedSet ranked_set) { + // Save the ranked entries from the previous call. + prev_ranked_entries_ = std::move(ranked_entries_); + + // Save the current entries. + ranked_entries_ = std::move(ranked_set); + + RankedSet ranked_deltas(ranking_size_); + for (const RankedEntry& entry : ranked_entries_) { + // Determine what count was recorded for this value last time. + uint32_t prev_count = 0; + if (GetPreviousCountForValue(entry.value, &prev_count)) + ranked_deltas.Add(entry.value, entry.count - prev_count); + } + + AnalyzeDeltas(ranked_deltas); +} + +void LeakAnalyzer::AnalyzeDeltas(const RankedSet& ranked_deltas) { + bool found_drop = false; + RankedSet::const_iterator drop_position = ranked_deltas.end(); + + if (ranked_deltas.size() > 1) { + RankedSet::const_iterator entry_iter = ranked_deltas.begin(); + RankedSet::const_iterator next_entry_iter = ranked_deltas.begin(); + ++next_entry_iter; + + // If the first entry is 0, that means all deltas are 0 or negative. Do + // not treat this as a suspicion of leaks; just quit. + if (entry_iter->count > 0) { + while (next_entry_iter != ranked_deltas.end()) { + const RankedEntry& entry = *entry_iter; + const RankedEntry& next_entry = *next_entry_iter; + + // Find the first major drop in values (i.e. by 50% or more). + if (entry.count > next_entry.count * 2) { + found_drop = true; + drop_position = next_entry_iter; + break; + } + ++entry_iter; + ++next_entry_iter; + } + } + } + + // All leak values before the drop are suspected during this analysis. + std::set<ValueType, std::less<ValueType>, Allocator<ValueType>> + current_suspects; + if (found_drop) { + for (RankedSet::const_iterator ranked_set_iter = ranked_deltas.begin(); + ranked_set_iter != drop_position; ++ranked_set_iter) { + current_suspects.insert(ranked_set_iter->value); + } + } + + // Reset the score to 0 for all previously suspected leak values that did + // not get suspected this time. + auto iter = suspected_histogram_.begin(); + while (iter != suspected_histogram_.end()) { + const ValueType& value = iter->first; + // Erase entries whose suspicion score reaches 0. + auto erase_iter = iter++; + if (current_suspects.find(value) == current_suspects.end()) + suspected_histogram_.erase(erase_iter); + } + + // For currently suspected values, increase the leak score. + for (const ValueType& value : current_suspects) { + auto histogram_iter = suspected_histogram_.find(value); + if (histogram_iter != suspected_histogram_.end()) { + histogram_iter->second += kSuspicionScoreIncrease; + } else if (suspected_histogram_.size() < ranking_size_) { + // Create a new entry if it didn't already exist. + suspected_histogram_[value] = kSuspicionScoreIncrease; + } + } + + // Now check the leak suspicion scores. Make sure to erase the suspected + // leaks from the previous call. + suspected_leaks_.clear(); + for (const auto& entry : suspected_histogram_) { + if (suspected_leaks_.size() > ranking_size_) + break; + + // Only report suspected values that have accumulated a suspicion score. + // This is achieved by maintaining suspicion for several cycles, with few + // skips. + if (entry.second >= score_threshold_) + suspected_leaks_.emplace_back(entry.first); + } +} + +bool LeakAnalyzer::GetPreviousCountForValue(const ValueType& value, + uint32_t* count) const { + // Determine what count was recorded for this value last time. + for (const RankedEntry& entry : prev_ranked_entries_) { + if (entry.value == value) { + *count = entry.count; + return true; + } + } + return false; +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_analyzer.h b/chromium/components/metrics/leak_detector/leak_analyzer.h new file mode 100644 index 00000000000..55ca297cf6a --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_analyzer.h @@ -0,0 +1,87 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_ + +#include <stdint.h> + +#include <map> +#include <vector> + +#include "base/macros.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/leak_detector_value_type.h" +#include "components/metrics/leak_detector/ranked_set.h" +#include "components/metrics/leak_detector/stl_allocator.h" + +namespace metrics { +namespace leak_detector { + +// This class looks for possible leak patterns in allocation data over time. +// Not thread-safe. +class LeakAnalyzer { + public: + using ValueType = LeakDetectorValueType; + + // This class uses CustomAllocator to avoid recursive malloc hook invocation + // when analyzing allocs and frees. + template <typename Type> + using Allocator = STLAllocator<Type, CustomAllocator>; + + LeakAnalyzer(uint32_t ranking_size, uint32_t num_suspicions_threshold); + ~LeakAnalyzer(); + + // Take in a RankedSet of allocations, sorted by count. Removes the contents + // of |ranked_entries| to be stored internally, which is why it is not passed + // in as a const reference. + void AddSample(RankedSet ranked_entries); + + // Used to report suspected leaks. Reported leaks are sorted by ValueType. + const std::vector<ValueType, Allocator<ValueType>>& suspected_leaks() const { + return suspected_leaks_; + } + + private: + // Analyze a list of allocation count deltas from the previous iteration. If + // anything looks like a possible leak, update the suspicion scores. + void AnalyzeDeltas(const RankedSet& ranked_deltas); + + // Returns the count for the given value from the previous analysis in + // |count|. Returns true if the given value was present in the previous + // analysis, or false if not. + bool GetPreviousCountForValue(const ValueType& value, uint32_t* count) const; + + // Look for the top |ranking_size_| entries when analyzing leaks. + const uint32_t ranking_size_; + + // Report suspected leaks when the suspicion score reaches this value. + const uint32_t score_threshold_; + + // A mapping of allocation values to suspicion score. All allocations in this + // container are suspected leaks. The score can increase or decrease over + // time. Once the score reaches |score_threshold_|, the entry is reported as + // a suspected leak in |suspected_leaks_|. + std::map<ValueType, + uint32_t, + std::less<ValueType>, + Allocator<std::pair<const ValueType, uint32_t>>> + suspected_histogram_; + + // Array of allocated values that passed the suspicion threshold and are being + // reported. + std::vector<ValueType, Allocator<ValueType>> suspected_leaks_; + + // The most recent allocation entries, since the last call to AddSample(). + RankedSet ranked_entries_; + // The previous allocation entries, from before the last call to AddSample(). + RankedSet prev_ranked_entries_; + + DISALLOW_COPY_AND_ASSIGN(LeakAnalyzer); +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_ diff --git a/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc b/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc new file mode 100644 index 00000000000..71555b4c21d --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc @@ -0,0 +1,366 @@ +// Copyright 2015 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 "components/metrics/leak_detector/leak_analyzer.h" + +#include <stdint.h> + +#include <algorithm> + +#include "base/macros.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/ranked_set.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { +namespace leak_detector { + +namespace { + +// Default ranking size and threshold used for leak analysis. +const int kDefaultRankedSetSize = 10; +const int kDefaultLeakThreshold = 5; + +// Makes it easier to instantiate LeakDetectorValueTypes. Instantiates with an +// integer value that indicates an allocation size. Storing the size allows us +// to track the storage of the LeakDetectorValueType object within LeakAnalyzer. +// +// There is no need to test this with call stacks in addition to sizes because +// call stacks will be contained in a LeakDetectorValueType object as well. +LeakDetectorValueType Size(uint32_t value) { + return LeakDetectorValueType(value); +} + +} // namespace + +class LeakAnalyzerTest : public ::testing::Test { + public: + LeakAnalyzerTest() {} + + void SetUp() override { CustomAllocator::Initialize(); } + void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); } + + private: + DISALLOW_COPY_AND_ASSIGN(LeakAnalyzerTest); +}; + +TEST_F(LeakAnalyzerTest, Empty) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + EXPECT_TRUE(analyzer.suspected_leaks().empty()); +} + +TEST_F(LeakAnalyzerTest, SingleSize) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 10); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected. + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } +} + +TEST_F(LeakAnalyzerTest, VariousSizesWithoutIncrease) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30); + set.Add(Size(32), 10); + set.Add(Size(56), 90); + set.Add(Size(64), 40); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected. + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } +} + +TEST_F(LeakAnalyzerTest, VariousSizesWithEqualIncrease) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); + set.Add(Size(32), 10 + i * 10); + set.Add(Size(56), 90 + i * 10); + set.Add(Size(64), 40 + i * 10); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected. + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } +} + +TEST_F(LeakAnalyzerTest, NotEnoughRunsToTriggerLeakReport) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + // Run this one iteration short of the number of cycles needed to trigger a + // leak report. Because LeakAnalyzer requires |kDefaultLeakThreshold| + // suspicions based on deltas between AddSample() calls, the below loop needs + // to run |kDefaultLeakThreshold + 1| times to trigger a leak report. + for (int i = 0; i <= kDefaultLeakThreshold - 1; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); // This one has a potential leak. + set.Add(Size(32), 10 + i * 2); + set.Add(Size(56), 90 + i); + set.Add(Size(64), 40 + i / 2); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected. + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } +} + +TEST_F(LeakAnalyzerTest, LeakSingleSize) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + // Run this past the number of iterations required to trigger a leak report. + for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(32), 10); + set.Add(Size(56), 90); + set.Add(Size(24), 30 + i * 10); // This one has a potential leak. + set.Add(Size(64), 40); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected initially... + if (i < kDefaultLeakThreshold) { + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } else { + // ... but there should be reported leaks once the threshold is reached. + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(1U, leaks.size()); + EXPECT_EQ(24U, leaks[0].size()); + } + } +} + +TEST_F(LeakAnalyzerTest, LeakSingleSizeOthersAlsoIncreasing) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); // This one has a potential leak. + set.Add(Size(32), 10 + i * 2); + set.Add(Size(56), 90 + i); + set.Add(Size(64), 40 + i / 2); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected initially... + if (i < kDefaultLeakThreshold) { + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } else { + // ... but there should be reported leaks once the threshold is reached. + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(1U, leaks.size()); + EXPECT_EQ(24U, leaks[0].size()); + } + } +} + +TEST_F(LeakAnalyzerTest, LeakMultipleSizes) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 5); + set.Add(Size(32), 10 + i * 40); + set.Add(Size(56), 90 + i * 30); + set.Add(Size(64), 40 + i * 20); + set.Add(Size(80), 20 + i * 3); + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected initially... + if (i < kDefaultLeakThreshold) { + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } else { + // ... but there should be reported leaks once the threshold is reached. + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(3U, leaks.size()); + // These should be in order of increasing allocation size. + EXPECT_EQ(32U, leaks[0].size()); + EXPECT_EQ(56U, leaks[1].size()); + EXPECT_EQ(64U, leaks[2].size()); + } + } +} + +TEST_F(LeakAnalyzerTest, LeakMultipleSizesValueOrder) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i <= kDefaultLeakThreshold; ++i) { + RankedSet set(kDefaultRankedSetSize); + // These are similar to LeakMultipleSizes, but the relative order of + // allocation increases is different from the relative order of sizes. + set.Add(Size(24), 30 + i * 5); + set.Add(Size(32), 10 + i * 20); + set.Add(Size(56), 90 + i * 40); + set.Add(Size(64), 40 + i * 30); + set.Add(Size(80), 20 + i * 3); + analyzer.AddSample(std::move(set)); + } + + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(3U, leaks.size()); + // These should be in order of increasing allocation size, NOT in order of + // allocation count or deltas. + EXPECT_EQ(32U, leaks[0].size()); + EXPECT_EQ(56U, leaks[1].size()); + EXPECT_EQ(64U, leaks[2].size()); +} + +TEST_F(LeakAnalyzerTest, EqualIncreasesNoLeak) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); + set.Add(Size(32), 10 + i * 10); + set.Add(Size(56), 90 + i * 10); + set.Add(Size(64), 40 + i * 10); + set.Add(Size(80), 20 + i * 10); + analyzer.AddSample(std::move(set)); + + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } +} + +TEST_F(LeakAnalyzerTest, NotBigEnoughDeltaGap) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) { + RankedSet set(kDefaultRankedSetSize); + // These all have different increments but there is no clear group of + // increases that are larger than the rest. + set.Add(Size(24), 30 + i * 80); + set.Add(Size(32), 10 + i * 45); + set.Add(Size(56), 90 + i * 25); + set.Add(Size(64), 40 + i * 15); + set.Add(Size(80), 20 + i * 10); + analyzer.AddSample(std::move(set)); + + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } +} + +TEST_F(LeakAnalyzerTest, RepeatedRisesUntilLeakFound) { + LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold); + + // Remember, there is an extra iteration beyond |kDefaultLeakThreshold| needed + // to actually trigger the leak detection. + for (int i = 0; i <= kDefaultLeakThreshold - 2; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); + set.Add(Size(32), 10); + set.Add(Size(56), 90); + set.Add(Size(64), 40); + set.Add(Size(80), 20); + analyzer.AddSample(std::move(set)); + + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } + + // Drop back down to 30. + for (int i = 0; i <= kDefaultLeakThreshold - 1; ++i) { + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); + set.Add(Size(32), 10); + set.Add(Size(56), 90); + set.Add(Size(64), 40); + set.Add(Size(80), 20); + analyzer.AddSample(std::move(set)); + + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } + + // Drop back down to 30. + for (int i = 0; i <= kDefaultLeakThreshold; ++i) { + // Initially there should not be any leak detected. + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + + RankedSet set(kDefaultRankedSetSize); + set.Add(Size(24), 30 + i * 10); + set.Add(Size(32), 10); + set.Add(Size(56), 90); + set.Add(Size(64), 40); + set.Add(Size(80), 20); + analyzer.AddSample(std::move(set)); + } + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(1U, leaks.size()); + EXPECT_EQ(24U, leaks[0].size()); +} + +TEST_F(LeakAnalyzerTest, LeakWithMultipleGroupsOfDeltas) { + const int kRankedSetSize = 20; + LeakAnalyzer analyzer(kRankedSetSize, kDefaultLeakThreshold); + + for (int i = 0; i <= kDefaultLeakThreshold; ++i) { + RankedSet set(kRankedSetSize); + set.Add(Size(24), 30 + i * 10); // A group of smaller deltas. + set.Add(Size(32), 10 + i * 3); + set.Add(Size(80), 20 + i * 5); + set.Add(Size(40), 30 + i * 7); + set.Add(Size(56), 90); + set.Add(Size(64), 40); + set.Add(Size(128), 100); + set.Add(Size(44), 100 + i * 10); // A group of medium deltas. + set.Add(Size(16), 60 + i * 50); + set.Add(Size(4), 20 + i * 40); + set.Add(Size(8), 100 + i * 60); + set.Add(Size(48), 100); + set.Add(Size(72), 60 + i * 240); // A group of largest deltas. + set.Add(Size(28), 100); + set.Add(Size(100), 100 + i * 200); + set.Add(Size(104), 60 + i * 128); + analyzer.AddSample(std::move(set)); + } + // Only the group of largest deltas should be caught. + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(3U, leaks.size()); + // These should be in order of increasing allocation size. + EXPECT_EQ(72U, leaks[0].size()); + EXPECT_EQ(100U, leaks[1].size()); + EXPECT_EQ(104U, leaks[2].size()); +} + +TEST_F(LeakAnalyzerTest, LeakMultipleSizesWithLargeThreshold) { + const int kLeakThreshold = 50; + LeakAnalyzer analyzer(kDefaultRankedSetSize, kLeakThreshold); + + for (int i = 0; i <= kLeakThreshold + 10; ++i) { + RankedSet set(kDefaultRankedSetSize); + // * - Cluster of larger deltas + set.Add(Size(24), 30 + i * 5); + set.Add(Size(32), 10 + i * 40); // * + set.Add(Size(56), 90 + i * 30); // * + set.Add(Size(40), 30 + i * 7); + set.Add(Size(64), 40 + i * 25); // * + set.Add(Size(80), 20 + i * 3); + set.Add(Size(128), 100); + set.Add(Size(44), 100 + i * 10); + set.Add(Size(16), 60 + i * 50); // * + analyzer.AddSample(std::move(set)); + + // No leaks should have been detected initially... + if (i < kLeakThreshold) { + EXPECT_TRUE(analyzer.suspected_leaks().empty()); + } else { + // ... but there should be reported leaks once the threshold is reached. + const auto& leaks = analyzer.suspected_leaks(); + ASSERT_EQ(4U, leaks.size()); + // These should be in order of increasing allocation size. + EXPECT_EQ(16U, leaks[0].size()); + EXPECT_EQ(32U, leaks[1].size()); + EXPECT_EQ(56U, leaks[2].size()); + EXPECT_EQ(64U, leaks[3].size()); + } + } +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_detector.cc b/chromium/components/metrics/leak_detector/leak_detector.cc new file mode 100644 index 00000000000..a7151a86382 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector.cc @@ -0,0 +1,340 @@ +// Copyright 2016 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 "components/metrics/leak_detector/leak_detector.h" + +#include <stdint.h> + +#include "base/allocator/allocator_extension.h" +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/threading/thread_local.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/leak_detector_impl.h" +#include "content/public/browser/browser_thread.h" + +#if defined(OS_CHROMEOS) +#include <link.h> // for dl_iterate_phdr +#else +#error "Getting binary mapping info is not supported on this platform." +#endif // defined(OS_CHROMEOS) + +namespace metrics { + +using LeakReport = LeakDetector::LeakReport; +using InternalLeakReport = leak_detector::LeakDetectorImpl::LeakReport; +template <typename T> +using InternalVector = leak_detector::LeakDetectorImpl::InternalVector<T>; + +namespace { + +// Add the thread-local alloc size count to the shared alloc size count +// (LeakDetector::total_alloc_size_) whenever the local counter reaches +// |LeakDetector::analysis_interval_bytes_| divided by this value. Choose a +// high enough value that there is plenty of granularity, but low enough that a +// thread is not frequently updating the shared counter. +const int kTotalAllocSizeUpdateIntervalDivisor = 1024; + +#if defined(OS_CHROMEOS) +// For storing the address range of the Chrome binary in memory. +struct MappingInfo { + uintptr_t addr; + size_t size; +}; +#endif // defined(OS_CHROMEOS) + +// Local data to be used in the alloc/free hook functions to keep track of +// things across hook function calls. +struct HookData { + // The total number of bytes nominally allocated from the allocator on the + // current thread. + size_t alloc_size; + + // Flag indicating that one of the alloc hooks have already been entered. Used + // to handle recursive hook calls. Anything allocated when this flag is set + // should also be freed when this flag is set. + bool entered_hook; +}; + +#if defined(OS_CHROMEOS) +// Callback for dl_iterate_phdr() to find the Chrome binary mapping. +int IterateLoadedObjects(struct dl_phdr_info* shared_object, + size_t /* size */, + void* data) { + for (int i = 0; i < shared_object->dlpi_phnum; i++) { + // Find the ELF segment header that contains the actual code of the Chrome + // binary. + const ElfW(Phdr)& segment_header = shared_object->dlpi_phdr[i]; + if (segment_header.p_type == SHT_PROGBITS && segment_header.p_offset == 0 && + data) { + MappingInfo* mapping = reinterpret_cast<MappingInfo*>(data); + + // Make sure the fields in the ELF header and MappingInfo have the + // same size. + static_assert(sizeof(mapping->addr) == sizeof(shared_object->dlpi_addr), + "Integer size mismatch between MappingInfo::addr and " + "dl_phdr_info::dlpi_addr."); + static_assert(sizeof(mapping->size) == sizeof(segment_header.p_offset), + "Integer size mismatch between MappingInfo::size and " + "ElfW(Phdr)::p_memsz."); + + mapping->addr = shared_object->dlpi_addr + segment_header.p_offset; + mapping->size = segment_header.p_memsz; + return 1; + } + } + return 0; +} +#endif // defined(OS_CHROMEOS) + +// Convert a pointer to a hash value. Returns only the upper eight bits. +inline uint64_t PointerToHash(const void* ptr) { + // The input data is the pointer address, not the location in memory pointed + // to by the pointer. + // The multiplier is taken from Farmhash code: + // https://github.com/google/farmhash/blob/master/src/farmhash.cc + const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL; + return reinterpret_cast<uint64_t>(ptr) * kMultiplier; +} + +// Converts a vector of leak reports generated by LeakDetectorImpl +// (InternalLeakReport) to a vector of leak reports suitable for sending to +// LeakDetector's observers (LeakReport). +void GetReportsForObservers( + const InternalVector<InternalLeakReport>& leak_reports, + std::vector<LeakReport>* reports_for_observers) { + reports_for_observers->clear(); + reports_for_observers->reserve(leak_reports.size()); + for (const InternalLeakReport& report : leak_reports) { + reports_for_observers->push_back(LeakReport()); + LeakReport* new_report = &reports_for_observers->back(); + + new_report->alloc_size_bytes = report.alloc_size_bytes(); + if (!report.call_stack().empty()) { + new_report->call_stack.resize(report.call_stack().size()); + memcpy(new_report->call_stack.data(), report.call_stack().data(), + report.call_stack().size() * sizeof(report.call_stack()[0])); + } + } +} + +// The only instance of LeakDetector that should be used. +base::LazyInstance<LeakDetector>::Leaky g_instance = LAZY_INSTANCE_INITIALIZER; + +// Thread-specific data to be used by hook functions. +base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky g_hook_data_tls = + LAZY_INSTANCE_INITIALIZER; + +// Returns the contents of |g_hook_data_tls| as a HookData structure. +inline HookData LoadHookDataFromTLS() { + uintptr_t ptr_value = + reinterpret_cast<uintptr_t>(g_hook_data_tls.Get().Get()); + + // The lower bit of |ptr_value| indicates whether a hook has already been + // entered. The remaining bits store the alloc size. + HookData result; + result.entered_hook = ptr_value & 0x01; + result.alloc_size = ptr_value >> 1; + return result; +} + +// Stores a HookData structure in |g_hook_data_tls|. HookData is a trivial +// struct so it is faster to pass by value. +inline void StoreHookDataToTLS(HookData hook_data) { + // NOTE: |alloc_size| loses its upper bit when it gets stored in the TLS here. + // The effective max value of |alloc_size| is thus half its nominal max value. + uintptr_t ptr_value = + (hook_data.entered_hook ? 1 : 0) | (hook_data.alloc_size << 1); + g_hook_data_tls.Get().Set(reinterpret_cast<void*>(ptr_value)); +} + +} // namespace + +LeakDetector::LeakReport::LeakReport() {} + +LeakDetector::LeakReport::LeakReport(const LeakReport& other) = default; + +LeakDetector::LeakReport::~LeakReport() {} + +// static +LeakDetector* LeakDetector::GetInstance() { + return g_instance.Pointer(); +} + +void LeakDetector::Init(float sampling_rate, + size_t max_call_stack_unwind_depth, + uint64_t analysis_interval_bytes, + uint32_t size_suspicion_threshold, + uint32_t call_stack_suspicion_threshold) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(sampling_rate > 0) << "Sampling rate cannot be zero or negative."; + + sampling_factor_ = base::saturated_cast<uint64_t>(sampling_rate * UINT64_MAX); + + analysis_interval_bytes_ = analysis_interval_bytes; + max_call_stack_unwind_depth_ = max_call_stack_unwind_depth; + + MappingInfo mapping = {0}; +#if defined(OS_CHROMEOS) + // Locate the Chrome binary mapping info. + dl_iterate_phdr(IterateLoadedObjects, &mapping); +#endif // defined(OS_CHROMEOS) + + // CustomAllocator can use the default allocator, as long as the hook + // functions can handle recursive calls. + leak_detector::CustomAllocator::Initialize(); + + // The initialization should be done only once. Check for this by examining + // whether |impl_| has already been initialized. + CHECK(!impl_.get()) << "Cannot initialize LeakDetector more than once!"; + impl_.reset(new leak_detector::LeakDetectorImpl( + mapping.addr, mapping.size, size_suspicion_threshold, + call_stack_suspicion_threshold)); + + // Register allocator hook functions. This must be done last since the + // preceding code will need to call the allocator. + base::allocator::SetHooks(&AllocHook, &FreeHook); +} + +void LeakDetector::AddObserver(Observer* observer) { + base::AutoLock lock(observers_lock_); + observers_.AddObserver(observer); +} + +void LeakDetector::RemoveObserver(Observer* observer) { + base::AutoLock lock(observers_lock_); + observers_.RemoveObserver(observer); +} + +LeakDetector::LeakDetector() + : total_alloc_size_(0), + last_analysis_alloc_size_(0), + analysis_interval_bytes_(0), + max_call_stack_unwind_depth_(0), + sampling_factor_(0) {} + +LeakDetector::~LeakDetector() {} + +// static +void LeakDetector::AllocHook(const void* ptr, size_t size) { + HookData hook_data = LoadHookDataFromTLS(); + if (hook_data.entered_hook) + return; + + hook_data.alloc_size += size; + + LeakDetector* detector = GetInstance(); + if (!detector->ShouldSample(ptr)) { + StoreHookDataToTLS(hook_data); + return; + } + + hook_data.entered_hook = true; + StoreHookDataToTLS(hook_data); + + // Get stack trace if necessary. + std::vector<void*> stack; + int depth = 0; + if (detector->impl_->ShouldGetStackTraceForSize(size)) { + stack.resize(detector->max_call_stack_unwind_depth_); + depth = base::allocator::GetCallStack(stack.data(), stack.size()); + } + + { + base::AutoLock lock(detector->recording_lock_); + detector->impl_->RecordAlloc(ptr, size, depth, stack.data()); + + const auto& analysis_interval_bytes = detector->analysis_interval_bytes_; + auto& total_alloc_size = detector->total_alloc_size_; + // Update the shared counter, |detector->total_alloc_size_|, once the local + // counter reaches a threshold that is a fraction of the analysis interval. + // The fraction should be small enough (and hence the value of + // kTotalAllocSizeUpdateIntervalDivisor should be large enough) that the + // shared counter is updated with sufficient granularity. This way, even if + // a few threads were slow to reach the threshold, the leak analysis would + // not be delayed by too much. + if (hook_data.alloc_size >= + analysis_interval_bytes / kTotalAllocSizeUpdateIntervalDivisor) { + total_alloc_size += hook_data.alloc_size; + hook_data.alloc_size = 0; + } + + // Check for leaks after |analysis_interval_bytes_| bytes have been + // allocated since the last time that was done. + if (total_alloc_size > + detector->last_analysis_alloc_size_ + analysis_interval_bytes) { + // Try to maintain regular intervals of size |analysis_interval_bytes_|. + detector->last_analysis_alloc_size_ = + total_alloc_size - total_alloc_size % analysis_interval_bytes; + + InternalVector<InternalLeakReport> leak_reports; + detector->impl_->TestForLeaks(&leak_reports); + + // Pass leak reports to observers. + std::vector<LeakReport> leak_reports_for_observers; + GetReportsForObservers(leak_reports, &leak_reports_for_observers); + detector->NotifyObservers(leak_reports_for_observers); + } + } + + { + // The internal memory of |stack| should be freed before setting + // |entered_hook| to false at the end of this function. Free it here by + // moving the internal memory to a temporary variable that will go out of + // scope. + std::vector<void*> dummy_stack; + dummy_stack.swap(stack); + } + + hook_data.entered_hook = false; + StoreHookDataToTLS(hook_data); +} + +// static +void LeakDetector::FreeHook(const void* ptr) { + LeakDetector* detector = GetInstance(); + if (!detector->ShouldSample(ptr)) + return; + + HookData hook_data = LoadHookDataFromTLS(); + if (hook_data.entered_hook) + return; + + hook_data.entered_hook = true; + StoreHookDataToTLS(hook_data); + + { + base::AutoLock lock(detector->recording_lock_); + detector->impl_->RecordFree(ptr); + } + + hook_data.entered_hook = false; + StoreHookDataToTLS(hook_data); +} + +inline bool LeakDetector::ShouldSample(const void* ptr) const { + return PointerToHash(ptr) < sampling_factor_; +} + +void LeakDetector::NotifyObservers(const std::vector<LeakReport>& reports) { + if (reports.empty()) + return; + + if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&LeakDetector::NotifyObservers, base::Unretained(this), + reports)); + return; + } + + for (const LeakReport& report : reports) { + base::AutoLock lock(observers_lock_); + FOR_EACH_OBSERVER(Observer, observers_, OnLeakFound(report)); + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_detector.h b/chromium/components/metrics/leak_detector/leak_detector.h new file mode 100644 index 00000000000..8dcba5e9de2 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector.h @@ -0,0 +1,174 @@ +// Copyright 2016 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <list> +#include <vector> + +#include "base/feature_list.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" + +namespace base { +template <typename T> +struct DefaultLazyInstanceTraits; +} + +namespace metrics { + +namespace leak_detector { +class LeakDetectorImpl; +} + +// LeakDetector is an interface layer that connects the allocator +// (base::allocator), the leak detector logic (LeakDetectorImpl), and any +// external classes interested in receiving leak reports (extend the Observer +// class). +// +// Only one instance of this class can exist. Access this instance using +// GetInstance(). Do not create an instance of this class directly. +// +// These member functions are thread-safe: +// - AllocHook +// - FreeHook +// - AddObserver +// - RemoveObserver +// +// All other functions must always be called from the same thread. This is +// enforced with a DCHECK. +class LeakDetector { + public: + // Contains a report of a detected memory leak. + struct LeakReport { + LeakReport(); + LeakReport(const LeakReport& other); + ~LeakReport(); + + size_t alloc_size_bytes; + + // Unlike the CallStack struct, which consists of addresses, this call stack + // will contain offsets in the executable binary. + std::vector<uintptr_t> call_stack; + }; + + // Interface for receiving leak reports. + class Observer { + public: + virtual ~Observer() {} + + // Called by leak detector to report a leak. + virtual void OnLeakFound(const LeakReport& report) = 0; + }; + + // Returns the sole instance, or creates it if it hasn't already been created. + static LeakDetector* GetInstance(); + + // Initializer arguments: + // sampling_rate: + // Pseudorandomly sample a fraction of the incoming allocations and frees, + // based on hash values. Setting to 0 means no allocs/frees are sampled. + // Setting to 1.0 or more means all allocs/frees are sampled. Anything in + // between will result in an approximately that fraction of allocs/frees + // being sampled. + // max_call_stack_unwind_depth: + // The max number of call stack frames to unwind. + // analysis_interval_bytes: + // Perform a leak analysis each time this many bytes have been allocated + // since the previous analysis. + // size_suspicion_threshold, call_stack_suspicion_threshold: + // A possible leak should be suspected this many times to take action on i + // For size analysis, the action is to start profiling by call stack. + // For call stack analysis, the action is to generate a leak report. + void Init(float sampling_rate, + size_t max_call_stack_unwind_depth, + uint64_t analysis_interval_bytes, + uint32_t size_suspicion_threshold, + uint32_t call_stack_suspicion_threshold); + + // Add |observer| to the list of stored Observers, i.e. |observers_|, to which + // the leak detector will report leaks. + void AddObserver(Observer* observer); + + // Remove |observer| from |observers_|. + void RemoveObserver(Observer* observer); + + private: + friend base::DefaultLazyInstanceTraits<LeakDetector>; + FRIEND_TEST_ALL_PREFIXES(LeakDetectorTest, NotifyObservers); + + // Keep these private, as this class is meant to be initialized only through + // the lazy instance, and never destroyed. + LeakDetector(); + ~LeakDetector(); + + // Allocator hook function that processes each alloc. Performs sampling and + // unwinds call stack if necessary. Passes the allocated memory |ptr| and + // allocation size |size| along with call stack info to RecordAlloc(). + static void AllocHook(const void* ptr, size_t size); + + // Allocator hook function that processes each free. Performs sampling and + // passes the allocation address |ptr| to |impl_|. + static void FreeHook(const void* ptr); + + // Give an pointer |ptr|, computes a hash of the pointer value and compares it + // against |sampling_factor_| to determine if it should be sampled. This + // allows the same pointer to be sampled during both alloc and free. + bool ShouldSample(const void* ptr) const; + + // Notifies all Observers in |observers_| with the given vector of leak + // reports. + void NotifyObservers(const std::vector<LeakReport>& reports); + + // List of observers to notify when there's a leak report. + // TODO(sque): Consider using ObserverListThreadSafe instead. + base::ObserverList<Observer> observers_; + + // For atomic access to |observers_|. + base::Lock observers_lock_; + + // Handles leak detection logic. Must be called under lock as LeakDetectorImpl + // uses shared resources. + scoped_ptr<leak_detector::LeakDetectorImpl> impl_; + + // For thread safety. + base::ThreadChecker thread_checker_; + + // Total number of bytes allocated, computed before sampling. + size_t total_alloc_size_; + + // The value of |total_alloc_size_| the last time there was a leak analysis, + // rounded down to the nearest multiple of |analysis_interval_bytes_|. + size_t last_analysis_alloc_size_; + + // For atomic access to |impl_|, |total_alloc_size_| and + // |last_analysis_alloc_size_|. + base::Lock recording_lock_; + + // Perform a leak analysis each time this many bytes have been allocated since + // the previous analysis. + size_t analysis_interval_bytes_; + + // When unwinding call stacks, unwind no more than this number of frames. + size_t max_call_stack_unwind_depth_; + + // Sampling factor used by ShouldSample(). It's full range of values + // corresponds to the allowable range of |sampling_rate| passed in during + // initialization: [0.0f, 1.0f] -> [0, UINT64_MAX]. + uint64_t sampling_factor_; + + DISALLOW_COPY_AND_ASSIGN(LeakDetector); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_ diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl.cc b/chromium/components/metrics/leak_detector/leak_detector_impl.cc new file mode 100644 index 00000000000..e174cc20cc8 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector_impl.cc @@ -0,0 +1,241 @@ +// Copyright 2015 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 "leak_detector_impl.h" + +#include <inttypes.h> +#include <stddef.h> + +#include <algorithm> +#include <new> + +#include "base/hash.h" +#include "base/process/process_handle.h" +#include "components/metrics/leak_detector/call_stack_table.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/ranked_set.h" + +namespace metrics { +namespace leak_detector { + +namespace { + +// Look for leaks in the the top N entries in each tier, where N is this value. +const int kRankedSetSize = 16; + +// Initial hash table size for |LeakDetectorImpl::address_map_|. +const int kAddressMapNumBuckets = 100003; + +// Number of entries in the alloc size table. As sizes are aligned to 32-bits +// the max supported allocation size is (kNumSizeEntries * 4 - 1). Any larger +// sizes are ignored. This value is chosen high enough that such large sizes +// are rare if not nonexistent. +const int kNumSizeEntries = 2048; + +// Record only the first |kNumSizeEntriesInHistory| size classes in +// |LeakDetectorImpl::size_breakdown_history_|. +const int kNumSizeEntriesInHistory = 32; + +// |LeakDetectorImpl::size_breakdown_history_| can have up to this many entries. +// Any older entries must be discarded to make way for new ones. +const int kMaxNumHistoryEntries = 32; + +using ValueType = LeakDetectorValueType; + +// Functions to convert an allocation size to/from the array index used for +// |LeakDetectorImpl::size_entries_|. +size_t SizeToIndex(const size_t size) { + int result = static_cast<int>(size / sizeof(uint32_t)); + if (result < kNumSizeEntries) + return result; + return 0; +} + +size_t IndexToSize(size_t index) { + return sizeof(uint32_t) * index; +} + +} // namespace + +LeakDetectorImpl::LeakReport::LeakReport() : alloc_size_bytes_(0) {} + +LeakDetectorImpl::LeakReport::LeakReport(const LeakReport& other) = default; + +LeakDetectorImpl::LeakReport::~LeakReport() {} + +bool LeakDetectorImpl::LeakReport::operator<(const LeakReport& other) const { + if (alloc_size_bytes_ != other.alloc_size_bytes_) + return alloc_size_bytes_ < other.alloc_size_bytes_; + for (size_t i = 0; i < call_stack_.size() && i < other.call_stack_.size(); + ++i) { + if (call_stack_[i] != other.call_stack_[i]) + return call_stack_[i] < other.call_stack_[i]; + } + return call_stack_.size() < other.call_stack_.size(); +} + +LeakDetectorImpl::LeakDetectorImpl(uintptr_t mapping_addr, + size_t mapping_size, + int size_suspicion_threshold, + int call_stack_suspicion_threshold) + : num_allocs_(0), + num_frees_(0), + alloc_size_(0), + free_size_(0), + num_allocs_with_call_stack_(0), + num_stack_tables_(0), + address_map_(kAddressMapNumBuckets), + size_leak_analyzer_(kRankedSetSize, size_suspicion_threshold), + size_entries_(kNumSizeEntries), + mapping_addr_(mapping_addr), + mapping_size_(mapping_size), + call_stack_suspicion_threshold_(call_stack_suspicion_threshold) {} + +LeakDetectorImpl::~LeakDetectorImpl() { + // Free any call stack tables. + for (AllocSizeEntry& entry : size_entries_) { + CallStackTable* table = entry.stack_table; + if (!table) + continue; + table->~CallStackTable(); + CustomAllocator::Free(table, sizeof(CallStackTable)); + } + size_entries_.clear(); +} + +bool LeakDetectorImpl::ShouldGetStackTraceForSize(size_t size) const { + return size_entries_[SizeToIndex(size)].stack_table != nullptr; +} + +void LeakDetectorImpl::RecordAlloc(const void* ptr, + size_t size, + int stack_depth, + const void* const stack[]) { + AllocInfo alloc_info; + alloc_info.size = size; + + alloc_size_ += alloc_info.size; + ++num_allocs_; + + AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)]; + ++entry->num_allocs; + + if (entry->stack_table && stack_depth > 0) { + alloc_info.call_stack = + call_stack_manager_.GetCallStack(stack_depth, stack); + entry->stack_table->Add(alloc_info.call_stack); + + ++num_allocs_with_call_stack_; + } + + uintptr_t addr = reinterpret_cast<uintptr_t>(ptr); + address_map_.insert(std::pair<uintptr_t, AllocInfo>(addr, alloc_info)); +} + +void LeakDetectorImpl::RecordFree(const void* ptr) { + // Look up address. + uintptr_t addr = reinterpret_cast<uintptr_t>(ptr); + auto iter = address_map_.find(addr); + // TODO(sque): Catch and report double frees. + if (iter == address_map_.end()) + return; + + const AllocInfo& alloc_info = iter->second; + + AllocSizeEntry* entry = &size_entries_[SizeToIndex(alloc_info.size)]; + ++entry->num_frees; + + const CallStack* call_stack = alloc_info.call_stack; + if (call_stack) { + if (entry->stack_table) + entry->stack_table->Remove(call_stack); + } + ++num_frees_; + free_size_ += alloc_info.size; + + address_map_.erase(iter); +} + +void LeakDetectorImpl::TestForLeaks(InternalVector<LeakReport>* reports) { + // Add net alloc counts for each size to a ranked list. + RankedSet size_ranked_set(kRankedSetSize); + for (size_t i = 0; i < size_entries_.size(); ++i) { + const AllocSizeEntry& entry = size_entries_[i]; + ValueType size_value(IndexToSize(i)); + size_ranked_set.Add(size_value, entry.GetNetAllocs()); + } + size_leak_analyzer_.AddSample(std::move(size_ranked_set)); + + // Record a snapshot of the current size table. + InternalVector<uint32_t> current_size_table_record; + current_size_table_record.reserve(kNumSizeEntriesInHistory); + for (const AllocSizeEntry& entry : size_entries_) { + if (current_size_table_record.size() == kNumSizeEntriesInHistory) + break; + current_size_table_record.push_back(entry.GetNetAllocs()); + } + size_breakdown_history_.emplace_back(std::move(current_size_table_record)); + if (size_breakdown_history_.size() > kMaxNumHistoryEntries) + size_breakdown_history_.pop_front(); + + // Get suspected leaks by size. + for (const ValueType& size_value : size_leak_analyzer_.suspected_leaks()) { + uint32_t size = size_value.size(); + AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)]; + if (entry->stack_table) + continue; + entry->stack_table = new (CustomAllocator::Allocate(sizeof(CallStackTable))) + CallStackTable(call_stack_suspicion_threshold_); + ++num_stack_tables_; + } + + // Check for leaks in each CallStackTable. It makes sense to this before + // checking the size allocations, because that could potentially create new + // CallStackTable. However, the overhead to check a new CallStackTable is + // small since this function is run very rarely. So handle the leak checks of + // Tier 2 here. + reports->clear(); + for (size_t i = 0; i < size_entries_.size(); ++i) { + const AllocSizeEntry& entry = size_entries_[i]; + CallStackTable* stack_table = entry.stack_table; + if (!stack_table || stack_table->empty()) + continue; + + size_t size = IndexToSize(i); + + // Get suspected leaks by call stack. + stack_table->TestForLeaks(); + const LeakAnalyzer& leak_analyzer = stack_table->leak_analyzer(); + for (const ValueType& call_stack_value : leak_analyzer.suspected_leaks()) { + const CallStack* call_stack = call_stack_value.call_stack(); + + // Return reports by storing in |*reports|. + reports->resize(reports->size() + 1); + LeakReport* report = &reports->back(); + report->alloc_size_bytes_ = size; + report->call_stack_.resize(call_stack->depth); + for (size_t j = 0; j < call_stack->depth; ++j) { + report->call_stack_[j] = GetOffset(call_stack->stack[j]); + } + // Copy over the historical size data. + report->size_breakdown_history_.reserve(size_breakdown_history_.size()); + report->size_breakdown_history_.assign(size_breakdown_history_.begin(), + size_breakdown_history_.end()); + } + } +} + +size_t LeakDetectorImpl::AddressHash::operator()(uintptr_t addr) const { + return base::Hash(reinterpret_cast<const char*>(&addr), sizeof(addr)); +} + +uintptr_t LeakDetectorImpl::GetOffset(const void* ptr) const { + uintptr_t ptr_value = reinterpret_cast<uintptr_t>(ptr); + if (ptr_value >= mapping_addr_ && ptr_value < mapping_addr_ + mapping_size_) + return ptr_value - mapping_addr_; + return ptr_value; +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl.h b/chromium/components/metrics/leak_detector/leak_detector_impl.h new file mode 100644 index 00000000000..fafd7b814f4 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector_impl.h @@ -0,0 +1,185 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <list> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "components/metrics/leak_detector/call_stack_manager.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/leak_analyzer.h" +#include "components/metrics/leak_detector/stl_allocator.h" + +namespace metrics { +namespace leak_detector { + +class CallStackTable; + +// Class that contains the actual leak detection mechanism. +// Not thread-safe. +class LeakDetectorImpl { + public: + // STL types that are safe to use within the memory leak detector. They use + // CustomAllocator to avoid recursive malloc hook invocation when analyzing + // allocs and frees. + template <typename T> + using InternalList = std::list<T, STLAllocator<T, CustomAllocator>>; + template <typename T> + using InternalVector = std::vector<T, STLAllocator<T, CustomAllocator>>; + + // Leak report generated by LeakDetectorImpl. + class LeakReport { + public: + LeakReport(); + LeakReport(const LeakReport& other); + ~LeakReport(); + + size_t alloc_size_bytes() const { return alloc_size_bytes_; } + + const InternalVector<uintptr_t>& call_stack() const { return call_stack_; } + + const InternalVector<InternalVector<uint32_t>>& size_breakdown_history() + const { + return size_breakdown_history_; + } + + // Used to compare the contents of two leak reports. + bool operator<(const LeakReport& other) const; + + private: + // LeakDetectorImpl needs access to class members when creating a new leak + // report. + friend class LeakDetectorImpl; + + // Number of bytes allocated by the leak site during each allocation. + size_t alloc_size_bytes_; + + // Unlike the CallStack struct, which consists of addresses, this call stack + // will contain offsets in the executable binary. + InternalVector<uintptr_t> call_stack_; + + // A snapshot of LeakDetectorImpl::size_breakdown_history_ when this report + // was generated. See comment description of that variable. + InternalVector<InternalVector<uint32_t>> size_breakdown_history_; + }; + + LeakDetectorImpl(uintptr_t mapping_addr, + size_t mapping_size, + int size_suspicion_threshold, + int call_stack_suspicion_threshold); + ~LeakDetectorImpl(); + + // Indicates whether the given allocation size has an associated call stack + // table, and thus requires a stack unwind. + bool ShouldGetStackTraceForSize(size_t size) const; + + // Record allocs and frees. + void RecordAlloc(const void* ptr, + size_t size, + int stack_depth, + const void* const call_stack[]); + void RecordFree(const void* ptr); + + // Run check for possible leaks based on the current profiling data. + void TestForLeaks(InternalVector<LeakReport>* reports); + + private: + // A record of allocations for a particular size. + struct AllocSizeEntry { + // Number of allocations and frees for this size. + uint32_t num_allocs; + uint32_t num_frees; + + // A stack table, if this size is being profiled for stack as well. + CallStackTable* stack_table; + + // Returns net number of allocs. + uint32_t GetNetAllocs() const { return num_allocs - num_frees; } + }; + + // Info for a single allocation. + struct AllocInfo { + AllocInfo() : call_stack(nullptr) {} + + // Number of bytes in this allocation. + size_t size; + + // Points to a unique call stack. + const CallStack* call_stack; + }; + + // Allocator class for allocation entry map. Maps allocated addresses to + // AllocInfo objects. + using AllocationEntryAllocator = + STLAllocator<std::pair<const uintptr_t, AllocInfo>, CustomAllocator>; + + // Hash class for addresses. + struct AddressHash { + size_t operator()(uintptr_t addr) const; + }; + + // Returns the offset of |ptr| within the current binary. If it is not in the + // current binary, just return |ptr| as an integer. + uintptr_t GetOffset(const void* ptr) const; + + // Owns all unique call stack objects, which are allocated on the heap. Any + // other class or function that references a call stack must get it from here, + // but may not take ownership of the call stack object. + CallStackManager call_stack_manager_; + + // Allocation stats. + uint64_t num_allocs_; + uint64_t num_frees_; + uint64_t alloc_size_; + uint64_t free_size_; + + uint32_t num_allocs_with_call_stack_; + uint32_t num_stack_tables_; + + // Stores all individual recorded allocations. + base::hash_map<uintptr_t, + AllocInfo, + AddressHash, + std::equal_to<uintptr_t>, + AllocationEntryAllocator> address_map_; + + // Used to analyze potential leak patterns in the allocation sizes. + LeakAnalyzer size_leak_analyzer_; + + // Allocation stats for each size. + InternalVector<AllocSizeEntry> size_entries_; + + // Tracks the net number of allocations per size over time. Each list item is + // a vector containing the allocation counts for each size. The vector element + // with index i corresponds to sizes |i * 4| to |i * 4 + 3|. The oldest size + // breakdowns is at the head of the list, and new size breakdowns should be + // added to the tail of the list. + InternalList<InternalVector<uint32_t>> size_breakdown_history_; + + // Address mapping info of the current binary. + uintptr_t mapping_addr_; + size_t mapping_size_; + + // Number of consecutive times an allocation size must trigger suspicion to be + // considered a leak suspect. + int size_suspicion_threshold_; + + // Number of consecutive times a call stack must trigger suspicion to be + // considered a leak suspect. + int call_stack_suspicion_threshold_; + + DISALLOW_COPY_AND_ASSIGN(LeakDetectorImpl); +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_ diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc b/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc new file mode 100644 index 00000000000..7a7e30a4413 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc @@ -0,0 +1,629 @@ +// Copyright 2015 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 "components/metrics/leak_detector/leak_detector_impl.h" + +#include <math.h> +#include <stddef.h> +#include <stdint.h> + +#include <complex> +#include <new> +#include <set> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { +namespace leak_detector { + +using InternalLeakReport = LeakDetectorImpl::LeakReport; +template <typename T> +using InternalVector = LeakDetectorImpl::InternalVector<T>; + +namespace { + +// Makes working with complex numbers easier. +using Complex = std::complex<double>; + +// The mapping location in memory for a fictional executable. +const uintptr_t kMappingAddr = 0x800000; +const size_t kMappingSize = 0x200000; + +// Some call stacks within the fictional executable. +// * - outside the mapping range, e.g. JIT code. +const uintptr_t kRawStack0[] = { + 0x800100, 0x900000, 0x880080, 0x810000, +}; +const uintptr_t kRawStack1[] = { + 0x940000, 0x980000, + 0xdeadbeef, // * + 0x9a0000, +}; +const uintptr_t kRawStack2[] = { + 0x8f0d00, 0x803abc, 0x9100a0, +}; +const uintptr_t kRawStack3[] = { + 0x90fcde, + 0x900df00d, // * + 0x801000, 0x880088, + 0xdeadcafe, // * + 0x9f0000, 0x8700a0, 0x96037c, +}; +const uintptr_t kRawStack4[] = { + 0x8c0000, 0x85d00d, 0x921337, + 0x780000, // * +}; +const uintptr_t kRawStack5[] = { + 0x990000, 0x888888, 0x830ac0, 0x8e0000, + 0xc00000, // * +}; + +// This struct makes it easier to pass call stack info to +// LeakDetectorImplTest::Alloc(). +struct TestCallStack { + const uintptr_t* stack; // A reference to the original stack data. + size_t depth; +}; + +const TestCallStack kStack0 = {kRawStack0, arraysize(kRawStack0)}; +const TestCallStack kStack1 = {kRawStack1, arraysize(kRawStack1)}; +const TestCallStack kStack2 = {kRawStack2, arraysize(kRawStack2)}; +const TestCallStack kStack3 = {kRawStack3, arraysize(kRawStack3)}; +const TestCallStack kStack4 = {kRawStack4, arraysize(kRawStack4)}; +const TestCallStack kStack5 = {kRawStack5, arraysize(kRawStack5)}; + +// The interval between consecutive analyses (LeakDetectorImpl::TestForLeaks), +// in number of bytes allocated. e.g. if |kAllocedSizeAnalysisInterval| = 1024 +// then call TestForLeaks() every 1024 bytes of allocation that occur. +const size_t kAllocedSizeAnalysisInterval = 8192; + +// Suspicion thresholds used by LeakDetectorImpl for size and call stacks. +const uint32_t kSizeSuspicionThreshold = 4; +const uint32_t kCallStackSuspicionThreshold = 4; + +// Returns the offset within [kMappingAddr, kMappingAddr + kMappingSize) if +// |addr| falls in that range. Otherwise, returns |addr|. +uintptr_t GetOffsetInMapping(uintptr_t addr) { + if (addr >= kMappingAddr && addr < kMappingAddr + kMappingSize) + return addr - kMappingAddr; + return addr; +} + +// Copied from leak_detector_impl.cc. Converts a size to a size class index. +// Any size in the range [index * 4, index * 4 + 3] falls into that size class. +uint32_t SizeToIndex(size_t size) { + return size / sizeof(uint32_t); +} + +} // namespace + +// This test suite will test the ability of LeakDetectorImpl to catch leaks in +// a program. Individual tests can run leaky code locally. +// +// The leaky code must call Alloc() and Free() for heap memory management. It +// should not call See comments on those +// functions for more details. +class LeakDetectorImplTest : public ::testing::Test { + public: + LeakDetectorImplTest() + : total_num_allocs_(0), + total_num_frees_(0), + total_alloced_size_(0), + next_analysis_total_alloced_size_(kAllocedSizeAnalysisInterval) {} + + void SetUp() override { + CustomAllocator::Initialize(); + + detector_.reset(new LeakDetectorImpl(kMappingAddr, kMappingSize, + kSizeSuspicionThreshold, + kCallStackSuspicionThreshold)); + } + + void TearDown() override { + // Free any memory that was leaked by test cases. Do not use Free() because + // that will try to modify |alloced_ptrs_|. + for (void* ptr : alloced_ptrs_) + delete[] reinterpret_cast<char*>(ptr); + alloced_ptrs_.clear(); + + // Must destroy all objects that use CustomAllocator before shutting down. + detector_.reset(); + stored_reports_.clear(); + + EXPECT_TRUE(CustomAllocator::Shutdown()); + } + + protected: + // Alloc and free functions that allocate and free heap memory and + // automatically pass alloc/free info to |detector_|. They emulate the + // alloc/free hook functions that would call into LeakDetectorImpl in + // real-life usage. They also keep track of individual allocations locally, so + // any leaked memory could be cleaned up. + // + // |stack| is just a nominal call stack object to identify the call site. It + // doesn't have to contain the stack trace of the actual call stack. + void* Alloc(size_t size, const TestCallStack& stack) { + void* ptr = new char[size]; + detector_->RecordAlloc(ptr, size, stack.depth, + reinterpret_cast<const void* const*>(stack.stack)); + + EXPECT_TRUE(alloced_ptrs_.find(ptr) == alloced_ptrs_.end()); + alloced_ptrs_.insert(ptr); + + ++total_num_allocs_; + total_alloced_size_ += size; + if (total_alloced_size_ >= next_analysis_total_alloced_size_) { + InternalVector<InternalLeakReport> reports; + detector_->TestForLeaks(&reports); + for (const InternalLeakReport& report : reports) { + auto iter = stored_reports_.find(report); + if (iter == stored_reports_.end()) { + stored_reports_.insert(report); + } else { + // InternalLeakReports are uniquely identified by |alloc_size_bytes_| + // and |call_stack_|. See InternalLeakReport::operator<(). + // If a report with the same size and call stack already exists, + // overwrite it with the new report, which has a newer history. + stored_reports_.erase(iter); + stored_reports_.insert(report); + } + } + + // Determine when the next leak analysis should occur. + while (total_alloced_size_ >= next_analysis_total_alloced_size_) + next_analysis_total_alloced_size_ += kAllocedSizeAnalysisInterval; + } + return ptr; + } + + // See comment for Alloc(). + void Free(void* ptr) { + auto find_ptr_iter = alloced_ptrs_.find(ptr); + EXPECT_FALSE(find_ptr_iter == alloced_ptrs_.end()); + if (find_ptr_iter == alloced_ptrs_.end()) + return; + alloced_ptrs_.erase(find_ptr_iter); + ++total_num_frees_; + + detector_->RecordFree(ptr); + + delete[] reinterpret_cast<char*>(ptr); + } + + // TEST CASE: Simple program that leaks memory regularly. Pass in + // enable_leaks=true to trigger some memory leaks. + void SimpleLeakyFunction(bool enable_leaks); + + // TEST CASE: Julia set fractal computation. Pass in enable_leaks=true to + // trigger some memory leaks. + void JuliaSet(bool enable_leaks); + + // Instance of the class being tested. + scoped_ptr<LeakDetectorImpl> detector_; + + // Number of pointers allocated and freed so far. + size_t total_num_allocs_; + size_t total_num_frees_; + + // Keeps count of total size allocated by Alloc(). + size_t total_alloced_size_; + + // The cumulative allocation size at which to trigger the TestForLeaks() call. + size_t next_analysis_total_alloced_size_; + + // Stores all pointers to memory allocated by by Alloc() so we can manually + // free the leaked pointers at the end. This also serves as redundant + // bookkeepping: it stores all pointers that have been allocated but not yet + // freed. + std::set<void*> alloced_ptrs_; + + // Store leak reports here. Use a set so duplicate reports are not stored. + std::set<InternalLeakReport> stored_reports_; + + private: + DISALLOW_COPY_AND_ASSIGN(LeakDetectorImplTest); +}; + +void LeakDetectorImplTest::SimpleLeakyFunction(bool enable_leaks) { + std::vector<uint32_t*> ptrs(7); + + const int kNumOuterIterations = 20; + for (int j = 0; j < kNumOuterIterations; ++j) { + // The inner loop allocates 256 bytes. Run it 32 times so that 8192 bytes + // (|kAllocedSizeAnalysisInterval|) are allocated for each iteration of the + // outer loop. + const int kNumInnerIterations = 32; + static_assert(kNumInnerIterations * 256 == kAllocedSizeAnalysisInterval, + "Inner loop iterations do not allocate the correct number of " + "bytes."); + for (int i = 0; i < kNumInnerIterations; ++i) { + size_t alloc_size_at_beginning = total_alloced_size_; + + ptrs[0] = new(Alloc(16, kStack0)) uint32_t; + ptrs[1] = new(Alloc(32, kStack1)) uint32_t; + ptrs[2] = new(Alloc(48, kStack2)) uint32_t; + // Allocate two 32-byte blocks and record them as from the same call site. + ptrs[3] = new(Alloc(32, kStack3)) uint32_t; + ptrs[4] = new(Alloc(32, kStack3)) uint32_t; + // Allocate two 48-byte blocks and record them as from the same call site. + ptrs[5] = new(Alloc(48, kStack4)) uint32_t; + ptrs[6] = new(Alloc(48, kStack4)) uint32_t; + + // Now free these pointers. + Free(ptrs[0]); + if (!enable_leaks) // Leak with size=32, call_stack=kStack1. + Free(ptrs[1]); + if (!enable_leaks) // Leak with size=48, call_stack=kStack2. + Free(ptrs[2]); + Free(ptrs[3]); + Free(ptrs[4]); + Free(ptrs[5]); + Free(ptrs[6]); + + // Make sure that the above code actually allocates 256 bytes. + EXPECT_EQ(alloc_size_at_beginning + 256, total_alloced_size_); + } + } +} + +void LeakDetectorImplTest::JuliaSet(bool enable_leaks) { + // The center region of the complex plane that is the basis for our Julia set + // computations is a circle of radius kRadius. + constexpr double kRadius = 2; + + // To track points in the complex plane, we will use a rectangular grid in the + // range defined by [-kRadius, kRadius] along both axes. + constexpr double kRangeMin = -kRadius; + constexpr double kRangeMax = kRadius; + + // Divide each axis into intervals, each of which is associated with a point + // on that axis at its center. + constexpr double kIntervalInverse = 64; + constexpr double kInterval = 1.0 / kIntervalInverse; + constexpr int kNumPoints = (kRangeMax - kRangeMin) / kInterval + 1; + + // Contains some useful functions for converting between points on the complex + // plane and in a gridlike data structure. + struct ComplexPlane { + static int GetXGridIndex(const Complex& value) { + return (value.real() + kInterval / 2 - kRangeMin) / kInterval; + } + static int GetYGridIndex(const Complex& value) { + return (value.imag() + kInterval / 2 - kRangeMin) / kInterval; + } + static int GetArrayIndex(const Complex& value) { + return GetXGridIndex(value) + GetYGridIndex(value) * kNumPoints; + } + static Complex GetComplexForGridPoint(size_t x, size_t y) { + return Complex(kRangeMin + x * kInterval, kRangeMin + y * kInterval); + } + }; + + // Make sure the choice of interval doesn't result in any loss of precision. + ASSERT_EQ(1.0, kInterval * kIntervalInverse); + + // Create a grid for part of the complex plane, with each axis within the + // range [kRangeMin, kRangeMax]. + constexpr size_t width = kNumPoints; + constexpr size_t height = kNumPoints; + std::vector<Complex*> grid(width * height); + + // Initialize an object for each point within the inner circle |z| < kRadius. + for (size_t i = 0; i < width; ++i) { + for (size_t j = 0; j < height; ++j) { + Complex point = ComplexPlane::GetComplexForGridPoint(i, j); + // Do not store any values outside the inner circle. + if (abs(point) <= kRadius) { + grid[i + j * width] = + new (Alloc(sizeof(Complex), kStack0)) Complex(point); + } + } + } + EXPECT_LE(alloced_ptrs_.size(), width * height); + + // Create a new grid for the result of the transformation. + std::vector<Complex*> next_grid(width * height, nullptr); + + // Number of times to run the Julia set iteration. This is not the same as the + // number of analyses performed by LeakDetectorImpl, which is determined by + // the total number of bytes allocated divided by + // |kAllocedSizeAnalysisInterval|. + const int kNumIterations = 20; + for (int n = 0; n < kNumIterations; ++n) { + for (int i = 0; i < kNumPoints; ++i) { + for (int j = 0; j < kNumPoints; ++j) { + if (!grid[i + j * width]) + continue; + + // NOTE: The below code is NOT an efficient way to compute a Julia set. + // This is only to test the leak detector with some nontrivial code. + + // A simple polynomial function for generating Julia sets is: + // f(z) = z^n + c + + // But in this algorithm, we need the inverse: + // fInv(z) = (z - c)^(1/n) + + // Here, let's use n=5 and c=0.544. + const Complex c(0.544, 0); + const Complex& z = *grid[i + j * width]; + + // This is the principal root. + Complex root = pow(z - c, 0.2); + + // Discard the result if it is too far out from the center of the plane. + if (abs(root) > kRadius) + continue; + + // The below code only allocates Complex objects of the same size. The + // leak detector expects various sizes, so increase the allocation size + // by a different amount at each call site. + + // Nth root produces N results. + // Place all root results on |next_grid|. + + // First, place the principal root. + if (!next_grid[ComplexPlane::GetArrayIndex(root)]) { + next_grid[ComplexPlane::GetArrayIndex(root)] = + new (Alloc(sizeof(Complex) + 24, kStack1)) Complex(root); + } + + double magnitude = abs(root); + double angle = arg(root); + // To generate other roots, rotate the principal root by increments of + // 1/N of a full circle. + const double kAngleIncrement = M_PI * 2 / 5; + + // Second root. + root = std::polar(magnitude, angle + kAngleIncrement); + if (!next_grid[ComplexPlane::GetArrayIndex(root)]) { + next_grid[ComplexPlane::GetArrayIndex(root)] = + new (Alloc(sizeof(Complex) + 40, kStack2)) Complex(root); + } + + // In some of the sections below, setting |enable_leaks| to true will + // trigger a memory leak by overwriting the old Complex pointer value + // without freeing it. Due to the nature of complex roots being confined + // to equal sections of the complex plane, each new pointer will + // displace an old pointer that was allocated from the same line of + // code. + + // Third root. + root = std::polar(magnitude, angle + kAngleIncrement * 2); + // *** LEAK *** + if (enable_leaks || !next_grid[ComplexPlane::GetArrayIndex(root)]) { + next_grid[ComplexPlane::GetArrayIndex(root)] = + new (Alloc(sizeof(Complex) + 40, kStack3)) Complex(root); + } + + // Fourth root. + root = std::polar(magnitude, angle + kAngleIncrement * 3); + // *** LEAK *** + if (enable_leaks || !next_grid[ComplexPlane::GetArrayIndex(root)]) { + next_grid[ComplexPlane::GetArrayIndex(root)] = + new (Alloc(sizeof(Complex) + 52, kStack4)) Complex(root); + } + + // Fifth root. + root = std::polar(magnitude, angle + kAngleIncrement * 4); + if (!next_grid[ComplexPlane::GetArrayIndex(root)]) { + next_grid[ComplexPlane::GetArrayIndex(root)] = + new (Alloc(sizeof(Complex) + 52, kStack5)) Complex(root); + } + } + } + + // Clear the previously allocated points. + for (Complex*& point : grid) { + if (point) { + Free(point); + point = nullptr; + } + } + + // Now swap the two grids for the next iteration. + grid.swap(next_grid); + } + + // Clear the previously allocated points. + for (Complex*& point : grid) { + if (point) { + Free(point); + point = nullptr; + } + } +} + +TEST_F(LeakDetectorImplTest, CheckTestFramework) { + EXPECT_EQ(0U, total_num_allocs_); + EXPECT_EQ(0U, total_num_frees_); + EXPECT_EQ(0U, alloced_ptrs_.size()); + + // Allocate some memory. + void* ptr0 = Alloc(12, kStack0); + void* ptr1 = Alloc(16, kStack0); + void* ptr2 = Alloc(24, kStack0); + EXPECT_EQ(3U, total_num_allocs_); + EXPECT_EQ(0U, total_num_frees_); + EXPECT_EQ(3U, alloced_ptrs_.size()); + + // Free one of the pointers. + Free(ptr1); + EXPECT_EQ(3U, total_num_allocs_); + EXPECT_EQ(1U, total_num_frees_); + EXPECT_EQ(2U, alloced_ptrs_.size()); + + // Allocate some more memory. + void* ptr3 = Alloc(72, kStack1); + void* ptr4 = Alloc(104, kStack1); + void* ptr5 = Alloc(96, kStack1); + void* ptr6 = Alloc(24, kStack1); + EXPECT_EQ(7U, total_num_allocs_); + EXPECT_EQ(1U, total_num_frees_); + EXPECT_EQ(6U, alloced_ptrs_.size()); + + // Free more pointers. + Free(ptr2); + Free(ptr4); + Free(ptr6); + EXPECT_EQ(7U, total_num_allocs_); + EXPECT_EQ(4U, total_num_frees_); + EXPECT_EQ(3U, alloced_ptrs_.size()); + + // Free remaining memory. + Free(ptr0); + Free(ptr3); + Free(ptr5); + EXPECT_EQ(7U, total_num_allocs_); + EXPECT_EQ(7U, total_num_frees_); + EXPECT_EQ(0U, alloced_ptrs_.size()); +} + +TEST_F(LeakDetectorImplTest, SimpleLeakyFunctionNoLeak) { + SimpleLeakyFunction(false /* enable_leaks */); + + // SimpleLeakyFunction() should have run cleanly without leaking. + EXPECT_EQ(total_num_allocs_, total_num_frees_); + EXPECT_EQ(0U, alloced_ptrs_.size()); + ASSERT_EQ(0U, stored_reports_.size()); +} + +TEST_F(LeakDetectorImplTest, SimpleLeakyFunctionWithLeak) { + SimpleLeakyFunction(true /* enable_leaks */); + + // SimpleLeakyFunction() should generated some leak reports. + EXPECT_GT(total_num_allocs_, total_num_frees_); + EXPECT_GT(alloced_ptrs_.size(), 0U); + ASSERT_EQ(2U, stored_reports_.size()); + + // The reports should be stored in order of size. + + // |report1| comes from the call site marked with kStack1, with size=32. + const InternalLeakReport& report1 = *stored_reports_.begin(); + EXPECT_EQ(32U, report1.alloc_size_bytes()); + ASSERT_EQ(kStack1.depth, report1.call_stack().size()); + for (size_t i = 0; i < kStack1.depth; ++i) { + EXPECT_EQ(GetOffsetInMapping(kStack1.stack[i]), + report1.call_stack()[i]) << i; + } + + // |report2| comes from the call site marked with kStack2, with size=48. + const InternalLeakReport& report2 = *(++stored_reports_.begin()); + EXPECT_EQ(48U, report2.alloc_size_bytes()); + ASSERT_EQ(kStack2.depth, report2.call_stack().size()); + for (size_t i = 0; i < kStack2.depth; ++i) { + EXPECT_EQ(GetOffsetInMapping(kStack2.stack[i]), + report2.call_stack()[i]) << i; + } + + // Check historical data recorded in the reports. + // - Each inner loop iteration allocates a net of 1x 32 bytes and 1x 48 bytes. + // - Each outer loop iteration allocates a net of 32x 32 bytes and 32x 48 + // bytes. + // - However, the leak analysis happens after the allocs but before the frees + // that come right after. So it should count the two extra allocs made at + // call sites |kStack3| and |kStack4|. The formula is |(i + 1) * 32 + 2|, + // where |i| is the iteration index. + // There should have been one leak analysis per outer loop iteration, for a + // total of 20 history records (|kNumOuterIterations|) per report. + + const auto& report1_size_history = report1.size_breakdown_history(); + EXPECT_EQ(20U, report1_size_history.size()); + + size_t index = 0; + for (const InternalVector<uint32_t>& entry : report1_size_history) { + ASSERT_GT(entry.size(), SizeToIndex(48)); + + // Check the two leaky sizes, 32 and 48. + EXPECT_EQ((index + 1) * 32 + 2, entry[SizeToIndex(32)]); + EXPECT_EQ((index + 1) * 32 + 2, entry[SizeToIndex(48)]); + + // Not related to the leaks, but there should be a dangling 16-byte + // allocation during each leak analysis, because it hasn't yet been freed. + EXPECT_EQ(1U, entry[SizeToIndex(16)]); + ++index; + } + + // |report2| should have the same size history as |report1|. + const auto& report2_size_history = report2.size_breakdown_history(); + EXPECT_TRUE(std::equal(report1_size_history.begin(), + report1_size_history.end(), + report2_size_history.begin())); +} + +TEST_F(LeakDetectorImplTest, JuliaSetNoLeak) { + JuliaSet(false /* enable_leaks */); + + // JuliaSet() should have run cleanly without leaking. + EXPECT_EQ(total_num_allocs_, total_num_frees_); + EXPECT_EQ(0U, alloced_ptrs_.size()); + ASSERT_EQ(0U, stored_reports_.size()); +} + +TEST_F(LeakDetectorImplTest, JuliaSetWithLeak) { + JuliaSet(true /* enable_leaks */); + + // JuliaSet() should have leaked some memory from two call sites. + EXPECT_GT(total_num_allocs_, total_num_frees_); + EXPECT_GT(alloced_ptrs_.size(), 0U); + + // There should be one unique leak report generated for each leaky call site. + ASSERT_EQ(2U, stored_reports_.size()); + + // The reports should be stored in order of size. + + // |report1| comes from the call site in JuliaSet() corresponding to + // |kStack3|. + const InternalLeakReport& report1 = *stored_reports_.begin(); + EXPECT_EQ(sizeof(Complex) + 40, report1.alloc_size_bytes()); + ASSERT_EQ(kStack3.depth, report1.call_stack().size()); + for (size_t i = 0; i < kStack3.depth; ++i) { + EXPECT_EQ(GetOffsetInMapping(kStack3.stack[i]), + report1.call_stack()[i]) << i; + } + + // |report2| comes from the call site in JuliaSet() corresponding to + // |kStack4|. + const InternalLeakReport& report2 = *(++stored_reports_.begin()); + EXPECT_EQ(sizeof(Complex) + 52, report2.alloc_size_bytes()); + ASSERT_EQ(kStack4.depth, report2.call_stack().size()); + for (size_t i = 0; i < kStack4.depth; ++i) { + EXPECT_EQ(GetOffsetInMapping(kStack4.stack[i]), + report2.call_stack()[i]) << i; + } + + // Check |report1|'s historical data. + const auto& report1_size_history = report1.size_breakdown_history(); + // Computing the exact number of leak analyses is not trivial, but we know it + // must be at least |kSizeSuspicionThreshold + kCallStackSuspicionThreshold| + // in order to have generated a report. + EXPECT_GT(report1_size_history.size(), + kSizeSuspicionThreshold + kCallStackSuspicionThreshold); + + // Make sure that the final allocation counts for the leaky sizes are larger + // than that of the non-leaky size by at least an order of magnitude. + const InternalVector<uint32_t>& final_entry = *report1_size_history.rbegin(); + uint32_t size_0_index = SizeToIndex(sizeof(Complex) + 24); + uint32_t size_1_index = SizeToIndex(sizeof(Complex) + 40); + uint32_t size_2_index = SizeToIndex(sizeof(Complex) + 52); + ASSERT_LT(size_0_index, final_entry.size()); + ASSERT_LT(size_1_index, final_entry.size()); + ASSERT_LT(size_2_index, final_entry.size()); + + EXPECT_GT(final_entry[size_1_index], final_entry[size_0_index] * 10); + EXPECT_GT(final_entry[size_2_index], final_entry[size_0_index] * 10); + + // |report2| should have the same size history as |report1|. + const auto& report2_size_history = report2.size_breakdown_history(); + EXPECT_TRUE(std::equal(report1_size_history.begin(), + report1_size_history.end(), + report2_size_history.begin())); +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_detector_unittest.cc b/chromium/components/metrics/leak_detector/leak_detector_unittest.cc new file mode 100644 index 00000000000..6f33c012bb6 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector_unittest.cc @@ -0,0 +1,119 @@ +// Copyright 2016 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 "components/metrics/leak_detector/leak_detector.h" + +#include <set> + +#include "base/allocator/allocator_extension.h" +#include "base/macros.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +// Default values for LeakDetector params. See header file for the meaning of +// each parameter. +const float kDefaultSamplingRate = 1.0f; +const size_t kDefaultMaxCallStackUnwindDepth = 4; +const uint64_t kDefaultAnalysisIntervalBytes = 32 * 1024 * 1024; // 32 MiB. +const uint32_t kDefaultSizeSuspicionThreshold = 4; +const uint32_t kDefaultCallStackSuspicionThreshold = 4; + +using LeakReport = LeakDetector::LeakReport; + +// Observer class that receives leak reports and stores them in |reports_|. +// Only one copy of each unique report will be stored. +class TestObserver : public LeakDetector::Observer { + public: + // Contains a comparator function used to compare LeakReports for uniqueness. + struct Comparator { + bool operator()(const LeakReport& a, const LeakReport& b) const { + if (a.alloc_size_bytes != b.alloc_size_bytes) + return a.alloc_size_bytes < b.alloc_size_bytes; + + return a.call_stack < b.call_stack; + } + }; + + TestObserver() {} + + void OnLeakFound(const LeakReport& report) override { + reports_.insert(report); + } + + const std::set<LeakReport, Comparator>& reports() const { return reports_; } + + private: + // Container for all leak reports received through OnLeakFound(). Stores only + // one copy of each unique report. + std::set<LeakReport, Comparator> reports_; + + DISALLOW_COPY_AND_ASSIGN(TestObserver); +}; + +} // namespace + +class LeakDetectorTest : public ::testing::Test { + public: + LeakDetectorTest() : detector_(LeakDetector::GetInstance()) { + detector_->Init(kDefaultSamplingRate, kDefaultMaxCallStackUnwindDepth, + kDefaultAnalysisIntervalBytes, + kDefaultSizeSuspicionThreshold, + kDefaultCallStackSuspicionThreshold); + } + + protected: + // Points to the instance of LeakDetector returned by GetInstance(). + LeakDetector* detector_; + + private: + // For supporting content::BrowserThread operations. + content::TestBrowserThreadBundle thread_bundle_; + + DISALLOW_COPY_AND_ASSIGN(LeakDetectorTest); +}; + +TEST_F(LeakDetectorTest, NotifyObservers) { + // Generate two sets of leak reports. + std::vector<LeakReport> reports1(3); + reports1[0].alloc_size_bytes = 8; + reports1[0].call_stack = {1, 2, 3, 4}; + reports1[1].alloc_size_bytes = 16; + reports1[1].call_stack = {5, 6, 7, 8}; + reports1[2].alloc_size_bytes = 24; + reports1[2].call_stack = {9, 10, 11, 12}; + + std::vector<LeakReport> reports2(3); + reports2[0].alloc_size_bytes = 32; + reports2[0].call_stack = {1, 2, 4, 8}; + reports2[1].alloc_size_bytes = 40; + reports2[1].call_stack = {16, 32, 64, 128}; + reports2[2].alloc_size_bytes = 48; + reports2[2].call_stack = {256, 512, 1024, 2048}; + + // Register three observers; + TestObserver obs1, obs2, obs3; + detector_->AddObserver(&obs1); + detector_->AddObserver(&obs2); + detector_->AddObserver(&obs3); + + // Pass both sets of reports to the leak detector. + detector_->NotifyObservers(reports1); + detector_->NotifyObservers(reports2); + + // Check that all three observers got both sets of reports, passed in + // separately. + for (const TestObserver* obs : {&obs1, &obs2, &obs3}) { + EXPECT_EQ(6U, obs->reports().size()); + for (const auto& report : {reports1[0], reports1[1], reports1[2], + reports2[0], reports2[1], reports2[2]}) { + EXPECT_TRUE(obs->reports().find(report) != obs->reports().end()); + } + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_detector_value_type.cc b/chromium/components/metrics/leak_detector/leak_detector_value_type.cc new file mode 100644 index 00000000000..4642f5150f8 --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector_value_type.cc @@ -0,0 +1,47 @@ +// Copyright 2015 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 "components/metrics/leak_detector/leak_detector_value_type.h" + +#include <stdio.h> + +namespace metrics { +namespace leak_detector { + +bool LeakDetectorValueType::operator==( + const LeakDetectorValueType& other) const { + if (type_ != other.type_) + return false; + + switch (type_) { + case TYPE_SIZE: + return size_ == other.size_; + case TYPE_CALL_STACK: + return call_stack_ == other.call_stack_; + case TYPE_NONE: + // "NONE" types are considered to be all identical. + return true; + } + return false; +} + +bool LeakDetectorValueType::operator<( + const LeakDetectorValueType& other) const { + if (type_ != other.type_) + return type_ < other.type_; + + switch (type_) { + case TYPE_SIZE: + return size_ < other.size_; + case TYPE_CALL_STACK: + return call_stack_ < other.call_stack_; + case TYPE_NONE: + // "NONE" types are considered to be all identical. + return false; + } + return false; +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/leak_detector_value_type.h b/chromium/components/metrics/leak_detector/leak_detector_value_type.h new file mode 100644 index 00000000000..40f61081e9a --- /dev/null +++ b/chromium/components/metrics/leak_detector/leak_detector_value_type.h @@ -0,0 +1,52 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_ + +#include <stddef.h> +#include <stdint.h> + +namespace metrics { +namespace leak_detector { + +// Used for tracking unique call stacks. +// Not thread-safe. +struct CallStack; + +class LeakDetectorValueType { + public: + // Supported types. + enum Type { + TYPE_NONE, + TYPE_SIZE, + TYPE_CALL_STACK, + }; + + LeakDetectorValueType() : type_(TYPE_NONE), size_(0), call_stack_(nullptr) {} + explicit LeakDetectorValueType(size_t size) + : type_(TYPE_SIZE), size_(size), call_stack_(nullptr) {} + explicit LeakDetectorValueType(const CallStack* call_stack) + : type_(TYPE_CALL_STACK), size_(0), call_stack_(call_stack) {} + + // Accessors. + Type type() const { return type_; } + size_t size() const { return size_; } + const CallStack* call_stack() const { return call_stack_; } + + // Comparators. + bool operator==(const LeakDetectorValueType& other) const; + bool operator<(const LeakDetectorValueType& other) const; + + private: + Type type_; + + size_t size_; + const CallStack* call_stack_; +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_ diff --git a/chromium/components/metrics/leak_detector/ranked_set.cc b/chromium/components/metrics/leak_detector/ranked_set.cc new file mode 100644 index 00000000000..5725fb5c01c --- /dev/null +++ b/chromium/components/metrics/leak_detector/ranked_set.cc @@ -0,0 +1,52 @@ +// Copyright 2015 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 "components/metrics/leak_detector/ranked_set.h" + +#include <algorithm> + +namespace metrics { +namespace leak_detector { + +RankedSet::RankedSet(size_t max_size) : max_size_(max_size) {} + +RankedSet::~RankedSet() {} + +RankedSet::RankedSet(RankedSet&& other) : max_size_(other.max_size_) { + entries_ = std::move(other.entries_); +} + +RankedSet& RankedSet::operator=(RankedSet&& other) { + max_size_ = other.max_size_; + entries_ = std::move(other.entries_); + return *this; +} + +bool RankedSet::Entry::operator<(const RankedSet::Entry& other) const { + if (count == other.count) + return value < other.value; + + return count > other.count; +} + +void RankedSet::Add(const ValueType& value, int count) { + // If the container is full, do not add any entry with |count| if does not + // exceed the lowest count of the entries in the list. + if (size() == max_size_ && count < min_count()) + return; + + Entry new_entry; + new_entry.value = value; + new_entry.count = count; + entries_.insert(new_entry); + + // Limit the container size if it exceeds the maximum allowed size, by + // deleting the last element. This should only iterate once because the size + // can only have increased by 1, but use a while loop just to be safe. + while (entries_.size() > max_size_) + entries_.erase(--entries_.end()); +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/ranked_set.h b/chromium/components/metrics/leak_detector/ranked_set.h new file mode 100644 index 00000000000..f91251593b0 --- /dev/null +++ b/chromium/components/metrics/leak_detector/ranked_set.h @@ -0,0 +1,96 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_ + +#include <stddef.h> + +#include <functional> // for std::less +#include <set> + +#include "base/macros.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/leak_detector_value_type.h" +#include "components/metrics/leak_detector/stl_allocator.h" + +namespace metrics { +namespace leak_detector { + +// RankedSet lets you add entries consisting of a value-count pair, and +// automatically sorts them internally by count in descending order. This allows +// for the user of this container to insert value-count pairs without having to +// explicitly sort them by count. +class RankedSet { + public: + using ValueType = LeakDetectorValueType; + + // A single entry in the RankedSet. The RankedSet sorts entries by |count| + // in descending order. + struct Entry { + ValueType value; + int count; + + // This less-than comparator is used for sorting Entries within a sorted + // container. It internally reverses the comparison so that higher-count + // entries are sorted ahead of lower-count entries. + bool operator<(const Entry& other) const; + }; + + // This class uses CustomAllocator to avoid recursive malloc hook invocation + // when analyzing allocs and frees. + using EntrySet = + std::set<Entry, std::less<Entry>, STLAllocator<Entry, CustomAllocator>>; + using const_iterator = EntrySet::const_iterator; + + explicit RankedSet(size_t max_size); + ~RankedSet(); + + // For move semantics. + RankedSet(RankedSet&& other); + RankedSet& operator=(RankedSet&& other); + + // Accessors for begin() and end() const iterators. + const_iterator begin() const { return entries_.begin(); } + const_iterator end() const { return entries_.end(); } + + size_t size() const { return entries_.size(); } + size_t max_size() const { return max_size_; } + + // Add a new value-count pair to the container. Will overwrite any existing + // entry with the same value and count. Will not overwrite an existing entry + // with the same value but a different count, or different values with the + // same count. + // + // Time complexity is O(log n). + void Add(const ValueType& value, int count); + + // Helper functions to directly add a size or call stack to the RankedSet. + void AddSize(size_t size, int count) { Add(ValueType(size), count); } + void AddCallStack(const CallStack* call_stack, int count) { + Add(ValueType(call_stack), count); + } + + private: + // Max and min counts. Returns 0 if the list is empty. + int max_count() const { + return entries_.empty() ? 0 : entries_.begin()->count; + } + int min_count() const { + return entries_.empty() ? 0 : entries_.rbegin()->count; + } + + // Max number of items that can be stored in the list. + size_t max_size_; + + // Actual storage container for entries. + EntrySet entries_; + + DISALLOW_COPY_AND_ASSIGN(RankedSet); +}; + +} // namespace leak_detector +} // namespace metrics + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_ diff --git a/chromium/components/metrics/leak_detector/ranked_set_unittest.cc b/chromium/components/metrics/leak_detector/ranked_set_unittest.cc new file mode 100644 index 00000000000..2e2de01c8b7 --- /dev/null +++ b/chromium/components/metrics/leak_detector/ranked_set_unittest.cc @@ -0,0 +1,324 @@ +// Copyright 2015 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 "components/metrics/leak_detector/ranked_set.h" + +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> + +#include "base/macros.h" +#include "components/metrics/leak_detector/custom_allocator.h" +#include "components/metrics/leak_detector/leak_detector_value_type.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { +namespace leak_detector { + +namespace { + +// Makes it easier to instantiate LeakDetectorValueTypes. +LeakDetectorValueType Value(uint32_t value) { + return LeakDetectorValueType(value); +} + +} // namespace + +class RankedSetTest : public ::testing::Test { + public: + RankedSetTest() {} + + void SetUp() override { CustomAllocator::Initialize(); } + void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); } + + private: + DISALLOW_COPY_AND_ASSIGN(RankedSetTest); +}; + +TEST_F(RankedSetTest, Iterators) { + RankedSet set(10); + EXPECT_TRUE(set.begin() == set.end()); + + set.Add(Value(0x1234), 100); + EXPECT_FALSE(set.begin() == set.end()); +} + +TEST_F(RankedSetTest, SingleInsertion) { + RankedSet set(10); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 100); + EXPECT_EQ(1U, set.size()); + + auto iter = set.begin(); + EXPECT_EQ(0x1234U, iter->value.size()); + EXPECT_EQ(100, iter->count); +} + +TEST_F(RankedSetTest, InOrderInsertion) { + RankedSet set(10); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 100); + EXPECT_EQ(1U, set.size()); + set.Add(Value(0x2345), 95); + EXPECT_EQ(2U, set.size()); + set.Add(Value(0x3456), 90); + EXPECT_EQ(3U, set.size()); + set.Add(Value(0x4567), 85); + EXPECT_EQ(4U, set.size()); + set.Add(Value(0x5678), 80); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues[] = { + {Value(0x1234), 100}, {Value(0x2345), 95}, {Value(0x3456), 90}, + {Value(0x4567), 85}, {Value(0x5678), 80}, + }; + + size_t index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues)); + EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues[index].count, entry.count); + ++index; + } +} + +TEST_F(RankedSetTest, ReverseOrderInsertion) { + RankedSet set(10); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 0); + EXPECT_EQ(1U, set.size()); + set.Add(Value(0x2345), 5); + EXPECT_EQ(2U, set.size()); + set.Add(Value(0x3456), 10); + EXPECT_EQ(3U, set.size()); + set.Add(Value(0x4567), 15); + EXPECT_EQ(4U, set.size()); + set.Add(Value(0x5678), 20); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues[] = { + {Value(0x5678), 20}, {Value(0x4567), 15}, {Value(0x3456), 10}, + {Value(0x2345), 5}, {Value(0x1234), 0}, + }; + + size_t index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues)); + EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues[index].count, entry.count); + ++index; + } +} + +TEST_F(RankedSetTest, UnorderedInsertion) { + RankedSet set(10); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 15); + set.Add(Value(0x2345), 20); + set.Add(Value(0x3456), 10); + set.Add(Value(0x4567), 30); + set.Add(Value(0x5678), 25); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues1[] = { + {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20}, + {Value(0x1234), 15}, {Value(0x3456), 10}, + }; + + size_t index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues1)); + EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues1[index].count, entry.count); + ++index; + } + + // Add more items. + set.Add(Value(0x6789), 35); + set.Add(Value(0x789a), 40); + set.Add(Value(0x89ab), 50); + set.Add(Value(0x9abc), 5); + set.Add(Value(0xabcd), 0); + EXPECT_EQ(10U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues2[] = { + {Value(0x89ab), 50}, {Value(0x789a), 40}, {Value(0x6789), 35}, + {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20}, + {Value(0x1234), 15}, {Value(0x3456), 10}, {Value(0x9abc), 5}, + {Value(0xabcd), 0}, + }; + + index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues2)); + EXPECT_EQ(kExpectedValues2[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues2[index].count, entry.count); + ++index; + } +} + +TEST_F(RankedSetTest, UnorderedInsertionWithSameCounts) { + RankedSet set(10); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 20); + set.Add(Value(0x2345), 20); + set.Add(Value(0x3456), 30); + set.Add(Value(0x4567), 30); + set.Add(Value(0x5678), 20); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. Entries + // with the same count should be ordered from lowest value to highest value. + const RankedSet::Entry kExpectedValues1[] = { + {Value(0x3456), 30}, {Value(0x4567), 30}, {Value(0x1234), 20}, + {Value(0x2345), 20}, {Value(0x5678), 20}, + }; + + size_t index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues1)); + EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues1[index].count, entry.count); + ++index; + } +} + +TEST_F(RankedSetTest, RepeatedEntries) { + RankedSet set(10); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 20); + set.Add(Value(0x3456), 30); + set.Add(Value(0x1234), 20); + set.Add(Value(0x3456), 30); + set.Add(Value(0x4567), 30); + set.Add(Value(0x4567), 30); + EXPECT_EQ(3U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues1[] = { + {Value(0x3456), 30}, {Value(0x4567), 30}, {Value(0x1234), 20}, + }; + + size_t index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues1)); + EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues1[index].count, entry.count); + ++index; + } +} + +TEST_F(RankedSetTest, InsertionWithOverflow) { + RankedSet set(5); + EXPECT_EQ(0U, set.size()); + + set.Add(Value(0x1234), 15); + set.Add(Value(0x2345), 20); + set.Add(Value(0x3456), 10); + set.Add(Value(0x4567), 30); + set.Add(Value(0x5678), 25); + EXPECT_EQ(5U, set.size()); + + // These values will not make it into the set, which is now full. + set.Add(Value(0x6789), 0); + EXPECT_EQ(5U, set.size()); + set.Add(Value(0x789a), 5); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues1[] = { + {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20}, + {Value(0x1234), 15}, {Value(0x3456), 10}, + }; + + size_t index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues1)); + EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues1[index].count, entry.count); + ++index; + } + + // Insert some more values that go in the middle of the set. + set.Add(Value(0x89ab), 27); + EXPECT_EQ(5U, set.size()); + set.Add(Value(0x9abc), 22); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues2[] = { + {Value(0x4567), 30}, {Value(0x89ab), 27}, {Value(0x5678), 25}, + {Value(0x9abc), 22}, {Value(0x2345), 20}, + }; + + index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues2)); + EXPECT_EQ(kExpectedValues2[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues2[index].count, entry.count); + ++index; + } + + // Insert some more values at the front of the set. + set.Add(Value(0xabcd), 40); + EXPECT_EQ(5U, set.size()); + set.Add(Value(0xbcde), 35); + EXPECT_EQ(5U, set.size()); + + // Iterate through the contents to make sure they match what went in. + const RankedSet::Entry kExpectedValues3[] = { + {Value(0xabcd), 40}, {Value(0xbcde), 35}, {Value(0x4567), 30}, + {Value(0x89ab), 27}, {Value(0x5678), 25}, + }; + + index = 0; + for (const auto& entry : set) { + EXPECT_LT(index, arraysize(kExpectedValues3)); + EXPECT_EQ(kExpectedValues3[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues3[index].count, entry.count); + ++index; + } +} + +TEST_F(RankedSetTest, MoveOperation) { + const RankedSet::Entry kExpectedValues[] = { + {Value(0x89ab), 50}, {Value(0x789a), 40}, {Value(0x6789), 35}, + {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20}, + {Value(0x1234), 15}, {Value(0x3456), 10}, {Value(0x9abc), 5}, + {Value(0xabcd), 0}, + }; + + RankedSet source(10); + for (const RankedSet::Entry& entry : kExpectedValues) { + source.Add(entry.value, entry.count); + } + EXPECT_EQ(10U, source.size()); + + RankedSet dest(25); // This should be changed by the move. + dest = std::move(source); + EXPECT_EQ(10U, dest.size()); + EXPECT_EQ(10U, dest.max_size()); + + size_t index = 0; + for (const auto& entry : dest) { + EXPECT_LT(index, arraysize(kExpectedValues)); + EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size()); + EXPECT_EQ(kExpectedValues[index].count, entry.count); + ++index; + } +} + +} // namespace leak_detector +} // namespace metrics diff --git a/chromium/components/metrics/leak_detector/stl_allocator.h b/chromium/components/metrics/leak_detector/stl_allocator.h new file mode 100644 index 00000000000..bfef0a38244 --- /dev/null +++ b/chromium/components/metrics/leak_detector/stl_allocator.h @@ -0,0 +1,61 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_ +#define COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_ + +#include <stddef.h> + +#include <limits> +#include <memory> + +#include "base/logging.h" + +// Generic allocator class for STL objects. +// deallocate() to use the template class Alloc's allocation. +// that uses a given type-less allocator Alloc, which must provide: +// static void* Alloc::Allocate(size_t size); +// static void Alloc::Free(void* ptr, size_t size); +// +// Inherits from the default allocator, std::allocator. Overrides allocate() and +// deallocate() and some other functions. +// +// STLAllocator<T, MyAlloc> provides the same thread-safety guarantees as +// MyAlloc. +// +// Usage example: +// set<T, less<T>, STLAllocator<T, MyAlloc> > my_set; + +template <typename T, class Alloc> +class STLAllocator : public std::allocator<T> { + public: + typedef size_t size_type; + typedef T* pointer; + + template <class T1> + struct rebind { + typedef STLAllocator<T1, Alloc> other; + }; + + STLAllocator() {} + explicit STLAllocator(const STLAllocator&) {} + template <class T1> + STLAllocator(const STLAllocator<T1, Alloc>&) {} + ~STLAllocator() {} + + pointer allocate(size_type n, const void* = 0) { + // Make sure the computation of the total allocation size does not cause an + // integer overflow. + RAW_CHECK(n < max_size()); + return static_cast<T*>(Alloc::Allocate(n * sizeof(T))); + } + + void deallocate(pointer p, size_type n) { Alloc::Free(p, n * sizeof(T)); } + + size_type max_size() const { + return std::numeric_limits<size_t>::max() / sizeof(T); + } +}; + +#endif // COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_ diff --git a/chromium/components/metrics/machine_id_provider.h b/chromium/components/metrics/machine_id_provider.h new file mode 100644 index 00000000000..d7fcc447639 --- /dev/null +++ b/chromium/components/metrics/machine_id_provider.h @@ -0,0 +1,46 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_ +#define COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" + +namespace metrics { + +// Provides machine characteristics used as a machine id. The implementation is +// platform specific with a default implementation that gives an empty id. The +// class is ref-counted thread safe so it can be used to post to the FILE thread +// and communicate back to the UI thread. +// This raw machine id should not be stored or transmitted over the network. +// TODO(jwd): Simplify implementation to get rid of the need for +// RefCountedThreadSafe (crbug.com/354882). +class MachineIdProvider : public base::RefCountedThreadSafe<MachineIdProvider> { + public: + // Get a string containing machine characteristics, to be used as a machine + // id. The implementation is platform specific, with a default implementation + // returning an empty string. + // The return value should not be stored to disk or transmitted. + std::string GetMachineId(); + + // Returns a pointer to a new MachineIdProvider or NULL if there is no + // provider implemented on a given platform. This is done to avoid posting a + // task to the FILE thread on platforms with no implementation. + static MachineIdProvider* CreateInstance(); + + private: + friend class base::RefCountedThreadSafe<MachineIdProvider>; + + MachineIdProvider(); + virtual ~MachineIdProvider(); + + DISALLOW_COPY_AND_ASSIGN(MachineIdProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_ diff --git a/chromium/components/metrics/machine_id_provider_stub.cc b/chromium/components/metrics/machine_id_provider_stub.cc new file mode 100644 index 00000000000..626f2b7cdc8 --- /dev/null +++ b/chromium/components/metrics/machine_id_provider_stub.cc @@ -0,0 +1,24 @@ +// Copyright 2014 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 "components/metrics/machine_id_provider.h" + +namespace metrics { + +MachineIdProvider::MachineIdProvider() { +} + +MachineIdProvider::~MachineIdProvider() { +} + +// static +MachineIdProvider* MachineIdProvider::CreateInstance() { + return NULL; +} + +std::string MachineIdProvider::GetMachineId() { + return std::string(); +} + +} // namespace metrics diff --git a/chromium/components/metrics/machine_id_provider_win.cc b/chromium/components/metrics/machine_id_provider_win.cc new file mode 100644 index 00000000000..41079a7dc2c --- /dev/null +++ b/chromium/components/metrics/machine_id_provider_win.cc @@ -0,0 +1,118 @@ +// Copyright 2014 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 "components/metrics/machine_id_provider.h" + +#include <windows.h> +#include <stdint.h> +#include <winioctl.h> + +#include "base/base_paths.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/threading/thread_restrictions.h" +#include "base/win/scoped_handle.h" + +namespace metrics { + +MachineIdProvider::MachineIdProvider() { +} + +MachineIdProvider::~MachineIdProvider() { +} + +// On windows, the machine id is based on the serial number of the drive Chrome +// is running from. +std::string MachineIdProvider::GetMachineId() { + base::ThreadRestrictions::AssertIOAllowed(); + + // Use the program's path to get the drive used for the machine id. This means + // that whenever the underlying drive changes, it's considered a new machine. + // This is fine as we do not support migrating Chrome installs to new drives. + base::FilePath executable_path; + + if (!PathService::Get(base::FILE_EXE, &executable_path)) { + NOTREACHED(); + return std::string(); + } + + std::vector<base::FilePath::StringType> path_components; + executable_path.GetComponents(&path_components); + if (path_components.empty()) { + NOTREACHED(); + return std::string(); + } + base::FilePath::StringType drive_name = L"\\\\.\\" + path_components[0]; + + base::win::ScopedHandle drive_handle( + CreateFile(drive_name.c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL)); + + STORAGE_PROPERTY_QUERY query = {}; + query.PropertyId = StorageDeviceProperty; + query.QueryType = PropertyStandardQuery; + + // Perform an initial query to get the number of bytes being returned. + DWORD bytes_returned; + STORAGE_DESCRIPTOR_HEADER header = {}; + BOOL status = DeviceIoControl(drive_handle.Get(), + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(STORAGE_PROPERTY_QUERY), + &header, + sizeof(STORAGE_DESCRIPTOR_HEADER), + &bytes_returned, + NULL); + + if (!status) + return std::string(); + + // Query for the actual serial number. + std::vector<int8_t> output_buf(header.Size); + status = DeviceIoControl(drive_handle.Get(), + IOCTL_STORAGE_QUERY_PROPERTY, + &query, + sizeof(STORAGE_PROPERTY_QUERY), + &output_buf[0], + output_buf.size(), + &bytes_returned, + NULL); + + if (!status) + return std::string(); + + const STORAGE_DEVICE_DESCRIPTOR* device_descriptor = + reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(&output_buf[0]); + + // The serial number is stored in the |output_buf| as a null-terminated + // string starting at the specified offset. + const DWORD offset = device_descriptor->SerialNumberOffset; + if (offset >= output_buf.size()) + return std::string(); + + // Make sure that the null-terminator exists. + const std::vector<int8_t>::iterator serial_number_begin = + output_buf.begin() + offset; + const std::vector<int8_t>::iterator null_location = + std::find(serial_number_begin, output_buf.end(), '\0'); + if (null_location == output_buf.end()) + return std::string(); + + const char* serial_number = + reinterpret_cast<const char*>(&output_buf[offset]); + + return std::string(serial_number); +} + +// static +MachineIdProvider* MachineIdProvider::CreateInstance() { + return new MachineIdProvider(); +} + +} // namespace metrics diff --git a/chromium/components/metrics/machine_id_provider_win_unittest.cc b/chromium/components/metrics/machine_id_provider_win_unittest.cc new file mode 100644 index 00000000000..11ffd8f73df --- /dev/null +++ b/chromium/components/metrics/machine_id_provider_win_unittest.cc @@ -0,0 +1,28 @@ +// Copyright 2014 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 "components/metrics/machine_id_provider.h" + +#include "base/memory/ref_counted.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +TEST(MachineIdProviderTest, GetId) { + scoped_refptr<MachineIdProvider> provider( + MachineIdProvider::CreateInstance()); + std::string id1 = provider->GetMachineId(); + + // TODO(rpaquay): See crbug/458230 + if (base::win::GetVersion() <= base::win::VERSION_XP) + return; + + EXPECT_NE(std::string(), id1); + + std::string id2 = provider->GetMachineId(); + EXPECT_EQ(id1, id2); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_log.cc b/chromium/components/metrics/metrics_log.cc new file mode 100644 index 00000000000..32db4184462 --- /dev/null +++ b/chromium/components/metrics/metrics_log.cc @@ -0,0 +1,423 @@ +// Copyright 2014 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 "components/metrics/metrics_log.h" + +#include <stddef.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/build_time.h" +#include "base/cpu.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/metrics_hashes.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/sys_info.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/histogram_encoder.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/proto/histogram_event.pb.h" +#include "components/metrics/proto/system_profile.pb.h" +#include "components/metrics/proto/user_action_event.pb.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/variations/active_field_trials.h" + +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#endif + +#if defined(OS_WIN) +#include "base/win/current_module.h" +#endif + +using base::SampleCountIterator; +typedef variations::ActiveGroupId ActiveGroupId; + +namespace metrics { + +namespace { + +// Any id less than 16 bytes is considered to be a testing id. +bool IsTestingID(const std::string& id) { + return id.size() < 16; +} + +// Computes a SHA-1 hash of |data| and returns it as a hex string. +std::string ComputeSHA1(const std::string& data) { + const std::string sha1 = base::SHA1HashString(data); + return base::HexEncode(sha1.data(), sha1.size()); +} + +void WriteFieldTrials(const std::vector<ActiveGroupId>& field_trial_ids, + SystemProfileProto* system_profile) { + for (std::vector<ActiveGroupId>::const_iterator it = + field_trial_ids.begin(); it != field_trial_ids.end(); ++it) { + SystemProfileProto::FieldTrial* field_trial = + system_profile->add_field_trial(); + field_trial->set_name_id(it->name); + field_trial->set_group_id(it->group); + } +} + +// Round a timestamp measured in seconds since epoch to one with a granularity +// of an hour. This can be used before uploaded potentially sensitive +// timestamps. +int64_t RoundSecondsToHour(int64_t time_in_seconds) { + return 3600 * (time_in_seconds / 3600); +} + +} // namespace + +MetricsLog::MetricsLog(const std::string& client_id, + int session_id, + LogType log_type, + MetricsServiceClient* client, + PrefService* local_state) + : closed_(false), + log_type_(log_type), + client_(client), + creation_time_(base::TimeTicks::Now()), + local_state_(local_state) { + if (IsTestingID(client_id)) + uma_proto_.set_client_id(0); + else + uma_proto_.set_client_id(Hash(client_id)); + + uma_proto_.set_session_id(session_id); + + const int32_t product = client_->GetProduct(); + // Only set the product if it differs from the default value. + if (product != uma_proto_.product()) + uma_proto_.set_product(product); + + SystemProfileProto* system_profile = uma_proto_.mutable_system_profile(); + system_profile->set_build_timestamp(GetBuildTime()); + system_profile->set_app_version(client_->GetVersionString()); + system_profile->set_channel(client_->GetChannel()); +#if defined(SYZYASAN) + system_profile->set_is_asan_build(true); +#endif +} + +MetricsLog::~MetricsLog() { +} + +// static +void MetricsLog::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityIncompleteSessionEndCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationFail, 0); + registry->RegisterIntegerPref( + prefs::kStabilityBreakpadRegistrationSuccess, 0); + registry->RegisterIntegerPref(prefs::kStabilityDebuggerPresent, 0); + registry->RegisterIntegerPref(prefs::kStabilityDebuggerNotPresent, 0); + registry->RegisterStringPref(prefs::kStabilitySavedSystemProfile, + std::string()); + registry->RegisterStringPref(prefs::kStabilitySavedSystemProfileHash, + std::string()); +} + +// static +uint64_t MetricsLog::Hash(const std::string& value) { + uint64_t hash = base::HashMetricName(value); + + // The following log is VERY helpful when folks add some named histogram into + // the code, but forgot to update the descriptive list of histograms. When + // that happens, all we get to see (server side) is a hash of the histogram + // name. We can then use this logging to find out what histogram name was + // being hashed to a given MD5 value by just running the version of Chromium + // in question with --enable-logging. + DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]"; + + return hash; +} + +// static +int64_t MetricsLog::GetBuildTime() { + static int64_t integral_build_time = 0; + if (!integral_build_time) + integral_build_time = static_cast<int64_t>(base::GetBuildTime().ToTimeT()); + return integral_build_time; +} + +// static +int64_t MetricsLog::GetCurrentTime() { + return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds(); +} + +void MetricsLog::RecordUserAction(const std::string& key) { + DCHECK(!closed_); + + UserActionEventProto* user_action = uma_proto_.add_user_action_event(); + user_action->set_name_hash(Hash(key)); + user_action->set_time(GetCurrentTime()); +} + +void MetricsLog::RecordHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot) { + DCHECK(!closed_); + EncodeHistogramDelta(histogram_name, snapshot, &uma_proto_); +} + +void MetricsLog::RecordStabilityMetrics( + const std::vector<MetricsProvider*>& metrics_providers, + base::TimeDelta incremental_uptime, + base::TimeDelta uptime) { + DCHECK(!closed_); + DCHECK(HasEnvironment()); + DCHECK(!HasStabilityMetrics()); + + PrefService* pref = local_state_; + DCHECK(pref); + + // Get stability attributes out of Local State, zeroing out stored values. + // NOTE: This could lead to some data loss if this report isn't successfully + // sent, but that's true for all the metrics. + + WriteRequiredStabilityAttributes(pref); + + // Record recent delta for critical stability metrics. We can't wait for a + // restart to gather these, as that delay biases our observation away from + // users that run happily for a looooong time. We send increments with each + // uma log upload, just as we send histogram data. + WriteRealtimeStabilityAttributes(pref, incremental_uptime, uptime); + + SystemProfileProto* system_profile = uma_proto()->mutable_system_profile(); + for (size_t i = 0; i < metrics_providers.size(); ++i) { + if (log_type() == INITIAL_STABILITY_LOG) + metrics_providers[i]->ProvideInitialStabilityMetrics(system_profile); + metrics_providers[i]->ProvideStabilityMetrics(system_profile); + } + + // Omit some stats unless this is the initial stability log. + if (log_type() != INITIAL_STABILITY_LOG) + return; + + int incomplete_shutdown_count = + pref->GetInteger(prefs::kStabilityIncompleteSessionEndCount); + pref->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0); + int breakpad_registration_success_count = + pref->GetInteger(prefs::kStabilityBreakpadRegistrationSuccess); + pref->SetInteger(prefs::kStabilityBreakpadRegistrationSuccess, 0); + int breakpad_registration_failure_count = + pref->GetInteger(prefs::kStabilityBreakpadRegistrationFail); + pref->SetInteger(prefs::kStabilityBreakpadRegistrationFail, 0); + int debugger_present_count = + pref->GetInteger(prefs::kStabilityDebuggerPresent); + pref->SetInteger(prefs::kStabilityDebuggerPresent, 0); + int debugger_not_present_count = + pref->GetInteger(prefs::kStabilityDebuggerNotPresent); + pref->SetInteger(prefs::kStabilityDebuggerNotPresent, 0); + + // TODO(jar): The following are all optional, so we *could* optimize them for + // values of zero (and not include them). + SystemProfileProto::Stability* stability = + system_profile->mutable_stability(); + stability->set_incomplete_shutdown_count(incomplete_shutdown_count); + stability->set_breakpad_registration_success_count( + breakpad_registration_success_count); + stability->set_breakpad_registration_failure_count( + breakpad_registration_failure_count); + stability->set_debugger_present_count(debugger_present_count); + stability->set_debugger_not_present_count(debugger_not_present_count); +} + +void MetricsLog::RecordGeneralMetrics( + const std::vector<MetricsProvider*>& metrics_providers) { + for (size_t i = 0; i < metrics_providers.size(); ++i) + metrics_providers[i]->ProvideGeneralMetrics(uma_proto()); +} + +void MetricsLog::GetFieldTrialIds( + std::vector<ActiveGroupId>* field_trial_ids) const { + variations::GetFieldTrialActiveGroupIds(field_trial_ids); +} + +bool MetricsLog::HasEnvironment() const { + return uma_proto()->system_profile().has_uma_enabled_date(); +} + +void MetricsLog::WriteMetricsEnableDefault( + MetricsServiceClient::EnableMetricsDefault metrics_default, + SystemProfileProto* system_profile) { + if (client_->IsReportingPolicyManaged()) { + // If it's managed, then it must be reporting, otherwise we wouldn't be + // sending metrics. + system_profile->set_uma_default_state( + SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED); + return; + } + + switch (metrics_default) { + case MetricsServiceClient::DEFAULT_UNKNOWN: + // Don't set the field if it's unknown. + break; + case MetricsServiceClient::OPT_IN: + system_profile->set_uma_default_state( + SystemProfileProto_UmaDefaultState_OPT_IN); + break; + case MetricsServiceClient::OPT_OUT: + system_profile->set_uma_default_state( + SystemProfileProto_UmaDefaultState_OPT_OUT); + } +} + +bool MetricsLog::HasStabilityMetrics() const { + return uma_proto()->system_profile().stability().has_launch_count(); +} + +// The server refuses data that doesn't have certain values. crashcount and +// launchcount are currently "required" in the "stability" group. +// TODO(isherman): Stop writing these attributes specially once the migration to +// protobufs is complete. +void MetricsLog::WriteRequiredStabilityAttributes(PrefService* pref) { + int launch_count = pref->GetInteger(prefs::kStabilityLaunchCount); + pref->SetInteger(prefs::kStabilityLaunchCount, 0); + int crash_count = pref->GetInteger(prefs::kStabilityCrashCount); + pref->SetInteger(prefs::kStabilityCrashCount, 0); + + SystemProfileProto::Stability* stability = + uma_proto()->mutable_system_profile()->mutable_stability(); + stability->set_launch_count(launch_count); + stability->set_crash_count(crash_count); +} + +void MetricsLog::WriteRealtimeStabilityAttributes( + PrefService* pref, + base::TimeDelta incremental_uptime, + base::TimeDelta uptime) { + // Update the stats which are critical for real-time stability monitoring. + // Since these are "optional," only list ones that are non-zero, as the counts + // are aggregated (summed) server side. + + SystemProfileProto::Stability* stability = + uma_proto()->mutable_system_profile()->mutable_stability(); + + const uint64_t incremental_uptime_sec = incremental_uptime.InSeconds(); + if (incremental_uptime_sec) + stability->set_incremental_uptime_sec(incremental_uptime_sec); + const uint64_t uptime_sec = uptime.InSeconds(); + if (uptime_sec) + stability->set_uptime_sec(uptime_sec); +} + +void MetricsLog::RecordEnvironment( + const std::vector<MetricsProvider*>& metrics_providers, + const std::vector<variations::ActiveGroupId>& synthetic_trials, + int64_t install_date, + int64_t metrics_reporting_enabled_date) { + DCHECK(!HasEnvironment()); + + SystemProfileProto* system_profile = uma_proto()->mutable_system_profile(); + + WriteMetricsEnableDefault(client_->GetDefaultOptIn(), system_profile); + + std::string brand_code; + if (client_->GetBrand(&brand_code)) + system_profile->set_brand_code(brand_code); + + // Reduce granularity of the enabled_date field to nearest hour. + system_profile->set_uma_enabled_date( + RoundSecondsToHour(metrics_reporting_enabled_date)); + + // Reduce granularity of the install_date field to nearest hour. + system_profile->set_install_date(RoundSecondsToHour(install_date)); + + system_profile->set_application_locale(client_->GetApplicationLocale()); + + SystemProfileProto::Hardware* hardware = system_profile->mutable_hardware(); + + // HardwareModelName() will return an empty string on platforms where it's + // not implemented or if an error occured. + hardware->set_hardware_class(base::SysInfo::HardwareModelName()); + + hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture()); + hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB()); +#if defined(OS_WIN) + hardware->set_dll_base(reinterpret_cast<uint64_t>(CURRENT_MODULE())); +#endif + +#if defined(OVERRIDE_OS_NAME_TO_BLIMP) + os->set_name("Blimp"); +#else + SystemProfileProto::OS* os = system_profile->mutable_os(); + std::string os_name = base::SysInfo::OperatingSystemName(); + os->set_name(os_name); +#endif + + os->set_version(base::SysInfo::OperatingSystemVersion()); +#if defined(OS_ANDROID) + os->set_fingerprint( + base::android::BuildInfo::GetInstance()->android_build_fp()); +#endif + + base::CPU cpu_info; + SystemProfileProto::Hardware::CPU* cpu = hardware->mutable_cpu(); + cpu->set_vendor_name(cpu_info.vendor_name()); + cpu->set_signature(cpu_info.signature()); + cpu->set_num_cores(base::SysInfo::NumberOfProcessors()); + + std::vector<ActiveGroupId> field_trial_ids; + GetFieldTrialIds(&field_trial_ids); + WriteFieldTrials(field_trial_ids, system_profile); + WriteFieldTrials(synthetic_trials, system_profile); + + for (size_t i = 0; i < metrics_providers.size(); ++i) + metrics_providers[i]->ProvideSystemProfileMetrics(system_profile); + + std::string serialied_system_profile; + std::string base64_system_profile; + if (system_profile->SerializeToString(&serialied_system_profile)) { + base::Base64Encode(serialied_system_profile, &base64_system_profile); + PrefService* local_state = local_state_; + local_state->SetString(prefs::kStabilitySavedSystemProfile, + base64_system_profile); + local_state->SetString(prefs::kStabilitySavedSystemProfileHash, + ComputeSHA1(serialied_system_profile)); + } +} + +bool MetricsLog::LoadSavedEnvironmentFromPrefs() { + PrefService* local_state = local_state_; + const std::string base64_system_profile = + local_state->GetString(prefs::kStabilitySavedSystemProfile); + if (base64_system_profile.empty()) + return false; + + const std::string system_profile_hash = + local_state->GetString(prefs::kStabilitySavedSystemProfileHash); + local_state->ClearPref(prefs::kStabilitySavedSystemProfile); + local_state->ClearPref(prefs::kStabilitySavedSystemProfileHash); + + SystemProfileProto* system_profile = uma_proto()->mutable_system_profile(); + std::string serialied_system_profile; + return base::Base64Decode(base64_system_profile, &serialied_system_profile) && + ComputeSHA1(serialied_system_profile) == system_profile_hash && + system_profile->ParseFromString(serialied_system_profile); +} + +void MetricsLog::CloseLog() { + DCHECK(!closed_); + closed_ = true; +} + +void MetricsLog::GetEncodedLog(std::string* encoded_log) { + DCHECK(closed_); + uma_proto_.SerializeToString(encoded_log); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_log.h b/chromium/components/metrics/metrics_log.h new file mode 100644 index 00000000000..e2324f8ac36 --- /dev/null +++ b/chromium/components/metrics/metrics_log.h @@ -0,0 +1,209 @@ +// Copyright 2014 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. + +// This file defines a set of user experience metrics data recorded by +// the MetricsService. This is the unit of data that is sent to the server. + +#ifndef COMPONENTS_METRICS_METRICS_LOG_H_ +#define COMPONENTS_METRICS_METRICS_LOG_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/time/time.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +class PrefRegistrySimple; +class PrefService; + +namespace base { +class DictionaryValue; +class HistogramSamples; +} + +namespace content { +struct WebPluginInfo; +} + +namespace variations { +struct ActiveGroupId; +} + +namespace metrics { + +class MetricsProvider; +class MetricsServiceClient; + +class MetricsLog { + public: + enum LogType { + INITIAL_STABILITY_LOG, // The initial log containing stability stats. + ONGOING_LOG, // Subsequent logs in a session. + }; + + // Creates a new metrics log of the specified type. + // |client_id| is the identifier for this profile on this installation + // |session_id| is an integer that's incremented on each application launch + // |client| is used to interact with the embedder. + // |local_state| is the PrefService that this instance should use. + // Note: |this| instance does not take ownership of the |client|, but rather + // stores a weak pointer to it. The caller should ensure that the |client| is + // valid for the lifetime of this class. + MetricsLog(const std::string& client_id, + int session_id, + LogType log_type, + MetricsServiceClient* client, + PrefService* local_state); + virtual ~MetricsLog(); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // Computes the MD5 hash of the given string, and returns the first 8 bytes of + // the hash. + static uint64_t Hash(const std::string& value); + + // Get the GMT buildtime for the current binary, expressed in seconds since + // January 1, 1970 GMT. + // The value is used to identify when a new build is run, so that previous + // reliability stats, from other builds, can be abandoned. + static int64_t GetBuildTime(); + + // Convenience function to return the current time at a resolution in seconds. + // This wraps base::TimeTicks, and hence provides an abstract time that is + // always incrementing for use in measuring time durations. + static int64_t GetCurrentTime(); + + // Records a user-initiated action. + void RecordUserAction(const std::string& key); + + // Record any changes in a given histogram for transmission. + void RecordHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot); + + // Records the current operating environment, including metrics provided by + // the specified set of |metrics_providers|. Takes the list of installed + // plugins, Google Update statistics, and synthetic trial IDs as parameters + // because those can't be obtained synchronously from the UI thread. + // A synthetic trial is one that is set up dynamically by code in Chrome. For + // example, a pref may be mapped to a synthetic trial such that the group + // is determined by the pref value. + void RecordEnvironment( + const std::vector<MetricsProvider*>& metrics_providers, + const std::vector<variations::ActiveGroupId>& synthetic_trials, + int64_t install_date, + int64_t metrics_reporting_enabled_date); + + // Loads the environment proto that was saved by the last RecordEnvironment() + // call from prefs and clears the pref value. Returns true on success or false + // if there was no saved environment in prefs or it could not be decoded. + bool LoadSavedEnvironmentFromPrefs(); + + // Writes application stability metrics, including stability metrics provided + // by the specified set of |metrics_providers|. The system profile portion of + // the log must have already been filled in by a call to RecordEnvironment() + // or LoadSavedEnvironmentFromPrefs(). + // NOTE: Has the side-effect of clearing the stability prefs.. + // + // If this log is of type INITIAL_STABILITY_LOG, records additional info such + // as number of incomplete shutdowns as well as extra breakpad and debugger + // stats. + void RecordStabilityMetrics( + const std::vector<MetricsProvider*>& metrics_providers, + base::TimeDelta incremental_uptime, + base::TimeDelta uptime); + + // Records general metrics based on the specified |metrics_providers|. + void RecordGeneralMetrics( + const std::vector<MetricsProvider*>& metrics_providers); + + // Stop writing to this record and generate the encoded representation. + // None of the Record* methods can be called after this is called. + void CloseLog(); + + // Fills |encoded_log| with the serialized protobuf representation of the + // record. Must only be called after CloseLog() has been called. + void GetEncodedLog(std::string* encoded_log); + + const base::TimeTicks& creation_time() const { + return creation_time_; + } + + int num_events() const { + return uma_proto_.omnibox_event_size() + + uma_proto_.user_action_event_size(); + } + + LogType log_type() const { return log_type_; } + + protected: + // Exposed for the sake of mocking/accessing in test code. + + // Fills |field_trial_ids| with the list of initialized field trials name and + // group ids. + virtual void GetFieldTrialIds( + std::vector<variations::ActiveGroupId>* field_trial_ids) const; + + ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; } + + // Exposed to allow subclass to access to export the uma_proto. Can be used + // by external components to export logs to Chrome. + const ChromeUserMetricsExtension* uma_proto() const { + return &uma_proto_; + } + + private: + // Returns true if the environment has already been filled in by a call to + // RecordEnvironment() or LoadSavedEnvironmentFromPrefs(). + bool HasEnvironment() const; + + // Write the default state of the enable metrics checkbox. + void WriteMetricsEnableDefault( + MetricsServiceClient::EnableMetricsDefault metrics_default, + SystemProfileProto* system_profile); + + // Returns true if the stability metrics have already been filled in by a + // call to RecordStabilityMetrics(). + bool HasStabilityMetrics() const; + + // Within the stability group, write required attributes. + void WriteRequiredStabilityAttributes(PrefService* pref); + + // Within the stability group, write attributes that need to be updated asap + // and can't be delayed until the user decides to restart chromium. + // Delaying these stats would bias metrics away from happy long lived + // chromium processes (ones that don't crash, and keep on running). + void WriteRealtimeStabilityAttributes(PrefService* pref, + base::TimeDelta incremental_uptime, + base::TimeDelta uptime); + + // closed_ is true when record has been packed up for sending, and should + // no longer be written to. It is only used for sanity checking. + bool closed_; + + // The type of the log, i.e. initial or ongoing. + const LogType log_type_; + + // Stores the protocol buffer representation for this log. + ChromeUserMetricsExtension uma_proto_; + + // Used to interact with the embedder. Weak pointer; must outlive |this| + // instance. + MetricsServiceClient* const client_; + + // The time when the current log was created. + const base::TimeTicks creation_time_; + + PrefService* local_state_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLog); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_H_ diff --git a/chromium/components/metrics/metrics_log_manager.cc b/chromium/components/metrics/metrics_log_manager.cc new file mode 100644 index 00000000000..661dacfa55f --- /dev/null +++ b/chromium/components/metrics/metrics_log_manager.cc @@ -0,0 +1,130 @@ +// Copyright 2014 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 "components/metrics/metrics_log_manager.h" + +#include <algorithm> +#include <utility> + +#include "base/strings/string_util.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_pref_names.h" + +namespace metrics { + +namespace { + +// The number of "initial" logs to save, and hope to send during a future Chrome +// session. Initial logs contain crash stats, and are pretty small. +const size_t kInitialLogsPersistLimit = 20; + +// The number of ongoing logs to save persistently, and hope to +// send during a this or future sessions. Note that each log may be pretty +// large, as presumably the related "initial" log wasn't sent (probably nothing +// was, as the user was probably off-line). As a result, the log probably kept +// accumulating while the "initial" log was stalled, and couldn't be sent. As a +// result, we don't want to save too many of these mega-logs. +// A "standard shutdown" will create a small log, including just the data that +// was not yet been transmitted, and that is normal (to have exactly one +// ongoing_log_ at startup). +const size_t kOngoingLogsPersistLimit = 8; + +// The number of bytes each of initial and ongoing logs that must be stored. +// This ensures that a reasonable amount of history will be stored even if there +// is a long series of very small logs. +const size_t kStorageByteLimitPerLogType = 300000; + +} // namespace + +MetricsLogManager::MetricsLogManager(PrefService* local_state, + size_t max_ongoing_log_size) + : unsent_logs_loaded_(false), + initial_log_queue_(local_state, + prefs::kMetricsInitialLogs, + kInitialLogsPersistLimit, + kStorageByteLimitPerLogType, + 0), + ongoing_log_queue_(local_state, + prefs::kMetricsOngoingLogs, + kOngoingLogsPersistLimit, + kStorageByteLimitPerLogType, + max_ongoing_log_size) {} + +MetricsLogManager::~MetricsLogManager() {} + +void MetricsLogManager::BeginLoggingWithLog(scoped_ptr<MetricsLog> log) { + DCHECK(!current_log_); + current_log_ = std::move(log); +} + +void MetricsLogManager::FinishCurrentLog() { + DCHECK(current_log_.get()); + current_log_->CloseLog(); + std::string log_data; + current_log_->GetEncodedLog(&log_data); + if (!log_data.empty()) + StoreLog(log_data, current_log_->log_type()); + current_log_.reset(); +} + +void MetricsLogManager::StageNextLogForUpload() { + DCHECK(!has_staged_log()); + if (!initial_log_queue_.empty()) + initial_log_queue_.StageLog(); + else + ongoing_log_queue_.StageLog(); +} + +void MetricsLogManager::DiscardStagedLog() { + DCHECK(has_staged_log()); + if (initial_log_queue_.has_staged_log()) + initial_log_queue_.DiscardStagedLog(); + else + ongoing_log_queue_.DiscardStagedLog(); + DCHECK(!has_staged_log()); +} + +void MetricsLogManager::DiscardCurrentLog() { + current_log_->CloseLog(); + current_log_.reset(); +} + +void MetricsLogManager::PauseCurrentLog() { + DCHECK(!paused_log_.get()); + paused_log_.reset(current_log_.release()); +} + +void MetricsLogManager::ResumePausedLog() { + DCHECK(!current_log_.get()); + current_log_.reset(paused_log_.release()); +} + +void MetricsLogManager::StoreLog(const std::string& log_data, + MetricsLog::LogType log_type) { + switch (log_type) { + case MetricsLog::INITIAL_STABILITY_LOG: + initial_log_queue_.StoreLog(log_data); + break; + case MetricsLog::ONGOING_LOG: + ongoing_log_queue_.StoreLog(log_data); + break; + } +} + +void MetricsLogManager::PersistUnsentLogs() { + DCHECK(unsent_logs_loaded_); + if (!unsent_logs_loaded_) + return; + + initial_log_queue_.SerializeLogs(); + ongoing_log_queue_.SerializeLogs(); +} + +void MetricsLogManager::LoadPersistedUnsentLogs() { + initial_log_queue_.DeserializeLogs(); + ongoing_log_queue_.DeserializeLogs(); + unsent_logs_loaded_ = true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_log_manager.h b/chromium/components/metrics/metrics_log_manager.h new file mode 100644 index 00000000000..c4f2082bfdf --- /dev/null +++ b/chromium/components/metrics/metrics_log_manager.h @@ -0,0 +1,118 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ +#define COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/persisted_logs.h" + +namespace metrics { + +// Manages all the log objects used by a MetricsService implementation. Keeps +// track of both an in progress log and a log that is staged for uploading as +// text, as well as saving logs to, and loading logs from, persistent storage. +class MetricsLogManager { + public: + // The metrics log manager will persist it's unsent logs by storing them in + // |local_state|, and will not persist ongoing logs over + // |max_ongoing_log_size|. + MetricsLogManager(PrefService* local_state, size_t max_ongoing_log_size); + ~MetricsLogManager(); + + // Makes |log| the current_log. This should only be called if there is not a + // current log. + void BeginLoggingWithLog(scoped_ptr<MetricsLog> log); + + // Returns the in-progress log. + MetricsLog* current_log() { return current_log_.get(); } + + // Closes current_log(), compresses it, and stores the compressed log for + // later, leaving current_log() NULL. + void FinishCurrentLog(); + + // Returns true if there are any logs waiting to be uploaded. + bool has_unsent_logs() const { + return initial_log_queue_.size() || ongoing_log_queue_.size(); + } + + // Populates staged_log_text() with the next stored log to send. + // Should only be called if has_unsent_logs() is true. + void StageNextLogForUpload(); + + // Returns true if there is a log that needs to be, or is being, uploaded. + bool has_staged_log() const { + return initial_log_queue_.has_staged_log() || + ongoing_log_queue_.has_staged_log(); + } + + // The text of the staged log, as a serialized protobuf. + // Will trigger a DCHECK if there is no staged log. + const std::string& staged_log() const { + return initial_log_queue_.has_staged_log() ? + initial_log_queue_.staged_log() : ongoing_log_queue_.staged_log(); + } + + // The SHA1 hash of the staged log. + // Will trigger a DCHECK if there is no staged log. + const std::string& staged_log_hash() const { + return initial_log_queue_.has_staged_log() ? + initial_log_queue_.staged_log_hash() : + ongoing_log_queue_.staged_log_hash(); + } + + // Discards the staged log. + void DiscardStagedLog(); + + // Closes and discards |current_log|. + void DiscardCurrentLog(); + + // Sets current_log to NULL, but saves the current log for future use with + // ResumePausedLog(). Only one log may be paused at a time. + // TODO(stuartmorgan): Pause/resume support is really a workaround for a + // design issue in initial log writing; that should be fixed, and pause/resume + // removed. + void PauseCurrentLog(); + + // Restores the previously paused log (if any) to current_log(). + // This should only be called if there is not a current log. + void ResumePausedLog(); + + // Saves any unsent logs to persistent storage. + void PersistUnsentLogs(); + + // Loads any unsent logs from persistent storage. + void LoadPersistedUnsentLogs(); + + // Saves |log_data| as the given type. Public to allow to push log created by + // external components. + void StoreLog(const std::string& log_data, MetricsLog::LogType log_type); + + private: + // Tracks whether unsent logs (if any) have been loaded from the serializer. + bool unsent_logs_loaded_; + + // The log that we are still appending to. + scoped_ptr<MetricsLog> current_log_; + + // A paused, previously-current log. + scoped_ptr<MetricsLog> paused_log_; + + // Logs that have not yet been sent. + PersistedLogs initial_log_queue_; + PersistedLogs ongoing_log_queue_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogManager); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ diff --git a/chromium/components/metrics/metrics_log_manager_unittest.cc b/chromium/components/metrics/metrics_log_manager_unittest.cc new file mode 100644 index 00000000000..c35d10043b7 --- /dev/null +++ b/chromium/components/metrics/metrics_log_manager_unittest.cc @@ -0,0 +1,306 @@ +// Copyright 2014 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 "components/metrics/metrics_log_manager.h" + +#include <stddef.h> + +#include <string> +#include <utility> +#include <vector> + +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/test_metrics_service_client.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +// Dummy serializer that just stores logs in memory. +class TestLogPrefService : public TestingPrefServiceSimple { + public: + TestLogPrefService() { + registry()->RegisterListPref(prefs::kMetricsInitialLogs); + registry()->RegisterListPref(prefs::kMetricsOngoingLogs); + } + + // Returns the number of logs of the given type. + size_t TypeCount(MetricsLog::LogType log_type) { + int list_length = 0; + if (log_type == MetricsLog::INITIAL_STABILITY_LOG) + list_length = GetList(prefs::kMetricsInitialLogs)->GetSize(); + else + list_length = GetList(prefs::kMetricsOngoingLogs)->GetSize(); + return list_length / 2; + } +}; + +} // namespace + +TEST(MetricsLogManagerTest, StandardFlow) { + TestMetricsServiceClient client; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + + // Make sure a new manager has a clean slate. + EXPECT_EQ(NULL, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); + EXPECT_FALSE(log_manager.has_unsent_logs()); + + // Check that the normal flow works. + MetricsLog* initial_log = new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service); + log_manager.BeginLoggingWithLog(make_scoped_ptr(initial_log)); + EXPECT_EQ(initial_log, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); + + log_manager.FinishCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + EXPECT_TRUE(log_manager.has_unsent_logs()); + EXPECT_FALSE(log_manager.has_staged_log()); + + MetricsLog* second_log = + new MetricsLog("id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service); + log_manager.BeginLoggingWithLog(make_scoped_ptr(second_log)); + EXPECT_EQ(second_log, log_manager.current_log()); + + log_manager.StageNextLogForUpload(); + EXPECT_TRUE(log_manager.has_staged_log()); + EXPECT_FALSE(log_manager.staged_log().empty()); + + log_manager.DiscardStagedLog(); + EXPECT_EQ(second_log, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); + EXPECT_FALSE(log_manager.has_unsent_logs()); + + EXPECT_FALSE(log_manager.has_unsent_logs()); +} + +TEST(MetricsLogManagerTest, AbandonedLog) { + TestMetricsServiceClient client; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + + MetricsLog* dummy_log = new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service); + log_manager.BeginLoggingWithLog(make_scoped_ptr(dummy_log)); + EXPECT_EQ(dummy_log, log_manager.current_log()); + + log_manager.DiscardCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); +} + +TEST(MetricsLogManagerTest, InterjectedLog) { + TestMetricsServiceClient client; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + + MetricsLog* ongoing_log = + new MetricsLog("id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service); + MetricsLog* temp_log = new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(ongoing_log)); + EXPECT_EQ(ongoing_log, log_manager.current_log()); + + log_manager.PauseCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(temp_log)); + EXPECT_EQ(temp_log, log_manager.current_log()); + log_manager.FinishCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + + log_manager.ResumePausedLog(); + EXPECT_EQ(ongoing_log, log_manager.current_log()); + + EXPECT_FALSE(log_manager.has_staged_log()); + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + EXPECT_FALSE(log_manager.has_unsent_logs()); +} + +TEST(MetricsLogManagerTest, InterjectedLogPreservesType) { + TestMetricsServiceClient client; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + log_manager.LoadPersistedUnsentLogs(); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); + log_manager.PauseCurrentLog(); + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + log_manager.ResumePausedLog(); + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + + // Verify that the remaining log (which is the original ongoing log) still + // has the right type. + log_manager.FinishCurrentLog(); + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); +} + +TEST(MetricsLogManagerTest, StoreAndLoad) { + TestMetricsServiceClient client; + TestLogPrefService pref_service; + // Set up some in-progress logging in a scoped log manager simulating the + // leadup to quitting, then persist as would be done on quit. + { + MetricsLogManager log_manager(&pref_service, 0); + log_manager.LoadPersistedUnsentLogs(); + + // Simulate a log having already been unsent from a previous session. + { + std::string log("proto"); + PersistedLogs ongoing_logs(&pref_service, prefs::kMetricsOngoingLogs, 1, + 1, 0); + ongoing_logs.StoreLog(log); + ongoing_logs.SerializeLogs(); + } + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + EXPECT_FALSE(log_manager.has_unsent_logs()); + log_manager.LoadPersistedUnsentLogs(); + EXPECT_TRUE(log_manager.has_unsent_logs()); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); + log_manager.StageNextLogForUpload(); + log_manager.FinishCurrentLog(); + + // Nothing should be written out until PersistUnsentLogs is called. + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + log_manager.PersistUnsentLogs(); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + } + + // Now simulate the relaunch, ensure that the log manager restores + // everything correctly, and verify that once the are handled they are not + // re-persisted. + { + MetricsLogManager log_manager(&pref_service, 0); + log_manager.LoadPersistedUnsentLogs(); + EXPECT_TRUE(log_manager.has_unsent_logs()); + + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + // The initial log should be sent first; update the persisted storage to + // verify. + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + + // Handle the first ongoing log. + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + EXPECT_TRUE(log_manager.has_unsent_logs()); + + // Handle the last log. + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + EXPECT_FALSE(log_manager.has_unsent_logs()); + + // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been + // called again. + EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + // Persist, and make sure nothing is left. + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + } +} + +TEST(MetricsLogManagerTest, StoreStagedLogTypes) { + TestMetricsServiceClient client; + + // Ensure that types are preserved when storing staged logs. + { + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + log_manager.LoadPersistedUnsentLogs(); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.PersistUnsentLogs(); + + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + } + + { + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + log_manager.LoadPersistedUnsentLogs(); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.PersistUnsentLogs(); + + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + } +} + +TEST(MetricsLogManagerTest, LargeLogDiscarding) { + TestMetricsServiceClient client; + TestLogPrefService pref_service; + // Set the size threshold very low, to verify that it's honored. + MetricsLogManager log_manager(&pref_service, 1); + log_manager.LoadPersistedUnsentLogs(); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + + // Only the ongoing log should be written out, due to the threshold. + log_manager.PersistUnsentLogs(); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); +} + +TEST(MetricsLogManagerTest, DiscardOrder) { + // Ensure that the correct log is discarded if new logs are pushed while + // a log is staged. + TestMetricsServiceClient client; + { + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); + log_manager.LoadPersistedUnsentLogs(); + + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service))); + log_manager.FinishCurrentLog(); + log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( + "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); + log_manager.StageNextLogForUpload(); + log_manager.FinishCurrentLog(); + log_manager.DiscardStagedLog(); + + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_log_unittest.cc b/chromium/components/metrics/metrics_log_unittest.cc new file mode 100644 index 00000000000..8eea886855a --- /dev/null +++ b/chromium/components/metrics/metrics_log_unittest.cc @@ -0,0 +1,465 @@ +// Copyright 2014 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 "components/metrics/metrics_log.h" + +#include <stddef.h> +#include <stdint.h> + +#include <string> + +#include "base/base64.h" +#include "base/macros.h" +#include "base/memory/scoped_vector.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/sample_vector.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" +#include "components/metrics/test_metrics_provider.h" +#include "components/metrics/test_metrics_service_client.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/testing_pref_service.h" +#include "components/variations/active_field_trials.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +const char kClientId[] = "bogus client ID"; +const int64_t kInstallDate = 1373051956; +const int64_t kInstallDateExpected = 1373050800; // Computed from kInstallDate. +const int64_t kEnabledDate = 1373001211; +const int64_t kEnabledDateExpected = 1373000400; // Computed from kEnabledDate. +const int kSessionId = 127; +const variations::ActiveGroupId kFieldTrialIds[] = { + {37, 43}, + {13, 47}, + {23, 17} +}; +const variations::ActiveGroupId kSyntheticTrials[] = { + {55, 15}, + {66, 16} +}; + +class TestMetricsLog : public MetricsLog { + public: + TestMetricsLog(const std::string& client_id, + int session_id, + LogType log_type, + MetricsServiceClient* client, + TestingPrefServiceSimple* prefs) + : MetricsLog(client_id, session_id, log_type, client, prefs), + prefs_(prefs) { + InitPrefs(); + } + + ~TestMetricsLog() override {} + + const ChromeUserMetricsExtension& uma_proto() const { + return *MetricsLog::uma_proto(); + } + + const SystemProfileProto& system_profile() const { + return uma_proto().system_profile(); + } + + private: + void InitPrefs() { + prefs_->SetString(prefs::kMetricsReportingEnabledTimestamp, + base::Int64ToString(kEnabledDate)); + } + + void GetFieldTrialIds( + std::vector<variations::ActiveGroupId>* field_trial_ids) const override { + ASSERT_TRUE(field_trial_ids->empty()); + + for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) { + field_trial_ids->push_back(kFieldTrialIds[i]); + } + } + + // Weak pointer to the PrefsService used by this log. + TestingPrefServiceSimple* prefs_; + + DISALLOW_COPY_AND_ASSIGN(TestMetricsLog); +}; + +} // namespace + +class MetricsLogTest : public testing::Test { + public: + MetricsLogTest() { + MetricsLog::RegisterPrefs(prefs_.registry()); + MetricsStateManager::RegisterPrefs(prefs_.registry()); + } + + ~MetricsLogTest() override {} + + protected: + // Check that the values in |system_values| correspond to the test data + // defined at the top of this file. + void CheckSystemProfile(const SystemProfileProto& system_profile) { + EXPECT_EQ(kInstallDateExpected, system_profile.install_date()); + EXPECT_EQ(kEnabledDateExpected, system_profile.uma_enabled_date()); + + ASSERT_EQ(arraysize(kFieldTrialIds) + arraysize(kSyntheticTrials), + static_cast<size_t>(system_profile.field_trial_size())); + for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) { + const SystemProfileProto::FieldTrial& field_trial = + system_profile.field_trial(i); + EXPECT_EQ(kFieldTrialIds[i].name, field_trial.name_id()); + EXPECT_EQ(kFieldTrialIds[i].group, field_trial.group_id()); + } + // Verify the right data is present for the synthetic trials. + for (size_t i = 0; i < arraysize(kSyntheticTrials); ++i) { + const SystemProfileProto::FieldTrial& field_trial = + system_profile.field_trial(i + arraysize(kFieldTrialIds)); + EXPECT_EQ(kSyntheticTrials[i].name, field_trial.name_id()); + EXPECT_EQ(kSyntheticTrials[i].group, field_trial.group_id()); + } + + EXPECT_EQ(TestMetricsServiceClient::kBrandForTesting, + system_profile.brand_code()); + + const SystemProfileProto::Hardware& hardware = + system_profile.hardware(); + + EXPECT_TRUE(hardware.has_cpu()); + EXPECT_TRUE(hardware.cpu().has_vendor_name()); + EXPECT_TRUE(hardware.cpu().has_signature()); + EXPECT_TRUE(hardware.cpu().has_num_cores()); + + // TODO(isherman): Verify other data written into the protobuf as a result + // of this call. + } + + protected: + TestingPrefServiceSimple prefs_; + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsLogTest); +}; + +TEST_F(MetricsLogTest, LogType) { + TestMetricsServiceClient client; + TestingPrefServiceSimple prefs; + + MetricsLog log1("id", 0, MetricsLog::ONGOING_LOG, &client, &prefs); + EXPECT_EQ(MetricsLog::ONGOING_LOG, log1.log_type()); + + MetricsLog log2("id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &prefs); + EXPECT_EQ(MetricsLog::INITIAL_STABILITY_LOG, log2.log_type()); +} + +TEST_F(MetricsLogTest, EmptyRecord) { + TestMetricsServiceClient client; + client.set_version_string("bogus version"); + TestingPrefServiceSimple prefs; + MetricsLog log("totally bogus client ID", 137, MetricsLog::ONGOING_LOG, + &client, &prefs); + log.CloseLog(); + + std::string encoded; + log.GetEncodedLog(&encoded); + + // A couple of fields are hard to mock, so these will be copied over directly + // for the expected output. + ChromeUserMetricsExtension parsed; + ASSERT_TRUE(parsed.ParseFromString(encoded)); + + ChromeUserMetricsExtension expected; + expected.set_client_id(5217101509553811875); // Hashed bogus client ID + expected.set_session_id(137); + expected.mutable_system_profile()->set_build_timestamp( + parsed.system_profile().build_timestamp()); + expected.mutable_system_profile()->set_app_version("bogus version"); + expected.mutable_system_profile()->set_channel(client.GetChannel()); + + EXPECT_EQ(expected.SerializeAsString(), encoded); +} + +TEST_F(MetricsLogTest, HistogramBucketFields) { + // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12. + base::BucketRanges ranges(8); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 7); + ranges.set_range(3, 8); + ranges.set_range(4, 9); + ranges.set_range(5, 10); + ranges.set_range(6, 11); + ranges.set_range(7, 12); + + base::SampleVector samples(1, &ranges); + samples.Accumulate(3, 1); // Bucket 1-5. + samples.Accumulate(6, 1); // Bucket 5-7. + samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped) + samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped) + samples.Accumulate(11, 1); // Bucket 11-12. + + TestMetricsServiceClient client; + TestingPrefServiceSimple prefs; + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + log.RecordHistogramDelta("Test", samples); + + const ChromeUserMetricsExtension& uma_proto = log.uma_proto(); + const HistogramEventProto& histogram_proto = + uma_proto.histogram_event(uma_proto.histogram_event_size() - 1); + + // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12. + // Should become: 1-/, 5-7, /-9, 10-/, /-12. + ASSERT_EQ(5, histogram_proto.bucket_size()); + + // 1-5 becomes 1-/ (max is same as next min). + EXPECT_TRUE(histogram_proto.bucket(0).has_min()); + EXPECT_FALSE(histogram_proto.bucket(0).has_max()); + EXPECT_EQ(1, histogram_proto.bucket(0).min()); + + // 5-7 stays 5-7 (no optimization possible). + EXPECT_TRUE(histogram_proto.bucket(1).has_min()); + EXPECT_TRUE(histogram_proto.bucket(1).has_max()); + EXPECT_EQ(5, histogram_proto.bucket(1).min()); + EXPECT_EQ(7, histogram_proto.bucket(1).max()); + + // 8-9 becomes /-9 (min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(2).has_min()); + EXPECT_TRUE(histogram_proto.bucket(2).has_max()); + EXPECT_EQ(9, histogram_proto.bucket(2).max()); + + // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized). + EXPECT_TRUE(histogram_proto.bucket(3).has_min()); + EXPECT_FALSE(histogram_proto.bucket(3).has_max()); + EXPECT_EQ(10, histogram_proto.bucket(3).min()); + + // 11-12 becomes /-12 (last record must keep max, min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(4).has_min()); + EXPECT_TRUE(histogram_proto.bucket(4).has_max()); + EXPECT_EQ(12, histogram_proto.bucket(4).max()); +} + +TEST_F(MetricsLogTest, RecordEnvironment) { + TestMetricsServiceClient client; + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + + std::vector<variations::ActiveGroupId> synthetic_trials; + // Add two synthetic trials. + synthetic_trials.push_back(kSyntheticTrials[0]); + synthetic_trials.push_back(kSyntheticTrials[1]); + + log.RecordEnvironment(std::vector<MetricsProvider*>(), synthetic_trials, + kInstallDate, kEnabledDate); + // Check that the system profile on the log has the correct values set. + CheckSystemProfile(log.system_profile()); + + // Check that the system profile has also been written to prefs. + const std::string base64_system_profile = + prefs_.GetString(prefs::kStabilitySavedSystemProfile); + EXPECT_FALSE(base64_system_profile.empty()); + std::string serialied_system_profile; + EXPECT_TRUE(base::Base64Decode(base64_system_profile, + &serialied_system_profile)); + SystemProfileProto decoded_system_profile; + EXPECT_TRUE(decoded_system_profile.ParseFromString(serialied_system_profile)); + CheckSystemProfile(decoded_system_profile); +} + +TEST_F(MetricsLogTest, LoadSavedEnvironmentFromPrefs) { + const char* kSystemProfilePref = prefs::kStabilitySavedSystemProfile; + const char* kSystemProfileHashPref = + prefs::kStabilitySavedSystemProfileHash; + + TestMetricsServiceClient client; + + // The pref value is empty, so loading it from prefs should fail. + { + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + EXPECT_FALSE(log.LoadSavedEnvironmentFromPrefs()); + } + + // Do a RecordEnvironment() call and check whether the pref is recorded. + { + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + log.RecordEnvironment(std::vector<MetricsProvider*>(), + std::vector<variations::ActiveGroupId>(), + kInstallDate, kEnabledDate); + EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty()); + EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty()); + } + + { + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + EXPECT_TRUE(log.LoadSavedEnvironmentFromPrefs()); + // Check some values in the system profile. + EXPECT_EQ(kInstallDateExpected, log.system_profile().install_date()); + EXPECT_EQ(kEnabledDateExpected, log.system_profile().uma_enabled_date()); + // Ensure that the call cleared the prefs. + EXPECT_TRUE(prefs_.GetString(kSystemProfilePref).empty()); + EXPECT_TRUE(prefs_.GetString(kSystemProfileHashPref).empty()); + } + + // Ensure that a non-matching hash results in the pref being invalid. + { + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + // Call RecordEnvironment() to record the pref again. + log.RecordEnvironment(std::vector<MetricsProvider*>(), + std::vector<variations::ActiveGroupId>(), + kInstallDate, kEnabledDate); + } + + { + // Set the hash to a bad value. + prefs_.SetString(kSystemProfileHashPref, "deadbeef"); + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + EXPECT_FALSE(log.LoadSavedEnvironmentFromPrefs()); + // Ensure that the prefs are cleared, even if the call failed. + EXPECT_TRUE(prefs_.GetString(kSystemProfilePref).empty()); + EXPECT_TRUE(prefs_.GetString(kSystemProfileHashPref).empty()); + } +} + +TEST_F(MetricsLogTest, RecordEnvironmentEnableDefault) { + TestMetricsServiceClient client; + TestMetricsLog log_unknown(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client, &prefs_); + + std::vector<variations::ActiveGroupId> synthetic_trials; + + log_unknown.RecordEnvironment(std::vector<MetricsProvider*>(), + synthetic_trials, kInstallDate, kEnabledDate); + EXPECT_FALSE(log_unknown.system_profile().has_uma_default_state()); + + client.set_enable_default(MetricsServiceClient::OPT_IN); + TestMetricsLog log_opt_in(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client, &prefs_); + log_opt_in.RecordEnvironment(std::vector<MetricsProvider*>(), + synthetic_trials, kInstallDate, kEnabledDate); + EXPECT_TRUE(log_opt_in.system_profile().has_uma_default_state()); + EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_IN, + log_opt_in.system_profile().uma_default_state()); + + client.set_enable_default(MetricsServiceClient::OPT_OUT); + TestMetricsLog log_opt_out(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client, &prefs_); + log_opt_out.RecordEnvironment(std::vector<MetricsProvider*>(), + synthetic_trials, kInstallDate, kEnabledDate); + EXPECT_TRUE(log_opt_out.system_profile().has_uma_default_state()); + EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_OUT, + log_opt_out.system_profile().uma_default_state()); + + client.set_reporting_is_managed(true); + TestMetricsLog log_managed(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client, &prefs_); + log_managed.RecordEnvironment(std::vector<MetricsProvider*>(), + synthetic_trials, kInstallDate, kEnabledDate); + EXPECT_TRUE(log_managed.system_profile().has_uma_default_state()); + EXPECT_EQ(SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED, + log_managed.system_profile().uma_default_state()); +} + +TEST_F(MetricsLogTest, InitialLogStabilityMetrics) { + TestMetricsServiceClient client; + TestMetricsLog log(kClientId, + kSessionId, + MetricsLog::INITIAL_STABILITY_LOG, + &client, + &prefs_); + TestMetricsProvider* test_provider = new TestMetricsProvider(); + ScopedVector<MetricsProvider> metrics_providers; + metrics_providers.push_back(test_provider); + log.RecordEnvironment(metrics_providers.get(), + std::vector<variations::ActiveGroupId>(), kInstallDate, + kEnabledDate); + log.RecordStabilityMetrics(metrics_providers.get(), base::TimeDelta(), + base::TimeDelta()); + const SystemProfileProto_Stability& stability = + log.system_profile().stability(); + // Required metrics: + EXPECT_TRUE(stability.has_launch_count()); + EXPECT_TRUE(stability.has_crash_count()); + // Initial log metrics: + EXPECT_TRUE(stability.has_incomplete_shutdown_count()); + EXPECT_TRUE(stability.has_breakpad_registration_success_count()); + EXPECT_TRUE(stability.has_breakpad_registration_failure_count()); + EXPECT_TRUE(stability.has_debugger_present_count()); + EXPECT_TRUE(stability.has_debugger_not_present_count()); + + // The test provider should have been called upon to provide initial + // stability and regular stability metrics. + EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called()); + EXPECT_TRUE(test_provider->provide_stability_metrics_called()); +} + +TEST_F(MetricsLogTest, OngoingLogStabilityMetrics) { + TestMetricsServiceClient client; + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + TestMetricsProvider* test_provider = new TestMetricsProvider(); + ScopedVector<MetricsProvider> metrics_providers; + metrics_providers.push_back(test_provider); + log.RecordEnvironment(metrics_providers.get(), + std::vector<variations::ActiveGroupId>(), kInstallDate, + kEnabledDate); + log.RecordStabilityMetrics(metrics_providers.get(), base::TimeDelta(), + base::TimeDelta()); + const SystemProfileProto_Stability& stability = + log.system_profile().stability(); + // Required metrics: + EXPECT_TRUE(stability.has_launch_count()); + EXPECT_TRUE(stability.has_crash_count()); + // Initial log metrics: + EXPECT_FALSE(stability.has_incomplete_shutdown_count()); + EXPECT_FALSE(stability.has_breakpad_registration_success_count()); + EXPECT_FALSE(stability.has_breakpad_registration_failure_count()); + EXPECT_FALSE(stability.has_debugger_present_count()); + EXPECT_FALSE(stability.has_debugger_not_present_count()); + + // The test provider should have been called upon to provide regular but not + // initial stability metrics. + EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called()); + EXPECT_TRUE(test_provider->provide_stability_metrics_called()); +} + +TEST_F(MetricsLogTest, ChromeChannelWrittenToProtobuf) { + TestMetricsServiceClient client; + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + EXPECT_TRUE(log.uma_proto().system_profile().has_channel()); +} + +TEST_F(MetricsLogTest, ProductNotSetIfDefault) { + TestMetricsServiceClient client; + EXPECT_EQ(ChromeUserMetricsExtension::CHROME, client.GetProduct()); + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + // Check that the product isn't set, since it's default and also verify the + // default value is indeed equal to Chrome. + EXPECT_FALSE(log.uma_proto().has_product()); + EXPECT_EQ(ChromeUserMetricsExtension::CHROME, log.uma_proto().product()); +} + +TEST_F(MetricsLogTest, ProductSetIfNotDefault) { + const int32_t kTestProduct = 100; + EXPECT_NE(ChromeUserMetricsExtension::CHROME, kTestProduct); + + TestMetricsServiceClient client; + client.set_product(kTestProduct); + TestMetricsLog log( + kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_); + // Check that the product is set to |kTestProduct|. + EXPECT_TRUE(log.uma_proto().has_product()); + EXPECT_EQ(kTestProduct, log.uma_proto().product()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_log_uploader.cc b/chromium/components/metrics/metrics_log_uploader.cc new file mode 100644 index 00000000000..41b83ed9249 --- /dev/null +++ b/chromium/components/metrics/metrics_log_uploader.cc @@ -0,0 +1,21 @@ +// Copyright 2014 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 "components/metrics/metrics_log_uploader.h" + +namespace metrics { + +MetricsLogUploader::MetricsLogUploader( + const std::string& server_url, + const std::string& mime_type, + const base::Callback<void(int)>& on_upload_complete) + : server_url_(server_url), + mime_type_(mime_type), + on_upload_complete_(on_upload_complete) { +} + +MetricsLogUploader::~MetricsLogUploader() { +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_log_uploader.h b/chromium/components/metrics/metrics_log_uploader.h new file mode 100644 index 00000000000..2ef0cf2577e --- /dev/null +++ b/chromium/components/metrics/metrics_log_uploader.h @@ -0,0 +1,44 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_ +#define COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" + +namespace metrics { + +// MetricsLogUploader is an abstract base class for uploading UMA logs on behalf +// of MetricsService. +class MetricsLogUploader { + public: + // Constructs the uploader that will upload logs to the specified |server_url| + // with the given |mime_type|. The |on_upload_complete| callback will be + // called with the HTTP response code of the upload or with -1 on an error. + MetricsLogUploader(const std::string& server_url, + const std::string& mime_type, + const base::Callback<void(int)>& on_upload_complete); + virtual ~MetricsLogUploader(); + + // Uploads a log with the specified |compressed_log_data| and |log_hash|. + // |log_hash| is expected to be the hex-encoded SHA1 hash of the log data + // before compression. + virtual void UploadLog(const std::string& compressed_log_data, + const std::string& log_hash) = 0; + + protected: + const std::string server_url_; + const std::string mime_type_; + const base::Callback<void(int)> on_upload_complete_; + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsLogUploader); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_ diff --git a/chromium/components/metrics/metrics_pref_names.cc b/chromium/components/metrics/metrics_pref_names.cc new file mode 100644 index 00000000000..3872061fb3e --- /dev/null +++ b/chromium/components/metrics/metrics_pref_names.cc @@ -0,0 +1,181 @@ +// Copyright 2014 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 "components/metrics/metrics_pref_names.h" + +namespace metrics { +namespace prefs { + +// Set once, to the current epoch time, on the first run of chrome on this +// machine. Attached to metrics reports forever thereafter. +const char kInstallDate[] = "uninstall_metrics.installation_date2"; + +// The metrics client GUID. +// Note: The name client_id2 is a result of creating +// new prefs to do a one-time reset of the previous values. +const char kMetricsClientID[] = "user_experience_metrics.client_id2"; + +// Array of strings that are each UMA logs that were supposed to be sent in the +// first minute of a browser session. These logs include things like crash count +// info, etc. +const char kMetricsInitialLogs[] = + "user_experience_metrics.initial_logs_list"; + +// The metrics entropy source. +// Note: The name low_entropy_source2 is a result of creating +// new prefs to do a one-time reset of the previous values. +const char kMetricsLowEntropySource[] = + "user_experience_metrics.low_entropy_source2"; + +// A machine ID used to detect when underlying hardware changes. It is only +// stored locally and never transmitted in metrics reports. +const char kMetricsMachineId[] = "user_experience_metrics.machine_id"; + +// Array of strings that are each UMA logs that were not sent because the +// browser terminated before these accumulated metrics could be sent. These +// logs typically include histograms and memory reports, as well as ongoing +// user activities. +const char kMetricsOngoingLogs[] = + "user_experience_metrics.ongoing_logs_list"; + +// Boolean that indicates a cloned install has been detected and the metrics +// client id and low entropy source should be reset. +const char kMetricsResetIds[] = "user_experience_metrics.reset_metrics_ids"; + +// Boolean that specifies whether or not crash reporting and metrics reporting +// are sent over the network for analysis. +const char kMetricsReportingEnabled[] = + "user_experience_metrics.reporting_enabled"; + +// Date/time when the user opted in to UMA and generated the client id for the +// very first time (local machine time, stored as a 64-bit time_t value). +const char kMetricsReportingEnabledTimestamp[] = + "user_experience_metrics.client_id_timestamp"; + +// The metrics client session ID. +const char kMetricsSessionID[] = "user_experience_metrics.session_id"; + +// The prefix of the last-seen timestamp for persistent histogram files. +// Values are named for the files themselves. +const char kMetricsLastSeenPrefix[] = + "user_experience_metrics.last_seen."; + +// Number of times the browser has been able to register crash reporting. +const char kStabilityBreakpadRegistrationSuccess[] = + "user_experience_metrics.stability.breakpad_registration_ok"; + +// Number of times the browser has failed to register crash reporting. +const char kStabilityBreakpadRegistrationFail[] = + "user_experience_metrics.stability.breakpad_registration_fail"; + +// Total number of child process crashes (other than renderer / extension +// renderer ones, and plugin children, which are counted separately) since the +// last report. +const char kStabilityChildProcessCrashCount[] = + "user_experience_metrics.stability.child_process_crash_count"; + +// Number of times the application exited uncleanly since the last report. +const char kStabilityCrashCount[] = + "user_experience_metrics.stability.crash_count"; + +// Number of times the browser has been run under a debugger. +const char kStabilityDebuggerPresent[] = + "user_experience_metrics.stability.debugger_present"; + +// Number of times the browser has not been run under a debugger. +const char kStabilityDebuggerNotPresent[] = + "user_experience_metrics.stability.debugger_not_present"; + +// An enum value to indicate the execution phase the browser was in. +const char kStabilityExecutionPhase[] = + "user_experience_metrics.stability.execution_phase"; + +// True if the previous run of the program exited cleanly. +const char kStabilityExitedCleanly[] = + "user_experience_metrics.stability.exited_cleanly"; + +// Number of times an extension renderer process crashed since the last report. +const char kStabilityExtensionRendererCrashCount[] = + "user_experience_metrics.stability.extension_renderer_crash_count"; + +// Number of times an extension renderer process failed to launch since the last +// report. +const char kStabilityExtensionRendererFailedLaunchCount[] = + "user_experience_metrics.stability.extension_renderer_failed_launch_count"; + +// Number of times the session end did not complete. +const char kStabilityIncompleteSessionEndCount[] = + "user_experience_metrics.stability.incomplete_session_end_count"; + +// Time when the app was last known to be running, in seconds since +// the epoch. +const char kStabilityLastTimestampSec[] = + "user_experience_metrics.stability.last_timestamp_sec"; + +// Number of times the application was launched since last report. +const char kStabilityLaunchCount[] = + "user_experience_metrics.stability.launch_count"; + +// Time when the app was last launched, in seconds since the epoch. +const char kStabilityLaunchTimeSec[] = + "user_experience_metrics.stability.launch_time_sec"; + +// Number of times a page load event occurred since the last report. +const char kStabilityPageLoadCount[] = + "user_experience_metrics.stability.page_load_count"; + +// Number of times a renderer process crashed since the last report. +const char kStabilityRendererCrashCount[] = + "user_experience_metrics.stability.renderer_crash_count"; + +// Number of times a renderer process failed to launch since the last report. +const char kStabilityRendererFailedLaunchCount[] = + "user_experience_metrics.stability.renderer_failed_launch_count"; + +// Number of times the renderer has become non-responsive since the last +// report. +const char kStabilityRendererHangCount[] = + "user_experience_metrics.stability.renderer_hang_count"; + +// Base64 encoded serialized UMA system profile proto from the previous session. +const char kStabilitySavedSystemProfile[] = + "user_experience_metrics.stability.saved_system_profile"; + +// SHA-1 hash of the serialized UMA system profile proto (hex encoded). +const char kStabilitySavedSystemProfileHash[] = + "user_experience_metrics.stability.saved_system_profile_hash"; + +// False if we received a session end and either we crashed during processing +// the session end or ran out of time and windows terminated us. +const char kStabilitySessionEndCompleted[] = + "user_experience_metrics.stability.session_end_completed"; + +// Build time, in seconds since an epoch, which is used to assure that stability +// metrics reported reflect stability of the same build. +const char kStabilityStatsBuildTime[] = + "user_experience_metrics.stability.stats_buildtime"; + +// Version string of previous run, which is used to assure that stability +// metrics reported under current version reflect stability of the same version. +const char kStabilityStatsVersion[] = + "user_experience_metrics.stability.stats_version"; + +// The keys below are strictly increasing counters over the lifetime of +// a chrome installation. They are (optionally) sent up to the uninstall +// survey in the event of uninstallation. +const char kUninstallLaunchCount[] = "uninstall_metrics.launch_count"; +const char kUninstallMetricsPageLoadCount[] = + "uninstall_metrics.page_load_count"; +const char kUninstallMetricsUptimeSec[] = "uninstall_metrics.uptime_sec"; + +// Dictionary for measuring cellular data used by UMA service during last 7 +// days. +const char kUmaCellDataUse[] = "user_experience_metrics.uma_cell_datause"; + +// Dictionary for measuring cellular data used by user including chrome services +// per day. +const char kUserCellDataUse[] = "user_experience_metrics.user_call_datause"; + +} // namespace prefs +} // namespace metrics diff --git a/chromium/components/metrics/metrics_pref_names.h b/chromium/components/metrics/metrics_pref_names.h new file mode 100644 index 00000000000..52733611995 --- /dev/null +++ b/chromium/components/metrics/metrics_pref_names.h @@ -0,0 +1,64 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ +#define COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ + +namespace metrics { +namespace prefs { + +// Alphabetical list of preference names specific to the metrics +// component. Document each in the .cc file. +extern const char kInstallDate[]; +extern const char kMetricsClientID[]; +extern const char kMetricsInitialLogs[]; +extern const char kMetricsLowEntropySource[]; +extern const char kMetricsMachineId[]; +extern const char kMetricsOngoingLogs[]; +extern const char kMetricsResetIds[]; + +// For finding out whether metrics and crash reporting is enabled use the +// relevant embedder-specific subclass of MetricsServiceAccessor instead of +// reading this pref directly; see the comments on metrics_service_accessor.h. +// (NOTE: If within //chrome, use +// ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled()). +extern const char kMetricsReportingEnabled[]; +extern const char kMetricsReportingEnabledTimestamp[]; +extern const char kMetricsSessionID[]; +extern const char kMetricsLastSeenPrefix[]; +extern const char kStabilityBreakpadRegistrationSuccess[]; +extern const char kStabilityBreakpadRegistrationFail[]; +extern const char kStabilityChildProcessCrashCount[]; +extern const char kStabilityCrashCount[]; +extern const char kStabilityDebuggerPresent[]; +extern const char kStabilityDebuggerNotPresent[]; +extern const char kStabilityExecutionPhase[]; +extern const char kStabilityExtensionRendererCrashCount[]; +extern const char kStabilityExtensionRendererFailedLaunchCount[]; +extern const char kStabilityExitedCleanly[]; +extern const char kStabilityIncompleteSessionEndCount[]; +extern const char kStabilityLastTimestampSec[]; +extern const char kStabilityLaunchCount[]; +extern const char kStabilityLaunchTimeSec[]; +extern const char kStabilityPageLoadCount[]; +extern const char kStabilityRendererCrashCount[]; +extern const char kStabilityRendererFailedLaunchCount[]; +extern const char kStabilityRendererHangCount[]; +extern const char kStabilitySavedSystemProfile[]; +extern const char kStabilitySavedSystemProfileHash[]; +extern const char kStabilitySessionEndCompleted[]; +extern const char kStabilityStatsBuildTime[]; +extern const char kStabilityStatsVersion[]; +extern const char kUninstallLaunchCount[]; +extern const char kUninstallMetricsPageLoadCount[]; +extern const char kUninstallMetricsUptimeSec[]; + +// For measuring data use for throttling UMA log uploads on cellular. +extern const char kUmaCellDataUse[]; +extern const char kUserCellDataUse[]; + +} // namespace prefs +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ diff --git a/chromium/components/metrics/metrics_provider.cc b/chromium/components/metrics/metrics_provider.cc new file mode 100644 index 00000000000..5ba7382be5e --- /dev/null +++ b/chromium/components/metrics/metrics_provider.cc @@ -0,0 +1,54 @@ +// Copyright 2014 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 "components/metrics/metrics_provider.h" + +namespace metrics { + +MetricsProvider::MetricsProvider() { +} + +MetricsProvider::~MetricsProvider() { +} + +void MetricsProvider::Init() { +} + +void MetricsProvider::OnDidCreateMetricsLog() { +} + +void MetricsProvider::OnRecordingEnabled() { +} + +void MetricsProvider::OnRecordingDisabled() { +} + +void MetricsProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) { +} + +bool MetricsProvider::HasInitialStabilityMetrics() { + return false; +} + +void MetricsProvider::ProvideInitialStabilityMetrics( + SystemProfileProto* system_profile_proto) { +} + +void MetricsProvider::ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto) { +} + +void MetricsProvider::ClearSavedStabilityMetrics() { +} + +void MetricsProvider::ProvideGeneralMetrics( + ChromeUserMetricsExtension* uma_proto) { +} + +void MetricsProvider::RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_provider.h b/chromium/components/metrics/metrics_provider.h new file mode 100644 index 00000000000..664e5dbc539 --- /dev/null +++ b/chromium/components/metrics/metrics_provider.h @@ -0,0 +1,86 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_METRICS_PROVIDER_H_ + +#include "base/macros.h" + +namespace base { +class HistogramSnapshotManager; +} // namespace base + +namespace metrics { + +class ChromeUserMetricsExtension; +class SystemProfileProto; +class SystemProfileProto_Stability; + +// MetricsProvider is an interface allowing different parts of the UMA protos to +// be filled out by different classes. +class MetricsProvider { + public: + MetricsProvider(); + virtual ~MetricsProvider(); + + // Called after initialiazation of MetricsService and field trials. + virtual void Init(); + + // Called when a new MetricsLog is created. + virtual void OnDidCreateMetricsLog(); + + // Called when metrics recording has been enabled. + virtual void OnRecordingEnabled(); + + // Called when metrics recording has been disabled. + virtual void OnRecordingDisabled(); + + // Provides additional metrics into the system profile. + virtual void ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto); + + // Called once at startup to see whether this provider has critical stability + // events to share in an initial stability log. + // Returning true can trigger ProvideInitialStabilityMetrics and + // ProvideStabilityMetrics on all other registered metrics providers. + // Default implementation always returns false. + virtual bool HasInitialStabilityMetrics(); + + // Called at most once at startup when an initial stability log is created. + // It provides critical statiblity metrics that need to be reported in an + // initial stability log. + // Default implementation is a no-op. + virtual void ProvideInitialStabilityMetrics( + SystemProfileProto* system_profile_proto); + + // Provides additional stability metrics. Stability metrics can be provided + // directly into |stability_proto| fields or by logging stability histograms + // via the UMA_STABILITY_HISTOGRAM_ENUMERATION() macro. + virtual void ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto); + + // Called to indicate that saved stability prefs should be cleared, e.g. + // because they are from an old version and should not be kept. + virtual void ClearSavedStabilityMetrics(); + + // Provides general metrics that are neither system profile nor stability + // metrics. May also be used to add histograms when final metrics are + // collected right before upload. + virtual void ProvideGeneralMetrics( + ChromeUserMetricsExtension* uma_proto); + + // Called during collection to explicitly load histogram snapshots using a + // snapshot manager. PrepareDeltas() will have already been called and + // FinishDeltas() will be called later; calls to only PrepareDelta(), not + // PrepareDeltas (plural), should be made. + virtual void RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager); + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/metrics_reporting_scheduler.cc b/chromium/components/metrics/metrics_reporting_scheduler.cc new file mode 100644 index 00000000000..ab117ad01e9 --- /dev/null +++ b/chromium/components/metrics/metrics_reporting_scheduler.cc @@ -0,0 +1,181 @@ +// Copyright 2014 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 "components/metrics/metrics_reporting_scheduler.h" + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "components/variations/variations_associated_data.h" + +using base::TimeDelta; + +namespace metrics { + +namespace { + +// The delay, in seconds, after startup before sending the first log message. +#if defined(OS_ANDROID) || defined(OS_IOS) +// Sessions are more likely to be short on a mobile device, so handle the +// initial log quickly. +const int kInitialUploadIntervalSeconds = 15; +#else +const int kInitialUploadIntervalSeconds = 60; +#endif + +// The delay, in seconds, between uploading when there are queued logs from +// previous sessions to send. +#if defined(OS_ANDROID) || defined(OS_IOS) +// Sending in a burst is better on a mobile device, since keeping the radio on +// is very expensive. +const int kUnsentLogsIntervalSeconds = 3; +#else +const int kUnsentLogsIntervalSeconds = 15; +#endif + +// When uploading metrics to the server fails, we progressively wait longer and +// longer before sending the next log. This backoff process helps reduce load +// on a server that is having issues. +// The following is the multiplier we use to expand that inter-log duration. +const double kBackoffMultiplier = 1.1; + +// The maximum backoff multiplier. +const int kMaxBackoffMultiplier = 10; + +enum InitSequence { + TIMER_FIRED_FIRST, + INIT_TASK_COMPLETED_FIRST, + INIT_SEQUENCE_ENUM_SIZE, +}; + +void LogMetricsInitSequence(InitSequence sequence) { + UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence, + INIT_SEQUENCE_ENUM_SIZE); +} + +void LogActualUploadInterval(TimeDelta interval) { + UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.ActualLogUploadInterval", + interval.InMinutes(), + 1, + base::TimeDelta::FromHours(12).InMinutes(), + 50); +} + +} // anonymous namespace + +MetricsReportingScheduler::MetricsReportingScheduler( + const base::Closure& upload_callback, + const base::Callback<base::TimeDelta(void)>& upload_interval_callback) + : upload_callback_(upload_callback), + upload_interval_(TimeDelta::FromSeconds(kInitialUploadIntervalSeconds)), + running_(false), + callback_pending_(false), + init_task_complete_(false), + waiting_for_init_task_complete_(false), + upload_interval_callback_(upload_interval_callback) { +} + +MetricsReportingScheduler::~MetricsReportingScheduler() {} + +void MetricsReportingScheduler::Start() { + running_ = true; + ScheduleNextUpload(); +} + +void MetricsReportingScheduler::Stop() { + running_ = false; + if (upload_timer_.IsRunning()) + upload_timer_.Stop(); +} + +// Callback from MetricsService when the startup init task has completed. +void MetricsReportingScheduler::InitTaskComplete() { + DCHECK(!init_task_complete_); + init_task_complete_ = true; + if (waiting_for_init_task_complete_) { + waiting_for_init_task_complete_ = false; + TriggerUpload(); + } else { + LogMetricsInitSequence(INIT_TASK_COMPLETED_FIRST); + } +} + +void MetricsReportingScheduler::UploadFinished(bool server_is_healthy, + bool more_logs_remaining) { + DCHECK(callback_pending_); + callback_pending_ = false; + // If the server is having issues, back off. Otherwise, reset to default + // (unless there are more logs to send, in which case the next upload should + // happen sooner). + if (!server_is_healthy) { + BackOffUploadInterval(); + } else if (more_logs_remaining) { + upload_interval_ = TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds); + } else { + upload_interval_ = GetStandardUploadInterval(); + last_upload_finish_time_ = base::TimeTicks::Now(); + } + + if (running_) + ScheduleNextUpload(); +} + +void MetricsReportingScheduler::UploadCancelled() { + DCHECK(callback_pending_); + callback_pending_ = false; + if (running_) + ScheduleNextUpload(); +} + +void MetricsReportingScheduler::SetUploadIntervalForTesting( + base::TimeDelta interval) { + upload_interval_ = interval; +} + +void MetricsReportingScheduler::TriggerUpload() { + // If the timer fired before the init task has completed, don't trigger the + // upload yet - wait for the init task to complete and do it then. + if (!init_task_complete_) { + LogMetricsInitSequence(TIMER_FIRED_FIRST); + waiting_for_init_task_complete_ = true; + return; + } + + if (!last_upload_finish_time_.is_null()) { + LogActualUploadInterval(base::TimeTicks::Now() - last_upload_finish_time_); + last_upload_finish_time_ = base::TimeTicks(); + } + + callback_pending_ = true; + upload_callback_.Run(); +} + +void MetricsReportingScheduler::ScheduleNextUpload() { + DCHECK(running_); + if (upload_timer_.IsRunning() || callback_pending_) + return; + + upload_timer_.Start(FROM_HERE, upload_interval_, this, + &MetricsReportingScheduler::TriggerUpload); +} + +void MetricsReportingScheduler::BackOffUploadInterval() { + DCHECK_GT(kBackoffMultiplier, 1.0); + upload_interval_ = TimeDelta::FromMicroseconds(static_cast<int64_t>( + kBackoffMultiplier * upload_interval_.InMicroseconds())); + + TimeDelta max_interval = kMaxBackoffMultiplier * GetStandardUploadInterval(); + if (upload_interval_ > max_interval || upload_interval_.InSeconds() < 0) { + upload_interval_ = max_interval; + } +} + +base::TimeDelta MetricsReportingScheduler::GetStandardUploadInterval() { + return upload_interval_callback_.Run(); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_reporting_scheduler.h b/chromium/components/metrics/metrics_reporting_scheduler.h new file mode 100644 index 00000000000..f4e3dd11b3c --- /dev/null +++ b/chromium/components/metrics/metrics_reporting_scheduler.h @@ -0,0 +1,99 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_ +#define COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "components/metrics/net/network_metrics_provider.h" + +namespace metrics { + +// Scheduler task to drive a MetricsService object's uploading. +class MetricsReportingScheduler { + public: + // Creates MetricsServiceScheduler object with the given |upload_callback| + // callback to call when uploading should happen and + // |upload_interval_callback| to determine the interval between uploads + // in steady state. + MetricsReportingScheduler( + const base::Closure& upload_callback, + const base::Callback<base::TimeDelta(void)>& upload_interval_callback); + ~MetricsReportingScheduler(); + + // Starts scheduling uploads. This in a no-op if the scheduler is already + // running, so it is safe to call more than once. + void Start(); + + // Stops scheduling uploads. + void Stop(); + + // Callback from MetricsService when the startup init task has completed. + void InitTaskComplete(); + + // Callback from MetricsService when a triggered upload finishes. + void UploadFinished(bool server_is_healthy, bool more_logs_remaining); + + // Callback from MetricsService when a triggered upload is cancelled by the + // MetricsService. + void UploadCancelled(); + + // Sets the upload interval to a specific value, exposed for unit tests. + void SetUploadIntervalForTesting(base::TimeDelta interval); + + private: + // Timer callback indicating it's time for the MetricsService to upload + // metrics. + void TriggerUpload(); + + // Schedules a future call to TriggerUpload if one isn't already pending. + void ScheduleNextUpload(); + + // Increases the upload interval each time it's called, to handle the case + // where the server is having issues. + void BackOffUploadInterval(); + + // Returns upload interval to use in steady state. + base::TimeDelta GetStandardUploadInterval(); + + // The MetricsService method to call when uploading should happen. + const base::Closure upload_callback_; + + base::OneShotTimer upload_timer_; + + // The interval between being told an upload is done and starting the next + // upload. + base::TimeDelta upload_interval_; + + // The tick count of the last time log upload has been finished and null if no + // upload has been done yet. + base::TimeTicks last_upload_finish_time_; + + // Indicates that the scheduler is running (i.e., that Start has been called + // more recently than Stop). + bool running_; + + // Indicates that the last triggered upload hasn't resolved yet. + bool callback_pending_; + + // Whether the InitTaskComplete() callback has been called. + bool init_task_complete_; + + // Whether the initial scheduled upload timer has fired before the init task + // has been completed. + bool waiting_for_init_task_complete_; + + // Callback function used to get the standard upload time. + base::Callback<base::TimeDelta(void)> upload_interval_callback_; + + DISALLOW_COPY_AND_ASSIGN(MetricsReportingScheduler); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_ diff --git a/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc b/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc new file mode 100644 index 00000000000..f5b9b5b0180 --- /dev/null +++ b/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc @@ -0,0 +1,71 @@ +// Copyright 2014 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 "components/metrics/metrics_reporting_scheduler.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +class MetricsReportingSchedulerTest : public testing::Test { + public: + MetricsReportingSchedulerTest() : callback_call_count_(0) {} + ~MetricsReportingSchedulerTest() override {} + + base::Closure GetCallback() { + return base::Bind(&MetricsReportingSchedulerTest::SchedulerCallback, + base::Unretained(this)); + } + + base::Callback<base::TimeDelta(void)> GetConnectionCallback() { + return base::Bind(&MetricsReportingSchedulerTest::GetStandardUploadInterval, + base::Unretained(this)); + } + + int callback_call_count() const { return callback_call_count_; } + + private: + void SchedulerCallback() { + ++callback_call_count_; + } + + base::TimeDelta GetStandardUploadInterval() { + return base::TimeDelta::FromMinutes(5); + } + + int callback_call_count_; + + base::MessageLoopForUI message_loop_; + + DISALLOW_COPY_AND_ASSIGN(MetricsReportingSchedulerTest); +}; + + +TEST_F(MetricsReportingSchedulerTest, InitTaskCompleteBeforeTimer) { + MetricsReportingScheduler scheduler(GetCallback(), GetConnectionCallback()); + scheduler.SetUploadIntervalForTesting(base::TimeDelta()); + scheduler.InitTaskComplete(); + scheduler.Start(); + EXPECT_EQ(0, callback_call_count()); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, callback_call_count()); +} + +TEST_F(MetricsReportingSchedulerTest, InitTaskCompleteAfterTimer) { + MetricsReportingScheduler scheduler(GetCallback(), GetConnectionCallback()); + scheduler.SetUploadIntervalForTesting(base::TimeDelta()); + scheduler.Start(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, callback_call_count()); + + scheduler.InitTaskComplete(); + EXPECT_EQ(1, callback_call_count()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_service.cc b/chromium/components/metrics/metrics_service.cc new file mode 100644 index 00000000000..38ef6e45ab1 --- /dev/null +++ b/chromium/components/metrics/metrics_service.cc @@ -0,0 +1,1193 @@ +// Copyright 2014 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. + +//------------------------------------------------------------------------------ +// Description of the life cycle of a instance of MetricsService. +// +// OVERVIEW +// +// A MetricsService instance is typically created at application startup. It is +// the central controller for the acquisition of log data, and the automatic +// transmission of that log data to an external server. Its major job is to +// manage logs, grouping them for transmission, and transmitting them. As part +// of its grouping, MS finalizes logs by including some just-in-time gathered +// memory statistics, snapshotting the current stats of numerous histograms, +// closing the logs, translating to protocol buffer format, and compressing the +// results for transmission. Transmission includes submitting a compressed log +// as data in a URL-post, and retransmitting (or retaining at process +// termination) if the attempted transmission failed. Retention across process +// terminations is done using the the PrefServices facilities. The retained logs +// (the ones that never got transmitted) are compressed and base64-encoded +// before being persisted. +// +// Logs fall into one of two categories: "initial logs," and "ongoing logs." +// There is at most one initial log sent for each complete run of Chrome (from +// startup, to browser shutdown). An initial log is generally transmitted some +// short time (1 minute?) after startup, and includes stats such as recent crash +// info, the number and types of plugins, etc. The external server's response +// to the initial log conceptually tells this MS if it should continue +// transmitting logs (during this session). The server response can actually be +// much more detailed, and always includes (at a minimum) how often additional +// ongoing logs should be sent. +// +// After the above initial log, a series of ongoing logs will be transmitted. +// The first ongoing log actually begins to accumulate information stating when +// the MS was first constructed. Note that even though the initial log is +// commonly sent a full minute after startup, the initial log does not include +// much in the way of user stats. The most common interlog period (delay) +// is 30 minutes. That time period starts when the first user action causes a +// logging event. This means that if there is no user action, there may be long +// periods without any (ongoing) log transmissions. Ongoing logs typically +// contain very detailed records of user activities (ex: opened tab, closed +// tab, fetched URL, maximized window, etc.) In addition, just before an +// ongoing log is closed out, a call is made to gather memory statistics. Those +// memory statistics are deposited into a histogram, and the log finalization +// code is then called. In the finalization, a call to a Histogram server +// acquires a list of all local histograms that have been flagged for upload +// to the UMA server. The finalization also acquires the most recent number +// of page loads, along with any counts of renderer or plugin crashes. +// +// When the browser shuts down, there will typically be a fragment of an ongoing +// log that has not yet been transmitted. At shutdown time, that fragment is +// closed (including snapshotting histograms), and persisted, for potential +// transmission during a future run of the product. +// +// There are two slightly abnormal shutdown conditions. There is a +// "disconnected scenario," and a "really fast startup and shutdown" scenario. +// In the "never connected" situation, the user has (during the running of the +// process) never established an internet connection. As a result, attempts to +// transmit the initial log have failed, and a lot(?) of data has accumulated in +// the ongoing log (which didn't yet get closed, because there was never even a +// contemplation of sending it). There is also a kindred "lost connection" +// situation, where a loss of connection prevented an ongoing log from being +// transmitted, and a (still open) log was stuck accumulating a lot(?) of data, +// while the earlier log retried its transmission. In both of these +// disconnected situations, two logs need to be, and are, persistently stored +// for future transmission. +// +// The other unusual shutdown condition, termed "really fast startup and +// shutdown," involves the deliberate user termination of the process before +// the initial log is even formed or transmitted. In that situation, no logging +// is done, but the historical crash statistics remain (unlogged) for inclusion +// in a future run's initial log. (i.e., we don't lose crash stats). +// +// With the above overview, we can now describe the state machine's various +// states, based on the State enum specified in the state_ member. Those states +// are: +// +// INITIALIZED, // Constructor was called. +// INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish. +// INIT_TASK_DONE, // Waiting for timer to send initial log. +// SENDING_LOGS, // Sending logs and creating new ones when we run out. +// +// In more detail, we have: +// +// INITIALIZED, // Constructor was called. +// The MS has been constructed, but has taken no actions to compose the +// initial log. +// +// INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish. +// Typically about 30 seconds after startup, a task is sent to a second thread +// (the file thread) to perform deferred (lower priority and slower) +// initialization steps such as getting the list of plugins. That task will +// (when complete) make an async callback (via a Task) to indicate the +// completion. +// +// INIT_TASK_DONE, // Waiting for timer to send initial log. +// The callback has arrived, and it is now possible for an initial log to be +// created. This callback typically arrives back less than one second after +// the deferred init task is dispatched. +// +// SENDING_LOGS, // Sending logs an creating new ones when we run out. +// Logs from previous sessions have been loaded, and initial logs have been +// created (an optional stability log and the first metrics log). We will +// send all of these logs, and when run out, we will start cutting new logs +// to send. We will also cut a new log if we expect a shutdown. +// +// The progression through the above states is simple, and sequential. +// States proceed from INITIAL to SENDING_LOGS, and remain in the latter until +// shutdown. +// +// Also note that whenever we successfully send a log, we mirror the list +// of logs into the PrefService. This ensures that IF we crash, we won't start +// up and retransmit our old logs again. +// +// Due to race conditions, it is always possible that a log file could be sent +// twice. For example, if a log file is sent, but not yet acknowledged by +// the external server, and the user shuts down, then a copy of the log may be +// saved for re-transmission. These duplicates could be filtered out server +// side, but are not expected to be a significant problem. +// +// +//------------------------------------------------------------------------------ + +#include "components/metrics/metrics_service.h" + +#include <stddef.h> +#include <algorithm> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/persistent_histogram_allocator.h" +#include "base/metrics/sparse_histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/rand_util.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" +#include "base/tracked_objects.h" +#include "base/values.h" +#include "build/build_config.h" +#include "components/metrics/data_use_tracker.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_log_manager.h" +#include "components/metrics/metrics_log_uploader.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_reporting_scheduler.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/variations/entropy_provider.h" +#include "components/variations/variations_associated_data.h" + +namespace metrics { + +namespace { + +// Check to see that we're being called on only one thread. +bool IsSingleThreaded() { + static base::PlatformThreadId thread_id = 0; + if (!thread_id) + thread_id = base::PlatformThread::CurrentId(); + return base::PlatformThread::CurrentId() == thread_id; +} + +// The delay, in seconds, after starting recording before doing expensive +// initialization work. +#if defined(OS_ANDROID) || defined(OS_IOS) +// On mobile devices, a significant portion of sessions last less than a minute. +// Use a shorter timer on these platforms to avoid losing data. +// TODO(dfalcantara): To avoid delaying startup, tighten up initialization so +// that it occurs after the user gets their initial page. +const int kInitializationDelaySeconds = 5; +#else +const int kInitializationDelaySeconds = 30; +#endif + +// The maximum number of events in a log uploaded to the UMA server. +const int kEventLimit = 2400; + +// If an upload fails, and the transmission was over this byte count, then we +// will discard the log, and not try to retransmit it. We also don't persist +// the log to the prefs for transmission during the next chrome session if this +// limit is exceeded. +const size_t kUploadLogAvoidRetransmitSize = 100 * 1024; + +// Interval, in minutes, between state saves. +const int kSaveStateIntervalMinutes = 5; + +enum ResponseStatus { + UNKNOWN_FAILURE, + SUCCESS, + BAD_REQUEST, // Invalid syntax or log too large. + NO_RESPONSE, + NUM_RESPONSE_STATUSES +}; + +ResponseStatus ResponseCodeToStatus(int response_code) { + switch (response_code) { + case -1: + return NO_RESPONSE; + case 200: + return SUCCESS; + case 400: + return BAD_REQUEST; + default: + return UNKNOWN_FAILURE; + } +} + +#if defined(OS_ANDROID) || defined(OS_IOS) +void MarkAppCleanShutdownAndCommit(CleanExitBeacon* clean_exit_beacon, + PrefService* local_state) { + clean_exit_beacon->WriteBeaconValue(true); + local_state->SetInteger(prefs::kStabilityExecutionPhase, + MetricsService::SHUTDOWN_COMPLETE); + // Start writing right away (write happens on a different thread). + local_state->CommitPendingWrite(); +} +#endif // defined(OS_ANDROID) || defined(OS_IOS) + +// Determines if current log should be sent based on sampling rate. Returns true +// if the sampling rate is not set. +bool ShouldUploadLog() { + std::string probability_str = variations::GetVariationParamValue( + "UMA_EnableCellularLogUpload", "Sample_Probability"); + if (probability_str.empty()) + return true; + + int probability; + // In case specified sampling rate is invalid. + if (!base::StringToInt(probability_str, &probability)) + return true; + return base::RandInt(1, 100) <= probability; +} + +} // namespace + +// static +MetricsService::ShutdownCleanliness MetricsService::clean_shutdown_status_ = + MetricsService::CLEANLY_SHUTDOWN; + +MetricsService::ExecutionPhase MetricsService::execution_phase_ = + MetricsService::UNINITIALIZED_PHASE; + +// static +void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) { + DCHECK(IsSingleThreaded()); + MetricsStateManager::RegisterPrefs(registry); + MetricsLog::RegisterPrefs(registry); + DataUseTracker::RegisterPrefs(registry); + + registry->RegisterInt64Pref(prefs::kInstallDate, 0); + + registry->RegisterInt64Pref(prefs::kStabilityLaunchTimeSec, 0); + registry->RegisterInt64Pref(prefs::kStabilityLastTimestampSec, 0); + registry->RegisterStringPref(prefs::kStabilityStatsVersion, std::string()); + registry->RegisterInt64Pref(prefs::kStabilityStatsBuildTime, 0); + registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, true); + registry->RegisterIntegerPref(prefs::kStabilityExecutionPhase, + UNINITIALIZED_PHASE); + registry->RegisterBooleanPref(prefs::kStabilitySessionEndCompleted, true); + registry->RegisterIntegerPref(prefs::kMetricsSessionID, -1); + + registry->RegisterListPref(prefs::kMetricsInitialLogs); + registry->RegisterListPref(prefs::kMetricsOngoingLogs); + + registry->RegisterInt64Pref(prefs::kUninstallLaunchCount, 0); + registry->RegisterInt64Pref(prefs::kUninstallMetricsUptimeSec, 0); +} + +MetricsService::MetricsService(MetricsStateManager* state_manager, + MetricsServiceClient* client, + PrefService* local_state) + : log_manager_(local_state, kUploadLogAvoidRetransmitSize), + histogram_snapshot_manager_(this), + state_manager_(state_manager), + client_(client), + local_state_(local_state), + clean_exit_beacon_(client->GetRegistryBackupKey(), local_state), + recording_state_(UNSET), + reporting_active_(false), + test_mode_active_(false), + state_(INITIALIZED), + log_upload_in_progress_(false), + idle_since_last_transmission_(false), + session_id_(-1), + data_use_tracker_(DataUseTracker::Create(local_state_)), + self_ptr_factory_(this), + state_saver_factory_(this) { + DCHECK(IsSingleThreaded()); + DCHECK(state_manager_); + DCHECK(client_); + DCHECK(local_state_); + + // Set the install date if this is our first run. + int64_t install_date = local_state_->GetInt64(prefs::kInstallDate); + if (install_date == 0) + local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT()); +} + +MetricsService::~MetricsService() { + DisableRecording(); +} + +void MetricsService::InitializeMetricsRecordingState() { + InitializeMetricsState(); + + base::Closure upload_callback = + base::Bind(&MetricsService::StartScheduledUpload, + self_ptr_factory_.GetWeakPtr()); + scheduler_.reset( + new MetricsReportingScheduler( + upload_callback, + // MetricsServiceClient outlives MetricsService, and + // MetricsReportingScheduler is tied to the lifetime of |this|. + base::Bind(&MetricsServiceClient::GetStandardUploadInterval, + base::Unretained(client_)))); + + for (MetricsProvider* provider : metrics_providers_) + provider->Init(); +} + +void MetricsService::Start() { + HandleIdleSinceLastTransmission(false); + EnableRecording(); + EnableReporting(); +} + +void MetricsService::StartRecordingForTests() { + test_mode_active_ = true; + EnableRecording(); + DisableReporting(); +} + +void MetricsService::Stop() { + HandleIdleSinceLastTransmission(false); + DisableReporting(); + DisableRecording(); +} + +void MetricsService::EnableReporting() { + if (reporting_active_) + return; + reporting_active_ = true; + StartSchedulerIfNecessary(); +} + +void MetricsService::DisableReporting() { + reporting_active_ = false; +} + +std::string MetricsService::GetClientId() { + return state_manager_->client_id(); +} + +int64_t MetricsService::GetInstallDate() { + return local_state_->GetInt64(prefs::kInstallDate); +} + +int64_t MetricsService::GetMetricsReportingEnabledDate() { + return local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp); +} + +bool MetricsService::WasLastShutdownClean() const { + return clean_exit_beacon_.exited_cleanly(); +} + +scoped_ptr<const base::FieldTrial::EntropyProvider> +MetricsService::CreateEntropyProvider() { + // TODO(asvitkine): Refactor the code so that MetricsService does not expose + // this method. + return state_manager_->CreateEntropyProvider(); +} + +void MetricsService::EnableRecording() { + DCHECK(IsSingleThreaded()); + + if (recording_state_ == ACTIVE) + return; + recording_state_ = ACTIVE; + + state_manager_->ForceClientIdCreation(); + client_->SetMetricsClientId(state_manager_->client_id()); + if (!log_manager_.current_log()) + OpenNewLog(); + + for (MetricsProvider* provider : metrics_providers_) + provider->OnRecordingEnabled(); + + base::RemoveActionCallback(action_callback_); + action_callback_ = base::Bind(&MetricsService::OnUserAction, + base::Unretained(this)); + base::AddActionCallback(action_callback_); +} + +void MetricsService::DisableRecording() { + DCHECK(IsSingleThreaded()); + + if (recording_state_ == INACTIVE) + return; + recording_state_ = INACTIVE; + + client_->OnRecordingDisabled(); + + base::RemoveActionCallback(action_callback_); + + for (MetricsProvider* provider : metrics_providers_) + provider->OnRecordingDisabled(); + + PushPendingLogsToPersistentStorage(); +} + +bool MetricsService::recording_active() const { + DCHECK(IsSingleThreaded()); + return recording_state_ == ACTIVE; +} + +bool MetricsService::reporting_active() const { + DCHECK(IsSingleThreaded()); + return reporting_active_; +} + +void MetricsService::RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) { + log_manager_.current_log()->RecordHistogramDelta(histogram.histogram_name(), + snapshot); +} + +void MetricsService::InconsistencyDetected( + base::HistogramBase::Inconsistency problem) { + UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowser", + problem, base::HistogramBase::NEVER_EXCEEDED_VALUE); +} + +void MetricsService::UniqueInconsistencyDetected( + base::HistogramBase::Inconsistency problem) { + UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowserUnique", + problem, base::HistogramBase::NEVER_EXCEEDED_VALUE); +} + +void MetricsService::InconsistencyDetectedInLoggedCount(int amount) { + UMA_HISTOGRAM_COUNTS("Histogram.InconsistentSnapshotBrowser", + std::abs(amount)); +} + +void MetricsService::HandleIdleSinceLastTransmission(bool in_idle) { + // If there wasn't a lot of action, maybe the computer was asleep, in which + // case, the log transmissions should have stopped. Here we start them up + // again. + if (!in_idle && idle_since_last_transmission_) + StartSchedulerIfNecessary(); + idle_since_last_transmission_ = in_idle; +} + +void MetricsService::OnApplicationNotIdle() { + if (recording_state_ == ACTIVE) + HandleIdleSinceLastTransmission(false); +} + +void MetricsService::RecordStartOfSessionEnd() { + LogCleanShutdown(); + RecordBooleanPrefValue(prefs::kStabilitySessionEndCompleted, false); +} + +void MetricsService::RecordCompletedSessionEnd() { + LogCleanShutdown(); + RecordBooleanPrefValue(prefs::kStabilitySessionEndCompleted, true); +} + +#if defined(OS_ANDROID) || defined(OS_IOS) +void MetricsService::OnAppEnterBackground() { + scheduler_->Stop(); + + MarkAppCleanShutdownAndCommit(&clean_exit_beacon_, local_state_); + + // At this point, there's no way of knowing when the process will be + // killed, so this has to be treated similar to a shutdown, closing and + // persisting all logs. Unlinke a shutdown, the state is primed to be ready + // to continue logging and uploading if the process does return. + if (recording_active() && state_ >= SENDING_LOGS) { + PushPendingLogsToPersistentStorage(); + // Persisting logs closes the current log, so start recording a new log + // immediately to capture any background work that might be done before the + // process is killed. + OpenNewLog(); + } +} + +void MetricsService::OnAppEnterForeground() { + clean_exit_beacon_.WriteBeaconValue(false); + StartSchedulerIfNecessary(); +} +#else +void MetricsService::LogNeedForCleanShutdown() { + clean_exit_beacon_.WriteBeaconValue(false); + // Redundant setting to be sure we call for a clean shutdown. + clean_shutdown_status_ = NEED_TO_SHUTDOWN; +} +#endif // defined(OS_ANDROID) || defined(OS_IOS) + +// static +void MetricsService::SetExecutionPhase(ExecutionPhase execution_phase, + PrefService* local_state) { + execution_phase_ = execution_phase; + local_state->SetInteger(prefs::kStabilityExecutionPhase, execution_phase_); +} + +void MetricsService::RecordBreakpadRegistration(bool success) { + if (!success) + IncrementPrefValue(prefs::kStabilityBreakpadRegistrationFail); + else + IncrementPrefValue(prefs::kStabilityBreakpadRegistrationSuccess); +} + +void MetricsService::RecordBreakpadHasDebugger(bool has_debugger) { + if (!has_debugger) + IncrementPrefValue(prefs::kStabilityDebuggerNotPresent); + else + IncrementPrefValue(prefs::kStabilityDebuggerPresent); +} + +void MetricsService::ClearSavedStabilityMetrics() { + for (MetricsProvider* provider : metrics_providers_) + provider->ClearSavedStabilityMetrics(); + + // Reset the prefs that are managed by MetricsService/MetricsLog directly. + local_state_->SetInteger(prefs::kStabilityCrashCount, 0); + local_state_->SetInteger(prefs::kStabilityExecutionPhase, + UNINITIALIZED_PHASE); + local_state_->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0); + local_state_->SetInteger(prefs::kStabilityLaunchCount, 0); + local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true); +} + +void MetricsService::PushExternalLog(const std::string& log) { + log_manager_.StoreLog(log, MetricsLog::ONGOING_LOG); +} + +UpdateUsagePrefCallbackType MetricsService::GetDataUseForwardingCallback() { + DCHECK(IsSingleThreaded()); + + if (data_use_tracker_) { + return data_use_tracker_->GetDataUseForwardingCallback( + base::ThreadTaskRunnerHandle::Get()); + } + return UpdateUsagePrefCallbackType(); +} + +//------------------------------------------------------------------------------ +// private methods +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Initialization methods + +void MetricsService::InitializeMetricsState() { + const int64_t buildtime = MetricsLog::GetBuildTime(); + const std::string version = client_->GetVersionString(); + bool version_changed = false; + if (local_state_->GetInt64(prefs::kStabilityStatsBuildTime) != buildtime || + local_state_->GetString(prefs::kStabilityStatsVersion) != version) { + local_state_->SetString(prefs::kStabilityStatsVersion, version); + local_state_->SetInt64(prefs::kStabilityStatsBuildTime, buildtime); + version_changed = true; + } + + log_manager_.LoadPersistedUnsentLogs(); + + session_id_ = local_state_->GetInteger(prefs::kMetricsSessionID); + + if (!clean_exit_beacon_.exited_cleanly()) { + IncrementPrefValue(prefs::kStabilityCrashCount); + // Reset flag, and wait until we call LogNeedForCleanShutdown() before + // monitoring. + clean_exit_beacon_.WriteBeaconValue(true); + } + + bool has_initial_stability_log = false; + if (!clean_exit_beacon_.exited_cleanly() || + ProvidersHaveInitialStabilityMetrics()) { + // TODO(rtenneti): On windows, consider saving/getting execution_phase from + // the registry. + int execution_phase = + local_state_->GetInteger(prefs::kStabilityExecutionPhase); + UMA_HISTOGRAM_SPARSE_SLOWLY("Chrome.Browser.CrashedExecutionPhase", + execution_phase); + + // If the previous session didn't exit cleanly, or if any provider + // explicitly requests it, prepare an initial stability log - + // provided UMA is enabled. + if (state_manager_->IsMetricsReportingEnabled()) + has_initial_stability_log = PrepareInitialStabilityLog(); + } + + // If no initial stability log was generated and there was a version upgrade, + // clear the stability stats from the previous version (so that they don't get + // attributed to the current version). This could otherwise happen due to a + // number of different edge cases, such as if the last version crashed before + // it could save off a system profile or if UMA reporting is disabled (which + // normally results in stats being accumulated). + if (!has_initial_stability_log && version_changed) + ClearSavedStabilityMetrics(); + + // Update session ID. + ++session_id_; + local_state_->SetInteger(prefs::kMetricsSessionID, session_id_); + + // Stability bookkeeping + IncrementPrefValue(prefs::kStabilityLaunchCount); + + DCHECK_EQ(UNINITIALIZED_PHASE, execution_phase_); + SetExecutionPhase(START_METRICS_RECORDING, local_state_); + + if (!local_state_->GetBoolean(prefs::kStabilitySessionEndCompleted)) { + IncrementPrefValue(prefs::kStabilityIncompleteSessionEndCount); + // This is marked false when we get a WM_ENDSESSION. + local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true); + } + + // Call GetUptimes() for the first time, thus allowing all later calls + // to record incremental uptimes accurately. + base::TimeDelta ignored_uptime_parameter; + base::TimeDelta startup_uptime; + GetUptimes(local_state_, &startup_uptime, &ignored_uptime_parameter); + DCHECK_EQ(0, startup_uptime.InMicroseconds()); + // For backwards compatibility, leave this intact in case Omaha is checking + // them. prefs::kStabilityLastTimestampSec may also be useless now. + // TODO(jar): Delete these if they have no uses. + local_state_->SetInt64(prefs::kStabilityLaunchTimeSec, + base::Time::Now().ToTimeT()); + + // Bookkeeping for the uninstall metrics. + IncrementLongPrefsValue(prefs::kUninstallLaunchCount); + + // Kick off the process of saving the state (so the uptime numbers keep + // getting updated) every n minutes. + ScheduleNextStateSave(); +} + +void MetricsService::OnUserAction(const std::string& action) { + if (!ShouldLogEvents()) + return; + + log_manager_.current_log()->RecordUserAction(action); + HandleIdleSinceLastTransmission(false); +} + +void MetricsService::FinishedInitTask() { + DCHECK_EQ(INIT_TASK_SCHEDULED, state_); + state_ = INIT_TASK_DONE; + + // Create the initial log. + if (!initial_metrics_log_.get()) { + initial_metrics_log_ = CreateLog(MetricsLog::ONGOING_LOG); + NotifyOnDidCreateMetricsLog(); + } + + scheduler_->InitTaskComplete(); +} + +void MetricsService::GetUptimes(PrefService* pref, + base::TimeDelta* incremental_uptime, + base::TimeDelta* uptime) { + base::TimeTicks now = base::TimeTicks::Now(); + // If this is the first call, init |first_updated_time_| and + // |last_updated_time_|. + if (last_updated_time_.is_null()) { + first_updated_time_ = now; + last_updated_time_ = now; + } + *incremental_uptime = now - last_updated_time_; + *uptime = now - first_updated_time_; + last_updated_time_ = now; + + const int64_t incremental_time_secs = incremental_uptime->InSeconds(); + if (incremental_time_secs > 0) { + int64_t metrics_uptime = pref->GetInt64(prefs::kUninstallMetricsUptimeSec); + metrics_uptime += incremental_time_secs; + pref->SetInt64(prefs::kUninstallMetricsUptimeSec, metrics_uptime); + } +} + +void MetricsService::NotifyOnDidCreateMetricsLog() { + DCHECK(IsSingleThreaded()); + for (MetricsProvider* provider : metrics_providers_) + provider->OnDidCreateMetricsLog(); +} + +//------------------------------------------------------------------------------ +// State save methods + +void MetricsService::ScheduleNextStateSave() { + state_saver_factory_.InvalidateWeakPtrs(); + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&MetricsService::SaveLocalState, + state_saver_factory_.GetWeakPtr()), + base::TimeDelta::FromMinutes(kSaveStateIntervalMinutes)); +} + +void MetricsService::SaveLocalState() { + RecordCurrentState(local_state_); + + // TODO(jar):110021 Does this run down the batteries???? + ScheduleNextStateSave(); +} + + +//------------------------------------------------------------------------------ +// Recording control methods + +void MetricsService::OpenNewLog() { + DCHECK(!log_manager_.current_log()); + + log_manager_.BeginLoggingWithLog(CreateLog(MetricsLog::ONGOING_LOG)); + NotifyOnDidCreateMetricsLog(); + if (state_ == INITIALIZED) { + // We only need to schedule that run once. + state_ = INIT_TASK_SCHEDULED; + + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::Bind(&MetricsService::StartInitTask, + self_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromSeconds(kInitializationDelaySeconds)); + } +} + +void MetricsService::StartInitTask() { + client_->InitializeSystemProfileMetrics( + base::Bind(&MetricsService::FinishedInitTask, + self_ptr_factory_.GetWeakPtr())); +} + +void MetricsService::CloseCurrentLog() { + if (!log_manager_.current_log()) + return; + + // TODO(jar): Integrate bounds on log recording more consistently, so that we + // can stop recording logs that are too big much sooner. + if (log_manager_.current_log()->num_events() > kEventLimit) { + UMA_HISTOGRAM_COUNTS("UMA.Discarded Log Events", + log_manager_.current_log()->num_events()); + log_manager_.DiscardCurrentLog(); + OpenNewLog(); // Start trivial log to hold our histograms. + } + + // If a persistent allocator is in use, update its internal histograms (such + // as how much memory is being used) before reporting. + base::PersistentHistogramAllocator* allocator = + base::GlobalHistogramAllocator::Get(); + if (allocator) + allocator->UpdateTrackingHistograms(); + + // Put incremental data (histogram deltas, and realtime stats deltas) at the + // end of all log transmissions (initial log handles this separately). + // RecordIncrementalStabilityElements only exists on the derived + // MetricsLog class. + MetricsLog* current_log = log_manager_.current_log(); + DCHECK(current_log); + RecordCurrentEnvironment(current_log); + base::TimeDelta incremental_uptime; + base::TimeDelta uptime; + GetUptimes(local_state_, &incremental_uptime, &uptime); + current_log->RecordStabilityMetrics(metrics_providers_.get(), + incremental_uptime, uptime); + + current_log->RecordGeneralMetrics(metrics_providers_.get()); + RecordCurrentHistograms(); + + log_manager_.FinishCurrentLog(); +} + +void MetricsService::PushPendingLogsToPersistentStorage() { + if (state_ < SENDING_LOGS) + return; // We didn't and still don't have time to get plugin list etc. + + CloseCurrentLog(); + log_manager_.PersistUnsentLogs(); +} + +//------------------------------------------------------------------------------ +// Transmission of logs methods + +void MetricsService::StartSchedulerIfNecessary() { + // Never schedule cutting or uploading of logs in test mode. + if (test_mode_active_) + return; + + // Even if reporting is disabled, the scheduler is needed to trigger the + // creation of the initial log, which must be done in order for any logs to be + // persisted on shutdown or backgrounding. + if (recording_active() && + (reporting_active() || state_ < SENDING_LOGS)) { + scheduler_->Start(); + } +} + +void MetricsService::StartScheduledUpload() { + DCHECK(state_ >= INIT_TASK_DONE); + // If we're getting no notifications, then the log won't have much in it, and + // it's possible the computer is about to go to sleep, so don't upload and + // stop the scheduler. + // If recording has been turned off, the scheduler doesn't need to run. + // If reporting is off, proceed if the initial log hasn't been created, since + // that has to happen in order for logs to be cut and stored when persisting. + // TODO(stuartmorgan): Call Stop() on the scheduler when reporting and/or + // recording are turned off instead of letting it fire and then aborting. + if (idle_since_last_transmission_ || + !recording_active() || + (!reporting_active() && state_ >= SENDING_LOGS)) { + scheduler_->Stop(); + scheduler_->UploadCancelled(); + return; + } + + // If there are unsent logs, send the next one. If not, start the asynchronous + // process of finalizing the current log for upload. + if (state_ == SENDING_LOGS && log_manager_.has_unsent_logs()) { + SendNextLog(); + } else { + // There are no logs left to send, so start creating a new one. + client_->CollectFinalMetricsForLog( + base::Bind(&MetricsService::OnFinalLogInfoCollectionDone, + self_ptr_factory_.GetWeakPtr())); + } +} + +void MetricsService::OnFinalLogInfoCollectionDone() { + // If somehow there is a log upload in progress, we return and hope things + // work out. The scheduler isn't informed since if this happens, the scheduler + // will get a response from the upload. + DCHECK(!log_upload_in_progress_); + if (log_upload_in_progress_) + return; + + // Abort if metrics were turned off during the final info gathering. + if (!recording_active()) { + scheduler_->Stop(); + scheduler_->UploadCancelled(); + return; + } + + if (state_ == INIT_TASK_DONE) { + PrepareInitialMetricsLog(); + } else { + DCHECK_EQ(SENDING_LOGS, state_); + CloseCurrentLog(); + OpenNewLog(); + } + SendNextLog(); +} + +void MetricsService::SendNextLog() { + DCHECK_EQ(SENDING_LOGS, state_); + if (!reporting_active()) { + scheduler_->Stop(); + scheduler_->UploadCancelled(); + return; + } + if (!log_manager_.has_unsent_logs()) { + // Should only get here if serializing the log failed somehow. + // Just tell the scheduler it was uploaded and wait for the next log + // interval. + scheduler_->UploadFinished(true, log_manager_.has_unsent_logs()); + return; + } + if (!log_manager_.has_staged_log()) + log_manager_.StageNextLogForUpload(); + + // Proceed to stage the log for upload if log size satisfies cellular log + // upload constrains. + bool is_cellular_logic = client_->IsUMACellularUploadLogicEnabled(); + if (is_cellular_logic && data_use_tracker_ && + !data_use_tracker_->ShouldUploadLogOnCellular( + log_manager_.staged_log_hash().size())) { + scheduler_->UploadCancelled(); + } else { + SendStagedLog(); + } +} + +bool MetricsService::ProvidersHaveInitialStabilityMetrics() { + // Check whether any metrics provider has initial stability metrics. + for (MetricsProvider* provider : metrics_providers_) { + if (provider->HasInitialStabilityMetrics()) + return true; + } + + return false; +} + +bool MetricsService::PrepareInitialStabilityLog() { + DCHECK_EQ(INITIALIZED, state_); + + scoped_ptr<MetricsLog> initial_stability_log( + CreateLog(MetricsLog::INITIAL_STABILITY_LOG)); + + // Do not call NotifyOnDidCreateMetricsLog here because the stability + // log describes stats from the _previous_ session. + + if (!initial_stability_log->LoadSavedEnvironmentFromPrefs()) + return false; + + log_manager_.PauseCurrentLog(); + log_manager_.BeginLoggingWithLog(std::move(initial_stability_log)); + + // Note: Some stability providers may record stability stats via histograms, + // so this call has to be after BeginLoggingWithLog(). + log_manager_.current_log()->RecordStabilityMetrics( + metrics_providers_.get(), base::TimeDelta(), base::TimeDelta()); + RecordCurrentStabilityHistograms(); + + // Note: RecordGeneralMetrics() intentionally not called since this log is for + // stability stats from a previous session only. + + log_manager_.FinishCurrentLog(); + log_manager_.ResumePausedLog(); + + // Store unsent logs, including the stability log that was just saved, so + // that they're not lost in case of a crash before upload time. + log_manager_.PersistUnsentLogs(); + + return true; +} + +void MetricsService::PrepareInitialMetricsLog() { + DCHECK_EQ(INIT_TASK_DONE, state_); + + RecordCurrentEnvironment(initial_metrics_log_.get()); + base::TimeDelta incremental_uptime; + base::TimeDelta uptime; + GetUptimes(local_state_, &incremental_uptime, &uptime); + + // Histograms only get written to the current log, so make the new log current + // before writing them. + log_manager_.PauseCurrentLog(); + log_manager_.BeginLoggingWithLog(std::move(initial_metrics_log_)); + + // Note: Some stability providers may record stability stats via histograms, + // so this call has to be after BeginLoggingWithLog(). + MetricsLog* current_log = log_manager_.current_log(); + current_log->RecordStabilityMetrics(metrics_providers_.get(), + base::TimeDelta(), base::TimeDelta()); + current_log->RecordGeneralMetrics(metrics_providers_.get()); + RecordCurrentHistograms(); + + log_manager_.FinishCurrentLog(); + log_manager_.ResumePausedLog(); + + // Store unsent logs, including the initial log that was just saved, so + // that they're not lost in case of a crash before upload time. + log_manager_.PersistUnsentLogs(); + + state_ = SENDING_LOGS; +} + +void MetricsService::SendStagedLog() { + DCHECK(log_manager_.has_staged_log()); + if (!log_manager_.has_staged_log()) + return; + + DCHECK(!log_upload_in_progress_); + log_upload_in_progress_ = true; + + if (!ShouldUploadLog()) { + SkipAndDiscardUpload(); + return; + } + + if (!log_uploader_) { + log_uploader_ = client_->CreateUploader( + base::Bind(&MetricsService::OnLogUploadComplete, + self_ptr_factory_.GetWeakPtr())); + } + + const std::string hash = + base::HexEncode(log_manager_.staged_log_hash().data(), + log_manager_.staged_log_hash().size()); + log_uploader_->UploadLog(log_manager_.staged_log(), hash); + + HandleIdleSinceLastTransmission(true); +} + + +void MetricsService::OnLogUploadComplete(int response_code) { + DCHECK_EQ(SENDING_LOGS, state_); + DCHECK(log_upload_in_progress_); + log_upload_in_progress_ = false; + + // Log a histogram to track response success vs. failure rates. + UMA_HISTOGRAM_ENUMERATION("UMA.UploadResponseStatus.Protobuf", + ResponseCodeToStatus(response_code), + NUM_RESPONSE_STATUSES); + + bool upload_succeeded = response_code == 200; + + // Provide boolean for error recovery (allow us to ignore response_code). + bool discard_log = false; + const size_t log_size = log_manager_.staged_log().length(); + if (upload_succeeded) { + UMA_HISTOGRAM_COUNTS_10000("UMA.LogSize.OnSuccess", log_size / 1024); + } else if (log_size > kUploadLogAvoidRetransmitSize) { + UMA_HISTOGRAM_COUNTS("UMA.Large Rejected Log was Discarded", + static_cast<int>(log_size)); + discard_log = true; + } else if (response_code == 400) { + // Bad syntax. Retransmission won't work. + discard_log = true; + } + + if (upload_succeeded || discard_log) { + log_manager_.DiscardStagedLog(); + // Store the updated list to disk now that the removed log is uploaded. + log_manager_.PersistUnsentLogs(); + } + + // Error 400 indicates a problem with the log, not with the server, so + // don't consider that a sign that the server is in trouble. + bool server_is_healthy = upload_succeeded || response_code == 400; + scheduler_->UploadFinished(server_is_healthy, log_manager_.has_unsent_logs()); + + if (server_is_healthy) + client_->OnLogUploadComplete(); +} + +void MetricsService::IncrementPrefValue(const char* path) { + int value = local_state_->GetInteger(path); + local_state_->SetInteger(path, value + 1); +} + +void MetricsService::IncrementLongPrefsValue(const char* path) { + int64_t value = local_state_->GetInt64(path); + local_state_->SetInt64(path, value + 1); +} + +bool MetricsService::UmaMetricsProperlyShutdown() { + CHECK(clean_shutdown_status_ == CLEANLY_SHUTDOWN || + clean_shutdown_status_ == NEED_TO_SHUTDOWN); + return clean_shutdown_status_ == CLEANLY_SHUTDOWN; +} + +void MetricsService::AddSyntheticTrialObserver( + variations::SyntheticTrialObserver* observer) { + synthetic_trial_observer_list_.AddObserver(observer); + if (!synthetic_trial_groups_.empty()) + observer->OnSyntheticTrialsChanged(synthetic_trial_groups_); +} + +void MetricsService::RemoveSyntheticTrialObserver( + variations::SyntheticTrialObserver* observer) { + synthetic_trial_observer_list_.RemoveObserver(observer); +} + +void MetricsService::RegisterSyntheticFieldTrial( + const variations::SyntheticTrialGroup& trial) { + for (size_t i = 0; i < synthetic_trial_groups_.size(); ++i) { + if (synthetic_trial_groups_[i].id.name == trial.id.name) { + if (synthetic_trial_groups_[i].id.group != trial.id.group) { + synthetic_trial_groups_[i].id.group = trial.id.group; + synthetic_trial_groups_[i].start_time = base::TimeTicks::Now(); + NotifySyntheticTrialObservers(); + } + return; + } + } + + variations::SyntheticTrialGroup trial_group = trial; + trial_group.start_time = base::TimeTicks::Now(); + synthetic_trial_groups_.push_back(trial_group); + NotifySyntheticTrialObservers(); +} + +void MetricsService::GetCurrentSyntheticFieldTrialsForTesting( + std::vector<variations::ActiveGroupId>* synthetic_trials) { + GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(), synthetic_trials); +} + +void MetricsService::RegisterMetricsProvider( + scoped_ptr<MetricsProvider> provider) { + DCHECK_EQ(INITIALIZED, state_); + metrics_providers_.push_back(std::move(provider)); +} + +void MetricsService::CheckForClonedInstall( + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + state_manager_->CheckForClonedInstall(task_runner); +} + +void MetricsService::NotifySyntheticTrialObservers() { + FOR_EACH_OBSERVER(variations::SyntheticTrialObserver, + synthetic_trial_observer_list_, + OnSyntheticTrialsChanged(synthetic_trial_groups_)); +} + +void MetricsService::GetSyntheticFieldTrialsOlderThan( + base::TimeTicks time, + std::vector<variations::ActiveGroupId>* synthetic_trials) { + DCHECK(synthetic_trials); + synthetic_trials->clear(); + for (size_t i = 0; i < synthetic_trial_groups_.size(); ++i) { + if (synthetic_trial_groups_[i].start_time <= time) + synthetic_trials->push_back(synthetic_trial_groups_[i].id); + } +} + +scoped_ptr<MetricsLog> MetricsService::CreateLog(MetricsLog::LogType log_type) { + return make_scoped_ptr(new MetricsLog(state_manager_->client_id(), + session_id_, + log_type, + client_, + local_state_)); +} + +void MetricsService::RecordCurrentEnvironment(MetricsLog* log) { + std::vector<variations::ActiveGroupId> synthetic_trials; + GetSyntheticFieldTrialsOlderThan(log->creation_time(), &synthetic_trials); + log->RecordEnvironment(metrics_providers_.get(), synthetic_trials, + GetInstallDate(), GetMetricsReportingEnabledDate()); +} + +void MetricsService::RecordCurrentHistograms() { + DCHECK(log_manager_.current_log()); + histogram_snapshot_manager_.StartDeltas(); + // "true" to the begin() call indicates that StatisticsRecorder should include + // histograms held in persistent storage. + auto end = base::StatisticsRecorder::end(); + for (auto it = base::StatisticsRecorder::begin(true); it != end; ++it) { + if ((*it)->flags() & base::Histogram::kUmaTargetedHistogramFlag) + histogram_snapshot_manager_.PrepareDelta(*it); + } + for (MetricsProvider* provider : metrics_providers_) + provider->RecordHistogramSnapshots(&histogram_snapshot_manager_); + histogram_snapshot_manager_.FinishDeltas(); +} + +void MetricsService::RecordCurrentStabilityHistograms() { + DCHECK(log_manager_.current_log()); + // "true" indicates that StatisticsRecorder should include histograms in + // persistent storage. + histogram_snapshot_manager_.PrepareDeltas( + base::StatisticsRecorder::begin(true), base::StatisticsRecorder::end(), + base::Histogram::kNoFlags, base::Histogram::kUmaStabilityHistogramFlag); +} + +void MetricsService::LogCleanShutdown() { + // Redundant setting to assure that we always reset this value at shutdown + // (and that we don't use some alternate path, and not call LogCleanShutdown). + clean_shutdown_status_ = CLEANLY_SHUTDOWN; + + clean_exit_beacon_.WriteBeaconValue(true); + RecordCurrentState(local_state_); + local_state_->SetInteger(prefs::kStabilityExecutionPhase, + MetricsService::SHUTDOWN_COMPLETE); +} + +bool MetricsService::ShouldLogEvents() { + // We simply don't log events to UMA if there is a single incognito + // session visible. The problem is that we always notify using the original + // profile in order to simplify notification processing. + return !client_->IsOffTheRecordSessionActive(); +} + +void MetricsService::RecordBooleanPrefValue(const char* path, bool value) { + DCHECK(IsSingleThreaded()); + local_state_->SetBoolean(path, value); + RecordCurrentState(local_state_); +} + +void MetricsService::RecordCurrentState(PrefService* pref) { + pref->SetInt64(prefs::kStabilityLastTimestampSec, + base::Time::Now().ToTimeT()); +} + +void MetricsService::SkipAndDiscardUpload() { + log_manager_.DiscardStagedLog(); + scheduler_->UploadCancelled(); + log_upload_in_progress_ = false; +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_service.h b/chromium/components/metrics/metrics_service.h new file mode 100644 index 00000000000..f02df4bcabf --- /dev/null +++ b/chromium/components/metrics/metrics_service.h @@ -0,0 +1,501 @@ +// Copyright 2014 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. + +// This file defines a service that collects information about the user +// experience in order to help improve future versions of the app. + +#ifndef COMPONENTS_METRICS_METRICS_SERVICE_H_ +#define COMPONENTS_METRICS_METRICS_SERVICE_H_ + +#include <stdint.h> + +#include <map> +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_snapshot_manager.h" +#include "base/metrics/user_metrics.h" +#include "base/observer_list.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/clean_exit_beacon.h" +#include "components/metrics/data_use_tracker.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_log_manager.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/net/network_metrics_provider.h" +#include "components/variations/synthetic_trials.h" + +class PrefService; +class PrefRegistrySimple; + +namespace base { +class DictionaryValue; +class HistogramSamples; +class PrefService; +} + +namespace variations { +struct ActiveGroupId; +} + +namespace net { +class URLFetcher; +} + +namespace metrics { + +class MetricsLogUploader; +class MetricsReportingScheduler; +class MetricsServiceAccessor; +class MetricsServiceClient; +class MetricsStateManager; + +// See metrics_service.cc for a detailed description. +class MetricsService : public base::HistogramFlattener { + public: + // The execution phase of the browser. + enum ExecutionPhase { + UNINITIALIZED_PHASE = 0, + START_METRICS_RECORDING = 100, + CREATE_PROFILE = 200, + STARTUP_TIMEBOMB_ARM = 300, + THREAD_WATCHER_START = 400, + MAIN_MESSAGE_LOOP_RUN = 500, + SHUTDOWN_TIMEBOMB_ARM = 600, + SHUTDOWN_COMPLETE = 700, + }; + + // Creates the MetricsService with the given |state_manager|, |client|, and + // |local_state|. Does not take ownership of the paramaters; instead stores + // a weak pointer to each. Caller should ensure that the parameters are valid + // for the lifetime of this class. + MetricsService(MetricsStateManager* state_manager, + MetricsServiceClient* client, + PrefService* local_state); + ~MetricsService() override; + + // Initializes metrics recording state. Updates various bookkeeping values in + // prefs and sets up the scheduler. This is a separate function rather than + // being done by the constructor so that field trials could be created before + // this is run. + void InitializeMetricsRecordingState(); + + // Starts the metrics system, turning on recording and uploading of metrics. + // Should be called when starting up with metrics enabled, or when metrics + // are turned on. + void Start(); + + // Starts the metrics system in a special test-only mode. Metrics won't ever + // be uploaded or persisted in this mode, but metrics will be recorded in + // memory. + void StartRecordingForTests(); + + // Shuts down the metrics system. Should be called at shutdown, or if metrics + // are turned off. + void Stop(); + + // Enable/disable transmission of accumulated logs and crash reports (dumps). + // Calling Start() automatically enables reporting, but sending is + // asyncronous so this can be called immediately after Start() to prevent + // any uploading. + void EnableReporting(); + void DisableReporting(); + + // Returns the client ID for this client, or the empty string if metrics + // recording is not currently running. + std::string GetClientId(); + + // Returns the install date of the application, in seconds since the epoch. + int64_t GetInstallDate(); + + // Returns the date at which the current metrics client ID was created as + // an int64_t containing seconds since the epoch. + int64_t GetMetricsReportingEnabledDate(); + + // Returns true if the last session exited cleanly. + bool WasLastShutdownClean() const; + + // Returns the preferred entropy provider used to seed persistent activities + // based on whether or not metrics reporting will be permitted on this client. + // + // If metrics reporting is enabled, this method returns an entropy provider + // that has a high source of entropy, partially based on the client ID. + // Otherwise, it returns an entropy provider that is based on a low entropy + // source. + scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider(); + + // At startup, prefs needs to be called with a list of all the pref names and + // types we'll be using. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // HistogramFlattener: + void RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) override; + void InconsistencyDetected( + base::HistogramBase::Inconsistency problem) override; + void UniqueInconsistencyDetected( + base::HistogramBase::Inconsistency problem) override; + void InconsistencyDetectedInLoggedCount(int amount) override; + + // This should be called when the application is not idle, i.e. the user seems + // to be interacting with the application. + void OnApplicationNotIdle(); + + // Invoked when we get a WM_SESSIONEND. This places a value in prefs that is + // reset when RecordCompletedSessionEnd is invoked. + void RecordStartOfSessionEnd(); + + // This should be called when the application is shutting down. It records + // that session end was successful. + void RecordCompletedSessionEnd(); + +#if defined(OS_ANDROID) || defined(OS_IOS) + // Called when the application is going into background mode. + void OnAppEnterBackground(); + + // Called when the application is coming out of background mode. + void OnAppEnterForeground(); +#else + // Set the dirty flag, which will require a later call to LogCleanShutdown(). + void LogNeedForCleanShutdown(); +#endif // defined(OS_ANDROID) || defined(OS_IOS) + + static void SetExecutionPhase(ExecutionPhase execution_phase, + PrefService* local_state); + + // Saves in the preferences if the crash report registration was successful. + // This count is eventually send via UMA logs. + void RecordBreakpadRegistration(bool success); + + // Saves in the preferences if the browser is running under a debugger. + // This count is eventually send via UMA logs. + void RecordBreakpadHasDebugger(bool has_debugger); + + bool recording_active() const; + bool reporting_active() const; + + // Redundant test to ensure that we are notified of a clean exit. + // This value should be true when process has completed shutdown. + static bool UmaMetricsProperlyShutdown(); + + // Public accessor that returns the list of synthetic field trials. It must + // only be used for testing. + void GetCurrentSyntheticFieldTrialsForTesting( + std::vector<variations::ActiveGroupId>* synthetic_trials); + + // Adds an observer to be notified when the synthetic trials list changes. + void AddSyntheticTrialObserver(variations::SyntheticTrialObserver* observer); + + // Removes an existing observer of synthetic trials list changes. + void RemoveSyntheticTrialObserver( + variations::SyntheticTrialObserver* observer); + + // Register the specified |provider| to provide additional metrics into the + // UMA log. Should be called during MetricsService initialization only. + void RegisterMetricsProvider(scoped_ptr<MetricsProvider> provider); + + // Check if this install was cloned or imaged from another machine. If a + // clone is detected, reset the client id and low entropy source. This + // should not be called more than once. + void CheckForClonedInstall( + scoped_refptr<base::SingleThreadTaskRunner> task_runner); + + // Clears the stability metrics that are saved in local state. + void ClearSavedStabilityMetrics(); + + // Pushes a log that has been generated by an external component. + void PushExternalLog(const std::string& log); + + // Returns a callback to data use pref updating function which can be called + // from any thread, but this function should be called on UI thread. + UpdateUsagePrefCallbackType GetDataUseForwardingCallback(); + + protected: + // Exposed for testing. + MetricsLogManager* log_manager() { return &log_manager_; } + + private: + friend class MetricsServiceAccessor; + + // The MetricsService has a lifecycle that is stored as a state. + // See metrics_service.cc for description of this lifecycle. + enum State { + INITIALIZED, // Constructor was called. + INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish. + INIT_TASK_DONE, // Waiting for timer to send initial log. + SENDING_LOGS, // Sending logs an creating new ones when we run out. + }; + + enum ShutdownCleanliness { + CLEANLY_SHUTDOWN = 0xdeadbeef, + NEED_TO_SHUTDOWN = ~CLEANLY_SHUTDOWN + }; + + // The current state of recording for the MetricsService. The state is UNSET + // until set to something else, at which point it remains INACTIVE or ACTIVE + // for the lifetime of the object. + enum RecordingState { + INACTIVE, + ACTIVE, + UNSET + }; + + typedef std::vector<variations::SyntheticTrialGroup> SyntheticTrialGroups; + + // Registers a field trial name and group to be used to annotate a UMA report + // with a particular Chrome configuration state. A UMA report will be + // annotated with this trial group if and only if all events in the report + // were created after the trial is registered. Only one group name may be + // registered at a time for a given trial_name. Only the last group name that + // is registered for a given trial name will be recorded. The values passed + // in must not correspond to any real field trial in the code. + void RegisterSyntheticFieldTrial( + const variations::SyntheticTrialGroup& trial_group); + + // Calls into the client to initialize some system profile metrics. + void StartInitTask(); + + // Callback that moves the state to INIT_TASK_DONE. When this is called, the + // state should be INIT_TASK_SCHEDULED. + void FinishedInitTask(); + + void OnUserAction(const std::string& action); + + // Get the amount of uptime since this process started and since the last + // call to this function. Also updates the cumulative uptime metric (stored + // as a pref) for uninstall. Uptimes are measured using TimeTicks, which + // guarantees that it is monotonic and does not jump if the user changes + // his/her clock. The TimeTicks implementation also makes the clock not + // count time the computer is suspended. + void GetUptimes(PrefService* pref, + base::TimeDelta* incremental_uptime, + base::TimeDelta* uptime); + + // Turns recording on or off. + // DisableRecording() also forces a persistent save of logging state (if + // anything has been recorded, or transmitted). + void EnableRecording(); + void DisableRecording(); + + // If in_idle is true, sets idle_since_last_transmission to true. + // If in_idle is false and idle_since_last_transmission_ is true, sets + // idle_since_last_transmission to false and starts the timer (provided + // starting the timer is permitted). + void HandleIdleSinceLastTransmission(bool in_idle); + + // Set up client ID, session ID, etc. + void InitializeMetricsState(); + + // Notifies providers when a new metrics log is created. + void NotifyOnDidCreateMetricsLog(); + + // Schedule the next save of LocalState information. This is called + // automatically by the task that performs each save to schedule the next one. + void ScheduleNextStateSave(); + + // Save the LocalState information immediately. This should not be called by + // anybody other than the scheduler to avoid doing too many writes. When you + // make a change, call ScheduleNextStateSave() instead. + void SaveLocalState(); + + // Opens a new log for recording user experience metrics. + void OpenNewLog(); + + // Closes out the current log after adding any last information. + void CloseCurrentLog(); + + // Pushes the text of the current and staged logs into persistent storage. + // Called when Chrome shuts down. + void PushPendingLogsToPersistentStorage(); + + // Ensures that scheduler is running, assuming the current settings are such + // that metrics should be reported. If not, this is a no-op. + void StartSchedulerIfNecessary(); + + // Starts the process of uploading metrics data. + void StartScheduledUpload(); + + // Called by the client via a callback when final log info collection is + // complete. + void OnFinalLogInfoCollectionDone(); + + // If recording is enabled, begins uploading the next completed log from + // the log manager, staging it if necessary. + void SendNextLog(); + + // Returns true if any of the registered metrics providers have critical + // stability metrics to report in an initial stability log. + bool ProvidersHaveInitialStabilityMetrics(); + + // Prepares the initial stability log, which is only logged when the previous + // run of Chrome crashed. This log contains any stability metrics left over + // from that previous run, and only these stability metrics. It uses the + // system profile from the previous session. Returns true if a log was + // created. + bool PrepareInitialStabilityLog(); + + // Prepares the initial metrics log, which includes startup histograms and + // profiler data, as well as incremental stability-related metrics. + void PrepareInitialMetricsLog(); + + // Uploads the currently staged log (which must be non-null). + void SendStagedLog(); + + // Called after transmission completes (either successfully or with failure). + void OnLogUploadComplete(int response_code); + + // Reads, increments and then sets the specified integer preference. + void IncrementPrefValue(const char* path); + + // Reads, increments and then sets the specified long preference that is + // stored as a string. + void IncrementLongPrefsValue(const char* path); + + // Records that the browser was shut down cleanly. + void LogCleanShutdown(); + + // Records state that should be periodically saved, like uptime and + // buffered plugin stability statistics. + void RecordCurrentState(PrefService* pref); + + // Checks whether events should currently be logged. + bool ShouldLogEvents(); + + // Sets the value of the specified path in prefs and schedules a save. + void RecordBooleanPrefValue(const char* path, bool value); + + // Notifies observers on a synthetic trial list change. + void NotifySyntheticTrialObservers(); + + // Returns a list of synthetic field trials that are older than |time|. + void GetSyntheticFieldTrialsOlderThan( + base::TimeTicks time, + std::vector<variations::ActiveGroupId>* synthetic_trials); + + // Creates a new MetricsLog instance with the given |log_type|. + scoped_ptr<MetricsLog> CreateLog(MetricsLog::LogType log_type); + + // Records the current environment (system profile) in |log|. + void RecordCurrentEnvironment(MetricsLog* log); + + // Record complete list of histograms into the current log. + // Called when we close a log. + void RecordCurrentHistograms(); + + // Record complete list of stability histograms into the current log, + // i.e., histograms with the |kUmaStabilityHistogramFlag| flag set. + void RecordCurrentStabilityHistograms(); + + // Skips staged upload and discards the log. Used in case of unsuccessful + // upload or intentional sampling of logs. + void SkipAndDiscardUpload(); + + // Manager for the various in-flight logs. + MetricsLogManager log_manager_; + + // |histogram_snapshot_manager_| prepares histogram deltas for transmission. + base::HistogramSnapshotManager histogram_snapshot_manager_; + + // Used to manage various metrics reporting state prefs, such as client id, + // low entropy source and whether metrics reporting is enabled. Weak pointer. + MetricsStateManager* const state_manager_; + + // Used to interact with the embedder. Weak pointer; must outlive |this| + // instance. + MetricsServiceClient* const client_; + + // Registered metrics providers. + ScopedVector<MetricsProvider> metrics_providers_; + + PrefService* local_state_; + + CleanExitBeacon clean_exit_beacon_; + + base::ActionCallback action_callback_; + + // Indicate whether recording and reporting are currently happening. + // These should not be set directly, but by calling SetRecording and + // SetReporting. + RecordingState recording_state_; + bool reporting_active_; + + // Indicate whether test mode is enabled, where the initial log should never + // be cut, and logs are neither persisted nor uploaded. + bool test_mode_active_; + + // The progression of states made by the browser are recorded in the following + // state. + State state_; + + // The initial metrics log, used to record startup metrics (histograms and + // profiler data). Note that if a crash occurred in the previous session, an + // initial stability log may be sent before this. + scoped_ptr<MetricsLog> initial_metrics_log_; + + // Instance of the helper class for uploading logs. + scoped_ptr<MetricsLogUploader> log_uploader_; + + // Whether there is a current log upload in progress. + bool log_upload_in_progress_; + + // Whether the MetricsService object has received any notifications since + // the last time a transmission was sent. + bool idle_since_last_transmission_; + + // A number that identifies the how many times the app has been launched. + int session_id_; + + // The scheduler for determining when uploads should happen. + scoped_ptr<MetricsReportingScheduler> scheduler_; + + // Stores the time of the first call to |GetUptimes()|. + base::TimeTicks first_updated_time_; + + // Stores the time of the last call to |GetUptimes()|. + base::TimeTicks last_updated_time_; + + // Field trial groups that map to Chrome configuration states. + SyntheticTrialGroups synthetic_trial_groups_; + + // List of observers of |synthetic_trial_groups_| changes. + base::ObserverList<variations::SyntheticTrialObserver> + synthetic_trial_observer_list_; + + // Execution phase the browser is in. + static ExecutionPhase execution_phase_; + + // Redundant marker to check that we completed our shutdown, and set the + // exited-cleanly bit in the prefs. + static ShutdownCleanliness clean_shutdown_status_; + + FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, IsPluginProcess); + FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, + PermutedEntropyCacheClearedWhenLowEntropyReset); + FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, RegisterSyntheticTrial); + + // Pointer used for obtaining data use pref updater callback on above layers. + scoped_ptr<DataUseTracker> data_use_tracker_; + + // Weak pointers factory used to post task on different threads. All weak + // pointers managed by this factory have the same lifetime as MetricsService. + base::WeakPtrFactory<MetricsService> self_ptr_factory_; + + // Weak pointers factory used for saving state. All weak pointers managed by + // this factory are invalidated in ScheduleNextStateSave. + base::WeakPtrFactory<MetricsService> state_saver_factory_; + + DISALLOW_COPY_AND_ASSIGN(MetricsService); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SERVICE_H_ diff --git a/chromium/components/metrics/metrics_service_accessor.cc b/chromium/components/metrics/metrics_service_accessor.cc new file mode 100644 index 00000000000..ac04ba9884d --- /dev/null +++ b/chromium/components/metrics/metrics_service_accessor.cc @@ -0,0 +1,70 @@ +// Copyright 2014 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 "components/metrics/metrics_service_accessor.h" + +#include "base/base_switches.h" +#include "base/command_line.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_service.h" +#include "components/prefs/pref_service.h" +#include "components/variations/metrics_util.h" + +namespace metrics { + +// static +bool MetricsServiceAccessor::IsMetricsReportingEnabled( + PrefService* pref_service) { + return IsMetricsReportingEnabledWithPrefValue( + pref_service->GetBoolean(prefs::kMetricsReportingEnabled)); +} + +// static +bool MetricsServiceAccessor::IsMetricsReportingEnabledWithPrefValue( + bool enabled_in_prefs) { +#if defined(GOOGLE_CHROME_BUILD) + // In official builds, disable metrics when reporting field trials are + // forced; otherwise, use the value of the user's preference to determine + // whether to enable metrics reporting. + return !base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kForceFieldTrials) && + enabled_in_prefs; +#else + // In non-official builds, disable metrics reporting completely. + return false; +#endif // defined(GOOGLE_CHROME_BUILD) +} + +// static +bool MetricsServiceAccessor::RegisterSyntheticFieldTrial( + MetricsService* metrics_service, + const std::string& trial_name, + const std::string& group_name) { + return RegisterSyntheticFieldTrialWithNameAndGroupHash( + metrics_service, HashName(trial_name), HashName(group_name)); +} + +// static +bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameHash( + MetricsService* metrics_service, + uint32_t trial_name_hash, + const std::string& group_name) { + return RegisterSyntheticFieldTrialWithNameAndGroupHash( + metrics_service, trial_name_hash, HashName(group_name)); +} + +// static +bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameAndGroupHash( + MetricsService* metrics_service, + uint32_t trial_name_hash, + uint32_t group_name_hash) { + if (!metrics_service) + return false; + + variations::SyntheticTrialGroup trial_group(trial_name_hash, group_name_hash); + metrics_service->RegisterSyntheticFieldTrial(trial_group); + return true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_service_accessor.h b/chromium/components/metrics/metrics_service_accessor.h new file mode 100644 index 00000000000..8d34675b5fe --- /dev/null +++ b/chromium/components/metrics/metrics_service_accessor.h @@ -0,0 +1,77 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_ +#define COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_ + +#include <stdint.h> +#include <string> + +#include "base/macros.h" + +class PrefService; + +namespace metrics { + +class MetricsService; + +// This class limits and documents access to metrics service helper methods. +// These methods are protected so each user has to inherit own program-specific +// specialization and enable access there by declaring friends. +class MetricsServiceAccessor { + protected: + // Constructor declared as protected to enable inheritance. Descendants should + // disallow instantiation. + MetricsServiceAccessor() {} + + // Returns whether metrics reporting is enabled, using the value of the + // kMetricsReportingEnabled pref in |pref_service| to determine whether user + // has enabled reporting. + // NOTE: This method currently does not return the correct value on ChromeOS + // and Android due to http://crbug.com/362192 and http://crbug.com/532084. See + // ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled(). + static bool IsMetricsReportingEnabled(PrefService* pref_service); + + // Returns whether metrics reporting is enabled, using the value of + // |enabled_in_prefs| to determine whether the user has enabled reporting. + // Exists because kMetricsReportingEnabled is currently not used on all + // platforms. + // TODO(gayane): Consolidate metric prefs on all platforms and eliminate this + // method. http://crbug.com/362192, http://crbug.com/532084 + static bool IsMetricsReportingEnabledWithPrefValue(bool enabled_in_prefs); + + // Registers a field trial name and group with |metrics_service| (if not + // null), to be used to annotate a UMA report with a particular configuration + // state. A UMA report will be annotated with this trial group if and only if + // all events in the report were created after the trial is registered. Only + // one group name may be registered at a time for a given trial name. Only the + // last group name that is registered for a given trial name will be recorded. + // The values passed in must not correspond to any real field trial in the + // code. Returns true on success. + // See the comment on MetricsService::RegisterSyntheticFieldTrial for details. + static bool RegisterSyntheticFieldTrial(MetricsService* metrics_service, + const std::string& trial_name, + const std::string& group_name); + + // Same as RegisterSyntheticFieldTrial above, but takes in the trial name as a + // hash rather than computing the hash from the string. + static bool RegisterSyntheticFieldTrialWithNameHash( + MetricsService* metrics_service, + uint32_t trial_name_hash, + const std::string& group_name); + + // Same as RegisterSyntheticFieldTrial above, but takes in the trial and group + // names as hashes rather than computing those hashes from the strings. + static bool RegisterSyntheticFieldTrialWithNameAndGroupHash( + MetricsService* metrics_service, + uint32_t trial_name_hash, + uint32_t group_name_hash); + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsServiceAccessor); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_ diff --git a/chromium/components/metrics/metrics_service_client.cc b/chromium/components/metrics/metrics_service_client.cc new file mode 100644 index 00000000000..cd5b53ca802 --- /dev/null +++ b/chromium/components/metrics/metrics_service_client.cc @@ -0,0 +1,26 @@ +// Copyright 2014 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 "components/metrics/metrics_service_client.h" + +namespace metrics { + +base::string16 MetricsServiceClient::GetRegistryBackupKey() { + return base::string16(); +} + +bool MetricsServiceClient::IsReportingPolicyManaged() { + return false; +} + +MetricsServiceClient::EnableMetricsDefault +MetricsServiceClient::GetDefaultOptIn() { + return DEFAULT_UNKNOWN; +} + +bool MetricsServiceClient::IsUMACellularUploadLogicEnabled() { + return false; +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_service_client.h b/chromium/components/metrics/metrics_service_client.h new file mode 100644 index 00000000000..43c5d136ca8 --- /dev/null +++ b/chromium/components/metrics/metrics_service_client.h @@ -0,0 +1,127 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_ +#define COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_ + +#include <stdint.h> +#include <string> + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/time/time.h" +#include "components/metrics/proto/system_profile.pb.h" + +namespace base { +class FilePath; +} + +namespace metrics { + +class MetricsLogUploader; +class MetricsService; + +// An abstraction of operations that depend on the embedder's (e.g. Chrome) +// environment. +class MetricsServiceClient { + public: + // Default value of the enable metrics recording. This relates to the state of + // the enable checkbox shown on first-run. This enum is used to store values + // in a pref, and shouldn't be renumbered. + enum EnableMetricsDefault { + // We only record the value during first-run. The default of existing + // installs is considered unknown. + DEFAULT_UNKNOWN, + // The first-run checkbox was unchecked by default. + OPT_IN, + // The first-run checkbox was checked by default. + OPT_OUT, + }; + + virtual ~MetricsServiceClient() {} + + // Returns the MetricsService instance that this client is associated with. + // With the exception of testing contexts, the returned instance must be valid + // for the lifetime of this object (typically, the embedder's client + // implementation will own the MetricsService instance being returned). + virtual MetricsService* GetMetricsService() = 0; + + // Registers the client id with other services (e.g. crash reporting), called + // when metrics recording gets enabled. + virtual void SetMetricsClientId(const std::string& client_id) = 0; + + // Notifies the client that recording is disabled, so that other services + // (such as crash reporting) can clear any association with metrics. + virtual void OnRecordingDisabled() = 0; + + // Whether there's an "off the record" (aka "Incognito") session active. + virtual bool IsOffTheRecordSessionActive() = 0; + + // Returns the product value to use in uploaded reports, which will be used to + // set the ChromeUserMetricsExtension.product field. See comments on that + // field on why it's an int32_t rather than an enum. + virtual int32_t GetProduct() = 0; + + // Returns the current application locale (e.g. "en-US"). + virtual std::string GetApplicationLocale() = 0; + + // Retrieves the brand code string associated with the install, returning + // false if no brand code is available. + virtual bool GetBrand(std::string* brand_code) = 0; + + // Returns the release channel (e.g. stable, beta, etc) of the application. + virtual SystemProfileProto::Channel GetChannel() = 0; + + // Returns the version of the application as a string. + virtual std::string GetVersionString() = 0; + + // Called by the metrics service when a log has been uploaded. + virtual void OnLogUploadComplete() = 0; + + // Gathers metrics that will be filled into the system profile protobuf, + // calling |done_callback| when complete. + virtual void InitializeSystemProfileMetrics( + const base::Closure& done_callback) = 0; + + // Called prior to a metrics log being closed, allowing the client to collect + // extra histograms that will go in that log. Asynchronous API - the client + // implementation should call |done_callback| when complete. + virtual void CollectFinalMetricsForLog( + const base::Closure& done_callback) = 0; + + // Creates a MetricsLogUploader with the specified parameters (see comments on + // MetricsLogUploader for details). + virtual scoped_ptr<MetricsLogUploader> CreateUploader( + const base::Callback<void(int)>& on_upload_complete) = 0; + + // Returns the standard interval between upload attempts. + virtual base::TimeDelta GetStandardUploadInterval() = 0; + + // Returns the name of a key under HKEY_CURRENT_USER that can be used to store + // backups of metrics data. Unused except on Windows. + virtual base::string16 GetRegistryBackupKey(); + + // Called on plugin loading errors. + virtual void OnPluginLoadingError(const base::FilePath& plugin_path) {} + + // Called on renderer crashes in some embedders (e.g., those that do not use + // //content and thus do not have //content's notification system available + // as a mechanism for observing renderer crashes). + virtual void OnRendererProcessCrash() {} + + // Returns whether metrics reporting is managed by policy. + virtual bool IsReportingPolicyManaged(); + + // Gets information about the default value for the enable metrics reporting + // checkbox shown during first-run. + virtual EnableMetricsDefault GetDefaultOptIn(); + + // Returns whether cellular logic is enabled for metrics reporting. + virtual bool IsUMACellularUploadLogicEnabled(); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_ diff --git a/chromium/components/metrics/metrics_service_unittest.cc b/chromium/components/metrics/metrics_service_unittest.cc new file mode 100644 index 00000000000..4a8aea770e9 --- /dev/null +++ b/chromium/components/metrics/metrics_service_unittest.cc @@ -0,0 +1,413 @@ +// Copyright 2014 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 "components/metrics/metrics_service.h" + +#include <stdint.h> + +#include <string> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/metrics_hashes.h" +#include "base/metrics/statistics_recorder.h" +#include "base/threading/platform_thread.h" +#include "components/metrics/client_info.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/metrics/test_metrics_provider.h" +#include "components/metrics/test_metrics_service_client.h" +#include "components/prefs/testing_pref_service.h" +#include "components/variations/metrics_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/zlib/google/compression_utils.h" + +namespace metrics { + +namespace { + +void StoreNoClientInfoBackup(const ClientInfo& /* client_info */) { +} + +scoped_ptr<ClientInfo> ReturnNoBackup() { + return scoped_ptr<ClientInfo>(); +} + +class TestMetricsService : public MetricsService { + public: + TestMetricsService(MetricsStateManager* state_manager, + MetricsServiceClient* client, + PrefService* local_state) + : MetricsService(state_manager, client, local_state) {} + ~TestMetricsService() override {} + + using MetricsService::log_manager; + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsService); +}; + +class TestMetricsLog : public MetricsLog { + public: + TestMetricsLog(const std::string& client_id, + int session_id, + MetricsServiceClient* client, + PrefService* local_state) + : MetricsLog(client_id, + session_id, + MetricsLog::ONGOING_LOG, + client, + local_state) {} + + ~TestMetricsLog() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsLog); +}; + +class MetricsServiceTest : public testing::Test { + public: + MetricsServiceTest() : is_metrics_reporting_enabled_(false) { + MetricsService::RegisterPrefs(testing_local_state_.registry()); + metrics_state_manager_ = MetricsStateManager::Create( + GetLocalState(), + base::Bind(&MetricsServiceTest::is_metrics_reporting_enabled, + base::Unretained(this)), + base::Bind(&StoreNoClientInfoBackup), + base::Bind(&ReturnNoBackup)); + } + + ~MetricsServiceTest() override { + MetricsService::SetExecutionPhase(MetricsService::UNINITIALIZED_PHASE, + GetLocalState()); + } + + MetricsStateManager* GetMetricsStateManager() { + return metrics_state_manager_.get(); + } + + PrefService* GetLocalState() { return &testing_local_state_; } + + // Sets metrics reporting as enabled for testing. + void EnableMetricsReporting() { + is_metrics_reporting_enabled_ = true; + } + + // Waits until base::TimeTicks::Now() no longer equals |value|. This should + // take between 1-15ms per the documented resolution of base::TimeTicks. + void WaitUntilTimeChanges(const base::TimeTicks& value) { + while (base::TimeTicks::Now() == value) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1)); + } + } + + // Returns true if there is a synthetic trial in the given vector that matches + // the given trial name and trial group; returns false otherwise. + bool HasSyntheticTrial( + const std::vector<variations::ActiveGroupId>& synthetic_trials, + const std::string& trial_name, + const std::string& trial_group) { + uint32_t trial_name_hash = HashName(trial_name); + uint32_t trial_group_hash = HashName(trial_group); + for (const variations::ActiveGroupId& trial : synthetic_trials) { + if (trial.name == trial_name_hash && trial.group == trial_group_hash) + return true; + } + return false; + } + + // Finds a histogram with the specified |name_hash| in |histograms|. + const base::HistogramBase* FindHistogram( + const base::StatisticsRecorder::Histograms& histograms, + uint64_t name_hash) { + for (const base::HistogramBase* histogram : histograms) { + if (name_hash == base::HashMetricName(histogram->histogram_name())) + return histogram; + } + return nullptr; + } + + // Checks whether |uma_log| contains any histograms that are not flagged + // with kUmaStabilityHistogramFlag. Stability logs should only contain such + // histograms. + void CheckForNonStabilityHistograms( + const ChromeUserMetricsExtension& uma_log) { + const int kStabilityFlags = base::HistogramBase::kUmaStabilityHistogramFlag; + base::StatisticsRecorder::Histograms histograms; + base::StatisticsRecorder::GetHistograms(&histograms); + for (int i = 0; i < uma_log.histogram_event_size(); ++i) { + const uint64_t hash = uma_log.histogram_event(i).name_hash(); + + const base::HistogramBase* histogram = FindHistogram(histograms, hash); + EXPECT_TRUE(histogram) << hash; + + EXPECT_EQ(kStabilityFlags, histogram->flags() & kStabilityFlags) << hash; + } + } + + private: + bool is_metrics_reporting_enabled() const { + return is_metrics_reporting_enabled_; + } + + bool is_metrics_reporting_enabled_; + TestingPrefServiceSimple testing_local_state_; + scoped_ptr<MetricsStateManager> metrics_state_manager_; + base::MessageLoop message_loop; + + DISALLOW_COPY_AND_ASSIGN(MetricsServiceTest); +}; + +} // namespace + +TEST_F(MetricsServiceTest, InitialStabilityLogAfterCleanShutDown) { + EnableMetricsReporting(); + GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true); + + TestMetricsServiceClient client; + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + + TestMetricsProvider* test_provider = new TestMetricsProvider(); + service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider)); + + service.InitializeMetricsRecordingState(); + // No initial stability log should be generated. + EXPECT_FALSE(service.log_manager()->has_unsent_logs()); + EXPECT_FALSE(service.log_manager()->has_staged_log()); + + // The test provider should not have been called upon to provide initial + // stability nor regular stability metrics. + EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called()); + EXPECT_FALSE(test_provider->provide_stability_metrics_called()); +} + +TEST_F(MetricsServiceTest, InitialStabilityLogAtProviderRequest) { + EnableMetricsReporting(); + + // Save an existing system profile to prefs, to correspond to what would be + // saved from a previous session. + TestMetricsServiceClient client; + TestMetricsLog log("client", 1, &client, GetLocalState()); + log.RecordEnvironment(std::vector<MetricsProvider*>(), + std::vector<variations::ActiveGroupId>(), 0, 0); + + // Record stability build time and version from previous session, so that + // stability metrics (including exited cleanly flag) won't be cleared. + GetLocalState()->SetInt64(prefs::kStabilityStatsBuildTime, + MetricsLog::GetBuildTime()); + GetLocalState()->SetString(prefs::kStabilityStatsVersion, + client.GetVersionString()); + + // Set the clean exit flag, as that will otherwise cause a stabilty + // log to be produced, irrespective provider requests. + GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true); + + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + // Add a metrics provider that requests a stability log. + TestMetricsProvider* test_provider = new TestMetricsProvider(); + test_provider->set_has_initial_stability_metrics(true); + service.RegisterMetricsProvider( + scoped_ptr<MetricsProvider>(test_provider)); + + service.InitializeMetricsRecordingState(); + + // The initial stability log should be generated and persisted in unsent logs. + MetricsLogManager* log_manager = service.log_manager(); + EXPECT_TRUE(log_manager->has_unsent_logs()); + EXPECT_FALSE(log_manager->has_staged_log()); + + // The test provider should have been called upon to provide initial + // stability and regular stability metrics. + EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called()); + EXPECT_TRUE(test_provider->provide_stability_metrics_called()); + + // Stage the log and retrieve it. + log_manager->StageNextLogForUpload(); + EXPECT_TRUE(log_manager->has_staged_log()); + + std::string uncompressed_log; + EXPECT_TRUE(compression::GzipUncompress(log_manager->staged_log(), + &uncompressed_log)); + + ChromeUserMetricsExtension uma_log; + EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log)); + + EXPECT_TRUE(uma_log.has_client_id()); + EXPECT_TRUE(uma_log.has_session_id()); + EXPECT_TRUE(uma_log.has_system_profile()); + EXPECT_EQ(0, uma_log.user_action_event_size()); + EXPECT_EQ(0, uma_log.omnibox_event_size()); + EXPECT_EQ(0, uma_log.profiler_event_size()); + EXPECT_EQ(0, uma_log.perf_data_size()); + CheckForNonStabilityHistograms(uma_log); + + // As there wasn't an unclean shutdown, this log has zero crash count. + EXPECT_EQ(0, uma_log.system_profile().stability().crash_count()); +} + +TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) { + EnableMetricsReporting(); + GetLocalState()->ClearPref(prefs::kStabilityExitedCleanly); + + // Set up prefs to simulate restarting after a crash. + + // Save an existing system profile to prefs, to correspond to what would be + // saved from a previous session. + TestMetricsServiceClient client; + TestMetricsLog log("client", 1, &client, GetLocalState()); + log.RecordEnvironment(std::vector<MetricsProvider*>(), + std::vector<variations::ActiveGroupId>(), 0, 0); + + // Record stability build time and version from previous session, so that + // stability metrics (including exited cleanly flag) won't be cleared. + GetLocalState()->SetInt64(prefs::kStabilityStatsBuildTime, + MetricsLog::GetBuildTime()); + GetLocalState()->SetString(prefs::kStabilityStatsVersion, + client.GetVersionString()); + + GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, false); + + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + // Add a provider. + TestMetricsProvider* test_provider = new TestMetricsProvider(); + service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider)); + service.InitializeMetricsRecordingState(); + + // The initial stability log should be generated and persisted in unsent logs. + MetricsLogManager* log_manager = service.log_manager(); + EXPECT_TRUE(log_manager->has_unsent_logs()); + EXPECT_FALSE(log_manager->has_staged_log()); + + // The test provider should have been called upon to provide initial + // stability and regular stability metrics. + EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called()); + EXPECT_TRUE(test_provider->provide_stability_metrics_called()); + + // Stage the log and retrieve it. + log_manager->StageNextLogForUpload(); + EXPECT_TRUE(log_manager->has_staged_log()); + + std::string uncompressed_log; + EXPECT_TRUE(compression::GzipUncompress(log_manager->staged_log(), + &uncompressed_log)); + + ChromeUserMetricsExtension uma_log; + EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log)); + + EXPECT_TRUE(uma_log.has_client_id()); + EXPECT_TRUE(uma_log.has_session_id()); + EXPECT_TRUE(uma_log.has_system_profile()); + EXPECT_EQ(0, uma_log.user_action_event_size()); + EXPECT_EQ(0, uma_log.omnibox_event_size()); + EXPECT_EQ(0, uma_log.profiler_event_size()); + EXPECT_EQ(0, uma_log.perf_data_size()); + CheckForNonStabilityHistograms(uma_log); + + EXPECT_EQ(1, uma_log.system_profile().stability().crash_count()); +} + +TEST_F(MetricsServiceTest, RegisterSyntheticTrial) { + TestMetricsServiceClient client; + MetricsService service(GetMetricsStateManager(), &client, GetLocalState()); + + // Add two synthetic trials and confirm that they show up in the list. + variations::SyntheticTrialGroup trial1(HashName("TestTrial1"), + HashName("Group1")); + service.RegisterSyntheticFieldTrial(trial1); + + variations::SyntheticTrialGroup trial2(HashName("TestTrial2"), + HashName("Group2")); + service.RegisterSyntheticFieldTrial(trial2); + // Ensure that time has advanced by at least a tick before proceeding. + WaitUntilTimeChanges(base::TimeTicks::Now()); + + service.log_manager_.BeginLoggingWithLog(scoped_ptr<MetricsLog>( + new MetricsLog("clientID", + 1, + MetricsLog::INITIAL_STABILITY_LOG, + &client, + GetLocalState()))); + // Save the time when the log was started (it's okay for this to be greater + // than the time recorded by the above call since it's used to ensure the + // value changes). + const base::TimeTicks begin_log_time = base::TimeTicks::Now(); + + std::vector<variations::ActiveGroupId> synthetic_trials; + service.GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(), + &synthetic_trials); + EXPECT_EQ(2U, synthetic_trials.size()); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial1", "Group1")); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2")); + + // Ensure that time has advanced by at least a tick before proceeding. + WaitUntilTimeChanges(begin_log_time); + + // Change the group for the first trial after the log started. + variations::SyntheticTrialGroup trial3(HashName("TestTrial1"), + HashName("Group2")); + service.RegisterSyntheticFieldTrial(trial3); + service.GetSyntheticFieldTrialsOlderThan(begin_log_time, &synthetic_trials); + EXPECT_EQ(1U, synthetic_trials.size()); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2")); + + // Add a new trial after the log started and confirm that it doesn't show up. + variations::SyntheticTrialGroup trial4(HashName("TestTrial3"), + HashName("Group3")); + service.RegisterSyntheticFieldTrial(trial4); + service.GetSyntheticFieldTrialsOlderThan(begin_log_time, &synthetic_trials); + EXPECT_EQ(1U, synthetic_trials.size()); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2")); + + // Ensure that time has advanced by at least a tick before proceeding. + WaitUntilTimeChanges(base::TimeTicks::Now()); + + // Start a new log and ensure all three trials appear in it. + service.log_manager_.FinishCurrentLog(); + service.log_manager_.BeginLoggingWithLog( + scoped_ptr<MetricsLog>(new MetricsLog( + "clientID", 1, MetricsLog::ONGOING_LOG, &client, GetLocalState()))); + service.GetSyntheticFieldTrialsOlderThan( + service.log_manager_.current_log()->creation_time(), &synthetic_trials); + EXPECT_EQ(3U, synthetic_trials.size()); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial1", "Group2")); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2")); + EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial3", "Group3")); + service.log_manager_.FinishCurrentLog(); +} + +TEST_F(MetricsServiceTest, + MetricsProviderOnRecordingDisabledCalledOnInitialStop) { + TestMetricsServiceClient client; + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + + TestMetricsProvider* test_provider = new TestMetricsProvider(); + service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider)); + + service.InitializeMetricsRecordingState(); + service.Stop(); + + EXPECT_TRUE(test_provider->on_recording_disabled_called()); +} + +TEST_F(MetricsServiceTest, MetricsProvidersInitialized) { + TestMetricsServiceClient client; + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + + TestMetricsProvider* test_provider = new TestMetricsProvider(); + service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider)); + + service.InitializeMetricsRecordingState(); + + EXPECT_TRUE(test_provider->init_called()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_state_manager.cc b/chromium/components/metrics/metrics_state_manager.cc new file mode 100644 index 00000000000..89817aa5992 --- /dev/null +++ b/chromium/components/metrics/metrics_state_manager.cc @@ -0,0 +1,314 @@ +// Copyright 2014 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 "components/metrics/metrics_state_manager.h" + +#include <stddef.h> + +#include "base/command_line.h" +#include "base/guid.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/sparse_histogram.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/cloned_install_detector.h" +#include "components/metrics/machine_id_provider.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_switches.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/variations/caching_permuted_entropy_provider.h" + +namespace metrics { + +namespace { + +// The argument used to generate a non-identifying entropy source. We want no +// more than 13 bits of entropy, so use this max to return a number in the range +// [0, 7999] as the entropy source (12.97 bits of entropy). +const int kMaxLowEntropySize = 8000; + +// Default prefs value for prefs::kMetricsLowEntropySource to indicate that +// the value has not yet been set. +const int kLowEntropySourceNotSet = -1; + +// Generates a new non-identifying entropy source used to seed persistent +// activities. +int GenerateLowEntropySource() { + return base::RandInt(0, kMaxLowEntropySize - 1); +} + +} // namespace + +// static +bool MetricsStateManager::instance_exists_ = false; + +MetricsStateManager::MetricsStateManager( + PrefService* local_state, + const base::Callback<bool(void)>& is_reporting_enabled_callback, + const StoreClientInfoCallback& store_client_info, + const LoadClientInfoCallback& retrieve_client_info) + : local_state_(local_state), + is_reporting_enabled_callback_(is_reporting_enabled_callback), + store_client_info_(store_client_info), + load_client_info_(retrieve_client_info), + low_entropy_source_(kLowEntropySourceNotSet), + entropy_source_returned_(ENTROPY_SOURCE_NONE) { + ResetMetricsIDsIfNecessary(); + if (IsMetricsReportingEnabled()) + ForceClientIdCreation(); + + DCHECK(!instance_exists_); + instance_exists_ = true; +} + +MetricsStateManager::~MetricsStateManager() { + DCHECK(instance_exists_); + instance_exists_ = false; +} + +bool MetricsStateManager::IsMetricsReportingEnabled() { + return is_reporting_enabled_callback_.Run(); +} + +void MetricsStateManager::ForceClientIdCreation() { + if (!client_id_.empty()) + return; + + client_id_ = local_state_->GetString(prefs::kMetricsClientID); + if (!client_id_.empty()) { + // It is technically sufficient to only save a backup of the client id when + // it is initially generated below, but since the backup was only introduced + // in M38, seed it explicitly from here for some time. + BackUpCurrentClientInfo(); + return; + } + + const scoped_ptr<ClientInfo> client_info_backup = + LoadClientInfoAndMaybeMigrate(); + if (client_info_backup) { + client_id_ = client_info_backup->client_id; + + const base::Time now = base::Time::Now(); + + // Save the recovered client id and also try to reinstantiate the backup + // values for the dates corresponding with that client id in order to avoid + // weird scenarios where we could report an old client id with a recent + // install date. + local_state_->SetString(prefs::kMetricsClientID, client_id_); + local_state_->SetInt64(prefs::kInstallDate, + client_info_backup->installation_date != 0 + ? client_info_backup->installation_date + : now.ToTimeT()); + local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, + client_info_backup->reporting_enabled_date != 0 + ? client_info_backup->reporting_enabled_date + : now.ToTimeT()); + + base::TimeDelta recovered_installation_age; + if (client_info_backup->installation_date != 0) { + recovered_installation_age = + now - base::Time::FromTimeT(client_info_backup->installation_date); + } + UMA_HISTOGRAM_COUNTS_10000("UMA.ClientIdBackupRecoveredWithAge", + recovered_installation_age.InHours()); + + // Flush the backup back to persistent storage in case we re-generated + // missing data above. + BackUpCurrentClientInfo(); + return; + } + + // Failing attempts at getting an existing client ID, generate a new one. + client_id_ = base::GenerateGUID(); + local_state_->SetString(prefs::kMetricsClientID, client_id_); + + // Record the timestamp of when the user opted in to UMA. + local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp, + base::Time::Now().ToTimeT()); + + BackUpCurrentClientInfo(); +} + +void MetricsStateManager::CheckForClonedInstall( + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + DCHECK(!cloned_install_detector_); + + MachineIdProvider* provider = MachineIdProvider::CreateInstance(); + if (!provider) + return; + + cloned_install_detector_.reset(new ClonedInstallDetector(provider)); + cloned_install_detector_->CheckForClonedInstall(local_state_, task_runner); +} + +scoped_ptr<const base::FieldTrial::EntropyProvider> +MetricsStateManager::CreateEntropyProvider() { + // For metrics reporting-enabled users, we combine the client ID and low + // entropy source to get the final entropy source. Otherwise, only use the low + // entropy source. + // This has two useful properties: + // 1) It makes the entropy source less identifiable for parties that do not + // know the low entropy source. + // 2) It makes the final entropy source resettable. + const int low_entropy_source_value = GetLowEntropySource(); + UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue", + low_entropy_source_value); + if (IsMetricsReportingEnabled()) { + UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH); + const std::string high_entropy_source = + client_id_ + base::IntToString(low_entropy_source_value); + return scoped_ptr<const base::FieldTrial::EntropyProvider>( + new SHA1EntropyProvider(high_entropy_source)); + } + + UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW); +#if defined(OS_ANDROID) || defined(OS_IOS) + return scoped_ptr<const base::FieldTrial::EntropyProvider>( + new CachingPermutedEntropyProvider(local_state_, + low_entropy_source_value, + kMaxLowEntropySize)); +#else + return scoped_ptr<const base::FieldTrial::EntropyProvider>( + new PermutedEntropyProvider(low_entropy_source_value, + kMaxLowEntropySize)); +#endif +} + +// static +scoped_ptr<MetricsStateManager> MetricsStateManager::Create( + PrefService* local_state, + const base::Callback<bool(void)>& is_reporting_enabled_callback, + const StoreClientInfoCallback& store_client_info, + const LoadClientInfoCallback& retrieve_client_info) { + scoped_ptr<MetricsStateManager> result; + // Note: |instance_exists_| is updated in the constructor and destructor. + if (!instance_exists_) { + result.reset(new MetricsStateManager(local_state, + is_reporting_enabled_callback, + store_client_info, + retrieve_client_info)); + } + return result; +} + +// static +void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(prefs::kMetricsResetIds, false); + // registry->RegisterIntegerPref(prefs::kMetricsDefaultOptIn, + // DEFAULT_UNKNOWN); + registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); + registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); + registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, + kLowEntropySourceNotSet); + + ClonedInstallDetector::RegisterPrefs(registry); + CachingPermutedEntropyProvider::RegisterPrefs(registry); +} + +void MetricsStateManager::BackUpCurrentClientInfo() { + ClientInfo client_info; + client_info.client_id = client_id_; + client_info.installation_date = local_state_->GetInt64(prefs::kInstallDate); + client_info.reporting_enabled_date = + local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp); + store_client_info_.Run(client_info); +} + +scoped_ptr<ClientInfo> MetricsStateManager::LoadClientInfoAndMaybeMigrate() { + scoped_ptr<ClientInfo> client_info = load_client_info_.Run(); + + // Prior to 2014-07, the client ID was stripped of its dashes before being + // saved. Migrate back to a proper GUID if this is the case. This migration + // code can be removed in M41+. + const size_t kGUIDLengthWithoutDashes = 32U; + if (client_info && + client_info->client_id.length() == kGUIDLengthWithoutDashes) { + DCHECK(client_info->client_id.find('-') == std::string::npos); + + std::string client_id_with_dashes; + client_id_with_dashes.reserve(kGUIDLengthWithoutDashes + 4U); + std::string::const_iterator client_id_it = client_info->client_id.begin(); + for (size_t i = 0; i < kGUIDLengthWithoutDashes + 4U; ++i) { + if (i == 8U || i == 13U || i == 18U || i == 23U) { + client_id_with_dashes.push_back('-'); + } else { + client_id_with_dashes.push_back(*client_id_it); + ++client_id_it; + } + } + DCHECK(client_id_it == client_info->client_id.end()); + client_info->client_id.assign(client_id_with_dashes); + } + + // The GUID retrieved (and possibly fixed above) should be valid unless + // retrieval failed. + DCHECK(!client_info || base::IsValidGUID(client_info->client_id)); + + return client_info; +} + +int MetricsStateManager::GetLowEntropySource() { + UpdateLowEntropySource(); + return low_entropy_source_; +} + +void MetricsStateManager::UpdateLowEntropySource() { + // Note that the default value for the low entropy source and the default pref + // value are both kLowEntropySourceNotSet, which is used to identify if the + // value has been set or not. + if (low_entropy_source_ != kLowEntropySourceNotSet) + return; + + const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess()); + // Only try to load the value from prefs if the user did not request a + // reset. + // Otherwise, skip to generating a new value. + if (!command_line->HasSwitch(switches::kResetVariationState)) { + int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource); + // If the value is outside the [0, kMaxLowEntropySize) range, re-generate + // it below. + if (value >= 0 && value < kMaxLowEntropySize) { + low_entropy_source_ = value; + return; + } + } + + low_entropy_source_ = GenerateLowEntropySource(); + local_state_->SetInteger(prefs::kMetricsLowEntropySource, + low_entropy_source_); + CachingPermutedEntropyProvider::ClearCache(local_state_); +} + +void MetricsStateManager::UpdateEntropySourceReturnedValue( + EntropySourceType type) { + if (entropy_source_returned_ != ENTROPY_SOURCE_NONE) + return; + + entropy_source_returned_ = type; + UMA_HISTOGRAM_ENUMERATION("UMA.EntropySourceType", type, + ENTROPY_SOURCE_ENUM_SIZE); +} + +void MetricsStateManager::ResetMetricsIDsIfNecessary() { + if (!local_state_->GetBoolean(prefs::kMetricsResetIds)) + return; + + UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true); + + DCHECK(client_id_.empty()); + DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_); + + local_state_->ClearPref(prefs::kMetricsClientID); + local_state_->ClearPref(prefs::kMetricsLowEntropySource); + local_state_->ClearPref(prefs::kMetricsResetIds); + + // Also clear the backed up client info. + store_client_info_.Run(ClientInfo()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_state_manager.h b/chromium/components/metrics/metrics_state_manager.h new file mode 100644 index 00000000000..cbf5bf71b6b --- /dev/null +++ b/chromium/components/metrics/metrics_state_manager.h @@ -0,0 +1,173 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ +#define COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/field_trial.h" +#include "components/metrics/client_info.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class ClonedInstallDetector; + +// Responsible for managing MetricsService state prefs, specifically the UMA +// client id and low entropy source. Code outside the metrics directory should +// not be instantiating or using this class directly. +class MetricsStateManager { + public: + // A callback that can be invoked to store client info to persistent storage. + // Storing an empty client_id will resulted in the backup being voided. + typedef base::Callback<void(const ClientInfo& client_info)> + StoreClientInfoCallback; + + // A callback that can be invoked to load client info stored through the + // StoreClientInfoCallback. + typedef base::Callback<scoped_ptr<ClientInfo>(void)> LoadClientInfoCallback; + + virtual ~MetricsStateManager(); + + // Returns true if the user opted in to sending metric reports. + bool IsMetricsReportingEnabled(); + + // Returns the client ID for this client, or the empty string if the user is + // not opted in to metrics reporting. + const std::string& client_id() const { return client_id_; } + + // Forces the client ID to be generated. This is useful in case it's needed + // before recording. + void ForceClientIdCreation(); + + // Checks if this install was cloned or imaged from another machine. If a + // clone is detected, resets the client id and low entropy source. This + // should not be called more than once. + void CheckForClonedInstall( + scoped_refptr<base::SingleThreadTaskRunner> task_runner); + + // Returns the preferred entropy provider used to seed persistent activities + // based on whether or not metrics reporting is permitted on this client. + // + // If metrics reporting is enabled, this method returns an entropy provider + // that has a high source of entropy, partially based on the client ID. + // Otherwise, it returns an entropy provider that is based on a low entropy + // source. + scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider(); + + // Creates the MetricsStateManager, enforcing that only a single instance + // of the class exists at a time. Returns NULL if an instance exists already. + static scoped_ptr<MetricsStateManager> Create( + PrefService* local_state, + const base::Callback<bool(void)>& is_reporting_enabled_callback, + const StoreClientInfoCallback& store_client_info, + const LoadClientInfoCallback& load_client_info); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_Low); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_High); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, LowEntropySource0NotReset); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, + PermutedEntropyCacheClearedWhenLowEntropyReset); + FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs); + + // Designates which entropy source was returned from this class. + // This is used for testing to validate that we return the correct source + // depending on the state of the service. + enum EntropySourceType { + ENTROPY_SOURCE_NONE, + ENTROPY_SOURCE_LOW, + ENTROPY_SOURCE_HIGH, + ENTROPY_SOURCE_ENUM_SIZE, + }; + + // Creates the MetricsStateManager with the given |local_state|. Calls + // |is_reporting_enabled_callback| to query whether metrics reporting is + // enabled. Clients should instead use Create(), which enforces that a single + // instance of this class be alive at any given time. + // |store_client_info| should back up client info to persistent storage such + // that it is later retrievable by |load_client_info|. + MetricsStateManager( + PrefService* local_state, + const base::Callback<bool(void)>& is_reporting_enabled_callback, + const StoreClientInfoCallback& store_client_info, + const LoadClientInfoCallback& load_client_info); + + // Backs up the current client info via |store_client_info_|. + void BackUpCurrentClientInfo(); + + // Loads the client info via |load_client_info_| and potentially migrates it + // before returning it if it comes back in its old form. + scoped_ptr<ClientInfo> LoadClientInfoAndMaybeMigrate(); + + // Returns the low entropy source for this client. This is a random value + // that is non-identifying amongst browser clients. This method will + // generate the entropy source value if it has not been called before. + int GetLowEntropySource(); + + // Generates the low entropy source value for this client if it is not + // already set. + void UpdateLowEntropySource(); + + // Updates |entropy_source_returned_| with |type| iff the current value is + // ENTROPY_SOURCE_NONE and logs the new value in a histogram. + void UpdateEntropySourceReturnedValue(EntropySourceType type); + + // Returns the first entropy source that was returned by this service since + // start up, or NONE if neither was returned yet. This is exposed for testing + // only. + EntropySourceType entropy_source_returned() const { + return entropy_source_returned_; + } + + // Reset the client id and low entropy source if the kMetricsResetMetricIDs + // pref is true. + void ResetMetricsIDsIfNecessary(); + + // Whether an instance of this class exists. Used to enforce that there aren't + // multiple instances of this class at a given time. + static bool instance_exists_; + + // Weak pointer to the local state prefs store. + PrefService* const local_state_; + + // A callback run by this MetricsStateManager to know whether reporting is + // enabled. + const base::Callback<bool(void)> is_reporting_enabled_callback_; + + // A callback run during client id creation so this MetricsStateManager can + // store a backup of the newly generated ID. + const StoreClientInfoCallback store_client_info_; + + // A callback run if this MetricsStateManager can't get the client id from + // its typical location and wants to attempt loading it from this backup. + const LoadClientInfoCallback load_client_info_; + + // The identifier that's sent to the server with the log reports. + std::string client_id_; + + // The non-identifying low entropy source value. + int low_entropy_source_; + + // The last entropy source returned by this service, used for testing. + EntropySourceType entropy_source_returned_; + + scoped_ptr<ClonedInstallDetector> cloned_install_detector_; + + DISALLOW_COPY_AND_ASSIGN(MetricsStateManager); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ diff --git a/chromium/components/metrics/metrics_state_manager_unittest.cc b/chromium/components/metrics/metrics_state_manager_unittest.cc new file mode 100644 index 00000000000..72280aea534 --- /dev/null +++ b/chromium/components/metrics/metrics_state_manager_unittest.cc @@ -0,0 +1,386 @@ +// Copyright 2014 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 "components/metrics/metrics_state_manager.h" + +#include <ctype.h> +#include <stddef.h> +#include <stdint.h> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/macros.h" +#include "components/metrics/client_info.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_service.h" +#include "components/metrics/metrics_switches.h" +#include "components/prefs/testing_pref_service.h" +#include "components/variations/caching_permuted_entropy_provider.h" +#include "components/variations/pref_names.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +class MetricsStateManagerTest : public testing::Test { + public: + MetricsStateManagerTest() : is_metrics_reporting_enabled_(false) { + MetricsService::RegisterPrefs(prefs_.registry()); + } + + scoped_ptr<MetricsStateManager> CreateStateManager() { + return MetricsStateManager::Create( + &prefs_, + base::Bind(&MetricsStateManagerTest::is_metrics_reporting_enabled, + base::Unretained(this)), + base::Bind(&MetricsStateManagerTest::MockStoreClientInfoBackup, + base::Unretained(this)), + base::Bind(&MetricsStateManagerTest::LoadFakeClientInfoBackup, + base::Unretained(this))); + } + + // Sets metrics reporting as enabled for testing. + void EnableMetricsReporting() { + is_metrics_reporting_enabled_ = true; + } + + protected: + TestingPrefServiceSimple prefs_; + + // Last ClientInfo stored by the MetricsStateManager via + // MockStoreClientInfoBackup. + scoped_ptr<ClientInfo> stored_client_info_backup_; + + // If set, will be returned via LoadFakeClientInfoBackup if requested by the + // MetricsStateManager. + scoped_ptr<ClientInfo> fake_client_info_backup_; + + private: + bool is_metrics_reporting_enabled() const { + return is_metrics_reporting_enabled_; + } + + // Stores the |client_info| in |stored_client_info_backup_| for verification + // by the tests later. + void MockStoreClientInfoBackup(const ClientInfo& client_info) { + stored_client_info_backup_.reset(new ClientInfo); + stored_client_info_backup_->client_id = client_info.client_id; + stored_client_info_backup_->installation_date = + client_info.installation_date; + stored_client_info_backup_->reporting_enabled_date = + client_info.reporting_enabled_date; + + // Respect the contract that storing an empty client_id voids the existing + // backup (required for the last section of the ForceClientIdCreation test + // below). + if (client_info.client_id.empty()) + fake_client_info_backup_.reset(); + } + + // Hands out a copy of |fake_client_info_backup_| if it is set. + scoped_ptr<ClientInfo> LoadFakeClientInfoBackup() { + if (!fake_client_info_backup_) + return scoped_ptr<ClientInfo>(); + + scoped_ptr<ClientInfo> backup_copy(new ClientInfo); + backup_copy->client_id = fake_client_info_backup_->client_id; + backup_copy->installation_date = + fake_client_info_backup_->installation_date; + backup_copy->reporting_enabled_date = + fake_client_info_backup_->reporting_enabled_date; + return backup_copy; + } + + bool is_metrics_reporting_enabled_; + + DISALLOW_COPY_AND_ASSIGN(MetricsStateManagerTest); +}; + +// Ensure the ClientId is formatted as expected. +TEST_F(MetricsStateManagerTest, ClientIdCorrectlyFormatted) { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + + const std::string client_id = state_manager->client_id(); + EXPECT_EQ(36U, client_id.length()); + + for (size_t i = 0; i < client_id.length(); ++i) { + char current = client_id[i]; + if (i == 8 || i == 13 || i == 18 || i == 23) + EXPECT_EQ('-', current); + else + EXPECT_TRUE(isxdigit(current)); + } +} + +TEST_F(MetricsStateManagerTest, EntropySourceUsed_Low) { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->CreateEntropyProvider(); + EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW, + state_manager->entropy_source_returned()); +} + +TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) { + EnableMetricsReporting(); + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->CreateEntropyProvider(); + EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH, + state_manager->entropy_source_returned()); +} + +TEST_F(MetricsStateManagerTest, LowEntropySource0NotReset) { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + + // Get the low entropy source once, to initialize it. + state_manager->GetLowEntropySource(); + + // Now, set it to 0 and ensure it doesn't get reset. + state_manager->low_entropy_source_ = 0; + EXPECT_EQ(0, state_manager->GetLowEntropySource()); + // Call it another time, just to make sure. + EXPECT_EQ(0, state_manager->GetLowEntropySource()); +} + +TEST_F(MetricsStateManagerTest, + PermutedEntropyCacheClearedWhenLowEntropyReset) { + const PrefService::Preference* low_entropy_pref = + prefs_.FindPreference(prefs::kMetricsLowEntropySource); + const char* kCachePrefName = + variations::prefs::kVariationsPermutedEntropyCache; + int low_entropy_value = -1; + + // First, generate an initial low entropy source value. + { + EXPECT_TRUE(low_entropy_pref->IsDefaultValue()); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->GetLowEntropySource(); + + EXPECT_FALSE(low_entropy_pref->IsDefaultValue()); + EXPECT_TRUE(low_entropy_pref->GetValue()->GetAsInteger(&low_entropy_value)); + } + + // Now, set a dummy value in the permuted entropy cache pref and verify that + // another call to GetLowEntropySource() doesn't clobber it when + // --reset-variation-state wasn't specified. + { + prefs_.SetString(kCachePrefName, "test"); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->GetLowEntropySource(); + + EXPECT_EQ("test", prefs_.GetString(kCachePrefName)); + EXPECT_EQ(low_entropy_value, + prefs_.GetInteger(prefs::kMetricsLowEntropySource)); + } + + // Verify that the cache does get reset if --reset-variations-state is passed. + { + base::CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kResetVariationState); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->GetLowEntropySource(); + + EXPECT_TRUE(prefs_.GetString(kCachePrefName).empty()); + } +} + +// Check that setting the kMetricsResetIds pref to true causes the client id to +// be reset. We do not check that the low entropy source is reset because we +// cannot ensure that metrics state manager won't generate the same id again. +TEST_F(MetricsStateManagerTest, ResetMetricsIDs) { + // Set an initial client id in prefs. It should not be possible for the + // metrics state manager to generate this id randomly. + const std::string kInitialClientId = "initial client id"; + prefs_.SetString(prefs::kMetricsClientID, kInitialClientId); + + // Make sure the initial client id isn't reset by the metrics state manager. + { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + EXPECT_EQ(kInitialClientId, state_manager->client_id()); + } + + // Set the reset pref to cause the IDs to be reset. + prefs_.SetBoolean(prefs::kMetricsResetIds, true); + + // Cause the actual reset to happen. + { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + EXPECT_NE(kInitialClientId, state_manager->client_id()); + + state_manager->GetLowEntropySource(); + + EXPECT_FALSE(prefs_.GetBoolean(prefs::kMetricsResetIds)); + } + + EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID)); +} + +TEST_F(MetricsStateManagerTest, ForceClientIdCreation) { + const int64_t kFakeInstallationDate = 12345; + prefs_.SetInt64(prefs::kInstallDate, kFakeInstallationDate); + + const int64_t test_begin_time = base::Time::Now().ToTimeT(); + + // Holds ClientInfo from previous scoped test for extra checks. + scoped_ptr<ClientInfo> previous_client_info; + + { + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + + // client_id shouldn't be auto-generated if metrics reporting is not + // enabled. + EXPECT_EQ(std::string(), state_manager->client_id()); + EXPECT_EQ(0, prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp)); + + // Confirm that the initial ForceClientIdCreation call creates the client id + // and backs it up via MockStoreClientInfoBackup. + EXPECT_FALSE(stored_client_info_backup_); + state_manager->ForceClientIdCreation(); + EXPECT_NE(std::string(), state_manager->client_id()); + EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp), + test_begin_time); + + ASSERT_TRUE(stored_client_info_backup_); + EXPECT_EQ(state_manager->client_id(), + stored_client_info_backup_->client_id); + EXPECT_EQ(kFakeInstallationDate, + stored_client_info_backup_->installation_date); + EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp), + stored_client_info_backup_->reporting_enabled_date); + + previous_client_info = std::move(stored_client_info_backup_); + } + + EnableMetricsReporting(); + + { + EXPECT_FALSE(stored_client_info_backup_); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + + // client_id should be auto-obtained from the constructor when metrics + // reporting is enabled. + EXPECT_EQ(previous_client_info->client_id, state_manager->client_id()); + + // The backup should also be refreshed when the client id re-initialized. + ASSERT_TRUE(stored_client_info_backup_); + EXPECT_EQ(previous_client_info->client_id, + stored_client_info_backup_->client_id); + EXPECT_EQ(kFakeInstallationDate, + stored_client_info_backup_->installation_date); + EXPECT_EQ(previous_client_info->reporting_enabled_date, + stored_client_info_backup_->reporting_enabled_date); + + // Re-forcing client id creation shouldn't cause another backup and + // shouldn't affect the existing client id. + stored_client_info_backup_.reset(); + state_manager->ForceClientIdCreation(); + EXPECT_FALSE(stored_client_info_backup_); + EXPECT_EQ(previous_client_info->client_id, state_manager->client_id()); + } + + const int64_t kBackupInstallationDate = 1111; + const int64_t kBackupReportingEnabledDate = 2222; + const char kBackupClientId[] = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; + fake_client_info_backup_.reset(new ClientInfo); + fake_client_info_backup_->client_id = kBackupClientId; + fake_client_info_backup_->installation_date = kBackupInstallationDate; + fake_client_info_backup_->reporting_enabled_date = + kBackupReportingEnabledDate; + + { + // The existence of a backup should result in the same behaviour as + // before if we already have a client id. + + EXPECT_FALSE(stored_client_info_backup_); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + EXPECT_EQ(previous_client_info->client_id, state_manager->client_id()); + + // The backup should also be refreshed when the client id re-initialized. + ASSERT_TRUE(stored_client_info_backup_); + EXPECT_EQ(previous_client_info->client_id, + stored_client_info_backup_->client_id); + EXPECT_EQ(kFakeInstallationDate, + stored_client_info_backup_->installation_date); + EXPECT_EQ(previous_client_info->reporting_enabled_date, + stored_client_info_backup_->reporting_enabled_date); + stored_client_info_backup_.reset(); + } + + prefs_.ClearPref(prefs::kMetricsClientID); + prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp); + + { + // The backup should kick in if the client id has gone missing. It should + // replace remaining and missing dates as well. + + EXPECT_FALSE(stored_client_info_backup_); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + EXPECT_EQ(kBackupClientId, state_manager->client_id()); + EXPECT_EQ(kBackupInstallationDate, prefs_.GetInt64(prefs::kInstallDate)); + EXPECT_EQ(kBackupReportingEnabledDate, + prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp)); + + EXPECT_TRUE(stored_client_info_backup_); + stored_client_info_backup_.reset(); + } + + const char kNoDashesBackupClientId[] = "AAAAAAAABBBBCCCCDDDDEEEEEEEEEEEE"; + fake_client_info_backup_.reset(new ClientInfo); + fake_client_info_backup_->client_id = kNoDashesBackupClientId; + + prefs_.ClearPref(prefs::kInstallDate); + prefs_.ClearPref(prefs::kMetricsClientID); + prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp); + + { + // When running the backup from old-style client ids, dashes should be + // re-added. And missing dates in backup should be replaced by Time::Now(). + + EXPECT_FALSE(stored_client_info_backup_); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + EXPECT_EQ(kBackupClientId, state_manager->client_id()); + EXPECT_GE(prefs_.GetInt64(prefs::kInstallDate), test_begin_time); + EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp), + test_begin_time); + + EXPECT_TRUE(stored_client_info_backup_); + previous_client_info = std::move(stored_client_info_backup_); + } + + prefs_.SetBoolean(prefs::kMetricsResetIds, true); + + { + // Upon request to reset metrics ids, the existing backup should not be + // restored. + + EXPECT_FALSE(stored_client_info_backup_); + + scoped_ptr<MetricsStateManager> state_manager(CreateStateManager()); + + // A brand new client id should have been generated. + EXPECT_NE(std::string(), state_manager->client_id()); + EXPECT_NE(previous_client_info->client_id, state_manager->client_id()); + + // The installation date should not have been affected. + EXPECT_EQ(previous_client_info->installation_date, + prefs_.GetInt64(prefs::kInstallDate)); + + // The metrics-reporting-enabled date will be reset to Now(). + EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp), + previous_client_info->reporting_enabled_date); + + stored_client_info_backup_.reset(); + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/metrics_switches.cc b/chromium/components/metrics/metrics_switches.cc new file mode 100644 index 00000000000..1aef619d6a4 --- /dev/null +++ b/chromium/components/metrics/metrics_switches.cc @@ -0,0 +1,15 @@ +// Copyright 2014 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 "components/metrics/metrics_switches.h" + +namespace metrics { +namespace switches { + +// Forces a reset of the one-time-randomized FieldTrials on this client, also +// known as the Chrome Variations state. +const char kResetVariationState[] = "reset-variation-state"; + +} // namespace switches +} // namespace metrics diff --git a/chromium/components/metrics/metrics_switches.h b/chromium/components/metrics/metrics_switches.h new file mode 100644 index 00000000000..b3fe7fd9c03 --- /dev/null +++ b/chromium/components/metrics/metrics_switches.h @@ -0,0 +1,18 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_METRICS_SWITCHES_H_ +#define COMPONENTS_METRICS_METRICS_SWITCHES_H_ + +namespace metrics { +namespace switches { + +// Alphabetical list of switches specific to the metrics component. Document +// each in the .cc file. +extern const char kResetVariationState[]; + +} // namespace switches +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SWITCHES_H_ diff --git a/chromium/components/metrics/net/DEPS b/chromium/components/metrics/net/DEPS new file mode 100644 index 00000000000..c7c67e4830e --- /dev/null +++ b/chromium/components/metrics/net/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+chromeos/network", + "+components/data_use_measurement/core", + "+net", + "+third_party/cros_system_api", +] diff --git a/chromium/components/metrics/net/net_metrics_log_uploader.cc b/chromium/components/metrics/net/net_metrics_log_uploader.cc new file mode 100644 index 00000000000..0ded667b787 --- /dev/null +++ b/chromium/components/metrics/net/net_metrics_log_uploader.cc @@ -0,0 +1,77 @@ +// Copyright 2014 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 "components/metrics/net/net_metrics_log_uploader.h" + +#include "base/metrics/histogram_macros.h" +#include "components/data_use_measurement/core/data_use_user_data.h" +#include "net/base/load_flags.h" +#include "net/base/network_change_notifier.h" +#include "net/url_request/url_fetcher.h" +#include "url/gurl.h" + +namespace { + +// Records the network connection type if upload was successful. +void RecordConnectionType(int response_code) { + if (response_code == 200) { + UMA_HISTOGRAM_ENUMERATION("UMA.LogUpload.ConnetionType", + net::NetworkChangeNotifier::GetConnectionType(), + net::NetworkChangeNotifier::CONNECTION_LAST); + } +} + +} // namespace + +namespace metrics { + +NetMetricsLogUploader::NetMetricsLogUploader( + net::URLRequestContextGetter* request_context_getter, + const std::string& server_url, + const std::string& mime_type, + const base::Callback<void(int)>& on_upload_complete) + : MetricsLogUploader(server_url, mime_type, on_upload_complete), + request_context_getter_(request_context_getter) { +} + +NetMetricsLogUploader::~NetMetricsLogUploader() { +} + +void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data, + const std::string& log_hash) { + current_fetch_ = + net::URLFetcher::Create(GURL(server_url_), net::URLFetcher::POST, this); + data_use_measurement::DataUseUserData::AttachToFetcher( + current_fetch_.get(), data_use_measurement::DataUseUserData::UMA); + current_fetch_->SetRequestContext(request_context_getter_); + current_fetch_->SetUploadData(mime_type_, compressed_log_data); + + // Tell the server that we're uploading gzipped protobufs. + current_fetch_->SetExtraRequestHeaders("content-encoding: gzip"); + + DCHECK(!log_hash.empty()); + current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-Log-SHA1: " + log_hash); + + // We already drop cookies server-side, but we might as well strip them out + // client-side as well. + current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | + net::LOAD_DO_NOT_SEND_COOKIES); + current_fetch_->Start(); +} + +void NetMetricsLogUploader::OnURLFetchComplete(const net::URLFetcher* source) { + // We're not allowed to re-use the existing |URLFetcher|s, so free them here. + // Note however that |source| is aliased to the fetcher, so we should be + // careful not to delete it too early. + DCHECK_EQ(current_fetch_.get(), source); + + int response_code = source->GetResponseCode(); + if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID) + response_code = -1; + current_fetch_.reset(); + RecordConnectionType(response_code); + on_upload_complete_.Run(response_code); +} + +} // namespace metrics diff --git a/chromium/components/metrics/net/net_metrics_log_uploader.h b/chromium/components/metrics/net/net_metrics_log_uploader.h new file mode 100644 index 00000000000..a62b8988f3c --- /dev/null +++ b/chromium/components/metrics/net/net_metrics_log_uploader.h @@ -0,0 +1,55 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_ +#define COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/metrics_log_uploader.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace metrics { + +// Implementation of MetricsLogUploader using the Chrome network stack. +class NetMetricsLogUploader : public MetricsLogUploader, + public net::URLFetcherDelegate { + public: + // Constructs a NetMetricsLogUploader with the specified request context and + // other params (see comments on MetricsLogUploader for details). The caller + // must ensure that |request_context_getter| remains valid for the lifetime + // of this class. + NetMetricsLogUploader(net::URLRequestContextGetter* request_context_getter, + const std::string& server_url, + const std::string& mime_type, + const base::Callback<void(int)>& on_upload_complete); + ~NetMetricsLogUploader() override; + + // MetricsLogUploader: + void UploadLog(const std::string& compressed_log_data, + const std::string& log_hash) override; + + private: + // net::URLFetcherDelegate: + void OnURLFetchComplete(const net::URLFetcher* source) override; + + // The request context for fetches done using the network stack. + net::URLRequestContextGetter* const request_context_getter_; + + // The outstanding transmission appears as a URL Fetch operation. + scoped_ptr<net::URLFetcher> current_fetch_; + + DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploader); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_ diff --git a/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc b/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc new file mode 100644 index 00000000000..a2d0ecd166b --- /dev/null +++ b/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc @@ -0,0 +1,59 @@ +// Copyright 2015 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 "components/metrics/net/net_metrics_log_uploader.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +class NetMetricsLogUploaderTest : public testing::Test { + public: + NetMetricsLogUploaderTest() : on_upload_complete_count_(0) { + } + + void CreateAndOnUploadCompleteReuseUploader() { + uploader_.reset(new NetMetricsLogUploader( + NULL, "http://dummy_server", "dummy_mime", + base::Bind(&NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader, + base::Unretained(this)))); + uploader_->UploadLog("initial_dummy_data", "initial_dummy_hash"); + } + + void OnUploadCompleteReuseUploader(int response_code) { + ++on_upload_complete_count_; + if (on_upload_complete_count_ == 1) + uploader_->UploadLog("dummy_data", "dummy_hash"); + } + + int on_upload_complete_count() const { + return on_upload_complete_count_; + } + + private: + scoped_ptr<NetMetricsLogUploader> uploader_; + int on_upload_complete_count_; + + DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploaderTest); +}; + +TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) { + net::TestURLFetcherFactory factory; + CreateAndOnUploadCompleteReuseUploader(); + + // Mimic the initial fetcher callback. + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + fetcher->delegate()->OnURLFetchComplete(fetcher); + + // Mimic the second fetcher callback. + fetcher = factory.GetFetcherByID(0); + fetcher->delegate()->OnURLFetchComplete(fetcher); + + EXPECT_EQ(on_upload_complete_count(), 2); +} + +} // namespace metrics diff --git a/chromium/components/metrics/net/network_metrics_provider.cc b/chromium/components/metrics/net/network_metrics_provider.cc new file mode 100644 index 00000000000..92540893984 --- /dev/null +++ b/chromium/components/metrics/net/network_metrics_provider.cc @@ -0,0 +1,265 @@ +// Copyright 2014 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 "components/metrics/net/network_metrics_provider.h" + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/sparse_histogram.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/task_runner_util.h" +#include "build/build_config.h" +#include "net/base/net_errors.h" + +#if defined(OS_CHROMEOS) +#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h" +#endif // OS_CHROMEOS + +namespace metrics { + +NetworkMetricsProvider::NetworkMetricsProvider(base::TaskRunner* io_task_runner) + : io_task_runner_(io_task_runner), + connection_type_is_ambiguous_(false), + wifi_phy_layer_protocol_is_ambiguous_(false), + wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN), + total_aborts_(0), + total_codes_(0), + weak_ptr_factory_(this) { + net::NetworkChangeNotifier::AddConnectionTypeObserver(this); + connection_type_ = net::NetworkChangeNotifier::GetConnectionType(); + ProbeWifiPHYLayerProtocol(); +} + +NetworkMetricsProvider::~NetworkMetricsProvider() { + net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); +} + +void NetworkMetricsProvider::ProvideGeneralMetrics( + ChromeUserMetricsExtension*) { + // ProvideGeneralMetrics is called on the main thread, at the time a metrics + // record is being finalized. + net::NetworkChangeNotifier::FinalizingMetricsLogRecord(); + LogAggregatedMetrics(); +} + +void NetworkMetricsProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile) { + SystemProfileProto::Network* network = system_profile->mutable_network(); + network->set_connection_type_is_ambiguous(connection_type_is_ambiguous_); + network->set_connection_type(GetConnectionType()); + network->set_wifi_phy_layer_protocol_is_ambiguous( + wifi_phy_layer_protocol_is_ambiguous_); + network->set_wifi_phy_layer_protocol(GetWifiPHYLayerProtocol()); + + // Update the connection type. Note that this is necessary to set the network + // type to "none" if there is no network connection for an entire UMA logging + // window, since OnConnectionTypeChanged() ignores transitions to the "none" + // state. + connection_type_ = net::NetworkChangeNotifier::GetConnectionType(); + // Reset the "ambiguous" flags, since a new metrics log session has started. + connection_type_is_ambiguous_ = false; + wifi_phy_layer_protocol_is_ambiguous_ = false; + + if (!wifi_access_point_info_provider_.get()) { +#if defined(OS_CHROMEOS) + wifi_access_point_info_provider_.reset( + new WifiAccessPointInfoProviderChromeos()); +#else + wifi_access_point_info_provider_.reset( + new WifiAccessPointInfoProvider()); +#endif // OS_CHROMEOS + } + + // Connected wifi access point information. + WifiAccessPointInfoProvider::WifiAccessPointInfo info; + if (wifi_access_point_info_provider_->GetInfo(&info)) + WriteWifiAccessPointProto(info, network); +} + +void NetworkMetricsProvider::OnConnectionTypeChanged( + net::NetworkChangeNotifier::ConnectionType type) { + // To avoid reporting an ambiguous connection type for users on flaky + // connections, ignore transitions to the "none" state. Note that the + // connection type is refreshed in ProvideSystemProfileMetrics() each time a + // new UMA logging window begins, so users who genuinely transition to offline + // mode for an extended duration will still be at least partially represented + // in the metrics logs. + if (type == net::NetworkChangeNotifier::CONNECTION_NONE) + return; + + if (type != connection_type_ && + connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE) { + connection_type_is_ambiguous_ = true; + } + connection_type_ = type; + + ProbeWifiPHYLayerProtocol(); +} + +SystemProfileProto::Network::ConnectionType +NetworkMetricsProvider::GetConnectionType() const { + switch (connection_type_) { + case net::NetworkChangeNotifier::CONNECTION_NONE: + return SystemProfileProto::Network::CONNECTION_NONE; + case net::NetworkChangeNotifier::CONNECTION_UNKNOWN: + return SystemProfileProto::Network::CONNECTION_UNKNOWN; + case net::NetworkChangeNotifier::CONNECTION_ETHERNET: + return SystemProfileProto::Network::CONNECTION_ETHERNET; + case net::NetworkChangeNotifier::CONNECTION_WIFI: + return SystemProfileProto::Network::CONNECTION_WIFI; + case net::NetworkChangeNotifier::CONNECTION_2G: + return SystemProfileProto::Network::CONNECTION_2G; + case net::NetworkChangeNotifier::CONNECTION_3G: + return SystemProfileProto::Network::CONNECTION_3G; + case net::NetworkChangeNotifier::CONNECTION_4G: + return SystemProfileProto::Network::CONNECTION_4G; + case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH: + return SystemProfileProto::Network::CONNECTION_BLUETOOTH; + } + NOTREACHED(); + return SystemProfileProto::Network::CONNECTION_UNKNOWN; +} + +SystemProfileProto::Network::WifiPHYLayerProtocol +NetworkMetricsProvider::GetWifiPHYLayerProtocol() const { + switch (wifi_phy_layer_protocol_) { + case net::WIFI_PHY_LAYER_PROTOCOL_NONE: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_NONE; + case net::WIFI_PHY_LAYER_PROTOCOL_ANCIENT: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_ANCIENT; + case net::WIFI_PHY_LAYER_PROTOCOL_A: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_A; + case net::WIFI_PHY_LAYER_PROTOCOL_B: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_B; + case net::WIFI_PHY_LAYER_PROTOCOL_G: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_G; + case net::WIFI_PHY_LAYER_PROTOCOL_N: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_N; + case net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN: + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN; + } + NOTREACHED(); + return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN; +} + +void NetworkMetricsProvider::ProbeWifiPHYLayerProtocol() { + PostTaskAndReplyWithResult( + io_task_runner_, + FROM_HERE, + base::Bind(&net::GetWifiPHYLayerProtocol), + base::Bind(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult, + weak_ptr_factory_.GetWeakPtr())); +} + +void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult( + net::WifiPHYLayerProtocol mode) { + if (wifi_phy_layer_protocol_ != net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN && + mode != wifi_phy_layer_protocol_) { + wifi_phy_layer_protocol_is_ambiguous_ = true; + } + wifi_phy_layer_protocol_ = mode; +} + +void NetworkMetricsProvider::WriteWifiAccessPointProto( + const WifiAccessPointInfoProvider::WifiAccessPointInfo& info, + SystemProfileProto::Network* network_proto) { + SystemProfileProto::Network::WifiAccessPoint* access_point_info = + network_proto->mutable_access_point_info(); + SystemProfileProto::Network::WifiAccessPoint::SecurityMode security = + SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN; + switch (info.security) { + case WifiAccessPointInfoProvider::WIFI_SECURITY_NONE: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_NONE; + break; + case WifiAccessPointInfoProvider::WIFI_SECURITY_WPA: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WPA; + break; + case WifiAccessPointInfoProvider::WIFI_SECURITY_WEP: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WEP; + break; + case WifiAccessPointInfoProvider::WIFI_SECURITY_RSN: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_RSN; + break; + case WifiAccessPointInfoProvider::WIFI_SECURITY_802_1X: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_802_1X; + break; + case WifiAccessPointInfoProvider::WIFI_SECURITY_PSK: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_PSK; + break; + case WifiAccessPointInfoProvider::WIFI_SECURITY_UNKNOWN: + security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN; + break; + } + access_point_info->set_security_mode(security); + + // |bssid| is xx:xx:xx:xx:xx:xx, extract the first three components and + // pack into a uint32_t. + std::string bssid = info.bssid; + if (bssid.size() == 17 && bssid[2] == ':' && bssid[5] == ':' && + bssid[8] == ':' && bssid[11] == ':' && bssid[14] == ':') { + std::string vendor_prefix_str; + uint32_t vendor_prefix; + + base::RemoveChars(bssid.substr(0, 9), ":", &vendor_prefix_str); + DCHECK_EQ(6U, vendor_prefix_str.size()); + if (base::HexStringToUInt(vendor_prefix_str, &vendor_prefix)) + access_point_info->set_vendor_prefix(vendor_prefix); + else + NOTREACHED(); + } + + // Return if vendor information is not provided. + if (info.model_number.empty() && info.model_name.empty() && + info.device_name.empty() && info.oui_list.empty()) + return; + + SystemProfileProto::Network::WifiAccessPoint::VendorInformation* vendor = + access_point_info->mutable_vendor_info(); + if (!info.model_number.empty()) + vendor->set_model_number(info.model_number); + if (!info.model_name.empty()) + vendor->set_model_name(info.model_name); + if (!info.device_name.empty()) + vendor->set_device_name(info.device_name); + + // Return if OUI list is not provided. + if (info.oui_list.empty()) + return; + + // Parse OUI list. + for (const base::StringPiece& oui_str : base::SplitStringPiece( + info.oui_list, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + uint32_t oui; + if (base::HexStringToUInt(oui_str, &oui)) + vendor->add_element_identifier(oui); + else + NOTREACHED(); + } +} + +void NetworkMetricsProvider::LogAggregatedMetrics() { + base::HistogramBase* error_codes = base::SparseHistogram::FactoryGet( + "Net.ErrorCodesForMainFrame3", + base::HistogramBase::kUmaTargetedHistogramFlag); + scoped_ptr<base::HistogramSamples> samples = error_codes->SnapshotSamples(); + base::HistogramBase::Count new_aborts = + samples->GetCount(-net::ERR_ABORTED) - total_aborts_; + base::HistogramBase::Count new_codes = samples->TotalCount() - total_codes_; + if (new_codes > 0) { + UMA_HISTOGRAM_COUNTS("Net.ErrAborted.CountPerUpload", new_aborts); + UMA_HISTOGRAM_PERCENTAGE("Net.ErrAborted.ProportionPerUpload", + (100 * new_aborts) / new_codes); + total_codes_ += new_codes; + total_aborts_ += new_aborts; + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/net/network_metrics_provider.h b/chromium/components/metrics/net/network_metrics_provider.h new file mode 100644 index 00000000000..5dbb5593b24 --- /dev/null +++ b/chromium/components/metrics/net/network_metrics_provider.h @@ -0,0 +1,88 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/metrics/histogram_base.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/net/wifi_access_point_info_provider.h" +#include "components/metrics/proto/system_profile.pb.h" +#include "net/base/network_change_notifier.h" +#include "net/base/network_interfaces.h" + +namespace metrics { + +// Registers as observer with net::NetworkChangeNotifier and keeps track of +// the network environment. +class NetworkMetricsProvider + : public MetricsProvider, + public net::NetworkChangeNotifier::ConnectionTypeObserver { + public: + // Creates a NetworkMetricsProvider, where |io_task_runner| is used to post + // network info collection tasks. + explicit NetworkMetricsProvider(base::TaskRunner* io_task_runner); + ~NetworkMetricsProvider() override; + + private: + // MetricsProvider: + void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override; + void ProvideSystemProfileMetrics(SystemProfileProto* system_profile) override; + + // ConnectionTypeObserver: + void OnConnectionTypeChanged( + net::NetworkChangeNotifier::ConnectionType type) override; + + SystemProfileProto::Network::ConnectionType GetConnectionType() const; + SystemProfileProto::Network::WifiPHYLayerProtocol GetWifiPHYLayerProtocol() + const; + + // Posts a call to net::GetWifiPHYLayerProtocol on the blocking pool. + void ProbeWifiPHYLayerProtocol(); + // Callback from the blocking pool with the result of + // net::GetWifiPHYLayerProtocol. + void OnWifiPHYLayerProtocolResult(net::WifiPHYLayerProtocol mode); + + // Writes info about the wireless access points that this system is + // connected to. + void WriteWifiAccessPointProto( + const WifiAccessPointInfoProvider::WifiAccessPointInfo& info, + SystemProfileProto::Network* network_proto); + + // Logs metrics that are functions of other metrics being uploaded. + void LogAggregatedMetrics(); + + // Task runner used for blocking file I/O. + base::TaskRunner* io_task_runner_; + + // True if |connection_type_| changed during the lifetime of the log. + bool connection_type_is_ambiguous_; + // The connection type according to net::NetworkChangeNotifier. + net::NetworkChangeNotifier::ConnectionType connection_type_; + + // True if |wifi_phy_layer_protocol_| changed during the lifetime of the log. + bool wifi_phy_layer_protocol_is_ambiguous_; + // The PHY mode of the currently associated access point obtained via + // net::GetWifiPHYLayerProtocol. + net::WifiPHYLayerProtocol wifi_phy_layer_protocol_; + + // Helper object for retrieving connected wifi access point information. + scoped_ptr<WifiAccessPointInfoProvider> wifi_access_point_info_provider_; + + // These metrics track histogram totals for the Net.ErrorCodesForMainFrame3 + // histogram. They are used to compute deltas at upload time. + base::HistogramBase::Count total_aborts_; + base::HistogramBase::Count total_codes_; + + base::WeakPtrFactory<NetworkMetricsProvider> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NetworkMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/net/version_utils.cc b/chromium/components/metrics/net/version_utils.cc new file mode 100644 index 00000000000..37452330d9e --- /dev/null +++ b/chromium/components/metrics/net/version_utils.cc @@ -0,0 +1,41 @@ +// Copyright 2015 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 "components/metrics/net/version_utils.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "components/version_info/version_info.h" + +namespace metrics { + +std::string GetVersionString() { + std::string version = version_info::GetVersionNumber(); +#if defined(ARCH_CPU_64_BITS) + version += "-64"; +#endif // defined(ARCH_CPU_64_BITS) + if (!version_info::IsOfficialBuild()) + version.append("-devel"); + return version; +} + +SystemProfileProto::Channel AsProtobufChannel( + version_info::Channel channel) { + switch (channel) { + case version_info::Channel::UNKNOWN: + return SystemProfileProto::CHANNEL_UNKNOWN; + case version_info::Channel::CANARY: + return SystemProfileProto::CHANNEL_CANARY; + case version_info::Channel::DEV: + return SystemProfileProto::CHANNEL_DEV; + case version_info::Channel::BETA: + return SystemProfileProto::CHANNEL_BETA; + case version_info::Channel::STABLE: + return SystemProfileProto::CHANNEL_STABLE; + } + NOTREACHED(); + return SystemProfileProto::CHANNEL_UNKNOWN; +} + +} // namespace metrics diff --git a/chromium/components/metrics/net/version_utils.h b/chromium/components/metrics/net/version_utils.h new file mode 100644 index 00000000000..853c846a875 --- /dev/null +++ b/chromium/components/metrics/net/version_utils.h @@ -0,0 +1,29 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_NET_VERSION_UTILS_H_ +#define COMPONENTS_METRICS_NET_VERSION_UTILS_H_ + +#include <string> + +#include "components/metrics/proto/system_profile.pb.h" + +namespace version_info { +enum class Channel; +} + +namespace metrics { + +// Build a string including the Chrome app version, suffixed by "-64" on 64-bit +// platforms, and "-devel" on developer builds. +std::string GetVersionString(); + +// Translates version_info::Channel to the equivalent +// SystemProfileProto::Channel. +SystemProfileProto::Channel AsProtobufChannel( + version_info::Channel channel); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_VERSION_UTILS_H_ diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider.cc b/chromium/components/metrics/net/wifi_access_point_info_provider.cc new file mode 100644 index 00000000000..21e04b181d2 --- /dev/null +++ b/chromium/components/metrics/net/wifi_access_point_info_provider.cc @@ -0,0 +1,25 @@ +// Copyright 2014 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 "components/metrics/net/wifi_access_point_info_provider.h" + +namespace metrics { + +WifiAccessPointInfoProvider::WifiAccessPointInfo::WifiAccessPointInfo() { +} + +WifiAccessPointInfoProvider::WifiAccessPointInfo::~WifiAccessPointInfo() { +} + +WifiAccessPointInfoProvider::WifiAccessPointInfoProvider() { +} + +WifiAccessPointInfoProvider::~WifiAccessPointInfoProvider() { +} + +bool WifiAccessPointInfoProvider::GetInfo(WifiAccessPointInfo *info) { + return false; +} + +} // namespace metrics diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider.h b/chromium/components/metrics/net/wifi_access_point_info_provider.h new file mode 100644 index 00000000000..3a761da1b12 --- /dev/null +++ b/chromium/components/metrics/net/wifi_access_point_info_provider.h @@ -0,0 +1,54 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_ +#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_ + +#include <string> + +#include "base/macros.h" + +namespace metrics { + +// Interface for accessing connected wireless access point information. +class WifiAccessPointInfoProvider { + public: + // Wifi access point security mode definitions. + enum WifiSecurityMode { + WIFI_SECURITY_UNKNOWN = 0, + WIFI_SECURITY_WPA = 1, + WIFI_SECURITY_WEP = 2, + WIFI_SECURITY_RSN = 3, + WIFI_SECURITY_802_1X = 4, + WIFI_SECURITY_PSK = 5, + WIFI_SECURITY_NONE + }; + + // Information of the currently connected wifi access point. + struct WifiAccessPointInfo { + WifiAccessPointInfo(); + ~WifiAccessPointInfo(); + WifiSecurityMode security; + std::string bssid; + std::string model_number; + std::string model_name; + std::string device_name; + std::string oui_list; + }; + + WifiAccessPointInfoProvider(); + virtual ~WifiAccessPointInfoProvider(); + + // Fill in the wifi access point info if device is currently connected to a + // wifi access point. Return true if device is connected to a wifi access + // point, false otherwise. + virtual bool GetInfo(WifiAccessPointInfo *info); + + private: + DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_ diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc new file mode 100644 index 00000000000..3654e93ef4f --- /dev/null +++ b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc @@ -0,0 +1,123 @@ +// Copyright 2014 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 "components/metrics/net/wifi_access_point_info_provider_chromeos.h" + +#include <stdint.h> + +#include "base/bind.h" +#include "base/location.h" +#include "base/strings/string_number_conversions.h" +#include "chromeos/network/network_configuration_handler.h" +#include "chromeos/network/network_handler.h" +#include "chromeos/network/network_state.h" +#include "chromeos/network/network_state_handler.h" +#include "chromeos/network/shill_property_util.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using chromeos::NetworkHandler; + +namespace metrics { + +WifiAccessPointInfoProviderChromeos::WifiAccessPointInfoProviderChromeos() { + NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE); + + // Update initial connection state. + DefaultNetworkChanged( + NetworkHandler::Get()->network_state_handler()->DefaultNetwork()); +} + +WifiAccessPointInfoProviderChromeos::~WifiAccessPointInfoProviderChromeos() { + NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, + FROM_HERE); +} + +bool WifiAccessPointInfoProviderChromeos::GetInfo(WifiAccessPointInfo* info) { + // Wifi access point information is not provided if the BSSID is empty. + // This assumes the BSSID is never empty when access point information exists. + if (wifi_access_point_info_.bssid.empty()) + return false; + + *info = wifi_access_point_info_; + return true; +} + +void WifiAccessPointInfoProviderChromeos::DefaultNetworkChanged( + const chromeos::NetworkState* default_network) { + // Reset access point info to prevent reporting of out-dated data. + wifi_access_point_info_ = WifiAccessPointInfo(); + + // Skip non-wifi connections + if (!default_network || default_network->type() != shill::kTypeWifi) + return; + + // Retrieve access point info for wifi connection. + NetworkHandler::Get()->network_configuration_handler()->GetShillProperties( + default_network->path(), + base::Bind(&WifiAccessPointInfoProviderChromeos::ParseInfo, AsWeakPtr()), + chromeos::network_handler::ErrorCallback()); +} + +void WifiAccessPointInfoProviderChromeos::ParseInfo( + const std::string &service_path, + const base::DictionaryValue& properties) { + // Skip services that contain "_nomap" in the SSID. + std::string ssid = chromeos::shill_property_util::GetSSIDFromProperties( + properties, false /* verbose_logging */, nullptr); + if (ssid.find("_nomap", 0) != std::string::npos) + return; + + std::string bssid; + if (!properties.GetStringWithoutPathExpansion(shill::kWifiBSsid, &bssid) || + bssid.empty()) + return; + + // Filter out BSSID with local bit set in the first byte. + uint32_t first_octet; + if (!base::HexStringToUInt(bssid.substr(0, 2), &first_octet)) + NOTREACHED(); + if (first_octet & 0x2) + return; + wifi_access_point_info_.bssid = bssid; + + // Parse security info. + std::string security; + properties.GetStringWithoutPathExpansion( + shill::kSecurityProperty, &security); + wifi_access_point_info_.security = WIFI_SECURITY_UNKNOWN; + if (security == shill::kSecurityWpa) + wifi_access_point_info_.security = WIFI_SECURITY_WPA; + else if (security == shill::kSecurityWep) + wifi_access_point_info_.security = WIFI_SECURITY_WEP; + else if (security == shill::kSecurityRsn) + wifi_access_point_info_.security = WIFI_SECURITY_RSN; + else if (security == shill::kSecurity8021x) + wifi_access_point_info_.security = WIFI_SECURITY_802_1X; + else if (security == shill::kSecurityPsk) + wifi_access_point_info_.security = WIFI_SECURITY_PSK; + else if (security == shill::kSecurityNone) + wifi_access_point_info_.security = WIFI_SECURITY_NONE; + + properties.GetStringWithoutPathExpansion( + shill::kWifiBSsid, &wifi_access_point_info_.bssid); + const base::DictionaryValue* vendor_dict = NULL; + if (!properties.GetDictionaryWithoutPathExpansion( + shill::kWifiVendorInformationProperty, + &vendor_dict)) + return; + + vendor_dict->GetStringWithoutPathExpansion( + shill::kVendorWPSModelNumberProperty, + &wifi_access_point_info_.model_number); + vendor_dict->GetStringWithoutPathExpansion( + shill::kVendorWPSModelNameProperty, + &wifi_access_point_info_.model_name); + vendor_dict->GetStringWithoutPathExpansion( + shill::kVendorWPSDeviceNameProperty, + &wifi_access_point_info_.device_name); + vendor_dict->GetStringWithoutPathExpansion(shill::kVendorOUIListProperty, + &wifi_access_point_info_.oui_list); +} + +} // namespace metrics diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h new file mode 100644 index 00000000000..d3102397741 --- /dev/null +++ b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h @@ -0,0 +1,48 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_ +#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "chromeos/network/network_state_handler_observer.h" +#include "components/metrics/net/wifi_access_point_info_provider.h" + +namespace metrics { + +// WifiAccessPointInfoProviderChromeos provides the connected wifi +// acccess point information for chromeos. +class WifiAccessPointInfoProviderChromeos + : public WifiAccessPointInfoProvider, + public chromeos::NetworkStateHandlerObserver, + public base::SupportsWeakPtr<WifiAccessPointInfoProviderChromeos> { + public: + WifiAccessPointInfoProviderChromeos(); + ~WifiAccessPointInfoProviderChromeos() override; + + // WifiAccessPointInfoProvider: + bool GetInfo(WifiAccessPointInfo* info) override; + + // NetworkStateHandlerObserver: + void DefaultNetworkChanged( + const chromeos::NetworkState* default_network) override; + + private: + // Callback from Shill.Service.GetProperties. Parses |properties| to obtain + // the wifi access point information. + void ParseInfo(const std::string& service_path, + const base::DictionaryValue& properties); + + WifiAccessPointInfo wifi_access_point_info_; + + DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProviderChromeos); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_ diff --git a/chromium/components/metrics/persisted_logs.cc b/chromium/components/metrics/persisted_logs.cc new file mode 100644 index 00000000000..c78ecec2596 --- /dev/null +++ b/chromium/components/metrics/persisted_logs.cc @@ -0,0 +1,171 @@ +// Copyright 2014 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 "components/metrics/persisted_logs.h" + +#include <string> + +#include "base/base64.h" +#include "base/md5.h" +#include "base/metrics/histogram_macros.h" +#include "base/sha1.h" +#include "base/timer/elapsed_timer.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "third_party/zlib/google/compression_utils.h" + +namespace metrics { + +namespace { + +PersistedLogs::LogReadStatus MakeRecallStatusHistogram( + PersistedLogs::LogReadStatus status) { + UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", + status, PersistedLogs::END_RECALL_STATUS); + return status; +} + +// Reads the value at |index| from |list_value| as a string and Base64-decodes +// it into |result|. Returns true on success. +bool ReadBase64String(const base::ListValue& list_value, + size_t index, + std::string* result) { + std::string base64_result; + if (!list_value.GetString(index, &base64_result)) + return false; + return base::Base64Decode(base64_result, result); +} + +// Base64-encodes |str| and appends the result to |list_value|. +void AppendBase64String(const std::string& str, base::ListValue* list_value) { + std::string base64_str; + base::Base64Encode(str, &base64_str); + list_value->AppendString(base64_str); +} + +} // namespace + +void PersistedLogs::LogHashPair::Init(const std::string& log_data) { + DCHECK(!log_data.empty()); + + if (!compression::GzipCompress(log_data, &compressed_log_data)) { + NOTREACHED(); + return; + } + + UMA_HISTOGRAM_PERCENTAGE( + "UMA.ProtoCompressionRatio", + static_cast<int>(100 * compressed_log_data.size() / log_data.size())); + + hash = base::SHA1HashString(log_data); +} + +PersistedLogs::PersistedLogs(PrefService* local_state, + const char* pref_name, + size_t min_log_count, + size_t min_log_bytes, + size_t max_log_size) + : local_state_(local_state), + pref_name_(pref_name), + min_log_count_(min_log_count), + min_log_bytes_(min_log_bytes), + max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)), + staged_log_index_(-1) { + DCHECK(local_state_); + // One of the limit arguments must be non-zero. + DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0); +} + +PersistedLogs::~PersistedLogs() {} + +void PersistedLogs::SerializeLogs() const { + ListPrefUpdate update(local_state_, pref_name_); + WriteLogsToPrefList(update.Get()); +} + +PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() { + return ReadLogsFromPrefList(*local_state_->GetList(pref_name_)); +} + +void PersistedLogs::StoreLog(const std::string& log_data) { + list_.push_back(LogHashPair()); + list_.back().Init(log_data); +} + +void PersistedLogs::StageLog() { + // CHECK, rather than DCHECK, because swap()ing with an empty list causes + // hard-to-identify crashes much later. + CHECK(!list_.empty()); + DCHECK(!has_staged_log()); + staged_log_index_ = list_.size() - 1; + DCHECK(has_staged_log()); +} + +void PersistedLogs::DiscardStagedLog() { + DCHECK(has_staged_log()); + DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size()); + list_.erase(list_.begin() + staged_log_index_); + staged_log_index_ = -1; +} + +void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const { + list_value->Clear(); + + // Keep the most recent logs which are smaller than |max_log_size_|. + // We keep at least |min_log_bytes_| and |min_log_count_| of logs before + // discarding older logs. + size_t start = list_.size(); + size_t saved_log_count = 0; + size_t bytes_used = 0; + for (; start > 0; --start) { + size_t log_size = list_[start - 1].compressed_log_data.length(); + if (bytes_used >= min_log_bytes_ && + saved_log_count >= min_log_count_) { + break; + } + // Oversized logs won't be persisted, so don't count them. + if (log_size > max_log_size_) + continue; + bytes_used += log_size; + ++saved_log_count; + } + + for (size_t i = start; i < list_.size(); ++i) { + size_t log_size = list_[i].compressed_log_data.length(); + if (log_size > max_log_size_) { + UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", + static_cast<int>(log_size)); + continue; + } + AppendBase64String(list_[i].compressed_log_data, list_value); + AppendBase64String(list_[i].hash, list_value); + } +} + +PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList( + const base::ListValue& list_value) { + if (list_value.empty()) + return MakeRecallStatusHistogram(LIST_EMPTY); + + // For each log, there's two entries in the list (the data and the hash). + DCHECK_EQ(0U, list_value.GetSize() % 2); + const size_t log_count = list_value.GetSize() / 2; + + // Resize |list_| ahead of time, so that values can be decoded directly into + // the elements of the list. + DCHECK(list_.empty()); + list_.resize(log_count); + + for (size_t i = 0; i < log_count; ++i) { + if (!ReadBase64String(list_value, i * 2, &list_[i].compressed_log_data) || + !ReadBase64String(list_value, i * 2 + 1, &list_[i].hash)) { + list_.clear(); + return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); + } + } + + return MakeRecallStatusHistogram(RECALL_SUCCESS); +} + +} // namespace metrics diff --git a/chromium/components/metrics/persisted_logs.h b/chromium/components/metrics/persisted_logs.h new file mode 100644 index 00000000000..73ad79a5b06 --- /dev/null +++ b/chromium/components/metrics/persisted_logs.h @@ -0,0 +1,143 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_PERSISTED_LOGS_H_ +#define COMPONENTS_METRICS_PERSISTED_LOGS_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/values.h" + +class PrefService; + +namespace metrics { + +// Maintains a list of unsent logs that are written and restored from disk. +class PersistedLogs { + public: + // Used to produce a histogram that keeps track of the status of recalling + // persisted per logs. + enum LogReadStatus { + RECALL_SUCCESS, // We were able to correctly recall a persisted log. + LIST_EMPTY, // Attempting to recall from an empty list. + LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger(). + LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3). + LIST_SIZE_CORRUPTION, // List size is not as expected. + LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString(). + CHECKSUM_CORRUPTION, // Failed to verify checksum. + CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using + // GetAsString(). + DECODE_FAIL, // Failed to decode log. + DEPRECATED_XML_PROTO_MISMATCH, // The XML and protobuf logs have + // inconsistent data. + END_RECALL_STATUS // Number of bins to use to create the histogram. + }; + + // Constructs a PersistedLogs that stores data in |local_state| under the + // preference |pref_name|. + // Calling code is responsible for ensuring that the lifetime of |local_state| + // is longer than the lifetime of PersistedLogs. + // + // When saving logs to disk, stores either the first |min_log_count| logs, or + // at least |min_log_bytes| bytes of logs, whichever is greater. + // + // If the optional |max_log_size| parameter is non-zero, all logs larger than + // that limit will be skipped when writing to disk. + PersistedLogs(PrefService* local_state, + const char* pref_name, + size_t min_log_count, + size_t min_log_bytes, + size_t max_log_size); + ~PersistedLogs(); + + // Write list to storage. + void SerializeLogs() const; + + // Reads the list from the preference. + LogReadStatus DeserializeLogs(); + + // Adds a log to the list. + void StoreLog(const std::string& log_data); + + // Stages the most recent log. The staged_log will remain the same even if + // additional logs are added. + void StageLog(); + + // Remove the staged log. + void DiscardStagedLog(); + + // True if a log has been staged. + bool has_staged_log() const { return staged_log_index_ != -1; } + + // Returns the element in the front of the list. + const std::string& staged_log() const { + DCHECK(has_staged_log()); + return list_[staged_log_index_].compressed_log_data; + } + + // Returns the element in the front of the list. + const std::string& staged_log_hash() const { + DCHECK(has_staged_log()); + return list_[staged_log_index_].hash; + } + + // The number of elements currently stored. + size_t size() const { return list_.size(); } + + // True if there are no stored logs. + bool empty() const { return list_.empty(); } + + private: + // Writes the list to the ListValue. + void WriteLogsToPrefList(base::ListValue* list) const; + + // Reads the list from the ListValue. + LogReadStatus ReadLogsFromPrefList(const base::ListValue& list); + + // A weak pointer to the PrefService object to read and write the preference + // from. Calling code should ensure this object continues to exist for the + // lifetime of the PersistedLogs object. + PrefService* local_state_; + + // The name of the preference to serialize logs to/from. + const char* pref_name_; + + // We will keep at least this |min_log_count_| logs or |min_log_bytes_| bytes + // of logs, whichever is greater, when writing to disk. These apply after + // skipping logs greater than |max_log_size_|. + const size_t min_log_count_; + const size_t min_log_bytes_; + + // Logs greater than this size will not be written to disk. + const size_t max_log_size_; + + struct LogHashPair { + // Initializes the members based on uncompressed |log_data|. + void Init(const std::string& log_data); + + // Compressed log data - a serialized protobuf that's been gzipped. + std::string compressed_log_data; + + // The SHA1 hash of log, stored to catch errors from memory corruption. + std::string hash; + }; + // A list of all of the stored logs, stored with SHA1 hashes to check for + // corruption while they are stored in memory. + std::vector<LogHashPair> list_; + + // The index and type of the log staged for upload. If nothing has been + // staged, the index will be -1. + int staged_log_index_; + + DISALLOW_COPY_AND_ASSIGN(PersistedLogs); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PERSISTED_LOGS_H_ diff --git a/chromium/components/metrics/persisted_logs_unittest.cc b/chromium/components/metrics/persisted_logs_unittest.cc new file mode 100644 index 00000000000..b4a79a73ea6 --- /dev/null +++ b/chromium/components/metrics/persisted_logs_unittest.cc @@ -0,0 +1,289 @@ +// Copyright 2014 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 "components/metrics/persisted_logs.h" + +#include <stddef.h> + +#include "base/base64.h" +#include "base/macros.h" +#include "base/rand_util.h" +#include "base/sha1.h" +#include "base/values.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/zlib/google/compression_utils.h" + +namespace metrics { + +namespace { + +const char kTestPrefName[] = "TestPref"; +const size_t kLogCountLimit = 3; +const size_t kLogByteLimit = 1000; + +// Compresses |log_data| and returns the result. +std::string Compress(const std::string& log_data) { + std::string compressed_log_data; + EXPECT_TRUE(compression::GzipCompress(log_data, &compressed_log_data)); + return compressed_log_data; +} + +// Generates and returns log data such that its size after compression is at +// least |min_compressed_size|. +std::string GenerateLogWithMinCompressedSize(size_t min_compressed_size) { + // Since the size check is done against a compressed log, generate enough + // data that compresses to larger than |log_size|. + std::string rand_bytes = base::RandBytesAsString(min_compressed_size); + while (Compress(rand_bytes).size() < min_compressed_size) + rand_bytes.append(base::RandBytesAsString(min_compressed_size)); + std::string base64_data_for_logging; + base::Base64Encode(rand_bytes, &base64_data_for_logging); + SCOPED_TRACE(testing::Message() << "Using random data " + << base64_data_for_logging); + return rand_bytes; +} + +class PersistedLogsTest : public testing::Test { + public: + PersistedLogsTest() { + prefs_.registry()->RegisterListPref(kTestPrefName); + } + + protected: + TestingPrefServiceSimple prefs_; + + private: + DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest); +}; + +class TestPersistedLogs : public PersistedLogs { + public: + TestPersistedLogs(PrefService* service, size_t min_log_bytes) + : PersistedLogs(service, kTestPrefName, kLogCountLimit, min_log_bytes, + 0) { + } + + // Stages and removes the next log, while testing it's value. + void ExpectNextLog(const std::string& expected_log) { + StageLog(); + EXPECT_EQ(staged_log(), Compress(expected_log)); + DiscardStagedLog(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestPersistedLogs); +}; + +} // namespace + +// Store and retrieve empty list_value. +TEST_F(PersistedLogsTest, EmptyLogList) { + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + + persisted_logs.SerializeLogs(); + const base::ListValue* list_value = prefs_.GetList(kTestPrefName); + EXPECT_EQ(0U, list_value->GetSize()); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::LIST_EMPTY, result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(0U, result_persisted_logs.size()); +} + +// Store and retrieve a single log value. +TEST_F(PersistedLogsTest, SingleElementLogList) { + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + + persisted_logs.StoreLog("Hello world!"); + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(1U, result_persisted_logs.size()); + + // Verify that the result log matches the initial log. + persisted_logs.StageLog(); + result_persisted_logs.StageLog(); + EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log()); + EXPECT_EQ(persisted_logs.staged_log_hash(), + result_persisted_logs.staged_log_hash()); +} + +// Store a set of logs over the length limit, but smaller than the min number of +// bytes. +TEST_F(PersistedLogsTest, LongButTinyLogList) { + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + + size_t log_count = kLogCountLimit * 5; + for (size_t i = 0; i < log_count; ++i) + persisted_logs.StoreLog("x"); + + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size()); + + result_persisted_logs.ExpectNextLog("x"); +} + +// Store a set of logs over the length limit, but that doesn't reach the minimum +// number of bytes until after passing the length limit. +TEST_F(PersistedLogsTest, LongButSmallLogList) { + size_t log_count = kLogCountLimit * 5; + size_t log_size = 50; + + std::string first_kept = "First to keep"; + first_kept.resize(log_size, ' '); + + std::string blank_log = std::string(log_size, ' '); + + std::string last_kept = "Last to keep"; + last_kept.resize(log_size, ' '); + + // Set the byte limit enough to keep everything but the first two logs. + const size_t min_log_bytes = + Compress(first_kept).length() + Compress(last_kept).length() + + (log_count - 4) * Compress(blank_log).length(); + TestPersistedLogs persisted_logs(&prefs_, min_log_bytes); + + persisted_logs.StoreLog("one"); + persisted_logs.StoreLog("two"); + persisted_logs.StoreLog(first_kept); + for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) { + persisted_logs.StoreLog(blank_log); + } + persisted_logs.StoreLog(last_kept); + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size()); + + result_persisted_logs.ExpectNextLog(last_kept); + while (result_persisted_logs.size() > 1) { + result_persisted_logs.ExpectNextLog(blank_log); + } + result_persisted_logs.ExpectNextLog(first_kept); +} + +// Store a set of logs within the length limit, but well over the minimum +// number of bytes. +TEST_F(PersistedLogsTest, ShortButLargeLogList) { + // Make the total byte count about twice the minimum. + size_t log_count = kLogCountLimit; + size_t log_size = (kLogByteLimit / log_count) * 2; + std::string log_data = GenerateLogWithMinCompressedSize(log_size); + + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + for (size_t i = 0; i < log_count; ++i) { + persisted_logs.StoreLog(log_data); + } + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size()); +} + +// Store a set of logs over the length limit, and over the minimum number of +// bytes. +TEST_F(PersistedLogsTest, LongAndLargeLogList) { + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + + // Include twice the max number of logs. + size_t log_count = kLogCountLimit * 2; + // Make the total byte count about four times the minimum. + size_t log_size = (kLogByteLimit / log_count) * 4; + + std::string target_log = "First to keep"; + target_log += GenerateLogWithMinCompressedSize(log_size); + + std::string log_data = GenerateLogWithMinCompressedSize(log_size); + for (size_t i = 0; i < log_count; ++i) { + if (i == log_count - kLogCountLimit) + persisted_logs.StoreLog(target_log); + else + persisted_logs.StoreLog(log_data); + } + + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(kLogCountLimit, result_persisted_logs.size()); + + while (result_persisted_logs.size() > 1) { + result_persisted_logs.ExpectNextLog(log_data); + } + result_persisted_logs.ExpectNextLog(target_log); +} + +// Check that the store/stage/discard functions work as expected. +TEST_F(PersistedLogsTest, Staging) { + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + std::string tmp; + + EXPECT_FALSE(persisted_logs.has_staged_log()); + persisted_logs.StoreLog("one"); + EXPECT_FALSE(persisted_logs.has_staged_log()); + persisted_logs.StoreLog("two"); + persisted_logs.StageLog(); + EXPECT_TRUE(persisted_logs.has_staged_log()); + EXPECT_EQ(persisted_logs.staged_log(), Compress("two")); + persisted_logs.StoreLog("three"); + EXPECT_EQ(persisted_logs.staged_log(), Compress("two")); + EXPECT_EQ(persisted_logs.size(), 3U); + persisted_logs.DiscardStagedLog(); + EXPECT_FALSE(persisted_logs.has_staged_log()); + EXPECT_EQ(persisted_logs.size(), 2U); + persisted_logs.StageLog(); + EXPECT_EQ(persisted_logs.staged_log(), Compress("three")); + persisted_logs.DiscardStagedLog(); + persisted_logs.StageLog(); + EXPECT_EQ(persisted_logs.staged_log(), Compress("one")); + persisted_logs.DiscardStagedLog(); + EXPECT_FALSE(persisted_logs.has_staged_log()); + EXPECT_EQ(persisted_logs.size(), 0U); +} + +TEST_F(PersistedLogsTest, DiscardOrder) { + // Ensure that the correct log is discarded if new logs are pushed while + // a log is staged. + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + + persisted_logs.StoreLog("one"); + persisted_logs.StageLog(); + persisted_logs.StoreLog("two"); + persisted_logs.DiscardStagedLog(); + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(1U, result_persisted_logs.size()); + result_persisted_logs.ExpectNextLog("two"); +} + + +TEST_F(PersistedLogsTest, Hashes) { + const char kFooText[] = "foo"; + const std::string foo_hash = base::SHA1HashString(kFooText); + + TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); + persisted_logs.StoreLog(kFooText); + persisted_logs.StageLog(); + + EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log()); + EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/content/DEPS b/chromium/components/metrics/profiler/content/DEPS new file mode 100644 index 00000000000..c74cf95b862 --- /dev/null +++ b/chromium/components/metrics/profiler/content/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+components/nacl/common", + "+content/public/browser", + "+content/public/common", + "+content/public/test", +] diff --git a/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc new file mode 100644 index 00000000000..e1449e30572 --- /dev/null +++ b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc @@ -0,0 +1,97 @@ +// Copyright 2015 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 "components/metrics/profiler/content/content_tracking_synchronizer_delegate.h" + +#include "components/metrics/profiler/tracking_synchronizer.h" +#include "components/nacl/common/nacl_process_type.h" +#include "content/public/browser/profiler_controller.h" +#include "content/public/common/process_type.h" + +namespace metrics { + +namespace { + +ProfilerEventProto::TrackedObject::ProcessType AsProtobufProcessType( + int process_type) { + switch (process_type) { + case content::PROCESS_TYPE_BROWSER: + return ProfilerEventProto::TrackedObject::BROWSER; + case content::PROCESS_TYPE_RENDERER: + return ProfilerEventProto::TrackedObject::RENDERER; + case content::PROCESS_TYPE_UTILITY: + return ProfilerEventProto::TrackedObject::UTILITY; + case content::PROCESS_TYPE_ZYGOTE: + return ProfilerEventProto::TrackedObject::ZYGOTE; + case content::PROCESS_TYPE_SANDBOX_HELPER: + return ProfilerEventProto::TrackedObject::SANDBOX_HELPER; + case content::PROCESS_TYPE_GPU: + return ProfilerEventProto::TrackedObject::GPU; + case content::PROCESS_TYPE_PPAPI_PLUGIN: + return ProfilerEventProto::TrackedObject::PPAPI_PLUGIN; + case content::PROCESS_TYPE_PPAPI_BROKER: + return ProfilerEventProto::TrackedObject::PPAPI_BROKER; + case PROCESS_TYPE_NACL_LOADER: + return ProfilerEventProto::TrackedObject::NACL_LOADER; + case PROCESS_TYPE_NACL_BROKER: + return ProfilerEventProto::TrackedObject::NACL_BROKER; + default: + NOTREACHED(); + return ProfilerEventProto::TrackedObject::UNKNOWN; + } +} + +} // namespace + +// static +scoped_ptr<TrackingSynchronizerDelegate> +ContentTrackingSynchronizerDelegate::Create( + TrackingSynchronizer* synchronizer) { + return make_scoped_ptr(new ContentTrackingSynchronizerDelegate(synchronizer)); +} + +ContentTrackingSynchronizerDelegate::ContentTrackingSynchronizerDelegate( + TrackingSynchronizer* synchronizer) + : synchronizer_(synchronizer) { + DCHECK(synchronizer_); + content::ProfilerController::GetInstance()->Register(this); +} + +ContentTrackingSynchronizerDelegate::~ContentTrackingSynchronizerDelegate() { + content::ProfilerController::GetInstance()->Unregister(this); +} + +void ContentTrackingSynchronizerDelegate::GetProfilerDataForChildProcesses( + int sequence_number, + int current_profiling_phase) { + // Get profiler data from renderer and browser child processes. + content::ProfilerController::GetInstance()->GetProfilerData( + sequence_number, current_profiling_phase); +} + +void ContentTrackingSynchronizerDelegate::OnProfilingPhaseCompleted( + int profiling_phase) { + // Notify renderer and browser child processes. + content::ProfilerController::GetInstance()->OnProfilingPhaseCompleted( + profiling_phase); +} + +void ContentTrackingSynchronizerDelegate::OnPendingProcesses( + int sequence_number, + int pending_processes, + bool end) { + // Notify |synchronizer_|. + synchronizer_->OnPendingProcesses(sequence_number, pending_processes, end); +} + +void ContentTrackingSynchronizerDelegate::OnProfilerDataCollected( + int sequence_number, + const tracked_objects::ProcessDataSnapshot& profiler_data, + content::ProcessType process_type) { + // Notify |synchronizer_|. + synchronizer_->OnProfilerDataCollected(sequence_number, profiler_data, + AsProtobufProcessType(process_type)); +} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h new file mode 100644 index 00000000000..c648347c8cf --- /dev/null +++ b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h @@ -0,0 +1,52 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_ +#define COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/profiler/tracking_synchronizer_delegate.h" +#include "content/public/browser/profiler_subscriber.h" + +namespace metrics { + +// Provides an implementation of TrackingSynchronizerDelegate for use on +// //content-based platforms. Specifically, drives the tracking of profiler data +// for child processes by interacting with content::ProfilerController. +class ContentTrackingSynchronizerDelegate : public TrackingSynchronizerDelegate, + public content::ProfilerSubscriber { + public: + ~ContentTrackingSynchronizerDelegate() override; + + // Creates a ContentTrackingSynchronizerDelegate that is associated with + // |synchronizer_|. + static scoped_ptr<TrackingSynchronizerDelegate> Create( + TrackingSynchronizer* synchronizer); + + private: + ContentTrackingSynchronizerDelegate(TrackingSynchronizer* synchronizer); + + // TrackingSynchronizerDelegate: + void GetProfilerDataForChildProcesses(int sequence_number, + int current_profiling_phase) override; + void OnProfilingPhaseCompleted(int profiling_phase) override; + + // content::ProfilerSubscriber: + void OnPendingProcesses(int sequence_number, + int pending_processes, + bool end) override; + void OnProfilerDataCollected( + int sequence_number, + const tracked_objects::ProcessDataSnapshot& profiler_data, + content::ProcessType process_type) override; + + TrackingSynchronizer* const synchronizer_; + + DISALLOW_COPY_AND_ASSIGN(ContentTrackingSynchronizerDelegate); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_ diff --git a/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc new file mode 100644 index 00000000000..881d92f3e6f --- /dev/null +++ b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc @@ -0,0 +1,35 @@ +// Copyright 2015 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 "components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h" +#include "components/metrics/profiler/tracking_synchronizer.h" + +namespace metrics { + +// static +scoped_ptr<TrackingSynchronizerDelegate> +IOSTrackingSynchronizerDelegate::Create(TrackingSynchronizer* synchronizer) { + return make_scoped_ptr(new IOSTrackingSynchronizerDelegate(synchronizer)); +} + +IOSTrackingSynchronizerDelegate::IOSTrackingSynchronizerDelegate( + TrackingSynchronizer* synchronizer) + : synchronizer_(synchronizer) { + DCHECK(synchronizer_); +} + +IOSTrackingSynchronizerDelegate::~IOSTrackingSynchronizerDelegate() {} + +void IOSTrackingSynchronizerDelegate::GetProfilerDataForChildProcesses( + int sequence_number, + int current_profiling_phase) { + // Notify |synchronizer_| that there are no processes pending (on iOS, there + // is only the browser process). + synchronizer_->OnPendingProcesses(sequence_number, 0, true); +} + +void IOSTrackingSynchronizerDelegate::OnProfilingPhaseCompleted( + int profiling_phase) {} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h new file mode 100644 index 00000000000..d08476d839d --- /dev/null +++ b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h @@ -0,0 +1,40 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_ +#define COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/profiler/tracking_synchronizer_delegate.h" + +namespace metrics { + +// Provides an implementation of TrackingSynchronizerDelegate for usage on iOS. +// This implementation is minimal, as on iOS there are no child processes. +class IOSTrackingSynchronizerDelegate : public TrackingSynchronizerDelegate { + public: + ~IOSTrackingSynchronizerDelegate() override; + + // Creates an IOSTrackingSynchronizerDelegate that is associated with + // |synchronizer_|. + static scoped_ptr<TrackingSynchronizerDelegate> Create( + TrackingSynchronizer* synchronizer); + + private: + IOSTrackingSynchronizerDelegate(TrackingSynchronizer* synchronizer); + + // TrackingSynchronizerDelegate: + void GetProfilerDataForChildProcesses(int sequence_number, + int current_profiling_phase) override; + void OnProfilingPhaseCompleted(int profiling_phase) override; + + TrackingSynchronizer* const synchronizer_; + + DISALLOW_COPY_AND_ASSIGN(IOSTrackingSynchronizerDelegate); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_ diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider.cc b/chromium/components/metrics/profiler/profiler_metrics_provider.cc new file mode 100644 index 00000000000..df64e293aee --- /dev/null +++ b/chromium/components/metrics/profiler/profiler_metrics_provider.cc @@ -0,0 +1,135 @@ +// Copyright 2014 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 "components/metrics/profiler/profiler_metrics_provider.h" + +#include <ctype.h> +#include <stddef.h> +#include <string> +#include <vector> + +#include "base/stl_util.h" +#include "base/tracked_objects.h" +#include "components/metrics/metrics_log.h" + +namespace metrics { +namespace { + +// Maps a thread name by replacing trailing sequence of digits with "*". +// Examples: +// 1. "BrowserBlockingWorker1/23857" => "BrowserBlockingWorker1/*" +// 2. "Chrome_IOThread" => "Chrome_IOThread" +std::string MapThreadName(const std::string& thread_name) { + size_t i = thread_name.length(); + + while (i > 0 && isdigit(thread_name[i - 1])) { + --i; + } + + if (i == thread_name.length()) + return thread_name; + + return thread_name.substr(0, i) + '*'; +} + +// Normalizes a source filename (which is platform- and build-method-dependent) +// by extracting the last component of the full file name. +// Example: "c:\b\build\slave\win\build\src\chrome\app\chrome_main.cc" => +// "chrome_main.cc". +std::string NormalizeFileName(const std::string& file_name) { + const size_t offset = file_name.find_last_of("\\/"); + return offset != std::string::npos ? file_name.substr(offset + 1) : file_name; +} + +void WriteProfilerData( + const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase, + base::ProcessId process_id, + ProfilerEventProto::TrackedObject::ProcessType process_type, + ProfilerEventProto* performance_profile) { + for (const auto& task : process_data_phase.tasks) { + const tracked_objects::DeathDataSnapshot& death_data = task.death_data; + ProfilerEventProto::TrackedObject* tracked_object = + performance_profile->add_tracked_object(); + tracked_object->set_birth_thread_name_hash( + MetricsLog::Hash(MapThreadName(task.birth.thread_name))); + tracked_object->set_exec_thread_name_hash( + MetricsLog::Hash(MapThreadName(task.death_thread_name))); + tracked_object->set_source_file_name_hash( + MetricsLog::Hash(NormalizeFileName(task.birth.location.file_name))); + tracked_object->set_source_function_name_hash( + MetricsLog::Hash(task.birth.location.function_name)); + tracked_object->set_source_line_number(task.birth.location.line_number); + tracked_object->set_exec_count(death_data.count); + tracked_object->set_exec_time_total(death_data.run_duration_sum); + tracked_object->set_exec_time_sampled(death_data.run_duration_sample); + tracked_object->set_queue_time_total(death_data.queue_duration_sum); + tracked_object->set_queue_time_sampled(death_data.queue_duration_sample); + tracked_object->set_process_type(process_type); + tracked_object->set_process_id(process_id); + } +} + +} // namespace + +ProfilerMetricsProvider::ProfilerMetricsProvider() { +} + +ProfilerMetricsProvider::ProfilerMetricsProvider( + const base::Callback<bool(void)>& cellular_callback) + : cellular_callback_(cellular_callback) { +} + +ProfilerMetricsProvider::~ProfilerMetricsProvider() { +} + +void ProfilerMetricsProvider::ProvideGeneralMetrics( + ChromeUserMetricsExtension* uma_proto) { + DCHECK_EQ(0, uma_proto->profiler_event_size()); + + for (auto& event : profiler_events_cache_) { + uma_proto->add_profiler_event()->Swap(&event.second); + } + + profiler_events_cache_.clear(); +} + +void ProfilerMetricsProvider::RecordProfilerData( + const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase, + base::ProcessId process_id, + ProfilerEventProto::TrackedObject::ProcessType process_type, + int profiling_phase, + base::TimeDelta phase_start, + base::TimeDelta phase_finish, + const ProfilerEvents& past_events) { + // Omit profiler data on connections where it's likely to cost the user money + // for us to upload it. + if (IsCellularLogicEnabled()) + return; + + const bool new_phase = !ContainsKey(profiler_events_cache_, profiling_phase); + ProfilerEventProto* profiler_event = &profiler_events_cache_[profiling_phase]; + + if (new_phase) { + profiler_event->set_profile_version( + ProfilerEventProto::VERSION_SPLIT_PROFILE); + profiler_event->set_time_source(ProfilerEventProto::WALL_CLOCK_TIME); + profiler_event->set_profiling_start_ms(phase_start.InMilliseconds()); + profiler_event->set_profiling_finish_ms(phase_finish.InMilliseconds()); + for (const auto& event : past_events) { + profiler_event->add_past_session_event(event); + } + } + + WriteProfilerData(process_data_phase, process_id, process_type, + profiler_event); +} + +bool ProfilerMetricsProvider::IsCellularLogicEnabled() { + if (cellular_callback_.is_null()) + return false; + + return cellular_callback_.Run(); +} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider.h b/chromium/components/metrics/profiler/profiler_metrics_provider.h new file mode 100644 index 00000000000..8585fce5549 --- /dev/null +++ b/chromium/components/metrics/profiler/profiler_metrics_provider.h @@ -0,0 +1,65 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/profiler/tracking_synchronizer_observer.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +namespace tracked_objects { +struct ProcessDataPhaseSnapshot; +} + +namespace metrics { + +// ProfilerMetricsProvider is responsible for filling out the |profiler_event| +// section of the UMA proto. +class ProfilerMetricsProvider : public MetricsProvider { + public: + explicit ProfilerMetricsProvider( + const base::Callback<bool(void)>& cellular_callback); + // Creates profiler metrics provider with a null callback. + ProfilerMetricsProvider(); + ~ProfilerMetricsProvider() override; + + // MetricsDataProvider: + void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override; + + // Records the passed profiled data, which should be a snapshot of the + // browser's profiled performance during startup for a single process. + void RecordProfilerData( + const tracked_objects::ProcessDataPhaseSnapshot& process_data, + base::ProcessId process_id, + ProfilerEventProto::TrackedObject::ProcessType process_type, + int profiling_phase, + base::TimeDelta phase_start, + base::TimeDelta phase_finish, + const ProfilerEvents& past_events); + + private: + // Returns true if current connection type is cellular and user is assigned to + // experimental group for enabled cellular uploads according to + // |cellular_callback_|. + bool IsCellularLogicEnabled(); + + // Saved cache of generated Profiler event protos, to be copied into the UMA + // proto when ProvideGeneralMetrics() is called. The map is from a profiling + // phase id to the profiler event proto that represents profiler data for the + // profiling phase. + std::map<int, ProfilerEventProto> profiler_events_cache_; + + // Returns true if current connection type is cellular and user is assigned to + // experimental group for enabled cellular uploads. + base::Callback<bool(void)> cellular_callback_; + + DISALLOW_COPY_AND_ASSIGN(ProfilerMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc b/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc new file mode 100644 index 00000000000..e2e31b922eb --- /dev/null +++ b/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc @@ -0,0 +1,277 @@ +// Copyright 2014 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 "components/metrics/profiler/profiler_metrics_provider.h" + +#include <stdint.h> + +#include "base/bind.h" +#include "base/metrics/metrics_hashes.h" +#include "base/tracked_objects.h" +#include "testing/gtest/include/gtest/gtest.h" + +using tracked_objects::ProcessDataPhaseSnapshot; +using tracked_objects::TaskSnapshot; + +namespace metrics { + +TEST(ProfilerMetricsProviderTest, RecordData) { + // WARNING: If you broke the below check, you've modified how + // HashMetricName works. Please also modify all server-side code that + // relies on the existing way of hashing. + EXPECT_EQ(UINT64_C(1518842999910132863), + base::HashMetricName("birth_thread*")); + + ProfilerMetricsProvider profiler_metrics_provider; + + { + // Add data from the browser process. + ProcessDataPhaseSnapshot process_data_phase; + process_data_phase.tasks.push_back(TaskSnapshot()); + process_data_phase.tasks.back().birth.location.file_name = "a/b/file.h"; + process_data_phase.tasks.back().birth.location.function_name = "function"; + process_data_phase.tasks.back().birth.location.line_number = 1337; + process_data_phase.tasks.back().birth.thread_name = "birth_thread"; + process_data_phase.tasks.back().death_data.count = 37; + process_data_phase.tasks.back().death_data.run_duration_sum = 31; + process_data_phase.tasks.back().death_data.run_duration_max = 17; + process_data_phase.tasks.back().death_data.run_duration_sample = 13; + process_data_phase.tasks.back().death_data.queue_duration_sum = 8; + process_data_phase.tasks.back().death_data.queue_duration_max = 5; + process_data_phase.tasks.back().death_data.queue_duration_sample = 3; + process_data_phase.tasks.back().death_thread_name = "Still_Alive"; + process_data_phase.tasks.push_back(TaskSnapshot()); + process_data_phase.tasks.back().birth.location.file_name = "c\\d\\file2"; + process_data_phase.tasks.back().birth.location.function_name = "function2"; + process_data_phase.tasks.back().birth.location.line_number = 1773; + process_data_phase.tasks.back().birth.thread_name = "birth_thread2"; + process_data_phase.tasks.back().death_data.count = 19; + process_data_phase.tasks.back().death_data.run_duration_sum = 23; + process_data_phase.tasks.back().death_data.run_duration_max = 11; + process_data_phase.tasks.back().death_data.run_duration_sample = 7; + process_data_phase.tasks.back().death_data.queue_duration_sum = 0; + process_data_phase.tasks.back().death_data.queue_duration_max = 0; + process_data_phase.tasks.back().death_data.queue_duration_sample = 0; + process_data_phase.tasks.back().death_thread_name = "death_thread"; + + profiler_metrics_provider.RecordProfilerData( + process_data_phase, 177, ProfilerEventProto::TrackedObject::BROWSER, 0, + base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(2), + ProfilerEvents()); + } + + { + // Add second phase from the browser process. + ProcessDataPhaseSnapshot process_data_phase; + process_data_phase.tasks.push_back(TaskSnapshot()); + process_data_phase.tasks.back().birth.location.file_name = "a/b/file10.h"; + process_data_phase.tasks.back().birth.location.function_name = "function10"; + process_data_phase.tasks.back().birth.location.line_number = 101337; + process_data_phase.tasks.back().birth.thread_name = "birth_thread_ten"; + process_data_phase.tasks.back().death_data.count = 1037; + process_data_phase.tasks.back().death_data.run_duration_sum = 1031; + process_data_phase.tasks.back().death_data.run_duration_max = 1017; + process_data_phase.tasks.back().death_data.run_duration_sample = 1013; + process_data_phase.tasks.back().death_data.queue_duration_sum = 108; + process_data_phase.tasks.back().death_data.queue_duration_max = 105; + process_data_phase.tasks.back().death_data.queue_duration_sample = 103; + process_data_phase.tasks.back().death_thread_name = "Already_Dead"; + process_data_phase.tasks.push_back(TaskSnapshot()); + process_data_phase.tasks.back().birth.location.file_name = "c\\d\\file210"; + process_data_phase.tasks.back().birth.location.function_name = + "function210"; + process_data_phase.tasks.back().birth.location.line_number = 101773; + process_data_phase.tasks.back().birth.thread_name = "birth_thread_ten2"; + process_data_phase.tasks.back().death_data.count = 1019; + process_data_phase.tasks.back().death_data.run_duration_sum = 1023; + process_data_phase.tasks.back().death_data.run_duration_max = 1011; + process_data_phase.tasks.back().death_data.run_duration_sample = 107; + process_data_phase.tasks.back().death_data.queue_duration_sum = 100; + process_data_phase.tasks.back().death_data.queue_duration_max = 100; + process_data_phase.tasks.back().death_data.queue_duration_sample = 100; + process_data_phase.tasks.back().death_thread_name = "death_thread_ten"; + + profiler_metrics_provider.RecordProfilerData( + process_data_phase, 177, ProfilerEventProto::TrackedObject::BROWSER, 1, + base::TimeDelta::FromMinutes(10), base::TimeDelta::FromMinutes(20), + ProfilerEvents(1, ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT)); + } + + { + // Add data from a renderer process. + ProcessDataPhaseSnapshot process_data_phase; + process_data_phase.tasks.push_back(TaskSnapshot()); + process_data_phase.tasks.back().birth.location.file_name = "file3"; + process_data_phase.tasks.back().birth.location.function_name = "function3"; + process_data_phase.tasks.back().birth.location.line_number = 7331; + process_data_phase.tasks.back().birth.thread_name = "birth_thread3"; + process_data_phase.tasks.back().death_data.count = 137; + process_data_phase.tasks.back().death_data.run_duration_sum = 131; + process_data_phase.tasks.back().death_data.run_duration_max = 117; + process_data_phase.tasks.back().death_data.run_duration_sample = 113; + process_data_phase.tasks.back().death_data.queue_duration_sum = 108; + process_data_phase.tasks.back().death_data.queue_duration_max = 105; + process_data_phase.tasks.back().death_data.queue_duration_sample = 103; + process_data_phase.tasks.back().death_thread_name = "death_thread3"; + process_data_phase.tasks.push_back(TaskSnapshot()); + process_data_phase.tasks.back().birth.location.file_name = ""; + process_data_phase.tasks.back().birth.location.function_name = ""; + process_data_phase.tasks.back().birth.location.line_number = 7332; + process_data_phase.tasks.back().birth.thread_name = ""; + process_data_phase.tasks.back().death_data.count = 138; + process_data_phase.tasks.back().death_data.run_duration_sum = 132; + process_data_phase.tasks.back().death_data.run_duration_max = 118; + process_data_phase.tasks.back().death_data.run_duration_sample = 114; + process_data_phase.tasks.back().death_data.queue_duration_sum = 109; + process_data_phase.tasks.back().death_data.queue_duration_max = 106; + process_data_phase.tasks.back().death_data.queue_duration_sample = 104; + process_data_phase.tasks.back().death_thread_name = ""; + + profiler_metrics_provider.RecordProfilerData( + process_data_phase, 1177, ProfilerEventProto::TrackedObject::RENDERER, + 0, base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(2), + ProfilerEvents()); + } + + // Capture the data and verify that it is as expected. + ChromeUserMetricsExtension uma_proto; + profiler_metrics_provider.ProvideGeneralMetrics(&uma_proto); + + // Phase 0 + ASSERT_EQ(2, uma_proto.profiler_event_size()); + + EXPECT_EQ(ProfilerEventProto::VERSION_SPLIT_PROFILE, + uma_proto.profiler_event(0).profile_version()); + EXPECT_EQ(ProfilerEventProto::WALL_CLOCK_TIME, + uma_proto.profiler_event(0).time_source()); + ASSERT_EQ(0, uma_proto.profiler_event(0).past_session_event_size()); + ASSERT_EQ(60000, uma_proto.profiler_event(0).profiling_start_ms()); + ASSERT_EQ(120000, uma_proto.profiler_event(0).profiling_finish_ms()); + ASSERT_EQ(4, uma_proto.profiler_event(0).tracked_object_size()); + + const ProfilerEventProto::TrackedObject* tracked_object = + &uma_proto.profiler_event(0).tracked_object(0); + EXPECT_EQ(base::HashMetricName("file.h"), + tracked_object->source_file_name_hash()); + EXPECT_EQ(base::HashMetricName("function"), + tracked_object->source_function_name_hash()); + EXPECT_EQ(1337, tracked_object->source_line_number()); + EXPECT_EQ(base::HashMetricName("birth_thread"), + tracked_object->birth_thread_name_hash()); + EXPECT_EQ(37, tracked_object->exec_count()); + EXPECT_EQ(31, tracked_object->exec_time_total()); + EXPECT_EQ(13, tracked_object->exec_time_sampled()); + EXPECT_EQ(8, tracked_object->queue_time_total()); + EXPECT_EQ(3, tracked_object->queue_time_sampled()); + EXPECT_EQ(base::HashMetricName("Still_Alive"), + tracked_object->exec_thread_name_hash()); + EXPECT_EQ(177U, tracked_object->process_id()); + EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER, + tracked_object->process_type()); + + tracked_object = &uma_proto.profiler_event(0).tracked_object(1); + EXPECT_EQ(base::HashMetricName("file2"), + tracked_object->source_file_name_hash()); + EXPECT_EQ(base::HashMetricName("function2"), + tracked_object->source_function_name_hash()); + EXPECT_EQ(1773, tracked_object->source_line_number()); + EXPECT_EQ(base::HashMetricName("birth_thread*"), + tracked_object->birth_thread_name_hash()); + EXPECT_EQ(19, tracked_object->exec_count()); + EXPECT_EQ(23, tracked_object->exec_time_total()); + EXPECT_EQ(7, tracked_object->exec_time_sampled()); + EXPECT_EQ(0, tracked_object->queue_time_total()); + EXPECT_EQ(0, tracked_object->queue_time_sampled()); + EXPECT_EQ(base::HashMetricName("death_thread"), + tracked_object->exec_thread_name_hash()); + EXPECT_EQ(177U, tracked_object->process_id()); + EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER, + tracked_object->process_type()); + + tracked_object = &uma_proto.profiler_event(0).tracked_object(2); + EXPECT_EQ(base::HashMetricName("file3"), + tracked_object->source_file_name_hash()); + EXPECT_EQ(base::HashMetricName("function3"), + tracked_object->source_function_name_hash()); + EXPECT_EQ(7331, tracked_object->source_line_number()); + EXPECT_EQ(base::HashMetricName("birth_thread*"), + tracked_object->birth_thread_name_hash()); + EXPECT_EQ(137, tracked_object->exec_count()); + EXPECT_EQ(131, tracked_object->exec_time_total()); + EXPECT_EQ(113, tracked_object->exec_time_sampled()); + EXPECT_EQ(108, tracked_object->queue_time_total()); + EXPECT_EQ(103, tracked_object->queue_time_sampled()); + EXPECT_EQ(base::HashMetricName("death_thread*"), + tracked_object->exec_thread_name_hash()); + EXPECT_EQ(1177U, tracked_object->process_id()); + EXPECT_EQ(ProfilerEventProto::TrackedObject::RENDERER, + tracked_object->process_type()); + + tracked_object = &uma_proto.profiler_event(0).tracked_object(3); + EXPECT_EQ(base::HashMetricName(""), tracked_object->source_file_name_hash()); + EXPECT_EQ(base::HashMetricName(""), + tracked_object->source_function_name_hash()); + EXPECT_EQ(7332, tracked_object->source_line_number()); + EXPECT_EQ(base::HashMetricName(""), tracked_object->birth_thread_name_hash()); + EXPECT_EQ(138, tracked_object->exec_count()); + EXPECT_EQ(132, tracked_object->exec_time_total()); + EXPECT_EQ(114, tracked_object->exec_time_sampled()); + EXPECT_EQ(109, tracked_object->queue_time_total()); + EXPECT_EQ(104, tracked_object->queue_time_sampled()); + EXPECT_EQ(base::HashMetricName(""), tracked_object->exec_thread_name_hash()); + EXPECT_EQ(ProfilerEventProto::TrackedObject::RENDERER, + tracked_object->process_type()); + + // Phase 1 + EXPECT_EQ(ProfilerEventProto::VERSION_SPLIT_PROFILE, + uma_proto.profiler_event(1).profile_version()); + EXPECT_EQ(ProfilerEventProto::WALL_CLOCK_TIME, + uma_proto.profiler_event(1).time_source()); + ASSERT_EQ(1, uma_proto.profiler_event(1).past_session_event_size()); + ASSERT_EQ(ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT, + uma_proto.profiler_event(1).past_session_event(0)); + ASSERT_EQ(600000, uma_proto.profiler_event(1).profiling_start_ms()); + ASSERT_EQ(1200000, uma_proto.profiler_event(1).profiling_finish_ms()); + ASSERT_EQ(2, uma_proto.profiler_event(1).tracked_object_size()); + + tracked_object = &uma_proto.profiler_event(1).tracked_object(0); + EXPECT_EQ(base::HashMetricName("file10.h"), + tracked_object->source_file_name_hash()); + EXPECT_EQ(base::HashMetricName("function10"), + tracked_object->source_function_name_hash()); + EXPECT_EQ(101337, tracked_object->source_line_number()); + EXPECT_EQ(base::HashMetricName("birth_thread_ten"), + tracked_object->birth_thread_name_hash()); + EXPECT_EQ(1037, tracked_object->exec_count()); + EXPECT_EQ(1031, tracked_object->exec_time_total()); + EXPECT_EQ(1013, tracked_object->exec_time_sampled()); + EXPECT_EQ(108, tracked_object->queue_time_total()); + EXPECT_EQ(103, tracked_object->queue_time_sampled()); + EXPECT_EQ(base::HashMetricName("Already_Dead"), + tracked_object->exec_thread_name_hash()); + EXPECT_EQ(177U, tracked_object->process_id()); + EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER, + tracked_object->process_type()); + + tracked_object = &uma_proto.profiler_event(1).tracked_object(1); + EXPECT_EQ(base::HashMetricName("file210"), + tracked_object->source_file_name_hash()); + EXPECT_EQ(base::HashMetricName("function210"), + tracked_object->source_function_name_hash()); + EXPECT_EQ(101773, tracked_object->source_line_number()); + EXPECT_EQ(base::HashMetricName("birth_thread_ten*"), + tracked_object->birth_thread_name_hash()); + EXPECT_EQ(1019, tracked_object->exec_count()); + EXPECT_EQ(1023, tracked_object->exec_time_total()); + EXPECT_EQ(107, tracked_object->exec_time_sampled()); + EXPECT_EQ(100, tracked_object->queue_time_total()); + EXPECT_EQ(100, tracked_object->queue_time_sampled()); + EXPECT_EQ(base::HashMetricName("death_thread_ten"), + tracked_object->exec_thread_name_hash()); + EXPECT_EQ(177U, tracked_object->process_id()); + EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER, + tracked_object->process_type()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/tracking_synchronizer.cc b/chromium/components/metrics/profiler/tracking_synchronizer.cc new file mode 100644 index 00000000000..67c8361cf72 --- /dev/null +++ b/chromium/components/metrics/profiler/tracking_synchronizer.cc @@ -0,0 +1,378 @@ +// Copyright (c) 2012 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 "components/metrics/profiler/tracking_synchronizer.h" + +#include <stddef.h> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/metrics/histogram.h" +#include "base/thread_task_runner_handle.h" +#include "base/threading/thread.h" +#include "base/tracked_objects.h" +#include "components/metrics/profiler/tracking_synchronizer_observer.h" +#include "components/variations/variations_associated_data.h" + +using base::TimeTicks; + +namespace metrics { + +namespace { + +// Negative numbers are never used as sequence numbers. We explicitly pick a +// negative number that is "so negative" that even when we add one (as is done +// when we generated the next sequence number) that it will still be negative. +// We have code that handles wrapping around on an overflow into negative +// territory. +const int kNeverUsableSequenceNumber = -2; + +// This singleton instance should be started during the single threaded +// portion of main(). It initializes globals to provide support for all future +// calls. This object is created on the UI thread, and it is destroyed after +// all the other threads have gone away. As a result, it is ok to call it +// from the UI thread, or for about:profiler. +static TrackingSynchronizer* g_tracking_synchronizer = NULL; + +} // namespace + +// The "RequestContext" structure describes an individual request received +// from the UI. All methods are accessible on UI thread. +class TrackingSynchronizer::RequestContext { + public: + // A map from sequence_number_ to the actual RequestContexts. + typedef std::map<int, RequestContext*> RequestContextMap; + + RequestContext( + const base::WeakPtr<TrackingSynchronizerObserver>& callback_object, + int sequence_number) + : callback_object_(callback_object), + sequence_number_(sequence_number), + received_process_group_count_(0), + processes_pending_(0) { + } + ~RequestContext() {} + + void SetReceivedProcessGroupCount(bool done) { + DCHECK(thread_checker_.CalledOnValidThread()); + received_process_group_count_ = done; + } + + // Methods for book keeping of processes_pending_. + void IncrementProcessesPending() { + DCHECK(thread_checker_.CalledOnValidThread()); + ++processes_pending_; + } + + void AddProcessesPending(int processes_pending) { + DCHECK(thread_checker_.CalledOnValidThread()); + processes_pending_ += processes_pending; + } + + void DecrementProcessesPending() { + DCHECK(thread_checker_.CalledOnValidThread()); + --processes_pending_; + } + + // Records that we are waiting for one less tracking data from a process for + // the given sequence number. If |received_process_group_count_| and + // |processes_pending_| are zero, then delete the current object by calling + // Unregister. + void DeleteIfAllDone() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (processes_pending_ <= 0 && received_process_group_count_) + RequestContext::Unregister(sequence_number_); + } + + // Register |callback_object| in |outstanding_requests_| map for the given + // |sequence_number|. + static RequestContext* Register( + int sequence_number, + const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) { + RequestContext* request = new RequestContext( + callback_object, sequence_number); + outstanding_requests_.Get()[sequence_number] = request; + + return request; + } + + // Find the |RequestContext| in |outstanding_requests_| map for the given + // |sequence_number|. + static RequestContext* GetRequestContext(int sequence_number) { + RequestContextMap::iterator it = + outstanding_requests_.Get().find(sequence_number); + if (it == outstanding_requests_.Get().end()) + return NULL; + + RequestContext* request = it->second; + DCHECK_EQ(sequence_number, request->sequence_number_); + return request; + } + + // Delete the entry for the given |sequence_number| from + // |outstanding_requests_| map. This method is called when all changes have + // been acquired, or when the wait time expires (whichever is sooner). + static void Unregister(int sequence_number) { + RequestContextMap::iterator it = + outstanding_requests_.Get().find(sequence_number); + if (it == outstanding_requests_.Get().end()) + return; + + RequestContext* request = it->second; + DCHECK_EQ(sequence_number, request->sequence_number_); + bool received_process_group_count = request->received_process_group_count_; + int unresponsive_processes = request->processes_pending_; + + if (request->callback_object_.get()) + request->callback_object_->FinishedReceivingProfilerData(); + + delete request; + outstanding_requests_.Get().erase(it); + + UMA_HISTOGRAM_BOOLEAN("Profiling.ReceivedProcessGroupCount", + received_process_group_count); + UMA_HISTOGRAM_COUNTS("Profiling.PendingProcessNotResponding", + unresponsive_processes); + } + + // Delete all the entries in |outstanding_requests_| map. + static void OnShutdown() { + // Just in case we have any pending tasks, clear them out. + while (!outstanding_requests_.Get().empty()) { + RequestContextMap::iterator it = outstanding_requests_.Get().begin(); + delete it->second; + outstanding_requests_.Get().erase(it); + } + } + + // Used to verify that methods are called on the UI thread (the thread on + // which this object was created). + base::ThreadChecker thread_checker_; + + // Requests are made to asynchronously send data to the |callback_object_|. + base::WeakPtr<TrackingSynchronizerObserver> callback_object_; + + // The sequence number used by the most recent update request to contact all + // processes. + int sequence_number_; + + // Indicates if we have received all pending processes count. + bool received_process_group_count_; + + // The number of pending processes (browser, all renderer processes and + // browser child processes) that have not yet responded to requests. + int processes_pending_; + + // Map of all outstanding RequestContexts, from sequence_number_ to + // RequestContext. + static base::LazyInstance<RequestContextMap>::Leaky outstanding_requests_; +}; + +// static +base::LazyInstance + <TrackingSynchronizer::RequestContext::RequestContextMap>::Leaky + TrackingSynchronizer::RequestContext::outstanding_requests_ = + LAZY_INSTANCE_INITIALIZER; + +// TrackingSynchronizer methods and members. + +TrackingSynchronizer::TrackingSynchronizer( + scoped_ptr<base::TickClock> clock, + const TrackingSynchronizerDelegateFactory& delegate_factory) + : last_used_sequence_number_(kNeverUsableSequenceNumber), + clock_(std::move(clock)) { + DCHECK(!g_tracking_synchronizer); + g_tracking_synchronizer = this; + phase_start_times_.push_back(clock_->NowTicks()); + delegate_ = delegate_factory.Run(this); +} + +TrackingSynchronizer::~TrackingSynchronizer() { + // Just in case we have any pending tasks, clear them out. + RequestContext::OnShutdown(); + + g_tracking_synchronizer = NULL; +} + +// static +void TrackingSynchronizer::FetchProfilerDataAsynchronously( + const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) { + if (!g_tracking_synchronizer) { + // System teardown is happening. + return; + } + + int sequence_number = g_tracking_synchronizer->RegisterAndNotifyAllProcesses( + callback_object); + + // Post a task that would be called after waiting for wait_time. This acts + // as a watchdog, to cancel the requests for non-responsive processes. + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::Bind(&RequestContext::Unregister, sequence_number), + base::TimeDelta::FromMinutes(1)); +} + +// static +void TrackingSynchronizer::OnProfilingPhaseCompleted( + ProfilerEventProto::ProfilerEvent profiling_event) { + if (!g_tracking_synchronizer) { + // System teardown is happening. + return; + } + + g_tracking_synchronizer->NotifyAllProcessesOfProfilingPhaseCompletion( + profiling_event); +} + +void TrackingSynchronizer::OnPendingProcesses(int sequence_number, + int pending_processes, + bool end) { + DCHECK(thread_checker_.CalledOnValidThread()); + + RequestContext* request = RequestContext::GetRequestContext(sequence_number); + if (!request) + return; + request->AddProcessesPending(pending_processes); + request->SetReceivedProcessGroupCount(end); + request->DeleteIfAllDone(); +} + +void TrackingSynchronizer::OnProfilerDataCollected( + int sequence_number, + const tracked_objects::ProcessDataSnapshot& profiler_data, + ProfilerEventProto::TrackedObject::ProcessType process_type) { + DCHECK(thread_checker_.CalledOnValidThread()); + DecrementPendingProcessesAndSendData(sequence_number, profiler_data, + process_type); +} + +int TrackingSynchronizer::RegisterAndNotifyAllProcesses( + const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) { + DCHECK(thread_checker_.CalledOnValidThread()); + + int sequence_number = GetNextAvailableSequenceNumber(); + + RequestContext* request = + RequestContext::Register(sequence_number, callback_object); + + // Increment pending process count for sending browser's profiler data. + request->IncrementProcessesPending(); + + const int current_profiling_phase = phase_completion_events_sequence_.size(); + + delegate_->GetProfilerDataForChildProcesses(sequence_number, + current_profiling_phase); + + // Send process data snapshot from browser process. + tracked_objects::ProcessDataSnapshot process_data_snapshot; + tracked_objects::ThreadData::Snapshot(current_profiling_phase, + &process_data_snapshot); + + DecrementPendingProcessesAndSendData( + sequence_number, process_data_snapshot, + ProfilerEventProto::TrackedObject::BROWSER); + + return sequence_number; +} + +void TrackingSynchronizer::RegisterPhaseCompletion( + ProfilerEventProto::ProfilerEvent profiling_event) { + phase_completion_events_sequence_.push_back(profiling_event); + phase_start_times_.push_back(clock_->NowTicks()); +} + +void TrackingSynchronizer::NotifyAllProcessesOfProfilingPhaseCompletion( + ProfilerEventProto::ProfilerEvent profiling_event) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (variations::GetVariationParamValue("UMALogPhasedProfiling", + "send_split_profiles") == "false") { + return; + } + + int profiling_phase = phase_completion_events_sequence_.size(); + + // If you hit this check, stop and think. You just added a new profiling + // phase. Each profiling phase takes additional memory in DeathData's list of + // snapshots. We cannot grow it indefinitely. Consider collapsing older phases + // after they were sent to UMA server, or other ways to save memory. + DCHECK_LT(profiling_phase, 1); + + RegisterPhaseCompletion(profiling_event); + + delegate_->OnProfilingPhaseCompleted(profiling_phase); + + // Notify browser process. + tracked_objects::ThreadData::OnProfilingPhaseCompleted(profiling_phase); +} + +void TrackingSynchronizer::SendData( + const tracked_objects::ProcessDataSnapshot& profiler_data, + ProfilerEventProto::TrackedObject::ProcessType process_type, + TrackingSynchronizerObserver* observer) const { + // We are going to loop though past profiling phases and notify the request + // about each phase that is contained in profiler_data. past_events + // will track the set of past profiling events as we go. + ProfilerEvents past_events; + + // Go through all completed phases, and through the current one. The current + // one is not in phase_completion_events_sequence_, but note the <= + // comparison. + for (size_t phase = 0; phase <= phase_completion_events_sequence_.size(); + ++phase) { + auto it = profiler_data.phased_snapshots.find(phase); + + if (it != profiler_data.phased_snapshots.end()) { + // If the phase is contained in the received snapshot, notify the + // request. + const base::TimeTicks phase_start = phase_start_times_[phase]; + const base::TimeTicks phase_finish = phase + 1 < phase_start_times_.size() + ? phase_start_times_[phase + 1] + : clock_->NowTicks(); + observer->ReceivedProfilerData( + ProfilerDataAttributes(phase, profiler_data.process_id, process_type, + phase_start, phase_finish), + it->second, past_events); + } + + if (phase < phase_completion_events_sequence_.size()) { + past_events.push_back(phase_completion_events_sequence_[phase]); + } + } +} + +void TrackingSynchronizer::DecrementPendingProcessesAndSendData( + int sequence_number, + const tracked_objects::ProcessDataSnapshot& profiler_data, + ProfilerEventProto::TrackedObject::ProcessType process_type) { + DCHECK(thread_checker_.CalledOnValidThread()); + + RequestContext* request = RequestContext::GetRequestContext(sequence_number); + if (!request) + return; + + TrackingSynchronizerObserver* observer = request->callback_object_.get(); + if (observer) + SendData(profiler_data, process_type, observer); + + // Delete request if we have heard back from all child processes. + request->DecrementProcessesPending(); + request->DeleteIfAllDone(); +} + +int TrackingSynchronizer::GetNextAvailableSequenceNumber() { + DCHECK(thread_checker_.CalledOnValidThread()); + + ++last_used_sequence_number_; + + // Watch out for wrapping to a negative number. + if (last_used_sequence_number_ < 0) + last_used_sequence_number_ = 1; + return last_used_sequence_number_; +} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/tracking_synchronizer.h b/chromium/components/metrics/profiler/tracking_synchronizer.h new file mode 100644 index 00000000000..511cf2b5971 --- /dev/null +++ b/chromium/components/metrics/profiler/tracking_synchronizer.h @@ -0,0 +1,167 @@ +// Copyright (c) 2012 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. + +#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_ +#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "base/tracked_objects.h" +#include "components/metrics/profiler/tracking_synchronizer_delegate.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +// This class maintains state that is used to upload profiler data from the +// various processes, into the browser process. Such transactions are usually +// instigated by the browser. In general, a process will respond by gathering +// profiler data, and transmitting the pickled profiler data. We collect the +// data in asynchronous mode that doesn't block the UI thread. +// +// To assure that all the processes have responded, a counter is maintained +// to indicate the number of pending (not yet responsive) processes. We tag +// each group of requests with a sequence number. For each group of requests, we +// create RequestContext object which stores the sequence number, pending +// processes and the callback_object that needs to be notified when we receive +// an update from processes. When an update arrives we find the RequestContext +// associated with sequence number and send the unpickled profiler data to the +// |callback_object_|. + +namespace metrics { + +class TrackingSynchronizerObserver; + +typedef base::Callback<scoped_ptr<TrackingSynchronizerDelegate>( + TrackingSynchronizer*)> TrackingSynchronizerDelegateFactory; + +class TrackingSynchronizer + : public base::RefCountedThreadSafe<TrackingSynchronizer> { + public: + // Construction also sets up the global singleton instance. This instance is + // used to communicate between the IO and UI thread, and is destroyed only as + // the main thread (browser_main) terminates, which means the IO thread has + // already completed, and will not need this instance any further. + // |clock| is a clock used for durations of profiling phases. + // |delegate| is used to abstract platform-specific profiling functionality. + TrackingSynchronizer( + scoped_ptr<base::TickClock> clock, + const TrackingSynchronizerDelegateFactory& delegate_factory); + + // Contact all processes, and get them to upload to the browser any/all + // changes to profiler data. It calls |callback_object|'s SetData method with + // the data received from each sub-process. + // This method is accessible on the UI thread. + static void FetchProfilerDataAsynchronously( + const base::WeakPtr<TrackingSynchronizerObserver>& callback_object); + + // Called when a profiling phase completes. |profiling_event| is the event + // that triggered the completion of the current phase, and begins a new phase. + // This method is accessible on the UI thread. + static void OnProfilingPhaseCompleted( + ProfilerEventProto::ProfilerEvent profiling_event); + + // Send profiler_data back to |callback_object_| by calling + // DecrementPendingProcessesAndSendData which records that we are waiting + // for one less profiler data from renderer or browser child process for the + // given sequence number. This method is accessible on UI thread. + void OnProfilerDataCollected( + int sequence_number, + const tracked_objects::ProcessDataSnapshot& profiler_data, + ProfilerEventProto::TrackedObject::ProcessType process_type); + + // Update the number of pending processes for the given |sequence_number|. + // This is called on UI thread. + void OnPendingProcesses(int sequence_number, int pending_processes, bool end); + + protected: + virtual ~TrackingSynchronizer(); + + // Update the sequence of completed phases with a new phase completion info. + void RegisterPhaseCompletion( + ProfilerEventProto::ProfilerEvent profiling_event); + + // Notify |observer| about |profiler_data| received from process of type + // |process_type|. + void SendData(const tracked_objects::ProcessDataSnapshot& profiler_data, + ProfilerEventProto::TrackedObject::ProcessType process_type, + TrackingSynchronizerObserver* observer) const; + + private: + friend class base::RefCountedThreadSafe<TrackingSynchronizer>; + + class RequestContext; + + // Establish a new sequence_number_, and use it to notify all the processes of + // the need to supply, to the browser, their tracking data. It also registers + // |callback_object| in |outstanding_requests_| map. Return the + // sequence_number_ that was used. This method is accessible on UI thread. + int RegisterAndNotifyAllProcesses( + const base::WeakPtr<TrackingSynchronizerObserver>& callback_object); + + // Notifies all processes of a completion of a profiling phase. + // |profiling_event| is the event associated with the phase change. + void NotifyAllProcessesOfProfilingPhaseCompletion( + ProfilerEventProto::ProfilerEvent profiling_event); + + // It finds the RequestContext for the given |sequence_number| and notifies + // the RequestContext's |callback_object_| about the |value|. This is called + // whenever we receive profiler data from processes. It also records that we + // are waiting for one less profiler data from a process for the given + // sequence number. If we have received a response from all renderers and + // browser processes, then it calls RequestContext's DeleteIfAllDone to delete + // the entry for sequence_number. This method is accessible on UI thread. + void DecrementPendingProcessesAndSendData( + int sequence_number, + const tracked_objects::ProcessDataSnapshot& profiler_data, + ProfilerEventProto::TrackedObject::ProcessType process_type); + + // Get a new sequence number to be sent to processes from browser process. + // This method is accessible on UI thread. + int GetNextAvailableSequenceNumber(); + + // Used to verify that certain methods are called on the UI thread (the thread + // on which this object was created). + base::ThreadChecker thread_checker_; + + // We don't track the actual processes that are contacted for an update, only + // the count of the number of processes, and we can sometimes time-out and + // give up on a "slow to respond" process. We use a sequence_number to be + // sure a response from a process is associated with the current round of + // requests. All sequence numbers used are non-negative. + // last_used_sequence_number_ is the most recently used number (used to avoid + // reuse for a long time). + int last_used_sequence_number_; + + // Sequence of events associated with already completed profiling phases. The + // index in the vector is the phase number. The current phase is not included. + std::vector<ProfilerEventProto::ProfilerEvent> + phase_completion_events_sequence_; + + // Clock for profiling phase durations. + const scoped_ptr<base::TickClock> clock_; + + // Times of starts of all profiling phases, including the current phase. The + // index in the vector is the phase number. + std::vector<base::TimeTicks> phase_start_times_; + + // This object's delegate. + // NOTE: Leave this ivar last so that the delegate is torn down first at + // destruction, as it has a reference to this object. + scoped_ptr<TrackingSynchronizerDelegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(TrackingSynchronizer); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_ diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h new file mode 100644 index 00000000000..23fc15fbfc0 --- /dev/null +++ b/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h @@ -0,0 +1,31 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_ +#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_ + + +namespace metrics { + +class TrackingSynchronizer; + +// An abstraction of TrackingSynchronizer-related operations that depend on the +// platform. +class TrackingSynchronizerDelegate { + public: + virtual ~TrackingSynchronizerDelegate() {} + + // Should perform the platform-specific action that is needed to start + // gathering profiler data for all relevant child processes. + virtual void GetProfilerDataForChildProcesses( + int sequence_number, + int current_profiling_phase) = 0; + + // Called when |profiling_phase| has completed. + virtual void OnProfilingPhaseCompleted(int profiling_phase) = 0; +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_ diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc b/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc new file mode 100644 index 00000000000..2a8eb3d2e4b --- /dev/null +++ b/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2015 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 "components/metrics/profiler/tracking_synchronizer_observer.h" + +namespace metrics { + +ProfilerDataAttributes::ProfilerDataAttributes( + int profiling_phase, + base::ProcessId process_id, + ProfilerEventProto::TrackedObject::ProcessType process_type, + base::TimeTicks phase_start, + base::TimeTicks phase_finish) + : profiling_phase(profiling_phase), + process_id(process_id), + process_type(process_type), + phase_start(phase_start), + phase_finish(phase_finish) {} + +} // namespace metrics diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_observer.h b/chromium/components/metrics/profiler/tracking_synchronizer_observer.h new file mode 100644 index 00000000000..c35de35cef2 --- /dev/null +++ b/chromium/components/metrics/profiler/tracking_synchronizer_observer.h @@ -0,0 +1,88 @@ +// Copyright (c) 2012 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. + +#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_ +#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +namespace base { +class TimeDelta; +} + +namespace tracked_objects { +struct ProcessDataPhaseSnapshot; +} + +namespace metrics { + +// Set of profiling events, in no guaranteed order. Implemented as a vector +// because we don't need to have an efficient .find() on it, so vector<> is more +// efficient. +typedef std::vector<ProfilerEventProto::ProfilerEvent> ProfilerEvents; + +// Attributes of profiler data passed to +// TrackingSynchronizerObserver::ReceivedProfilerData. +struct ProfilerDataAttributes { + ProfilerDataAttributes( + int profiling_phase, + base::ProcessId process_id, + ProfilerEventProto::TrackedObject::ProcessType process_type, + base::TimeTicks phase_start, + base::TimeTicks phase_finish); + + // 0-indexed profiling phase number. + const int profiling_phase; + + // ID of the process that reported the data. + const base::ProcessId process_id; + + // Type of the process that reported the data. + const ProfilerEventProto::TrackedObject::ProcessType process_type; + + // Time of the profiling phase start. + const base::TimeTicks phase_start; + + // Time of the profiling phase finish. + const base::TimeTicks phase_finish; +}; + +// Observer for notifications from the TrackingSynchronizer class. +class TrackingSynchronizerObserver { + public: + // Received |process_data_phase| for profiling phase and process defined by + // |attributes|. + // Each completed phase is associated with an event that triggered the + // completion of the phase. |past_events| contains the set of events that + // completed prior to the reported phase. This data structure is useful for + // quickly computing the full set of profiled traces that occurred before or + // after a given event. + // The observer should assume there might be more data coming until + // FinishedReceivingData() is called. + virtual void ReceivedProfilerData( + const ProfilerDataAttributes& attributes, + const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase, + const ProfilerEvents& past_events) = 0; + + // The observer should not expect any more calls to |ReceivedProfilerData()| + // (without re-registering). This is sent either when data from all processes + // has been gathered, or when the request times out. + virtual void FinishedReceivingProfilerData() {} + + protected: + TrackingSynchronizerObserver() {} + virtual ~TrackingSynchronizerObserver() {} + + private: + DISALLOW_COPY_AND_ASSIGN(TrackingSynchronizerObserver); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_ diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc b/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc new file mode 100644 index 00000000000..f1df9eb4181 --- /dev/null +++ b/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc @@ -0,0 +1,154 @@ +// Copyright 2015 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 "components/metrics/profiler/tracking_synchronizer.h" + +#include <utility> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/tracked_objects.h" +#include "components/metrics/profiler/tracking_synchronizer_delegate.h" +#include "components/metrics/profiler/tracking_synchronizer_observer.h" +#include "testing/gtest/include/gtest/gtest.h" + +using tracked_objects::ProcessDataPhaseSnapshot; +using tracked_objects::TaskSnapshot; + +namespace metrics { + +namespace { + +class TestDelegate : public TrackingSynchronizerDelegate { + public: + ~TestDelegate() override {} + + static scoped_ptr<TrackingSynchronizerDelegate> Create( + TrackingSynchronizer* synchronizer) { + return make_scoped_ptr(new TestDelegate()); + } + + private: + TestDelegate() {} + + // TrackingSynchronizerDelegate: + void GetProfilerDataForChildProcesses(int sequence_number, + int current_profiling_phase) override {} + void OnProfilingPhaseCompleted(int profiling_phase) override {} +}; + +class TestObserver : public TrackingSynchronizerObserver { + public: + TestObserver() {} + + ~TestObserver() override { + EXPECT_TRUE(got_phase_0_); + EXPECT_TRUE(got_phase_1_); + } + + void ReceivedProfilerData( + const ProfilerDataAttributes& attributes, + const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase, + const ProfilerEvents& past_events) override { + EXPECT_EQ(static_cast<base::ProcessId>(239), attributes.process_id); + EXPECT_EQ(ProfilerEventProto::TrackedObject::PPAPI_PLUGIN, + attributes.process_type); + ASSERT_EQ(1u, process_data_phase.tasks.size()); + + switch (attributes.profiling_phase) { + case 0: + EXPECT_FALSE(got_phase_0_); + got_phase_0_ = true; + + EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(111), + attributes.phase_start); + EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(333), + attributes.phase_finish); + + EXPECT_EQ("death_thread0", + process_data_phase.tasks[0].death_thread_name); + EXPECT_EQ(0u, past_events.size()); + break; + + case 1: + EXPECT_FALSE(got_phase_1_); + got_phase_1_ = true; + + EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(333), + attributes.phase_start); + EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(777), + attributes.phase_finish); + + EXPECT_EQ("death_thread1", + process_data_phase.tasks[0].death_thread_name); + ASSERT_EQ(1u, past_events.size()); + EXPECT_EQ(ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT, + past_events[0]); + break; + + default: + bool profiling_phase_is_neither_0_nor_1 = true; + EXPECT_FALSE(profiling_phase_is_neither_0_nor_1); + } + } + + private: + bool got_phase_0_ = false; + bool got_phase_1_ = false; + + DISALLOW_COPY_AND_ASSIGN(TestObserver); +}; + +class TestTrackingSynchronizer : public TrackingSynchronizer { + public: + explicit TestTrackingSynchronizer(scoped_ptr<base::TickClock> clock) + : TrackingSynchronizer(std::move(clock), + base::Bind(&TestDelegate::Create)) {} + + using TrackingSynchronizer::RegisterPhaseCompletion; + using TrackingSynchronizer::SendData; + + private: + ~TestTrackingSynchronizer() override {} +}; + +} // namespace + +TEST(TrackingSynchronizerTest, ProfilerData) { + // Testing how TrackingSynchronizer reports 2 phases of profiling. + auto clock = new base::SimpleTestTickClock(); // Will be owned by + // |tracking_synchronizer|. + clock->Advance(base::TimeDelta::FromMilliseconds(111)); + + scoped_refptr<TestTrackingSynchronizer> tracking_synchronizer = + new TestTrackingSynchronizer(make_scoped_ptr(clock)); + + clock->Advance(base::TimeDelta::FromMilliseconds(222)); + + tracking_synchronizer->RegisterPhaseCompletion( + ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT); + + tracked_objects::ProcessDataSnapshot profiler_data; + ProcessDataPhaseSnapshot snapshot0; + tracked_objects::TaskSnapshot task_snapshot0; + task_snapshot0.death_thread_name = "death_thread0"; + snapshot0.tasks.push_back(task_snapshot0); + ProcessDataPhaseSnapshot snapshot1; + profiler_data.phased_snapshots[0] = snapshot0; + tracked_objects::TaskSnapshot task_snapshot1; + task_snapshot1.death_thread_name = "death_thread1"; + snapshot1.tasks.push_back(task_snapshot1); + profiler_data.phased_snapshots[1] = snapshot1; + profiler_data.process_id = 239; + + clock->Advance(base::TimeDelta::FromMilliseconds(444)); + TestObserver test_observer; + tracking_synchronizer->SendData( + profiler_data, ProfilerEventProto::TrackedObject::PPAPI_PLUGIN, + &test_observer); +} + +} // namespace metrics diff --git a/chromium/components/metrics/proto/BUILD.gn b/chromium/components/metrics/proto/BUILD.gn new file mode 100644 index 00000000000..3271f184cdf --- /dev/null +++ b/chromium/components/metrics/proto/BUILD.gn @@ -0,0 +1,24 @@ +# Copyright 2014 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. + +import("//third_party/protobuf/proto_library.gni") + +# GYP version: components/ +proto_library("proto") { + sources = [ + "call_stack_profile.proto", + "cast_logs.proto", + "chrome_user_metrics_extension.proto", + "histogram_event.proto", + "memory_leak_report.proto", + "omnibox_event.proto", + "omnibox_input_type.proto", + "perf_data.proto", + "perf_stat.proto", + "profiler_event.proto", + "sampled_profile.proto", + "system_profile.proto", + "user_action_event.proto", + ] +} diff --git a/chromium/components/metrics/proto/call_stack_profile.proto b/chromium/components/metrics/proto/call_stack_profile.proto new file mode 100644 index 00000000000..e41b3390224 --- /dev/null +++ b/chromium/components/metrics/proto/call_stack_profile.proto @@ -0,0 +1,63 @@ +// Copyright 2015 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. + +// Call stack sample data for a given profiling session. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "CallStackProfileProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 5 +message CallStackProfile { + // Describes an entry in the callstack. + message Entry { + // Instruction pointer subtracted by module base. + optional uint64 address = 1; + + // Index to the module identifier in |module_ids| of CallStackProfile. + optional int32 module_id_index = 2; + } + + // A sample consisting of one or more callstacks with the same stack frames + // and instruction pointers. + message Sample { + // The callstack. Sample.entries[0] represents the call on the top of the + // stack. + repeated Entry entry = 1; + + // Number of times this stack signature occurs. + optional int64 count = 2; + } + + // Uniquely identifies a module. + message ModuleIdentifier { + // A hash that uniquely identifies a particular program version with high + // probability. This is parsed from headers of the loaded module. + // For binaries generated by GNU tools: + // Contents of the .note.gnu.build-id field. + // On Windows: + // GUID + AGE in the debug image headers of a module. + optional string build_id = 1; + + // MD5Sum Prefix of the module name. This is the same hashing scheme as used + // to hash UMA histogram names. + optional fixed64 name_md5_prefix = 2; + } + + // The callstack and counts. + repeated Sample sample = 1; + + // List of module ids found in this sample. + repeated ModuleIdentifier module_id = 2; + + // Duration of this profile. + optional int32 profile_duration_ms = 3; + + // Time between samples. + optional int32 sampling_period_ms = 4; +} diff --git a/chromium/components/metrics/proto/cast_logs.proto b/chromium/components/metrics/proto/cast_logs.proto new file mode 100644 index 00000000000..0d01ef73b27 --- /dev/null +++ b/chromium/components/metrics/proto/cast_logs.proto @@ -0,0 +1,182 @@ +// Copyright 2014 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. +// +// Cast-enabled device specific log data included in ChromeUserMetricsExtension. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "CastLogsProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 7 +message CastLogsProto { + // Cast specific device information. + // Next tag: 5 + message CastDeviceInfo { + // The product type of Cast device sent from Cast-enabled devices. + // Next tag: 5 + enum CastProductType { + CAST_PRODUCT_TYPE_UNKNOWN = 0; + CAST_PRODUCT_TYPE_CHROMECAST = 1; + CAST_PRODUCT_TYPE_AUDIO = 3; + CAST_PRODUCT_TYPE_ANDROID_TV = 4; + } + optional CastProductType type = 1; + + // The hardware revision of each product. + optional string hardware_revision = 2; + + // The manufacturer of Cast device, this value is empty when the device + // is manufactured by Google. + optional string manufacturer = 3; + + // The model of the Cast device. + optional string model = 4; + } + // The device sends this information at least once per day. + optional CastDeviceInfo cast_device_info = 1; + + // Information about Cast connection between sender application and + // Cast-enabled device. + // Next tag: 4 + message CastConnectionInfo { + optional fixed32 transport_connection_id = 1; + + optional fixed32 virtual_connection_id = 2; + + // This message describes a detail sender device and sdk. Those are + // parsed from the user agent string sent from sender sdk during connection. + // Next tag: 9 + message SenderInfo { + // The identifier for the sender device, that is not tied any kind of + // device id outside of UMA, and this id is reset when user resets sender + // device. + optional fixed64 sender_device_id = 1; + + // SDK type the sender application was using. + // Next tag: 3 + enum SDKType { + SDK_UNKNOWN = 0; + + // Native SDK type, + // E.G. Android sdk, iOS sdk. + SDK_NATIVE = 1; + + // SDK via Chrome extension. + SDK_CHROME_EXTENSION = 2; + } + optional SDKType sdk_type = 2; + + // Version of sender sdk/extension used to connection. + optional string version = 3; + + // Chrome browser version where the Chrome extension running. + // Only Chrome extension sends this information. + optional string chrome_browser_version = 4; + + // Platform of sender device. + // Next tag: 7 + enum Platform { + // Any platform other then cases below. + PLATFORM_OTHER = 0; + + PLATFORM_ANDROID = 1; + PLATFORM_IOS = 2; + PLATFORM_WINDOWS = 3; + PLATFORM_OSX = 4; + PLATFORM_CHROMEOS = 5; + PLATFORM_LINUX = 6; + } + optional Platform platform = 5; + + // Sender device system version. + optional string system_version = 6; + + // What type of connection type used to establish between sender and + // receiver. + enum ConnectionType { + CONNECTION_TYPE_UNKNOWN = 0; + CONNECTION_TYPE_LOCAL = 1; + CONNECTION_TYPE_RELAY = 2; + } + optional ConnectionType transport_connection_type = 7; + + // Sender device model. + optional string model = 8; + } + optional SenderInfo sender_info = 3; + } + + // Virtual connection established between sender application and Cast device. + repeated CastConnectionInfo cast_connection_info = 2; + + // Stores Cast-enabled device specific events with a various context data. + // Next tag: 10 + message CastEventProto { + // The name of the action, hashed by same logic used to hash user action + // event and histogram. + optional fixed64 name_hash = 1; + + // The timestamp for the event, in milliseconds. + optional int64 time_msec = 2; + + // The Cast receiver app ID related with this event. + optional fixed32 app_id = 3; + + // The identifier for receiver application session. + optional fixed64 application_session_id = 4; + + // Receiver side Cast SDK version. + optional fixed64 cast_receiver_version = 5; + + // Cast MPL version. + optional fixed64 cast_mpl_version = 9; + + // transport_connection_id related with this event. + optional fixed32 transport_connection_id = 6; + + // virtual_connection_id related with this event. + optional fixed32 virtual_connection_id = 7; + + // An optional value for the associated event + optional int64 value = 8; + + // An optional value for the multi-room group uuid. + optional fixed64 group_uuid = 10; + } + repeated CastEventProto cast_event = 3; + + // Virtual release track for device. + optional fixed32 virtual_release_track = 4; + + // Cast specific device information which is expected to change over time. + // Next tag: 2 + message CastDeviceMutableInfo { + // This is the last type of reboot the device encountered + // Next tag: 9 + enum RebootType { + REBOOT_TYPE_UNKNOWN = 0; + REBOOT_TYPE_FORCED = 1; + REBOOT_TYPE_API = 2; + REBOOT_TYPE_NIGHTLY = 3; + REBOOT_TYPE_OTA = 4; + REBOOT_TYPE_WATCHDOG = 5; + REBOOT_TYPE_PROCESS_MANAGER = 6; + REBOOT_TYPE_CRASH_UPLOADER = 7; + REBOOT_TYPE_FDR = 8; + } + optional RebootType last_reboot_type = 1; + } + optional CastDeviceMutableInfo cast_device_mutable_info = 5; + + // Unique identifier that is randomly generated on first setup, on re-setup + // (FDR), and when user opted-in from opted-out status. If user is opted-out + // then this field should not be set. + // This is used for joining logs from Cast sender SDK to evaluate Cast + // sender/receiver communication quality. + optional fixed64 receiver_metrics_id = 6; +} diff --git a/chromium/components/metrics/proto/chrome_user_metrics_extension.proto b/chromium/components/metrics/proto/chrome_user_metrics_extension.proto new file mode 100644 index 00000000000..a80081a774d --- /dev/null +++ b/chromium/components/metrics/proto/chrome_user_metrics_extension.proto @@ -0,0 +1,76 @@ +// Copyright 2014 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. +// +// Protocol buffer for Chrome UMA (User Metrics Analysis). + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "ChromeUserMetricsExtensionProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +import "cast_logs.proto"; +import "histogram_event.proto"; +import "omnibox_event.proto"; +import "profiler_event.proto"; +import "system_profile.proto"; +import "user_action_event.proto"; +import "perf_data.proto"; +import "sampled_profile.proto"; + +// Next tag: 13 +message ChromeUserMetricsExtension { + // The product (i.e. end user application) for a given UMA log. + enum Product { + // Google Chrome product family. + CHROME = 0; + + // UMA metrics from Android Webview. + ANDROID_WEBVIEW = 20; + } + // The product corresponding to this log. The field type is int32 instead of + // Product so that downstream users of the Chromium metrics component can + // introduce products without needing to make changes to the Chromium code + // (though they still need to add the new product to the server-side enum). + // Note: The default value is Chrome, so Chrome products will not transmit + // this field. + optional int32 product = 10 [default = 0]; + + // The id of the client install that generated these events. + // + // For Chrome clients, this id is unique to a top-level (one level above the + // "Default" directory) Chrome user data directory [1], and so is shared among + // all Chrome user profiles contained in this user data directory. + // An id of 0 is reserved for test data (monitoring and internal testing) and + // should normally be ignored in analysis of the data. + // [1] http://www.chromium.org/user-experience/user-data-directory + optional fixed64 client_id = 1; + + // The session id for this user. + // Values such as tab ids are only meaningful within a particular session. + // The client keeps track of the session id and sends it with each event. + // The session id is simply an integer that is incremented each time the user + // relaunches Chrome. + optional int32 session_id = 2; + + // Information about the user's browser and system configuration. + optional SystemProfileProto system_profile = 3; + + // This message will log one or more of the following event types: + repeated UserActionEventProto user_action_event = 4; + repeated OmniboxEventProto omnibox_event = 5; + repeated HistogramEventProto histogram_event = 6; + repeated ProfilerEventProto profiler_event = 7; + + // This field is no longer used. Use |sampled_profile| instead. + repeated PerfDataProto perf_data = 8 [deprecated=true]; + + // A list of all collected sample-based profiles since the last UMA upload. + repeated SampledProfile sampled_profile = 11; + + // Additional data related with Cast-enabled devices. + optional CastLogsProto cast_logs = 12; +} diff --git a/chromium/components/metrics/proto/histogram_event.proto b/chromium/components/metrics/proto/histogram_event.proto new file mode 100644 index 00000000000..8b054172eae --- /dev/null +++ b/chromium/components/metrics/proto/histogram_event.proto @@ -0,0 +1,49 @@ +// Copyright 2014 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. +// +// Histogram-collected metrics. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "HistogramEventProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 4 +message HistogramEventProto { + // The name of the histogram, hashed. + optional fixed64 name_hash = 1; + + // The sum of all the sample values. + // Together with the total count of the sample values, this allows us to + // compute the average value. The count of all sample values is just the sum + // of the counts of all the buckets. As of M51, when the value of this field + // would be 0, the field will be omitted instead. + optional int64 sum = 2; + + // The per-bucket data. + message Bucket { + // Each bucket's range is bounded by min <= x < max. + // It is valid to omit one of these two fields in a bucket, but not both. + // If the min field is omitted, its value is assumed to be equal to max - 1. + // If the max field is omitted, its value is assumed to be equal to the next + // bucket's min value (possibly computed per above). The last bucket in a + // histogram should always include the max field. + optional int64 min = 1; + optional int64 max = 2; + + // The bucket's index in the list of buckets, sorted in ascending order. + // This field was intended to provide extra redundancy to detect corrupted + // records, but was never used. As of M31, it is no longer sent by Chrome + // clients to reduce the UMA upload size. + optional int32 bucket_index = 3 [deprecated = true]; + + // The number of entries in this bucket. As of M51, when the value of this + // field would be 1, the field will be omitted instead. + optional int64 count = 4 [default = 1]; + } + repeated Bucket bucket = 3; +} diff --git a/chromium/components/metrics/proto/memory_leak_report.proto b/chromium/components/metrics/proto/memory_leak_report.proto new file mode 100644 index 00000000000..b465f16b4fe --- /dev/null +++ b/chromium/components/metrics/proto/memory_leak_report.proto @@ -0,0 +1,81 @@ +// Copyright 2016 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Next tag: 8 +message MemoryLeakReportProto { + // The call stack at which the leak was found. This is a list of offsets + // within the program binary. The first entry is the deepest level of the call + // stack. + // + // Some call stack entries may not be within the Chrome binary (e.g. + // JavaScript code). Those entries are given as the absolute offset in memory. + // + // The offsets within Chrome are determined by whether the original call stack + // address was within the executable region of the Chrome binary's mapping in + // memory. To symbolize these results, look up these values as offsets within + // the Chrome debug binary. If the value doesn't fit within the Chrome + // binary's offset range, then it is considered to be from another binary. + repeated uint64 call_stack = 1; + + // Size of the memory allocation involved in the leak. + optional uint32 size_bytes = 2; + + ////////////////////////////////////////////////////////////////////////////// + + // The rate at which allocations are pseudorandomly sampled. Ranges from 0 to + // 1. A rate of 1 means all incoming allocations are sampled by the leak + // detector, which is the maximum possible. + optional float sampling_rate = 3; + + // The max depth to which the call stacks were unwound by the leak detector. + // This may be greater than the size of |call_stack|. + optional uint32 max_stack_depth = 4; + + // The leak analysis takes place every so often, with an interval based on the + // number of bytes allocated. This is independent of the sampling rate as it + // is computed from allocation sizes before sampling. + optional uint64 analysis_interval_bytes = 5; + + // Suspicion thresholds used in leak analysis for size and call stacks, + // respectively. If an allocation size or call stack is suspected this many + // times in a row, the leak analysis escalates to the next level. For + // allocation sizes, the next level is to start analyzing by call stack. For + // call stacks, the next level is to generate a memory leak report. + optional uint32 size_suspicion_threshold = 6; + optional uint32 call_stack_suspicion_threshold = 7; + + ////////////////////////////////////////////////////////////////////////////// + + // Represents a single snapshot of the internal bookkeeping of the Runtime + // Memory Leak Detector, which tracks the number of extant allocations (a + // block of heap memory that has been allocated but not yet freed). + // + // Next tag: 3 + message AllocationBreakdown { + // Table of number of extant allocations for each allocation size. The i-th + // entry in the vector is the net number of allocations for sizes in the + // range [i * 4, i * 4 + 3]. + repeated uint32 counts_by_size = 1; + + // The number of extant allocations with size = |size_bytes| and made from + // the call site given by |call_stack|. If it is not set, it means tracking + // of allocs per call site for allocation size = |size_bytes| has not yet + // begun at the time of this entry. + optional uint32 count_for_call_stack = 2; + } + + // A record of past allocation data leading up to the circumstances that + // generated the current leak report. + // + // A new snapshot is taken every |analysis_interval_bytes| of memory + // allocation. The oldest record is at the beginning. The most recent record, + // taken at the time the report was generated, is at the end. + repeated AllocationBreakdown alloc_breakdown_history = 8; +} diff --git a/chromium/components/metrics/proto/omnibox_event.proto b/chromium/components/metrics/proto/omnibox_event.proto new file mode 100644 index 00000000000..5d4f7199657 --- /dev/null +++ b/chromium/components/metrics/proto/omnibox_event.proto @@ -0,0 +1,286 @@ +// Copyright 2014 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. +// +// Stores information about an omnibox interaction. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "OmniboxEventProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +import "omnibox_input_type.proto"; + +// Next tag: 17 +message OmniboxEventProto { + // The timestamp for the event, in seconds since the epoch. + optional int64 time = 1; + + // The id of the originating tab for this omnibox interaction. + // This is the current tab *unless* the user opened the target in a new tab. + // In those cases, this is unset. Tab ids are unique for a given session_id + // (in the containing protocol buffer ChromeUserMetricsExtensionProto). + optional int32 tab_id = 2; + + // The number of characters the user had typed before autocompleting. + optional int32 typed_length = 3; + + // Whether the user deleted text immediately before selecting an omnibox + // suggestion. This is usually the result of pressing backspace or delete. + optional bool just_deleted_text = 11; + + // The number of terms that the user typed in the omnibox. + optional int32 num_typed_terms = 4; + + // The index of the item that the user selected in the omnibox popup list. + // This corresponds the index of the |suggestion| below. + optional int32 selected_index = 5; + + // DEPRECATED. Whether or not the top match was hidden in the omnibox + // suggestions dropdown. + optional bool DEPRECATED_is_top_result_hidden_in_dropdown = 14 + [deprecated = true]; + + // Whether the omnibox popup is open. It can be closed if, for instance, + // the user clicks in the omnibox and hits return to reload the same page. + // If the popup is closed, the suggestion list will contain only one item + // and selected_index will be 0 (pointing to that single item). Because + // paste-and-search/paste-and-go actions ignore the current content of the + // omnibox dropdown (if it is open) when they happen, we pretend the + // dropdown is closed when logging these. + optional bool is_popup_open = 15; + + // True if this is a paste-and-search or paste-and-go action. (The codebase + // refers to both these types as paste-and-go.) + optional bool is_paste_and_go = 16; + + // The length of the inline autocomplete text in the omnibox. + // The sum |typed_length| + |completed_length| gives the full length of the + // user-visible text in the omnibox. + // This field is only set for suggestions that are allowed to be the default + // match and omitted otherwise. The first suggestion is always allowed to + // be the default match. (This is an enforced constraint.) Hence, if + // |selected_index| == 0, then this field will always be set. + optional int32 completed_length = 6; + + // The amount of time, in milliseconds, since the user first began modifying + // the text in the omnibox. If at some point after modifying the text, the + // user reverts the modifications (thus seeing the current web page's URL + // again), then writes in the omnibox again, this elapsed time should start + // from the time of the second series of modification. + optional int64 typing_duration_ms = 7; + + // The amount of time, in milliseconds, since the last time the default + // (inline) match changed. This may be longer than the time since the + // last keystroke. (The last keystroke may not have changed the default + // match.) It may also be shorter than the time since the last keystroke + // because the default match might have come from an asynchronous + // provider. Regardless, it should always be less than or equal to + // the field |typing_duration_ms|. + optional int64 duration_since_last_default_match_update_ms = 13; + + // The type of page currently displayed when the user used the omnibox. + enum PageClassification { + // An invalid URL; shouldn't happen. + INVALID_SPEC = 0; + + // chrome://newtab/. This can be either the built-in version or a + // replacement new tab page from an extension. Note that when Instant + // Extended is enabled, the new tab page will be reported as either + // INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS or + // INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS below, + // unless an extension is replacing the new tab page, in which case + // it will still be reported as NTP. + NTP = 1; + + // about:blank. + BLANK = 2; + + // The user's home page. Note that if the home page is set to any + // of the new tab page versions or to about:blank, then we'll + // classify the page into those categories, not HOME_PAGE. + HOME_PAGE = 3; + + // The catch-all entry of everything not included somewhere else + // on this list. + OTHER = 4; + + // The instant new tab page enum value was deprecated on August 2, 2013. + OBSOLETE_INSTANT_NTP = 5; + + // The user is on a search result page that's doing search term + // replacement, meaning the search terms should've appeared in the omnibox + // before the user started editing it, not the URL of the page. + SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT = 6; + + // The new tab page in which this omnibox interaction first started + // with the user having focus in the omnibox. + INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS = 7; + + // The new tab page in which this omnibox interaction first started + // with the user having focus in the fakebox. + INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS = 8; + + // The user is on a search result page that's not doing search term + // replacement, meaning the URL of the page should've appeared in the + // omnibox before the user started editing it, not the search terms. + SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT = 9; + + // The user is on the home screen. + APP_HOME = 10; + + // The user is in the search app. + APP_SEARCH = 11; + + // The user is in the maps app. + APP_MAPS = 12; + + // When adding new classifications, please consider adding them in + // chrome/browser/resources/omnibox/omnibox.html + // so that these new options are displayed on about:omnibox. + } + optional PageClassification current_page_classification = 10; + + optional OmniboxInputType.Type input_type = 8; + + // An enum used in multiple places below. + enum ProviderType { + UNKNOWN_PROVIDER = 0; // Unknown provider (should not reach here) + HISTORY_URL = 1; // URLs in history, or user-typed URLs + HISTORY_CONTENTS = 2; // Matches for page contents of pages in history + HISTORY_QUICK = 3; // Matches for recently or frequently visited pages + // in history + SEARCH = 4; // Search suggestions for the default search engine + KEYWORD = 5; // Keyword-triggered searches + BUILTIN = 6; // Built-in URLs, such as chrome://version + SHORTCUTS = 7; // Recently selected omnibox suggestions + EXTENSION_APPS = 8; // DEPRECATED. Suggestions from extensions or apps + CONTACT = 9; // DEPRECATED. The user's contacts + BOOKMARK = 10; // The user's bookmarks + ZERO_SUGGEST = 11; // Suggestions based on the current page + // This enum value is currently only used by Android GSA. It represents + // a suggestion from the phone. + ON_DEVICE = 12; + // This enum value is currently only used by Android GSA. It represents + // a suggestion powered by a Chrome content provider. + ON_DEVICE_CHROME = 13; + CLIPBOARD_URL = 14; // Suggestion coming from clipboard (iOS only). + } + + // The result set displayed on the completion popup + // Next tag: 7 + message Suggestion { + // Where does this result come from? + optional ProviderType provider = 1; + + // What kind of result this is. + // This corresponds to the AutocompleteMatch::Type enumeration in + // components/omnibox/autocomplete_match.h (except for Android + // GSA result types). + enum ResultType { + UNKNOWN_RESULT_TYPE = 0; // Unknown type (should not reach here) + URL_WHAT_YOU_TYPED = 1; // The input as a URL + HISTORY_URL = 2; // A past page whose URL contains the input + HISTORY_TITLE = 3; // A past page whose title contains the input + HISTORY_BODY = 4; // DEPRECATED. A past page whose body + // contains the input + HISTORY_KEYWORD = 5; // A past page whose keyword contains the + // input + NAVSUGGEST = 6; // A suggested URL + SEARCH_WHAT_YOU_TYPED = 7; // The input as a search query (with the + // default engine) + SEARCH_HISTORY = 8; // A past search (with the default engine) + // containing the input + SEARCH_SUGGEST = 9; // A suggested search (with the default + // engine) for a query. + SEARCH_OTHER_ENGINE = 10; // A search with a non-default engine + EXTENSION_APP = 11; // DEPRECATED. An Extension App with a + // title/url that contains the input. + CONTACT = 12; // One of the user's contacts + BOOKMARK_TITLE = 13; // A bookmark whose title contains the input. + SEARCH_SUGGEST_ENTITY = 14; // A suggested search for an entity. + SEARCH_SUGGEST_TAIL = 15; // A suggested search to complete the tail + // of the query. + SEARCH_SUGGEST_PERSONALIZED = 16; // A personalized suggested search. + SEARCH_SUGGEST_PROFILE = 17; // A personalized suggested search for a + // Google+ profile. + APP_RESULT = 18; // Result from an installed app + // (eg: a gmail email). + // Used by Android GSA for on-device + // suggestion logging. + APP = 19; // An app result (eg: the gmail app). + // Used by Android GSA for on-device + // suggestion logging. + LEGACY_ON_DEVICE = 20; // An on-device result from a legacy + // provider. That is, this result is not + // from the on-device suggestion provider + // (go/icing). This field is + // used by Android GSA for on-device + // suggestion logging. + NAVSUGGEST_PERSONALIZED = 21; // A personalized url. + SEARCH_SUGGEST_ANSWER = 22; // DEPRECATED. Answers no longer have their + // own type but instead can be attached to + // suggestions of any type. + CALCULATOR = 23; // A calculator answer. + CLIPBOARD = 24; // An URL based on the clipboard. + } + optional ResultType result_type = 2; + + // The relevance score for this suggestion. + optional int32 relevance = 3; + + // How many times this result was typed in / selected from the omnibox. + // Only set for some providers and result_types. At the time of + // writing this comment, it is only set for HistoryURL and + // HistoryQuickProvider matches. + optional int32 typed_count = 5; + + // Whether this item is starred (bookmarked) or not. + optional bool is_starred = 4 [deprecated=true]; + + // Whether this item is disabled in the UI (not clickable). + optional bool is_disabled = 6; + } + repeated Suggestion suggestion = 9; + + // A data structure that holds per-provider information, general information + // not associated with a particular result. + // Next tag: 6 + message ProviderInfo { + // Which provider generated this ProviderInfo entry. + optional ProviderType provider = 1; + + // The provider's done() value, i.e., whether it's completed processing + // the query. Providers which don't do any asynchronous processing + // will always be done. + optional bool provider_done = 2; + + // The set of field trials that have triggered in the most recent query, + // possibly affecting the shown suggestions. Each element is a hash + // of the corresponding field trial name. + // See chrome/browser/autocomplete/search_provider.cc for a specific usage + // example. + repeated fixed32 field_trial_triggered = 3; + + // Same as above except that the set of field trials is a union of all field + // trials that have triggered within the current omnibox session including + // the most recent query. + // See AutocompleteController::ResetSession() for more details on the + // definition of a session. + // See chrome/browser/autocomplete/search_provider.cc for a specific usage + // example. + repeated fixed32 field_trial_triggered_in_session = 4; + + // The number of times this provider returned a non-zero number of + // suggestions during this omnibox session. + // Note that each provider may define a session differently for its + // purposes. + optional int32 times_returned_results_in_session = 5; + } + // A list of diagnostic information about each provider. Providers + // will appear at most once in this list. + repeated ProviderInfo provider_info = 12; +} diff --git a/chromium/components/metrics/proto/omnibox_input_type.proto b/chromium/components/metrics/proto/omnibox_input_type.proto new file mode 100644 index 00000000000..8d16bcb65d7 --- /dev/null +++ b/chromium/components/metrics/proto/omnibox_input_type.proto @@ -0,0 +1,39 @@ +// Copyright 2014 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. +// +// Stores information about an omnibox interaction. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "OmniboxInputTypeProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics.OmniboxInputType; + +// What kind of input the user provided. +// Note that the type below may be misleading. For example, "http:/" alone +// cannot be opened as a URL, so it is marked as a QUERY; yet the user +// probably intends to type more and have it eventually become a URL, so we +// need to make sure we still run it through inline autocomplete. +enum Type { + // Empty input (should not reach here) + INVALID = 0; + + // Valid input whose type cannot be determined + UNKNOWN = 1; + + // DEPRECATED. Input autodetected as UNKNOWN, which the user wants to treat + // as an URL by specifying a desired_tld. + DEPRECATED_REQUESTED_URL = 2; + + // Input autodetected as a URL + URL = 3; + + // Input autodetected as a query + QUERY = 4; + + // Input forced to be a query by an initial '?' + FORCED_QUERY = 5; +} diff --git a/chromium/components/metrics/proto/perf_data.proto b/chromium/components/metrics/proto/perf_data.proto new file mode 100644 index 00000000000..99f67f8021b --- /dev/null +++ b/chromium/components/metrics/proto/perf_data.proto @@ -0,0 +1,412 @@ +// Copyright 2014 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "PerfDataProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Stores information from a perf session generated via running: +// "perf record" +// +// See $kernel/tools/perf/design.txt for more details. + +// Please do not modify this protobuf directly, except to mirror the upstream +// version found here: +// https://chromium.googlesource.com/chromiumos/platform/chromiumos-wide-profiling/+/master/perf_data.proto +// with some fields omitted for privacy reasons. Because it is a read-only copy +// of the upstream protobuf, "Next tag:" comments are also absent. + +message PerfDataProto { + + // Perf event attribute. Stores the event description. + // This data structure is defined in the linux kernel: + // $kernel/tools/perf/util/event.h. + message PerfEventAttr { + // Type of the event. Type is an enumeration and can be: + // IP: an instruction-pointer was stored in the event. + // MMAP: a DLL was loaded. + // FORK: a process was forked. + // etc. + optional uint32 type = 1; + + // Size of the event data in bytes. + optional uint32 size = 2; + + // The config stores the CPU-specific counter information. + optional uint64 config = 3; + + // Sample period of the event. Indicates how often the event is + // triggered in terms of # of events. After |sample_period| events, an event + // will be recorded and stored. + optional uint64 sample_period = 4; + + // Sample frequency of the event. Indicates how often the event is + // triggered in terms of # per second. The kernel will try to record + // |sample_freq| events per second. + optional uint64 sample_freq = 5; + + // Sample type is a bitfield that records attributes of the sample. Example, + // whether an entire callchain was recorded, etc. + optional uint64 sample_type = 6; + + // Bitfield that indicates whether reads on the counter will return the + // total time enabled and total time running. + optional uint64 read_format = 7; + + // Indicates whether the counter starts off disabled. + optional bool disabled = 8; + + // Indicates whether child processes inherit the counter. + optional bool inherit = 9; + + // Indicates whether the counter is pinned to a particular CPU. + optional bool pinned = 10; + + // Indicates whether this counter's group has exclusive access to the CPU's + // counters. + optional bool exclusive = 11; + + // The following bits restrict events to be counted when the CPU is in user, + // kernel, hypervisor or idle modes. + optional bool exclude_user = 12; + optional bool exclude_kernel = 13; + optional bool exclude_hv = 14; + optional bool exclude_idle = 15; + + // Indicates whether mmap events should be recorded. + optional bool mmap = 16; + + // Indicates whether process comm information should be recorded upon + // process creation. + optional bool comm = 17; + + // Indicates that we are in frequency mode, not period mode. + optional bool freq = 18; + + // Indicates whether we have per-task counts. + optional bool inherit_stat = 19; + + // Indicates whether we enable perf events after an exec() function call. + optional bool enable_on_exec = 20; + + // Indicates whether we trace fork/exit. + optional bool task = 21; + + // Indicates whether we are using a watermark to wake up. + optional bool watermark = 22; + + // CPUs often "skid" when recording events. That means the instruction + // pointer may not be the same as the one that caused the counter overflow. + // Indicates the capabilities of the CPU in terms of recording precise + // instruction pointer. + optional uint32 precise_ip = 23; + + // Indicates whether we have non-exec mmap data. + optional bool mmap_data = 24; + + // If set, all the event types will have the same sample_type. + optional bool sample_id_all = 25; + + // Indicates whether we are counting events from the host (when running a + // VM). + optional bool exclude_host = 26; + + // Exclude events that happen on a guest OS. + optional bool exclude_guest = 27; + + // Contains the number of events after which we wake up. + optional uint32 wakeup_events = 28; + + // Contains the number of bytes after which we wake up. + optional uint32 wakeup_watermark = 29; + + // Information about the type of the breakpoint. + optional uint32 bp_type = 30; + + // Contains the breakpoint address. + optional uint64 bp_addr = 31; + + // This is an extension of config (see above). + optional uint64 config1 = 32; + + // The length of the breakpoint data in bytes. + optional uint64 bp_len = 33; + + // This is an extension of config (see above). + optional uint64 config2 = 34; + + // Contains the type of branch, example: user, kernel, call, return, etc. + optional uint64 branch_sample_type = 35; + } + + // Describes a perf.data file attribute. + message PerfFileAttr { + optional PerfEventAttr attr = 1; + + // List of perf file attribute ids. Each id describes an event. + repeated uint64 ids = 2; + } + + // This message contains information about a perf sample itself, as opposed to + // a perf event captured by a sample. + message SampleInfo { + // Process ID / thread ID from which this sample was taken. + optional uint32 pid = 1; + optional uint32 tid = 2; + + // Time this sample was taken (NOT the same as an event time). + // It is the number of nanoseconds since bootup. + optional uint64 sample_time_ns = 3; + + // The ID of the sample's event type (cycles, instructions, etc). + // The event type IDs are defined in PerfFileAttr. + optional uint64 id = 4; + + // The CPU on which this sample was taken. + optional uint32 cpu = 5; + } + + message CommEvent { + // Process id. + optional uint32 pid = 1; + + // Thread id. + optional uint32 tid = 2; + + // Comm string's md5 prefix. + // The comm string was field 3 and has been intentionally left out. + optional uint64 comm_md5_prefix = 4; + + // Time the sample was taken. + // Deprecated, use |sample_info| instead. + optional uint64 sample_time = 5 [deprecated=true]; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 6; + } + + message MMapEvent { + // Process id. + optional uint32 pid = 1; + + // Thread id. + optional uint32 tid = 2; + + // Start address. + optional uint64 start = 3; + + // Length. + optional uint64 len = 4; + + // PG Offset. + optional uint64 pgoff = 5; + + // Filename's md5 prefix. + // The filename was field 6 and has been intentionally left out. + optional uint64 filename_md5_prefix = 7; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 8; + } + + message BranchStackEntry { + // Branch source address. + optional uint64 from_ip = 1; + + // Branch destination address. + optional uint64 to_ip = 2; + + // Indicates a mispredicted branch. + optional bool mispredicted = 3; + } + + message SampleEvent { + // Instruction pointer. + optional uint64 ip = 1; + + // Process id. + optional uint32 pid = 2; + + // Thread id. + optional uint32 tid = 3; + + // The time after boot when the sample was recorded, in nanoseconds. + optional uint64 sample_time_ns = 4; + + // The address of the sample. + optional uint64 addr = 5; + + // The id of the sample. + optional uint64 id = 6; + + // The stream id of the sample. + optional uint64 stream_id = 7; + + // The period of the sample. + optional uint64 period = 8; + + // The CPU where the event was recorded. + optional uint32 cpu = 9; + + // The raw size of the event in bytes. + optional uint32 raw_size = 10; + + // Sample callchain info. + repeated uint64 callchain = 11; + + // Branch stack info. + repeated BranchStackEntry branch_stack = 12; + + // Not added from original: fields 13 and 14. + + // Sample weight for special events. + optional uint64 weight = 15; + + // Sample data source flags. + // Possible flag values: + // http://lxr.free-electrons.com/source/include/uapi/linux/perf_event.h#L849 + optional uint64 data_src = 16; + + // Sample transaction flags for special events. + // Flag fields: + // http://lxr.free-electrons.com/source/include/uapi/linux/perf_event.h#L209 + optional uint64 transaction = 17; + } + + // ForkEvent is used for both FORK and EXIT events, which have the same data + // format. We don't want to call this "ForkOrExitEvent", in case a separate + // exit event is introduced in the future. + message ForkEvent { + // Forked process ID. + optional uint32 pid = 1; + + // Parent process ID. + optional uint32 ppid = 2; + + // Forked process thread ID. + optional uint32 tid = 3; + + // Parent process thread ID. + optional uint32 ptid = 4; + + // Time of fork event in nanoseconds since bootup. + optional uint64 fork_time_ns = 5; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 11; + } + + message EventHeader { + // Type of event. + optional uint32 type = 1; + optional uint32 misc = 2; + // Size of event. + optional uint32 size = 3; + } + + message PerfEvent { + optional EventHeader header = 1; + + optional MMapEvent mmap_event = 2; + optional SampleEvent sample_event = 3; + optional CommEvent comm_event = 4; + // FORK and EXIT events are structurally identical. They only differ by the + // event type. But using two distinct fields makes things easier. + optional ForkEvent fork_event = 5; + optional ForkEvent exit_event = 9; + + // Not added from original: optional LostEvent lost_event = 6; + // Not added from original: optional ThrottleEvent throttle_event = 7; + // Not added from original: optional ReadEvent read_event = 8; + } + + message PerfEventStats { + // Total number of events read from perf data. + optional uint32 num_events_read = 1; + + // Total number of various types of events. + optional uint32 num_sample_events = 2; + optional uint32 num_mmap_events = 3; + optional uint32 num_fork_events = 4; + optional uint32 num_exit_events = 5; + + // Number of sample events that were successfully mapped by the address + // mapper, a quipper module that is used to obscure addresses and convert + // them to DSO name + offset. Sometimes it fails to process sample events. + // This field allows us to track the success rate of the address mapper. + optional uint32 num_sample_events_mapped = 6; + + // Whether address remapping was enabled. + optional bool did_remap = 7; + } + + message PerfBuildID { + // Misc field in perf_event_header. + // Indicates whether the file is mapped in kernel mode or user mode. + optional uint32 misc = 1; + + // Process ID. + optional uint32 pid = 2; + + // Build id. Should always contain kBuildIDArraySize bytes of data. + // perf_reader.h in Chrome OS defines kBuildIDArraySize = 20. + optional bytes build_hash = 3; + + // Filename Md5sum prefix. + // The filename was field 4 and has been intentionally left out. + optional uint64 filename_md5_prefix = 5; + } + + repeated PerfFileAttr file_attrs = 1; + repeated PerfEvent events = 2; + + // Time when quipper generated this perf data / protobuf, given as seconds + // since the epoch. + optional uint64 timestamp_sec = 3; + + // Records some stats about the serialized perf events. + optional PerfEventStats stats = 4; + + // Not added from original: repeated uint64 metadata_mask = 5; + + // Build ID metadata. + repeated PerfBuildID build_ids = 7; + + // Not added from original: repeated PerfUint32Metadata uint32_metadata = 8; + // Not added from original: repeated PerfUint64Metadata uint64_metadata = 9; + // Not added from original: + // optional PerfCPUTopologyMetadata cpu_topology = 11; + // Not added from original: + // repeated PerfNodeTopologyMetadata numa_topology = 12; + + message StringMetadata { + message StringAndMd5sumPrefix { + // The string value was field 1 and has been intentionally left out. + + // The string value's md5sum prefix. + optional uint64 value_md5_prefix = 2; + } + + // Not added from original: optional StringAndMd5sumPrefix hostname = 1; + // Not added from original: + // optional StringAndMd5sumPrefix kernel_version =2; + // Not added from original: optional StringAndMd5sumPrefix perf_version = 3; + // Not added from original: optional StringAndMd5sumPrefix architecture = 4; + // Not added from original: + // optional StringAndMd5sumPrefix cpu_description = 5; + // Not added from original: optional StringAndMd5sumPrefix cpu_id = 6; + // Not added from original: + // repeated StringAndMd5sumPrefix perf_command_line_token = 7; + + // The command line stored as a single string. + optional StringAndMd5sumPrefix perf_command_line_whole = 8; + } + + // All the string metadata from the perf data file. + optional StringMetadata string_metadata = 13; +} diff --git a/chromium/components/metrics/proto/perf_stat.proto b/chromium/components/metrics/proto/perf_stat.proto new file mode 100644 index 00000000000..bdfb3c458bf --- /dev/null +++ b/chromium/components/metrics/proto/perf_stat.proto @@ -0,0 +1,52 @@ +// Copyright 2015 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Stores output generated by the "perf stat" command. +// +// See https://perf.wiki.kernel.org/index.php/Tutorial#Counting_with_perf_stat +// for more details. + +// Next tag: 3 +message PerfStatProto { + // All lines printed by "perf stat". + repeated PerfStatLine line = 1; + + // The command line used to run "perf stat". + optional string command_line = 2; + + // Represents one line of "perf stat" output. + // Next tag: 4 + message PerfStatLine{ + // Time since the start of the "perf stat" command, in milliseconds. + // + // When running "perf stat" and printing the counters at the end, this is + // the total time taken by the run. + // + // Alternatively, "perf stat" can print its stats at regular intervals until + // the end of the run. For example, if "perf stat" runs for one second and + // prints at 200-ms intervals, it will print counter values for each event + // a total of five times. According to "perf stat" usage instructions, the + // printing interval should be no less than 100 ms. + optional uint64 time_ms = 1; + + // Current count value of the event being counted. May be different from the + // nominal counter value reported by "perf stat", depending on the event. + // For example, memory access counters are in units of 64 bytes. A counter + // value of 1024 would represent 65536 bytes, and we would set this field to + // 65536. + optional uint64 count = 2; + + // Name of event whose counter is listed on this line. + // This string should also appear as part of |PerfStatProto::command_line|. + // "perf stat" will preserve the event name exactly as it is passed in via + // the command line. + optional string event = 3; + } +} diff --git a/chromium/components/metrics/proto/profiler_event.proto b/chromium/components/metrics/proto/profiler_event.proto new file mode 100644 index 00000000000..0b9d4744579 --- /dev/null +++ b/chromium/components/metrics/proto/profiler_event.proto @@ -0,0 +1,127 @@ +// Copyright 2014 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. +// +// Performance metrics collected via Chrome's built-in profiler. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "ProfilerEventProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 7 +message ProfilerEventProto { + // The version of this profile. + enum ProfileVersion { + VERSION_UNKNOWN = 0; // Unknown version (should not reach here). + VERSION_STARTUP_PROFILE = 1; // Startup profile, logged approximately 60 + // seconds after launch. + VERSION_SPLIT_PROFILE = 2; // Part of a profile logged in pieces, where + // we finish a piece when a ProfilerEvent or a + // special end-of-recording event gets + // triggered. + } + optional ProfileVersion profile_version = 1; + + // The source based upon which "time" measurements are made. + // We currently only measure wall clock time; but we are exploring other + // measurement sources as well, such as CPU time or TCMalloc statistics. + enum TimeSource { + UNKNOWN_TIME_SOURCE = 0; // Unknown type (should not reach here). + WALL_CLOCK_TIME = 1; // Total time elapsed between the start and end of + // the task's execution. + } + optional TimeSource time_source = 2; + + // An event in the browser life that causes the client-side profiler framework + // to finish recording of its current instance of ProfilerEventProto, and + // start recording a new one. + // It's not guaranteed that the events get triggered in the order they are + // defined. + enum ProfilerEvent { + // The first non-empty paint of the first web contents happened. + // Corresponds to the Startup.FirstWebContents.NonEmptyPaint2 histogram. + EVENT_FIRST_NONEMPTY_PAINT = 0; + } + + // The set of events, in no particular order, that were triggered in the + // current Chrome session before the recording of this ProfilerEventProto + // started. It doesn't include the event that triggered the end of this + // ProfilerEventProto. A given event will not occur twice in this set. + // The field can be used to find all ProfilerEventProto instances recorded + // before or not before a given event. + repeated ProfilerEvent past_session_event = 4; + + // Time when profiling started. This is recorded as a time delta relative to + // the start time of the profiler data recording in the current browser + // session. + optional int64 profiling_start_ms = 5; + + // Time when profiling finished. This is recorded as a time delta relative to + // the start time of the profiler data recording in the current browser + // session. + optional int64 profiling_finish_ms = 6; + + // Data for a single tracked object (typically, a Task). + message TrackedObject { + // The name of the thread from which this task was posted, hashed. + optional fixed64 birth_thread_name_hash = 1; + + // The name of the thread on which this task was executed, hashed. + optional fixed64 exec_thread_name_hash = 2; + + // The source file name from which this task was posted, hashed. + optional fixed64 source_file_name_hash = 3; + + // Function name from which this task was posted, hashed. + optional fixed64 source_function_name_hash = 4; + + // The line number within the source file from which this task was posted. + optional int32 source_line_number = 5; + + // The number of times this task was executed. + optional int32 exec_count = 6; + + // The total execution time for instances this task. + optional int32 exec_time_total = 7; + + // The execution time for a uniformly randomly sampled instance of this + // task. + optional int32 exec_time_sampled = 8; + + // The total time instances this task spent waiting (e.g. in a message loop) + // before they were run. + optional int32 queue_time_total = 9; + + // The time that a uniformly randomly sampled instance of this task spent + // waiting (e.g. in a message loop) before it was run. + optional int32 queue_time_sampled = 10; + + // The type of process within which this task was executed. + enum ProcessType { + UNKNOWN = 0; // Should not reach here + BROWSER = 1; + RENDERER = 2; + PLUGIN = 3; // Deprecated. Should not be sent as of M51 due to + // NPAPI removal. + WORKER = 4; + NACL_LOADER = 5; + UTILITY = 6; + PROFILE_IMPORT = 7; + ZYGOTE = 8; + SANDBOX_HELPER = 9; + NACL_BROKER = 10; + GPU = 11; + PPAPI_PLUGIN = 12; + PPAPI_BROKER = 13; + } + optional ProcessType process_type = 11; + + // The local PID for the process within which this task was executed. + optional uint32 process_id = 12; + } + repeated TrackedObject tracked_object = 3; +} diff --git a/chromium/components/metrics/proto/sampled_profile.proto b/chromium/components/metrics/proto/sampled_profile.proto new file mode 100644 index 00000000000..b8936e114c6 --- /dev/null +++ b/chromium/components/metrics/proto/sampled_profile.proto @@ -0,0 +1,83 @@ +// Copyright 2014 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "SampledProfileProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +import "call_stack_profile.proto"; +import "perf_data.proto"; +import "perf_stat.proto"; + +// Protocol buffer for collected sample-based profiling data. +// Contains the parameters and data from a single profile collection event. + +// Next tag: 11 +message SampledProfile { + // Indicates the event that triggered this collection. + enum TriggerEvent { + UNKNOWN_TRIGGER_EVENT = 0; + + // The profile was triggered by periodic sampling. Periodically sampled + // profiles are collected once per uniformly sized period interval. Within + // each interval, the sampled data is collected at a random time. For + // example, if the interval is 60 s, then data would be collected at a + // random point in each of the intervals [0, 60 s), [60 s, 120 s), etc. + PERIODIC_COLLECTION = 1; + + // The profile was collected upon resume from suspend. + RESUME_FROM_SUSPEND = 2; + + // The profile was collected upon restoring a previous session. + RESTORE_SESSION = 3; + + // The profile was collected at process startup. + PROCESS_STARTUP = 4; + + // The profile was collected after jank was detected while executing a task. + JANKY_TASK = 5; + + // The profile was collected after a thread was determined to be hung. + THREAD_HUNG = 6; + } + optional TriggerEvent trigger_event = 1; + + // Fields 2-3: Time durations are given in ticks, and represent system uptime + // rather than wall time. + + // Time after system boot when the collection took place, in milliseconds. + optional int64 ms_after_boot = 2; + + // Time after last login when the collection took place, in milliseconds. + optional int64 ms_after_login = 3; + + // The duration for which the machine was suspended prior to collecting the + // sampled profile. Only set when |trigger_event| is RESUME_FROM_SUSPEND. + optional int64 suspend_duration_ms = 5; + + // Number of milliseconds after a resume that profile was collected. Only set + // when |trigger_event| is RESUME_FROM_SUSPEND. + optional int64 ms_after_resume = 6; + + // Number of tabs restored during a session restore. Only set when + // |trigger_event| is RESTORE_SESSION. + optional int32 num_tabs_restored = 7; + + // Number of milliseconds after a session restore that a profile was + // collected. Only set when |trigger_event| is RESTORE_SESSION. + optional int64 ms_after_restore = 8; + + // Sampled profile data collected from Linux perf tool. + optional PerfDataProto perf_data = 4; + + // Sampled profile data collected by periodic sampling of call stacks. + optional CallStackProfile call_stack_profile = 9; + + // Perf counter data collected using "perf stat". + optional PerfStatProto perf_stat = 10; +} diff --git a/chromium/components/metrics/proto/system_profile.proto b/chromium/components/metrics/proto/system_profile.proto new file mode 100644 index 00000000000..d1fa799148f --- /dev/null +++ b/chromium/components/metrics/proto/system_profile.proto @@ -0,0 +1,775 @@ +// Copyright 2014 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. +// +// Stores information about the user's brower and system configuration. +// The system configuration fields are recorded once per client session. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "SystemProfileProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 23 +message SystemProfileProto { + // The time when the client was compiled/linked, in seconds since the epoch. + optional int64 build_timestamp = 1; + + // A version number string for the application. + // Most commonly this is the browser version number found in a user agent + // string, and is typically a 4-tuple of numbers separated by periods. In + // cases where the user agent version might be ambiguous (example: Linux 64- + // bit build, rather than 32-bit build, or a Windows version used in some + // special context, such as ChromeFrame running in IE), then this may include + // some additional postfix to provide clarification not available in the UA + // string. + // + // An example of a browser version 4-tuple is "5.0.322.0". Currently used + // postfixes are: + // + // "-64": a 64-bit build + // "-F": Chrome is running under control of ChromeFrame + // "-devel": this is not an official build of Chrome + // + // A full version number string could look similar to: + // "5.0.322.0-F-devel". + // + // This value, when available, is more trustworthy than the UA string + // associated with the request; and including the postfix, may be more + // specific. + optional string app_version = 2; + + // The brand code or distribution tag assigned to a partner, if available. + // Brand codes are only available on Windows. Not every Windows install + // though will have a brand code. + optional string brand_code = 12; + + // The possible channels for an installation, from least to most stable. + enum Channel { + CHANNEL_UNKNOWN = 0; // Unknown channel -- perhaps an unofficial build? + CHANNEL_CANARY = 1; + CHANNEL_DEV = 2; + CHANNEL_BETA = 3; + CHANNEL_STABLE = 4; + } + optional Channel channel = 10; + + // True if Chrome build is ASan-instrumented. + optional bool is_asan_build = 20 [default = false]; + + // The date the user enabled UMA, in seconds since the epoch. + // If the user has toggled the UMA enabled state multiple times, this will + // be the most recent date on which UMA was enabled. + // For privacy, this is rounded to the nearest hour. + optional int64 uma_enabled_date = 3; + + // The time when the client was installed, in seconds since the epoch. + // For privacy, this is rounded to the nearest hour. + optional int64 install_date = 16; + + // The user's selected application locale, i.e. the user interface language. + // The locale includes a language code and, possibly, also a country code, + // e.g. "en-US". + optional string application_locale = 4; + + // Information on the user's operating system. + message OS { + // The user's operating system. This should be one of: + // - Android + // - Windows NT + // - Linux (includes ChromeOS) + // - iPhone OS + // - Mac OS X + optional string name = 1; + + // The version of the OS. The meaning of this field is OS-dependent. + optional string version = 2; + + // The fingerprint of the build. This field is used only on Android. + optional string fingerprint = 3; + + // Whether the version of iOS appears to be "jailbroken". This field is + // used only on iOS. Chrome for iOS detects whether device contains a + // DynamicLibraries/ directory. It's a necessary but insufficient indicator + // of whether the operating system has been jailbroken. + optional bool is_jailbroken = 4; + } + optional OS os = 5; + + // Next tag for Hardware: 18 + // Information on the user's hardware. + message Hardware { + // The CPU architecture (x86, PowerPC, x86_64, ...) + optional string cpu_architecture = 1; + + // The amount of RAM present on the system, in megabytes. + optional int64 system_ram_mb = 2; + + // The base memory address that chrome.dll was loaded at. + // (Logged only on Windows.) + optional int64 dll_base = 3; + + // The hardware_class describes the current machine model, e.g. "MacPro1,1" + // on Mac, or "Nexus 5" on Android. Only implemented on OS X, Android, and + // Chrome OS. + // + // For Chrome OS, the device hardware class ID is a unique string associated + // with each Chrome OS device product revision generally assigned at + // hardware qualification time. The hardware class effectively identifies + // the configured system components such as CPU, WiFi adapter, etc. + // + // An example of such a hardware class is "IEC MARIO PONY 6101". An + // internal database associates this hardware class with the qualified + // device specifications including OEM information, schematics, hardware + // qualification reports, test device tags, etc. + optional string hardware_class = 4; + + // The number of physical screens. + optional int32 screen_count = 5; + + // The screen dimensions of the primary screen, in pixels. + optional int32 primary_screen_width = 6; + optional int32 primary_screen_height = 7; + + // The device scale factor of the primary screen. + optional float primary_screen_scale_factor = 12; + + // Max DPI for any attached screen. (Windows only) + optional float max_dpi_x = 9; + optional float max_dpi_y = 10; + + // Information on the CPU obtained by CPUID. + message CPU { + // A 12 character string naming the vendor, e.g. "GeniuneIntel". + optional string vendor_name = 1; + + // The signature reported by CPUID (from EAX). + optional uint32 signature = 2; + + // Number of logical processors/cores on the current machine. + optional uint32 num_cores = 3; + } + optional CPU cpu = 13; + + // Information on the GPU + message Graphics { + // The GPU manufacturer's vendor id. + optional uint32 vendor_id = 1; + + // The GPU manufacturer's device id for the chip set. + optional uint32 device_id = 2; + + // The driver version on the GPU. + optional string driver_version = 3; + + // The driver date on the GPU. + optional string driver_date = 4; + + // The GL_VENDOR string. An example of a gl_vendor string is + // "Imagination Technologies". "" if we are not using OpenGL. + optional string gl_vendor = 6; + + // The GL_RENDERER string. An example of a gl_renderer string is + // "PowerVR SGX 540". "" if we are not using OpenGL. + optional string gl_renderer = 7; + } + optional Graphics gpu = 8; + + // Information about Bluetooth devices paired with the system. + message Bluetooth { + // Whether Bluetooth is present on this system. + optional bool is_present = 1; + + // Whether Bluetooth is enabled on this system. + optional bool is_enabled = 2; + + // Describes a paired device. + message PairedDevice { + // Assigned class of the device. This is a bitfield according to the + // Bluetooth specification available at the following URL: + // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband + optional uint32 bluetooth_class = 1; + + // Decoded device type. + enum Type { + DEVICE_UNKNOWN = 0; + DEVICE_COMPUTER = 1; + DEVICE_PHONE = 2; + DEVICE_MODEM = 3; + DEVICE_AUDIO = 4; + DEVICE_CAR_AUDIO = 5; + DEVICE_VIDEO = 6; + DEVICE_PERIPHERAL = 7; + DEVICE_JOYSTICK = 8; + DEVICE_GAMEPAD = 9; + DEVICE_KEYBOARD = 10; + DEVICE_MOUSE = 11; + DEVICE_TABLET = 12; + DEVICE_KEYBOARD_MOUSE_COMBO = 13; + } + optional Type type = 2; + + // Vendor prefix of the Bluetooth address, these are OUI registered by + // the IEEE and are encoded with the first byte in bits 16-23, the + // second byte in bits 8-15 and the third byte in bits 0-7. + // + // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11 + optional uint32 vendor_prefix = 4; + + // The Vendor ID of a device, returned in vendor_id below, can be + // either allocated by the Bluetooth SIG or USB IF, providing two + // completely overlapping namespaces for identifiers. + // + // This field should be read along with vendor_id to correctly + // identify the vendor. For example Google is identified by either + // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or + // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1. + // + // If the device does not support the Device ID specification the + // unknown value will be set. + enum VendorIDSource { + VENDOR_ID_UNKNOWN = 0; + VENDOR_ID_BLUETOOTH = 1; + VENDOR_ID_USB = 2; + } + optional VendorIDSource vendor_id_source = 8; + + // Vendor ID of the device, where available. + optional uint32 vendor_id = 5; + + // Product ID of the device, where available. + optional uint32 product_id = 6; + + // Device ID of the device, generally the release or version number in + // BCD format, where available. + optional uint32 device_id = 7; + } + repeated PairedDevice paired_device = 3; + } + optional Bluetooth bluetooth = 11; + + // Whether the internal display produces touch events. Omitted if unknown. + // Logged on ChromeOS only. + optional bool internal_display_supports_touch = 14; + + // Vendor ids and product ids of external touchscreens. + message TouchScreen { + // Touch screen vendor id. + optional uint32 vendor_id = 1; + // Touch screen product id. + optional uint32 product_id = 2; + } + // Lists vendor and product ids of external touchscreens. + // Logged on ChromeOS only. + repeated TouchScreen external_touchscreen = 15; + + // Drive messages are currently logged on Windows 7+, iOS, and Android. + message Drive { + // Whether this drive incurs a time penalty when randomly accessed. This + // should be true for spinning disks but false for SSDs or other + // flash-based drives. + optional bool has_seek_penalty = 1; + } + // The drive that the application executable was loaded from. + optional Drive app_drive = 16; + // The drive that the current user data directory was loaded from. + optional Drive user_data_drive = 17; + } + optional Hardware hardware = 6; + + // Information about the network connection. + message Network { + // Set to true if connection_type changed during the lifetime of the log. + optional bool connection_type_is_ambiguous = 1; + + // Derived from net::NetworkChangeNotifier::ConnectionType translated + // through NetworkMetricsProvider::GetConnectionType. + enum ConnectionType { + CONNECTION_UNKNOWN = 0; + CONNECTION_ETHERNET = 1; + CONNECTION_WIFI = 2; + CONNECTION_2G = 3; + CONNECTION_3G = 4; + CONNECTION_4G = 5; + CONNECTION_BLUETOOTH = 6; + CONNECTION_NONE = 7; + } + // The connection type according to NetworkChangeNotifier. + optional ConnectionType connection_type = 2; + + // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log. + optional bool wifi_phy_layer_protocol_is_ambiguous = 3; + + // See net::WifiPHYLayerProtocol. + enum WifiPHYLayerProtocol { + WIFI_PHY_LAYER_PROTOCOL_NONE = 0; + WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1; + WIFI_PHY_LAYER_PROTOCOL_A = 2; + WIFI_PHY_LAYER_PROTOCOL_B = 3; + WIFI_PHY_LAYER_PROTOCOL_G = 4; + WIFI_PHY_LAYER_PROTOCOL_N = 5; + WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6; + } + // The physical layer mode of the associated wifi access point, if any. + optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4; + + // Describe wifi access point information. + message WifiAccessPoint { + // Vendor prefix of the access point's BSSID, these are OUIs + // (Organizationally Unique Identifiers) registered by + // the IEEE and are encoded with the first byte in bits 16-23, the + // second byte in bits 8-15 and the third byte in bits 0-7. + optional uint32 vendor_prefix = 1; + + // Access point seurity mode definitions. + enum SecurityMode { + SECURITY_UNKNOWN = 0; + SECURITY_WPA = 1; + SECURITY_WEP = 2; + SECURITY_RSN = 3; + SECURITY_802_1X = 4; + SECURITY_PSK = 5; + SECURITY_NONE = 6; + } + // The security mode of the access point. + optional SecurityMode security_mode = 2; + + // Vendor specific information. + message VendorInformation { + // The model number, for example "0". + optional string model_number = 1; + + // The model name (sometimes the same as the model_number), + // for example "WZR-HP-AG300H". + optional string model_name = 2; + + // The device name (sometimes the same as the model_number), + // for example "Dummynet" + optional string device_name = 3; + + // The list of vendor-specific OUIs (Organziationally Unqiue + // Identifiers). These are provided by the vendor through WPS + // (Wireless Provisioning Service) information elements, which + // identifies the content of the element. + repeated uint32 element_identifier = 4; + } + // The wireless access point vendor information. + optional VendorInformation vendor_info = 3; + } + // Information of the wireless AP that device is connected to. + optional WifiAccessPoint access_point_info = 5; + } + optional Network network = 13; + + // Information on the Google Update install that is managing this client. + message GoogleUpdate { + // Whether the Google Update install is system-level or user-level. + optional bool is_system_install = 1; + + // The date at which Google Update last started performing an automatic + // update check, in seconds since the Unix epoch. + optional int64 last_automatic_start_timestamp = 2; + + // The date at which Google Update last successfully sent an update check + // and recieved an intact response from the server, in seconds since the + // Unix epoch. (The updates don't need to be successfully installed.) + optional int64 last_update_check_timestamp = 3; + + // Describes a product being managed by Google Update. (This can also + // describe Google Update itself.) + message ProductInfo { + // The current version of the product that is installed. + optional string version = 1; + + // The date at which Google Update successfully updated this product, + // stored in seconds since the Unix epoch. This is updated when an update + // is successfully applied, or if the server reports that no update + // is available. + optional int64 last_update_success_timestamp = 2; + + // The result reported by the product updater on its last run. + enum InstallResult { + INSTALL_RESULT_SUCCESS = 0; + INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1; + INSTALL_RESULT_FAILED_MSI_ERROR = 2; + INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3; + INSTALL_RESULT_EXIT_CODE = 4; + } + optional InstallResult last_result = 3; + + // The error code reported by the product updater on its last run. This + // will typically be a error code specific to the product installer. + optional int32 last_error = 4; + + // The extra error code reported by the product updater on its last run. + // This will typically be a Win32 error code. + optional int32 last_extra_error = 5; + } + optional ProductInfo google_update_status = 4; + optional ProductInfo client_status = 5; + } + optional GoogleUpdate google_update = 11; + + // Information on all installed plugins. + message Plugin { + // The plugin's self-reported name and filename (without path). + optional string name = 1; + optional string filename = 2; + + // The plugin's version. + optional string version = 3; + + // True if the plugin is disabled. + // If a client has multiple local Chrome user accounts, this is logged based + // on the first user account launched during the current session. + optional bool is_disabled = 4; + + // True if the plugin is PPAPI. + optional bool is_pepper = 5; + } + repeated Plugin plugin = 7; + + // Figures that can be used to generate application stability metrics. + // All values are counts of events since the last time that these + // values were reported. + // Next tag: 26 + message Stability { + // Total amount of time that the program was running, in seconds, + // since the last time a log was recorded, as measured using a client-side + // clock implemented via TimeTicks, which guarantees that it is monotonic + // and does not jump if the user changes his/her clock. The TimeTicks + // implementation also makes the clock not count time the computer is + // suspended. + optional int64 incremental_uptime_sec = 1; + + // Total amount of time that the program was running, in seconds, + // since startup, as measured using a client-side clock implemented + // via TimeTicks, which guarantees that it is monotonic and does not + // jump if the user changes his/her clock. The TimeTicks implementation + // also makes the clock not count time the computer is suspended. + // This field was added for M-35. + optional int64 uptime_sec = 23; + + // Page loads along with renderer crashes, hangs and failed launches, since + // page load count roughly corresponds to usage. + optional int32 page_load_count = 2; + optional int32 renderer_crash_count = 3; + optional int32 renderer_hang_count = 4; + optional int32 renderer_failed_launch_count = 24; + + // Number of renderer crashes and failed launches that were for extensions. + // These are not counted in the renderer counts above. + optional int32 extension_renderer_crash_count = 5; + optional int32 extension_renderer_failed_launch_count = 25; + + // Number of non-renderer child process crashes. + optional int32 child_process_crash_count = 6; + + // Number of times the browser has crashed while logged in as the "other + // user" (guest) account. + // Logged on ChromeOS only. + optional int32 other_user_crash_count = 7; + + // Number of times the kernel has crashed. + // Logged on ChromeOS only. + optional int32 kernel_crash_count = 8; + + // Number of times the system has shut down uncleanly. + // Logged on ChromeOS only. + optional int32 unclean_system_shutdown_count = 9; + + // + // All the remaining fields in the Stability are recorded at most once per + // client session. + // + + // The number of times the program was launched. + // This will typically be equal to 1. However, it is possible that Chrome + // was unable to upload stability metrics for previous launches (e.g. due to + // crashing early during startup), and hence this value might be greater + // than 1. + optional int32 launch_count = 15; + // The number of times that it didn't exit cleanly (which we assume to be + // mostly crashes). + optional int32 crash_count = 16; + + // The number of times the program began, but did not complete, the shutdown + // process. (For example, this may occur when Windows is shutting down, and + // it only gives the process a few seconds to clean up.) + optional int32 incomplete_shutdown_count = 17; + + // The number of times the program was able register with breakpad crash + // services. + optional int32 breakpad_registration_success_count = 18; + + // The number of times the program failed to register with breakpad crash + // services. If crash registration fails then when the program crashes no + // crash report will be generated. + optional int32 breakpad_registration_failure_count = 19; + + // The number of times the program has run under a debugger. This should + // be an exceptional condition. Running under a debugger prevents crash + // dumps from being generated. + optional int32 debugger_present_count = 20; + + // The number of times the program has run without a debugger attached. + // This should be most common scenario and should be very close to + // |launch_count|. + optional int32 debugger_not_present_count = 21; + + // Stability information for all installed plugins. + message PluginStability { + // The relevant plugin's information (name, etc.) + optional Plugin plugin = 1; + + // The number of times this plugin's process was launched. + optional int32 launch_count = 2; + + // The number of times this plugin was instantiated on a web page. + // This will be >= |launch_count|. + // (A page load with multiple sections drawn by this plugin will + // increase this count multiple times.) + optional int32 instance_count = 3; + + // The number of times this plugin process crashed. + // This value will be <= |launch_count|. + optional int32 crash_count = 4; + + // The number of times this plugin could not be loaded. + optional int32 loading_error_count = 5; + } + repeated PluginStability plugin_stability = 22; + } + optional Stability stability = 8; + + // Description of a field trial or experiment that the user is currently + // enrolled in. + // All metrics reported in this upload can potentially be influenced by the + // field trial. + message FieldTrial { + // The name of the field trial, as a 32-bit identifier. + // Currently, the identifier is a hash of the field trial's name. + optional fixed32 name_id = 1; + + // The user's group within the field trial, as a 32-bit identifier. + // Currently, the identifier is a hash of the group's name. + optional fixed32 group_id = 2; + } + repeated FieldTrial field_trial = 9; + + // Information about the A/V output device(s) (typically just a TV). + // However, a configuration may have one or more intermediate A/V devices + // between the source device and the TV (e.g. an A/V receiver, video + // processor, etc.). + message ExternalAudioVideoDevice { + // The manufacturer name (possibly encoded as a 3-letter code, e.g. "YMH" + // for Yamaha). + optional string manufacturer_name = 1; + + // The model name (e.g. "RX-V1900"). Some devices may report generic names + // like "receiver" or use the full manufacturer name (e.g "PHILIPS"). + optional string model_name = 2; + + // The product code (e.g. "0218"). + optional string product_code = 3; + + // The device types. A single device can have multiple types (e.g. a set-top + // box could be both a tuner and a player). The same type may even be + // repeated (e.g a device that reports two tuners). + enum AVDeviceType { + AV_DEVICE_TYPE_UNKNOWN = 0; + AV_DEVICE_TYPE_TV = 1; + AV_DEVICE_TYPE_RECORDER = 2; + AV_DEVICE_TYPE_TUNER = 3; + AV_DEVICE_TYPE_PLAYER = 4; + AV_DEVICE_TYPE_AUDIO_SYSTEM = 5; + } + repeated AVDeviceType av_device_type = 4; + + // The year of manufacture. + optional int32 manufacture_year = 5; + + // The week of manufacture. + // Note: per the Wikipedia EDID article, numbering for this field may not + // be consistent between manufacturers. + optional int32 manufacture_week = 6; + + // Max horizontal resolution in pixels. + optional int32 horizontal_resolution = 7; + + // Max vertical resolution in pixels. + optional int32 vertical_resolution = 8; + + // Audio capabilities of the device. + // Ref: http://en.wikipedia.org/wiki/Extended_display_identification_data + // Next tag: 7 + message AudioDescription { + // Audio format + enum AudioFormat { + AUDIO_FORMAT_UNKNOWN = 0; + AUDIO_FORMAT_LPCM = 1; + AUDIO_FORMAT_AC_3 = 2; + AUDIO_FORMAT_MPEG1 = 3; + AUDIO_FORMAT_MP3 = 4; + AUDIO_FORMAT_MPEG2 = 5; + AUDIO_FORMAT_AAC = 6; + AUDIO_FORMAT_DTS = 7; + AUDIO_FORMAT_ATRAC = 8; + AUDIO_FORMAT_ONE_BIT = 9; + AUDIO_FORMAT_DD_PLUS = 10; + AUDIO_FORMAT_DTS_HD = 11; + AUDIO_FORMAT_MLP_DOLBY_TRUEHD = 12; + AUDIO_FORMAT_DST_AUDIO = 13; + AUDIO_FORMAT_MICROSOFT_WMA_PRO = 14; + } + optional AudioFormat audio_format = 1; + + // Number of channels (e.g. 1, 2, 8, etc.). + optional int32 num_channels = 2; + + // Supported sample frequencies in Hz (e.g. 32000, 44100, etc.). + // Multiple frequencies may be specified. + repeated int32 sample_frequency_hz = 3; + + // Maximum bit rate in bits/s. + optional int32 max_bit_rate_per_second = 4; + + // Bit depth (e.g. 16, 20, 24, etc.). + optional int32 bit_depth = 5; + + // Output mode: analog vs digital. + enum OutputMode { + ANALOG = 0; + DIGITAL = 1; + } + optional OutputMode output_mode = 6; + + } + repeated AudioDescription audio_description = 9; + + // The position in AV setup. + // A value of 0 means this device is the TV. + // A value of 1 means this device is directly connected to one of + // the TV's inputs. + // Values > 1 indicate there are 1 or more devices between this device + // and the TV. + optional int32 position_in_setup = 10; + + // Whether this device is in the path to the TV. + optional bool is_in_path_to_tv = 11; + + // The CEC version the device supports. + // CEC stands for Consumer Electronics Control, a part of the HDMI + // specification. Not all HDMI devices support CEC. + // Only devices that support CEC will report a value here. + optional int32 cec_version = 12; + + // This message reports CEC commands seen by a device. + // After each log is sent, this information is cleared and gathered again. + // By collecting CEC status information by opcode we can determine + // which CEC features can be supported. + message CECCommand { + // The CEC command opcode. CEC supports up to 256 opcodes. + // We add only one CECCommand message per unique opcode. Only opcodes + // seen by the device will be reported. The remainder of the message + // accumulates status for this opcode (and device). + optional int32 opcode = 1; + + // The total number of commands received from the external device. + optional int32 num_received_direct = 2; + + // The number of commands received from the external device as part of a + // broadcast message. + optional int32 num_received_broadcast = 3; + + // The total number of commands sent to the external device. + optional int32 num_sent_direct = 4; + + // The number of commands sent to the external device as part of a + // broadcast message. + optional int32 num_sent_broadcast = 5; + + // The number of aborted commands for unknown reasons. + optional int32 num_aborted_unknown_reason = 6; + + // The number of aborted commands because of an unrecognized opcode. + optional int32 num_aborted_unrecognized = 7; + } + repeated CECCommand cec_command = 13; + } + repeated ExternalAudioVideoDevice external_audio_video_device = 14; + + // Information about the current wireless access point. Collected directly + // from the wireless access point via standard apis if the device is + // connected to the Internet wirelessly. Introduced for Chrome on TV devices + // but also can be collected by ChromeOS, Android or other clients. + message ExternalAccessPoint { + // The manufacturer name, for example "ASUSTeK Computer Inc.". + optional string manufacturer = 1; + + // The model name, for example "Wi-Fi Protected Setup Router". + optional string model_name = 2; + + // The model number, for example "RT-N16". + optional string model_number = 3; + + // The device name (sometime same as model_number), for example "RT-N16". + optional string device_name = 4; + } + optional ExternalAccessPoint external_access_point = 15; + + // Number of users currently signed into a multiprofile session. + // A zero value indicates that the user count changed while the log is open. + // Logged only on ChromeOS. + optional uint32 multi_profile_user_count = 17; + + // Information about extensions that are installed, masked to provide better + // privacy. Only extensions from a single profile are reported; this will + // generally be the profile used when the browser is started. The profile + // reported on will remain consistent at least until the browser is + // relaunched (or the profile is deleted by the user). + // + // Each client first picks a value for client_key derived from its UMA + // client_id: + // client_key = client_id % 4096 + // Then, each installed extension is mapped into a hash bucket according to + // bucket = CityHash64(StringPrintf("%d:%s", + // client_key, extension_id)) % 1024 + // The client reports the set of hash buckets occupied by all installed + // extensions. If multiple extensions map to the same bucket, that bucket is + // still only reported once. + repeated int32 occupied_extension_bucket = 18; + + // The state of loaded extensions for this system. The system can have either + // no applicable extensions, extensions only from the webstore and verified by + // the webstore, extensions only from the webstore but not verified, or + // extensions not from the store. If there is a single off-store extension, + // then HAS_OFFSTORE is reported. This should be kept in sync with the + // corresponding enum in chrome/browser/metrics/extensions_metrics_provider.cc + enum ExtensionsState { + NO_EXTENSIONS = 0; + NO_OFFSTORE_VERIFIED = 1; + NO_OFFSTORE_UNVERIFIED = 2; + HAS_OFFSTORE = 3; + } + optional ExtensionsState offstore_extensions_state = 19; + + // The nature of the choice the user was given concerning metrics recording. + // Specifically, whether the enable metrics/crash reporting checkbox that was + // shown on first run was checked or unchecked by default. + // This state is recorded on first run, and uploaded in every UMA log. + // Consequently this should only be defined for clients that were installed + // after the recording code was implemented. + enum UmaDefaultState { + // The enable checkbox was unchecked by default. + OPT_IN = 0; + // The enable checkbox was checked by default. + OPT_OUT = 1; + // Policy mandated that UMA be enaled, the user had no choice. + POLICY_FORCED_ENABLED = 2; + } + optional UmaDefaultState uma_default_state = 22; +} diff --git a/chromium/components/metrics/proto/user_action_event.proto b/chromium/components/metrics/proto/user_action_event.proto new file mode 100644 index 00000000000..30a93180d07 --- /dev/null +++ b/chromium/components/metrics/proto/user_action_event.proto @@ -0,0 +1,23 @@ +// Copyright 2014 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. +// +// Stores information about an event that occurs in response to a user action, +// e.g. an interaction with a browser UI element. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "UserActionEventProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 3 +message UserActionEventProto { + // The name of the action, hashed. + optional fixed64 name_hash = 1; + + // The timestamp for the event, in seconds since the epoch. + optional int64 time = 2; +} diff --git a/chromium/components/metrics/serialization/metric_sample.cc b/chromium/components/metrics/serialization/metric_sample.cc new file mode 100644 index 00000000000..a0124744bb0 --- /dev/null +++ b/chromium/components/metrics/serialization/metric_sample.cc @@ -0,0 +1,197 @@ +// Copyright 2014 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 "components/metrics/serialization/metric_sample.h" + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" + +namespace metrics { + +MetricSample::MetricSample(MetricSample::SampleType sample_type, + const std::string& metric_name, + int sample, + int min, + int max, + int bucket_count) + : type_(sample_type), + name_(metric_name), + sample_(sample), + min_(min), + max_(max), + bucket_count_(bucket_count) { +} + +MetricSample::~MetricSample() { +} + +bool MetricSample::IsValid() const { + return name().find(' ') == std::string::npos && + name().find('\0') == std::string::npos && !name().empty(); +} + +std::string MetricSample::ToString() const { + if (type_ == CRASH) { + return base::StringPrintf("crash%c%s%c", + '\0', + name().c_str(), + '\0'); + } else if (type_ == SPARSE_HISTOGRAM) { + return base::StringPrintf("sparsehistogram%c%s %d%c", + '\0', + name().c_str(), + sample_, + '\0'); + } else if (type_ == LINEAR_HISTOGRAM) { + return base::StringPrintf("linearhistogram%c%s %d %d%c", + '\0', + name().c_str(), + sample_, + max_, + '\0'); + } else if (type_ == HISTOGRAM) { + return base::StringPrintf("histogram%c%s %d %d %d %d%c", + '\0', + name().c_str(), + sample_, + min_, + max_, + bucket_count_, + '\0'); + } else { + // The type can only be USER_ACTION. + CHECK_EQ(type_, USER_ACTION); + return base::StringPrintf("useraction%c%s%c", + '\0', + name().c_str(), + '\0'); + } +} + +int MetricSample::sample() const { + CHECK_NE(type_, USER_ACTION); + CHECK_NE(type_, CRASH); + return sample_; +} + +int MetricSample::min() const { + CHECK_EQ(type_, HISTOGRAM); + return min_; +} + +int MetricSample::max() const { + CHECK_NE(type_, CRASH); + CHECK_NE(type_, USER_ACTION); + CHECK_NE(type_, SPARSE_HISTOGRAM); + return max_; +} + +int MetricSample::bucket_count() const { + CHECK_EQ(type_, HISTOGRAM); + return bucket_count_; +} + +// static +scoped_ptr<MetricSample> MetricSample::CrashSample( + const std::string& crash_name) { + return scoped_ptr<MetricSample>( + new MetricSample(CRASH, crash_name, 0, 0, 0, 0)); +} + +// static +scoped_ptr<MetricSample> MetricSample::HistogramSample( + const std::string& histogram_name, + int sample, + int min, + int max, + int bucket_count) { + return scoped_ptr<MetricSample>(new MetricSample( + HISTOGRAM, histogram_name, sample, min, max, bucket_count)); +} + +// static +scoped_ptr<MetricSample> MetricSample::ParseHistogram( + const std::string& serialized_histogram) { + std::vector<base::StringPiece> parts = base::SplitStringPiece( + serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (parts.size() != 5) + return scoped_ptr<MetricSample>(); + int sample, min, max, bucket_count; + if (parts[0].empty() || !base::StringToInt(parts[1], &sample) || + !base::StringToInt(parts[2], &min) || + !base::StringToInt(parts[3], &max) || + !base::StringToInt(parts[4], &bucket_count)) { + return scoped_ptr<MetricSample>(); + } + + return HistogramSample(parts[0].as_string(), sample, min, max, bucket_count); +} + +// static +scoped_ptr<MetricSample> MetricSample::SparseHistogramSample( + const std::string& histogram_name, + int sample) { + return scoped_ptr<MetricSample>( + new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0)); +} + +// static +scoped_ptr<MetricSample> MetricSample::ParseSparseHistogram( + const std::string& serialized_histogram) { + std::vector<base::StringPiece> parts = base::SplitStringPiece( + serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (parts.size() != 2) + return scoped_ptr<MetricSample>(); + int sample; + if (parts[0].empty() || !base::StringToInt(parts[1], &sample)) + return scoped_ptr<MetricSample>(); + + return SparseHistogramSample(parts[0].as_string(), sample); +} + +// static +scoped_ptr<MetricSample> MetricSample::LinearHistogramSample( + const std::string& histogram_name, + int sample, + int max) { + return scoped_ptr<MetricSample>( + new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0)); +} + +// static +scoped_ptr<MetricSample> MetricSample::ParseLinearHistogram( + const std::string& serialized_histogram) { + std::vector<base::StringPiece> parts = base::SplitStringPiece( + serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + int sample, max; + if (parts.size() != 3) + return scoped_ptr<MetricSample>(); + if (parts[0].empty() || !base::StringToInt(parts[1], &sample) || + !base::StringToInt(parts[2], &max)) { + return scoped_ptr<MetricSample>(); + } + + return LinearHistogramSample(parts[0].as_string(), sample, max); +} + +// static +scoped_ptr<MetricSample> MetricSample::UserActionSample( + const std::string& action_name) { + return scoped_ptr<MetricSample>( + new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0)); +} + +bool MetricSample::IsEqual(const MetricSample& metric) { + return type_ == metric.type_ && name_ == metric.name_ && + sample_ == metric.sample_ && min_ == metric.min_ && + max_ == metric.max_ && bucket_count_ == metric.bucket_count_; +} + +} // namespace metrics diff --git a/chromium/components/metrics/serialization/metric_sample.h b/chromium/components/metrics/serialization/metric_sample.h new file mode 100644 index 00000000000..247d2e99653 --- /dev/null +++ b/chromium/components/metrics/serialization/metric_sample.h @@ -0,0 +1,118 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_ +#define COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace metrics { + +// This class is used by libmetrics (ChromeOS) to serialize +// and deserialize measurements to send them to a metrics sending service. +// It is meant to be a simple container with serialization functions. +class MetricSample { + public: + // Types of metric sample used. + enum SampleType { + CRASH, + HISTOGRAM, + LINEAR_HISTOGRAM, + SPARSE_HISTOGRAM, + USER_ACTION + }; + + ~MetricSample(); + + // Returns true if the sample is valid (can be serialized without ambiguity). + // + // This function should be used to filter bad samples before serializing them. + bool IsValid() const; + + // Getters for type and name. All types of metrics have these so we do not + // need to check the type. + SampleType type() const { return type_; } + const std::string& name() const { return name_; } + + // Getters for sample, min, max, bucket_count. + // Check the metric type to make sure the request make sense. (ex: a crash + // sample does not have a bucket_count so we crash if we call bucket_count() + // on it.) + int sample() const; + int min() const; + int max() const; + int bucket_count() const; + + // Returns a serialized version of the sample. + // + // The serialized message for each type is: + // crash: crash\0|name_|\0 + // user action: useraction\0|name_|\0 + // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0 + // sparsehistogram: sparsehistogram\0|name_| |sample_|\0 + // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0 + std::string ToString() const; + + // Builds a crash sample. + static scoped_ptr<MetricSample> CrashSample(const std::string& crash_name); + + // Builds a histogram sample. + static scoped_ptr<MetricSample> HistogramSample( + const std::string& histogram_name, + int sample, + int min, + int max, + int bucket_count); + // Deserializes a histogram sample. + static scoped_ptr<MetricSample> ParseHistogram(const std::string& serialized); + + // Builds a sparse histogram sample. + static scoped_ptr<MetricSample> SparseHistogramSample( + const std::string& histogram_name, + int sample); + // Deserializes a sparse histogram sample. + static scoped_ptr<MetricSample> ParseSparseHistogram( + const std::string& serialized); + + // Builds a linear histogram sample. + static scoped_ptr<MetricSample> LinearHistogramSample( + const std::string& histogram_name, + int sample, + int max); + // Deserializes a linear histogram sample. + static scoped_ptr<MetricSample> ParseLinearHistogram( + const std::string& serialized); + + // Builds a user action sample. + static scoped_ptr<MetricSample> UserActionSample( + const std::string& action_name); + + // Returns true if sample and this object represent the same sample (type, + // name, sample, min, max, bucket_count match). + bool IsEqual(const MetricSample& sample); + + private: + MetricSample(SampleType sample_type, + const std::string& metric_name, + const int sample, + const int min, + const int max, + const int bucket_count); + + const SampleType type_; + const std::string name_; + const int sample_; + const int min_; + const int max_; + const int bucket_count_; + + DISALLOW_COPY_AND_ASSIGN(MetricSample); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_ diff --git a/chromium/components/metrics/serialization/serialization_utils.cc b/chromium/components/metrics/serialization/serialization_utils.cc new file mode 100644 index 00000000000..0038b4b2006 --- /dev/null +++ b/chromium/components/metrics/serialization/serialization_utils.cc @@ -0,0 +1,224 @@ +// Copyright 2014 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 "components/metrics/serialization/serialization_utils.h" + +#include <errno.h> +#include <stdint.h> +#include <sys/file.h> +#include <string> +#include <utility> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "components/metrics/serialization/metric_sample.h" + +#define READ_WRITE_ALL_FILE_FLAGS \ + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) + +namespace metrics { +namespace { + +// Reads the next message from |file_descriptor| into |message|. +// +// |message| will be set to the empty string if no message could be read (EOF) +// or the message was badly constructed. +// +// Returns false if no message can be read from this file anymore (EOF or +// unrecoverable error). +bool ReadMessage(int fd, std::string* message) { + CHECK(message); + + int result; + int32_t message_size; + const int32_t message_header_size = sizeof(message_size); + // The file containing the metrics does not leave the device so the writer and + // the reader will always have the same endianness. + result = HANDLE_EINTR(read(fd, &message_size, message_header_size)); + if (result < 0) { + DPLOG(ERROR) << "reading metrics message header"; + return false; + } + if (result == 0) { + // This indicates a normal EOF. + return false; + } + if (result < message_header_size) { + DLOG(ERROR) << "bad read size " << result << ", expecting " + << sizeof(message_size); + return false; + } + + // kMessageMaxLength applies to the entire message: the 4-byte + // length field and the content. + if (message_size > SerializationUtils::kMessageMaxLength) { + DLOG(ERROR) << "message too long : " << message_size; + if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) { + DLOG(ERROR) << "error while skipping message. abort"; + return false; + } + // Badly formatted message was skipped. Treat the badly formatted sample as + // an empty sample. + message->clear(); + return true; + } + + if (message_size < message_header_size) { + DLOG(ERROR) << "message too short : " << message_size; + return false; + } + + message_size -= message_header_size; // The message size includes itself. + char buffer[SerializationUtils::kMessageMaxLength]; + if (!base::ReadFromFD(fd, buffer, message_size)) { + DPLOG(ERROR) << "reading metrics message body"; + return false; + } + *message = std::string(buffer, message_size); + return true; +} + +} // namespace + +scoped_ptr<MetricSample> SerializationUtils::ParseSample( + const std::string& sample) { + if (sample.empty()) + return scoped_ptr<MetricSample>(); + + std::vector<std::string> parts = base::SplitString( + sample, std::string(1, '\0'), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + // We should have two null terminated strings so split should produce + // three chunks. + if (parts.size() != 3) { + DLOG(ERROR) << "splitting message on \\0 produced " << parts.size() + << " parts (expected 3)"; + return scoped_ptr<MetricSample>(); + } + const std::string& name = parts[0]; + const std::string& value = parts[1]; + + if (base::LowerCaseEqualsASCII(name, "crash")) { + return MetricSample::CrashSample(value); + } else if (base::LowerCaseEqualsASCII(name, "histogram")) { + return MetricSample::ParseHistogram(value); + } else if (base::LowerCaseEqualsASCII(name, "linearhistogram")) { + return MetricSample::ParseLinearHistogram(value); + } else if (base::LowerCaseEqualsASCII(name, "sparsehistogram")) { + return MetricSample::ParseSparseHistogram(value); + } else if (base::LowerCaseEqualsASCII(name, "useraction")) { + return MetricSample::UserActionSample(value); + } else { + DLOG(ERROR) << "invalid event type: " << name << ", value: " << value; + } + return scoped_ptr<MetricSample>(); +} + +void SerializationUtils::ReadAndTruncateMetricsFromFile( + const std::string& filename, + ScopedVector<MetricSample>* metrics) { + struct stat stat_buf; + int result; + + result = stat(filename.c_str(), &stat_buf); + if (result < 0) { + if (errno != ENOENT) + DPLOG(ERROR) << "bad metrics file stat: " << filename; + + // Nothing to collect---try later. + return; + } + if (stat_buf.st_size == 0) { + // Also nothing to collect. + return; + } + base::ScopedFD fd(open(filename.c_str(), O_RDWR)); + if (fd.get() < 0) { + DPLOG(ERROR) << "cannot open: " << filename; + return; + } + result = flock(fd.get(), LOCK_EX); + if (result < 0) { + DPLOG(ERROR) << "cannot lock: " << filename; + return; + } + + // This processes all messages in the log. When all messages are + // read and processed, or an error occurs, truncate the file to zero size. + for (;;) { + std::string message; + + if (!ReadMessage(fd.get(), &message)) + break; + + scoped_ptr<MetricSample> sample = ParseSample(message); + if (sample) + metrics->push_back(std::move(sample)); + } + + result = ftruncate(fd.get(), 0); + if (result < 0) + DPLOG(ERROR) << "truncate metrics log: " << filename; + + result = flock(fd.get(), LOCK_UN); + if (result < 0) + DPLOG(ERROR) << "unlock metrics log: " << filename; +} + +bool SerializationUtils::WriteMetricToFile(const MetricSample& sample, + const std::string& filename) { + if (!sample.IsValid()) + return false; + + base::ScopedFD file_descriptor(open(filename.c_str(), + O_WRONLY | O_APPEND | O_CREAT, + READ_WRITE_ALL_FILE_FLAGS)); + + if (file_descriptor.get() < 0) { + DPLOG(ERROR) << "error openning the file: " << filename; + return false; + } + + fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS); + // Grab a lock to avoid chrome truncating the file + // underneath us. Keep the file locked as briefly as possible. + // Freeing file_descriptor will close the file and and remove the lock. + if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) { + DPLOG(ERROR) << "error locking: " << filename; + return false; + } + + std::string msg = sample.ToString(); + int32_t size = msg.length() + sizeof(int32_t); + if (size > kMessageMaxLength) { + DPLOG(ERROR) << "cannot write message: too long: " << filename; + return false; + } + + // The file containing the metrics samples will only be read by programs on + // the same device so we do not check endianness. + if (!base::WriteFileDescriptor(file_descriptor.get(), + reinterpret_cast<char*>(&size), + sizeof(size))) { + DPLOG(ERROR) << "error writing message length: " << filename; + return false; + } + + if (!base::WriteFileDescriptor( + file_descriptor.get(), msg.c_str(), msg.size())) { + DPLOG(ERROR) << "error writing message: " << filename; + return false; + } + + return true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/serialization/serialization_utils.h b/chromium/components/metrics/serialization/serialization_utils.h new file mode 100644 index 00000000000..17e5e4cebc5 --- /dev/null +++ b/chromium/components/metrics/serialization/serialization_utils.h @@ -0,0 +1,48 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_ +#define COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" + +namespace metrics { + +class MetricSample; + +// Metrics helpers to serialize and deserialize metrics collected by +// ChromeOS. +namespace SerializationUtils { + +// Deserializes a sample passed as a string and return a sample. +// The return value will either be a scoped_ptr to a Metric sample (if the +// deserialization was successful) or a NULL scoped_ptr. +scoped_ptr<MetricSample> ParseSample(const std::string& sample); + +// Reads all samples from a file and truncate the file when done. +void ReadAndTruncateMetricsFromFile(const std::string& filename, + ScopedVector<MetricSample>* metrics); + +// Serializes a sample and write it to filename. +// The format for the message is: +// message_size, serialized_message +// where +// * message_size is the total length of the message (message_size + +// serialized_message) on 4 bytes +// * serialized_message is the serialized version of sample (using ToString) +// +// NB: the file will never leave the device so message_size will be written +// with the architecture's endianness. +bool WriteMetricToFile(const MetricSample& sample, const std::string& filename); + +// Maximum length of a serialized message +static const int kMessageMaxLength = 1024; + +} // namespace SerializationUtils +} // namespace metrics + +#endif // COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_ diff --git a/chromium/components/metrics/serialization/serialization_utils_unittest.cc b/chromium/components/metrics/serialization/serialization_utils_unittest.cc new file mode 100644 index 00000000000..fe8f2b2d081 --- /dev/null +++ b/chromium/components/metrics/serialization/serialization_utils_unittest.cc @@ -0,0 +1,171 @@ +// Copyright 2014 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 "components/metrics/serialization/serialization_utils.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "components/metrics/serialization/metric_sample.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { +namespace { + +class SerializationUtilsTest : public testing::Test { + protected: + SerializationUtilsTest() { + bool success = temporary_dir.CreateUniqueTempDir(); + if (success) { + base::FilePath dir_path = temporary_dir.path(); + filename = dir_path.value() + "chromeossampletest"; + filepath = base::FilePath(filename); + } + } + + void SetUp() override { base::DeleteFile(filepath, false); } + + void TestSerialization(MetricSample* sample) { + std::string serialized(sample->ToString()); + ASSERT_EQ('\0', serialized.back()); + scoped_ptr<MetricSample> deserialized = + SerializationUtils::ParseSample(serialized); + ASSERT_TRUE(deserialized); + EXPECT_TRUE(sample->IsEqual(*deserialized.get())); + } + + std::string filename; + base::ScopedTempDir temporary_dir; + base::FilePath filepath; +}; + +TEST_F(SerializationUtilsTest, CrashSerializeTest) { + TestSerialization(MetricSample::CrashSample("test").get()); +} + +TEST_F(SerializationUtilsTest, HistogramSerializeTest) { + TestSerialization( + MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get()); +} + +TEST_F(SerializationUtilsTest, LinearSerializeTest) { + TestSerialization( + MetricSample::LinearHistogramSample("linearhist", 12, 30).get()); +} + +TEST_F(SerializationUtilsTest, SparseSerializeTest) { + TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get()); +} + +TEST_F(SerializationUtilsTest, UserActionSerializeTest) { + TestSerialization(MetricSample::UserActionSample("myaction").get()); +} + +TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) { + scoped_ptr<MetricSample> sample1 = + MetricSample::SparseHistogramSample("no space", 10); + scoped_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample( + base::StringPrintf("here%cbhe", '\0'), 1, 3); + + EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename)); + EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename)); + int64_t size = 0; + + ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size)); + + EXPECT_EQ(0, size); +} + +TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) { + std::string input( + base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0')); + EXPECT_EQ(NULL, MetricSample::ParseSparseHistogram(input).get()); +} + +TEST_F(SerializationUtilsTest, MessageSeparatedByZero) { + scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash"); + + SerializationUtils::WriteMetricToFile(*crash.get(), filename); + int64_t size = 0; + ASSERT_TRUE(base::GetFileSize(filepath, &size)); + // 4 bytes for the size + // 5 bytes for crash + // 7 bytes for mycrash + // 2 bytes for the \0 + // -> total of 18 + EXPECT_EQ(size, 18); +} + +TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) { + // Creates a message that is bigger than the maximum allowed size. + // As we are adding extra character (crash, \0s, etc), if the name is + // kMessageMaxLength long, it will be too long. + std::string name(SerializationUtils::kMessageMaxLength, 'c'); + + scoped_ptr<MetricSample> crash = MetricSample::CrashSample(name); + EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename)); + int64_t size = 0; + ASSERT_TRUE(base::GetFileSize(filepath, &size)); + EXPECT_EQ(0, size); +} + +TEST_F(SerializationUtilsTest, ReadLongMessageTest) { + base::File test_file(filepath, + base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND); + std::string message(SerializationUtils::kMessageMaxLength + 1, 'c'); + + int32_t message_size = message.length() + sizeof(int32_t); + test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size), + sizeof(message_size)); + test_file.WriteAtCurrentPos(message.c_str(), message.length()); + test_file.Close(); + + scoped_ptr<MetricSample> crash = MetricSample::CrashSample("test"); + SerializationUtils::WriteMetricToFile(*crash.get(), filename); + + ScopedVector<MetricSample> samples; + SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples); + ASSERT_EQ(size_t(1), samples.size()); + ASSERT_TRUE(samples[0] != NULL); + EXPECT_TRUE(crash->IsEqual(*samples[0])); +} + +TEST_F(SerializationUtilsTest, WriteReadTest) { + scoped_ptr<MetricSample> hist = + MetricSample::HistogramSample("myhist", 1, 2, 3, 4); + scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash"); + scoped_ptr<MetricSample> lhist = + MetricSample::LinearHistogramSample("linear", 1, 10); + scoped_ptr<MetricSample> shist = + MetricSample::SparseHistogramSample("mysparse", 30); + scoped_ptr<MetricSample> action = MetricSample::UserActionSample("myaction"); + + SerializationUtils::WriteMetricToFile(*hist.get(), filename); + SerializationUtils::WriteMetricToFile(*crash.get(), filename); + SerializationUtils::WriteMetricToFile(*lhist.get(), filename); + SerializationUtils::WriteMetricToFile(*shist.get(), filename); + SerializationUtils::WriteMetricToFile(*action.get(), filename); + ScopedVector<MetricSample> vect; + SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect); + ASSERT_EQ(vect.size(), size_t(5)); + for (int i = 0; i < 5; i++) { + ASSERT_TRUE(vect[0] != NULL); + } + EXPECT_TRUE(hist->IsEqual(*vect[0])); + EXPECT_TRUE(crash->IsEqual(*vect[1])); + EXPECT_TRUE(lhist->IsEqual(*vect[2])); + EXPECT_TRUE(shist->IsEqual(*vect[3])); + EXPECT_TRUE(action->IsEqual(*vect[4])); + + int64_t size = 0; + ASSERT_TRUE(base::GetFileSize(filepath, &size)); + ASSERT_EQ(0, size); +} + +} // namespace +} // namespace metrics diff --git a/chromium/components/metrics/stability_metrics_helper.cc b/chromium/components/metrics/stability_metrics_helper.cc new file mode 100644 index 00000000000..52b91f0f127 --- /dev/null +++ b/chromium/components/metrics/stability_metrics_helper.cc @@ -0,0 +1,221 @@ +// Copyright 2015 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 "components/metrics/stability_metrics_helper.h" + +#include <stdint.h> + +#include <vector> + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" +#include "build/build_config.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/proto/system_profile.pb.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +#if defined(OS_WIN) +#include <windows.h> // Needed for STATUS_* codes +#endif + +#if defined(OS_CHROMEOS) +#include "components/metrics/system_memory_stats_recorder.h" +#endif + +namespace metrics { + +namespace { + +enum RendererType { + RENDERER_TYPE_RENDERER = 1, + RENDERER_TYPE_EXTENSION, + // NOTE: Add new action types only immediately above this line. Also, + // make sure the enum list in tools/metrics/histograms/histograms.xml is + // updated with any change in here. + RENDERER_TYPE_COUNT +}; + +// Converts an exit code into something that can be inserted into our +// histograms (which expect non-negative numbers less than MAX_INT). +int MapCrashExitCodeForHistogram(int exit_code) { +#if defined(OS_WIN) + // Since |abs(STATUS_GUARD_PAGE_VIOLATION) == MAX_INT| it causes problems in + // histograms.cc. Solve this by remapping it to a smaller value, which + // hopefully doesn't conflict with other codes. + if (static_cast<DWORD>(exit_code) == STATUS_GUARD_PAGE_VIOLATION) + return 0x1FCF7EC3; // Randomly picked number. +#endif + + return std::abs(exit_code); +} + +void RecordChildKills(int histogram_type) { + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills", + histogram_type, RENDERER_TYPE_COUNT); +} + +} // namespace + +StabilityMetricsHelper::StabilityMetricsHelper(PrefService* local_state) + : local_state_(local_state) { + DCHECK(local_state_); +} + +StabilityMetricsHelper::~StabilityMetricsHelper() {} + +void StabilityMetricsHelper::ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto) { + SystemProfileProto_Stability* stability_proto = + system_profile_proto->mutable_stability(); + + int count = local_state_->GetInteger(prefs::kStabilityPageLoadCount); + if (count) { + stability_proto->set_page_load_count(count); + local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0); + } + + count = local_state_->GetInteger(prefs::kStabilityChildProcessCrashCount); + if (count) { + stability_proto->set_child_process_crash_count(count); + local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0); + } + + count = local_state_->GetInteger(prefs::kStabilityRendererCrashCount); + if (count) { + stability_proto->set_renderer_crash_count(count); + local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0); + } + + count = local_state_->GetInteger(prefs::kStabilityRendererFailedLaunchCount); + if (count) { + stability_proto->set_renderer_failed_launch_count(count); + local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0); + } + + count = + local_state_->GetInteger(prefs::kStabilityExtensionRendererCrashCount); + if (count) { + stability_proto->set_extension_renderer_crash_count(count); + local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0); + } + + count = local_state_->GetInteger( + prefs::kStabilityExtensionRendererFailedLaunchCount); + if (count) { + stability_proto->set_extension_renderer_failed_launch_count(count); + local_state_->SetInteger( + prefs::kStabilityExtensionRendererFailedLaunchCount, 0); + } + + count = local_state_->GetInteger(prefs::kStabilityRendererHangCount); + if (count) { + stability_proto->set_renderer_hang_count(count); + local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0); + } +} + +void StabilityMetricsHelper::ClearSavedStabilityMetrics() { + // Clear all the prefs used in this class in UMA reports (which doesn't + // include |kUninstallMetricsPageLoadCount| as it's not sent up by UMA). + local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0); + local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0); + local_state_->SetInteger(prefs::kStabilityExtensionRendererFailedLaunchCount, + 0); + local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0); + local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0); + local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0); + local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0); +} + +// static +void StabilityMetricsHelper::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(prefs::kStabilityChildProcessCrashCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererCrashCount, + 0); + registry->RegisterIntegerPref( + prefs::kStabilityExtensionRendererFailedLaunchCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererFailedLaunchCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererHangCount, 0); + + registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0); +} + +void StabilityMetricsHelper::BrowserChildProcessCrashed() { + IncrementPrefValue(prefs::kStabilityChildProcessCrashCount); +} + +void StabilityMetricsHelper::LogLoadStarted() { + base::RecordAction(base::UserMetricsAction("PageLoad")); + // TODO(asvitkine): Check if this is used for anything and if not, remove. + LOCAL_HISTOGRAM_BOOLEAN("Chrome.UmaPageloadCounter", true); + IncrementPrefValue(prefs::kStabilityPageLoadCount); + IncrementLongPrefsValue(prefs::kUninstallMetricsPageLoadCount); + // We need to save the prefs, as page load count is a critical stat, and it + // might be lost due to a crash :-(. +} + +void StabilityMetricsHelper::LogRendererCrash(bool was_extension_process, + base::TerminationStatus status, + int exit_code) { + int histogram_type = + was_extension_process ? RENDERER_TYPE_EXTENSION : RENDERER_TYPE_RENDERER; + if (status == base::TERMINATION_STATUS_PROCESS_CRASHED || + status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION) { + if (was_extension_process) { + IncrementPrefValue(prefs::kStabilityExtensionRendererCrashCount); + + UMA_HISTOGRAM_SPARSE_SLOWLY("CrashExitCodes.Extension", + MapCrashExitCodeForHistogram(exit_code)); + } else { + IncrementPrefValue(prefs::kStabilityRendererCrashCount); + + UMA_HISTOGRAM_SPARSE_SLOWLY("CrashExitCodes.Renderer", + MapCrashExitCodeForHistogram(exit_code)); + } + + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildCrashes", + histogram_type, RENDERER_TYPE_COUNT); + } else if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED) { + RecordChildKills(histogram_type); +#if defined(OS_CHROMEOS) + } else if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM) { + RecordChildKills(histogram_type); + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills.OOM", + was_extension_process ? 2 : 1, 3); + RecordMemoryStats(was_extension_process + ? RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED + : RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED); +#endif + } else if (status == base::TERMINATION_STATUS_STILL_RUNNING) { + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.DisconnectedAlive", + histogram_type, RENDERER_TYPE_COUNT); + } else if (status == base::TERMINATION_STATUS_LAUNCH_FAILED) { + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildLaunchFailures", + histogram_type, RENDERER_TYPE_COUNT); + if (was_extension_process) + IncrementPrefValue(prefs::kStabilityExtensionRendererFailedLaunchCount); + else + IncrementPrefValue(prefs::kStabilityRendererFailedLaunchCount); + } +} + +void StabilityMetricsHelper::IncrementPrefValue(const char* path) { + int value = local_state_->GetInteger(path); + local_state_->SetInteger(path, value + 1); +} + +void StabilityMetricsHelper::IncrementLongPrefsValue(const char* path) { + int64_t value = local_state_->GetInt64(path); + local_state_->SetInt64(path, value + 1); +} + +void StabilityMetricsHelper::LogRendererHang() { + IncrementPrefValue(prefs::kStabilityRendererHangCount); +} + +} // namespace metrics diff --git a/chromium/components/metrics/stability_metrics_helper.h b/chromium/components/metrics/stability_metrics_helper.h new file mode 100644 index 00000000000..42a9ca20217 --- /dev/null +++ b/chromium/components/metrics/stability_metrics_helper.h @@ -0,0 +1,63 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_ +#define COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_ + +#include "base/macros.h" +#include "base/metrics/user_metrics.h" +#include "base/process/kill.h" + +class PrefRegistrySimple; +class PrefService; + +namespace metrics { + +class SystemProfileProto; + +// StabilityMetricsHelper is a class that providers functionality common to +// different embedders' stability metrics providers. +class StabilityMetricsHelper { + public: + explicit StabilityMetricsHelper(PrefService* local_state); + ~StabilityMetricsHelper(); + + // Provides stability metrics. + void ProvideStabilityMetrics(SystemProfileProto* system_profile_proto); + + // Clears the gathered stability metrics. + void ClearSavedStabilityMetrics(); + + // Records a browser child process crash. + void BrowserChildProcessCrashed(); + + // Logs the initiation of a page load. + void LogLoadStarted(); + + // Records a renderer process crash. + void LogRendererCrash(bool was_exception_process, + base::TerminationStatus status, + int exit_code); + + // Records a renderer process hang. + void LogRendererHang(); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + // Increment an Integer pref value specified by |path|. + void IncrementPrefValue(const char* path); + + // Increment a 64-bit Integer pref value specified by |path|. + void IncrementLongPrefsValue(const char* path); + + PrefService* local_state_; + + DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelper); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_ diff --git a/chromium/components/metrics/stability_metrics_helper_unittest.cc b/chromium/components/metrics/stability_metrics_helper_unittest.cc new file mode 100644 index 00000000000..6ca84df0f28 --- /dev/null +++ b/chromium/components/metrics/stability_metrics_helper_unittest.cc @@ -0,0 +1,96 @@ +// Copyright 2015 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 "components/metrics/stability_metrics_helper.h" + +#include "base/macros.h" +#include "components/metrics/proto/system_profile.pb.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +class StabilityMetricsHelperTest : public testing::Test { + protected: + StabilityMetricsHelperTest() : prefs_(new TestingPrefServiceSimple) { + StabilityMetricsHelper::RegisterPrefs(prefs()->registry()); + } + + TestingPrefServiceSimple* prefs() { return prefs_.get(); } + + private: + scoped_ptr<TestingPrefServiceSimple> prefs_; + + DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelperTest); +}; + +} // namespace + +TEST_F(StabilityMetricsHelperTest, BrowserChildProcessCrashed) { + StabilityMetricsHelper helper(prefs()); + + helper.BrowserChildProcessCrashed(); + helper.BrowserChildProcessCrashed(); + + // Call ProvideStabilityMetrics to check that it will force pending tasks to + // be executed immediately. + metrics::SystemProfileProto system_profile; + + helper.ProvideStabilityMetrics(&system_profile); + + // Check current number of instances created. + const metrics::SystemProfileProto_Stability& stability = + system_profile.stability(); + + EXPECT_EQ(2, stability.child_process_crash_count()); +} + +TEST_F(StabilityMetricsHelperTest, LogRendererCrash) { + StabilityMetricsHelper helper(prefs()); + + // Crash and abnormal termination should increment renderer crash count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_CRASHED, 1); + + helper.LogRendererCrash(false, base::TERMINATION_STATUS_ABNORMAL_TERMINATION, + 1); + + // Kill does not increment renderer crash count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_WAS_KILLED, + 1); + + // Failed launch increments failed launch count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_LAUNCH_FAILED, 1); + + metrics::SystemProfileProto system_profile; + + // Call ProvideStabilityMetrics to check that it will force pending tasks to + // be executed immediately. + helper.ProvideStabilityMetrics(&system_profile); + + EXPECT_EQ(2, system_profile.stability().renderer_crash_count()); + EXPECT_EQ(1, system_profile.stability().renderer_failed_launch_count()); + EXPECT_EQ(0, system_profile.stability().extension_renderer_crash_count()); + + helper.ClearSavedStabilityMetrics(); + + // Crash and abnormal termination should increment extension crash count. + helper.LogRendererCrash(true, base::TERMINATION_STATUS_PROCESS_CRASHED, 1); + + // Failed launch increments extension failed launch count. + helper.LogRendererCrash(true, base::TERMINATION_STATUS_LAUNCH_FAILED, 1); + + system_profile.Clear(); + helper.ProvideStabilityMetrics(&system_profile); + + EXPECT_EQ(0, system_profile.stability().renderer_crash_count()); + EXPECT_EQ(1, system_profile.stability().extension_renderer_crash_count()); + EXPECT_EQ( + 1, system_profile.stability().extension_renderer_failed_launch_count()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/system_memory_stats_recorder.h b/chromium/components/metrics/system_memory_stats_recorder.h new file mode 100644 index 00000000000..bdd30f00f42 --- /dev/null +++ b/chromium/components/metrics/system_memory_stats_recorder.h @@ -0,0 +1,30 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_ +#define COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_ + +namespace metrics { + +// Record a memory size in megabytes, over a potential interval up to 32 GB. +#define UMA_HISTOGRAM_LARGE_MEMORY_MB(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50) + +// The type of memory UMA stats to be recorded in RecordMemoryStats. +enum RecordMemoryStatsType { + // When a tab was discarded. + RECORD_MEMORY_STATS_TAB_DISCARDED, + + // Right after the renderer for contents was killed. + RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED, + + // Right after the renderer for extensions was killed. + RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED, +}; + +void RecordMemoryStats(RecordMemoryStatsType type); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_ diff --git a/chromium/components/metrics/system_memory_stats_recorder_linux.cc b/chromium/components/metrics/system_memory_stats_recorder_linux.cc new file mode 100644 index 00000000000..c69dbaa0460 --- /dev/null +++ b/chromium/components/metrics/system_memory_stats_recorder_linux.cc @@ -0,0 +1,98 @@ +// Copyright 2015 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 "components/metrics/system_memory_stats_recorder.h" + +#include "base/metrics/histogram_macros.h" +#include "base/process/process_metrics.h" +#include "build/build_config.h" + +namespace metrics { + +// Record a size in megabytes, a potential interval from 250MB up to 32 GB. +#define UMA_HISTOGRAM_ALLOCATED_MEGABYTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 250, 32768, 50) + +// Records a statistics |sample| for UMA histogram |name| +// using a linear distribution of buckets. +#define UMA_HISTOGRAM_LINEAR(name, sample, max, buckets) \ + STATIC_HISTOGRAM_POINTER_BLOCK( \ + name, Add(sample), \ + base::LinearHistogram::FactoryGet( \ + name, \ + 1, /* Minimum. The 0 bin for underflow is automatically added. */ \ + max + 1, /* Ensure bucket size of |maximum| / |bucket_count|. */ \ + buckets + 2, /* Account for the underflow and overflow bins. */ \ + base::Histogram::kUmaTargetedHistogramFlag)) + +#define UMA_HISTOGRAM_MEGABYTES_LINEAR(name, sample) \ + UMA_HISTOGRAM_LINEAR(name, sample, 2500, 50) + +void RecordMemoryStats(RecordMemoryStatsType type) { + base::SystemMemoryInfoKB memory; + if (!base::GetSystemMemoryInfo(&memory)) + return; +#if defined(OS_CHROMEOS) + // Record graphics GEM object size in a histogram with 50 MB buckets. + int mem_graphics_gem_mb = 0; + if (memory.gem_size != -1) + mem_graphics_gem_mb = memory.gem_size / 1024 / 1024; + + // Record shared memory (used by renderer/GPU buffers). + int mem_shmem_mb = memory.shmem / 1024; +#endif + + // On Intel, graphics objects are in anonymous pages, but on ARM they are + // not. For a total "allocated count" add in graphics pages on ARM. + int mem_allocated_mb = (memory.active_anon + memory.inactive_anon) / 1024; +#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY) + mem_allocated_mb += mem_graphics_gem_mb; +#endif + + int mem_available_mb = + (memory.active_file + memory.inactive_file + memory.free) / 1024; + + switch (type) { + case RECORD_MEMORY_STATS_TAB_DISCARDED: { +#if defined(OS_CHROMEOS) + UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemGraphicsMB", + mem_graphics_gem_mb); + UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemShmemMB", mem_shmem_mb); +#endif + UMA_HISTOGRAM_ALLOCATED_MEGABYTES("Tabs.Discard.MemAllocatedMB", + mem_allocated_mb); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Tabs.Discard.MemAvailableMB", + mem_available_mb); + break; + } + case RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED: { +#if defined(OS_CHROMEOS) + UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemGraphicsMB", + mem_graphics_gem_mb); + UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemShmemMB", + mem_shmem_mb); +#endif + UMA_HISTOGRAM_ALLOCATED_MEGABYTES( + "Memory.OOMKill.Contents.MemAllocatedMB", mem_allocated_mb); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Contents.MemAvailableMB", + mem_available_mb); + break; + } + case RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED: { +#if defined(OS_CHROMEOS) + UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemGraphicsMB", + mem_graphics_gem_mb); + UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemShmemMB", + mem_shmem_mb); +#endif + UMA_HISTOGRAM_ALLOCATED_MEGABYTES( + "Memory.OOMKill.Extensions.MemAllocatedMB", mem_allocated_mb); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Extensions.MemAvailableMB", + mem_available_mb); + break; + } + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/system_memory_stats_recorder_win.cc b/chromium/components/metrics/system_memory_stats_recorder_win.cc new file mode 100644 index 00000000000..cdd314989f3 --- /dev/null +++ b/chromium/components/metrics/system_memory_stats_recorder_win.cc @@ -0,0 +1,47 @@ +// Copyright 2015 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 "components/metrics/system_memory_stats_recorder.h" + +#include <windows.h> + +#include "base/metrics/histogram_macros.h" +#include "base/process/process_metrics.h" + +namespace metrics { +namespace { +enum { kMBytes = 1024 * 1024 }; +} + +void RecordMemoryStats(RecordMemoryStatsType type) { + MEMORYSTATUSEX mem_status; + mem_status.dwLength = sizeof(mem_status); + if (!::GlobalMemoryStatusEx(&mem_status)) + return; + + switch (type) { + case RECORD_MEMORY_STATS_TAB_DISCARDED: { + UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Stats.Win.MemoryLoad", + mem_status.dwMemoryLoad, 0, 100, 101); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPhys2", + mem_status.ullTotalPhys / kMBytes); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPhys2", + mem_status.ullAvailPhys / kMBytes); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPageFile2", + mem_status.ullTotalPageFile / kMBytes); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPageFile2", + mem_status.ullAvailPageFile / kMBytes); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalVirtual2", + mem_status.ullTotalVirtual / kMBytes); + UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailVirtual2", + mem_status.ullAvailVirtual / kMBytes); + break; + } + default: + NOTREACHED() << L"Received unexpected notification"; + break; + } +} + +} // namespace metrics diff --git a/chromium/components/metrics/test_metrics_provider.cc b/chromium/components/metrics/test_metrics_provider.cc new file mode 100644 index 00000000000..409d8d129d6 --- /dev/null +++ b/chromium/components/metrics/test_metrics_provider.cc @@ -0,0 +1,35 @@ +// Copyright 2015 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 "components/metrics/test_metrics_provider.h" + +#include "base/metrics/histogram_macros.h" + +namespace metrics { + +void TestMetricsProvider::Init() { + init_called_ = true; +} + +void TestMetricsProvider::OnRecordingDisabled() { + on_recording_disabled_called_ = true; +} + +bool TestMetricsProvider::HasInitialStabilityMetrics() { + return has_initial_stability_metrics_; +} + +void TestMetricsProvider::ProvideInitialStabilityMetrics( + SystemProfileProto* system_profile_proto) { + UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Initial", 1, 2); + provide_initial_stability_metrics_called_ = true; +} + +void TestMetricsProvider::ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto) { + UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Regular", 1, 2); + provide_stability_metrics_called_ = true; +} + +} // namespace metrics diff --git a/chromium/components/metrics/test_metrics_provider.h b/chromium/components/metrics/test_metrics_provider.h new file mode 100644 index 00000000000..6536ea23198 --- /dev/null +++ b/chromium/components/metrics/test_metrics_provider.h @@ -0,0 +1,57 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_ + +#include "base/macros.h" +#include "components/metrics/metrics_provider.h" + +namespace metrics { + +// A simple implementation of MetricsProvider that checks that its providing +// functions are called, for use in tests. +class TestMetricsProvider : public MetricsProvider { + public: + TestMetricsProvider() + : init_called_(false), + on_recording_disabled_called_(false), + has_initial_stability_metrics_(false), + provide_initial_stability_metrics_called_(false), + provide_stability_metrics_called_(false) {} + + // MetricsProvider: + void Init() override; + void OnRecordingDisabled() override; + bool HasInitialStabilityMetrics() override; + void ProvideInitialStabilityMetrics( + SystemProfileProto* system_profile_proto) override; + void ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto) override; + + bool init_called() { return init_called_; } + bool on_recording_disabled_called() { return on_recording_disabled_called_; } + void set_has_initial_stability_metrics(bool has_initial_stability_metrics) { + has_initial_stability_metrics_ = has_initial_stability_metrics; + } + bool provide_initial_stability_metrics_called() const { + return provide_initial_stability_metrics_called_; + } + bool provide_stability_metrics_called() const { + return provide_stability_metrics_called_; + } + + private: + bool init_called_; + bool on_recording_disabled_called_; + bool has_initial_stability_metrics_; + bool provide_initial_stability_metrics_called_; + bool provide_stability_metrics_called_; + + DISALLOW_COPY_AND_ASSIGN(TestMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/test_metrics_service_client.cc b/chromium/components/metrics/test_metrics_service_client.cc new file mode 100644 index 00000000000..7aeaddf9c69 --- /dev/null +++ b/chromium/components/metrics/test_metrics_service_client.cc @@ -0,0 +1,93 @@ +// Copyright 2014 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 "components/metrics/test_metrics_service_client.h" + +#include "base/callback.h" +#include "components/metrics/metrics_log_uploader.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +namespace metrics { + +// static +const char TestMetricsServiceClient::kBrandForTesting[] = "brand_for_testing"; + +TestMetricsServiceClient::TestMetricsServiceClient() + : version_string_("5.0.322.0-64-devel"), + product_(ChromeUserMetricsExtension::CHROME), + reporting_is_managed_(false), + enable_default_(MetricsServiceClient::DEFAULT_UNKNOWN) {} + +TestMetricsServiceClient::~TestMetricsServiceClient() { +} + +metrics::MetricsService* TestMetricsServiceClient::GetMetricsService() { + return nullptr; +} + +void TestMetricsServiceClient::SetMetricsClientId( + const std::string& client_id) { + client_id_ = client_id; +} + +void TestMetricsServiceClient::OnRecordingDisabled() { +} + +bool TestMetricsServiceClient::IsOffTheRecordSessionActive() { + return false; +} + +int32_t TestMetricsServiceClient::GetProduct() { + return product_; +} + +std::string TestMetricsServiceClient::GetApplicationLocale() { + return "en-US"; +} + +bool TestMetricsServiceClient::GetBrand(std::string* brand_code) { + *brand_code = kBrandForTesting; + return true; +} + +SystemProfileProto::Channel TestMetricsServiceClient::GetChannel() { + return SystemProfileProto::CHANNEL_BETA; +} + +std::string TestMetricsServiceClient::GetVersionString() { + return version_string_; +} + +void TestMetricsServiceClient::OnLogUploadComplete() { +} + +void TestMetricsServiceClient::InitializeSystemProfileMetrics( + const base::Closure& done_callback) { + done_callback.Run(); +} + +void TestMetricsServiceClient::CollectFinalMetricsForLog( + const base::Closure& done_callback) { + done_callback.Run(); +} + +scoped_ptr<MetricsLogUploader> TestMetricsServiceClient::CreateUploader( + const base::Callback<void(int)>& on_upload_complete) { + return scoped_ptr<MetricsLogUploader>(); +} + +base::TimeDelta TestMetricsServiceClient::GetStandardUploadInterval() { + return base::TimeDelta::FromMinutes(5); +} + +bool TestMetricsServiceClient::IsReportingPolicyManaged() { + return reporting_is_managed_; +} + +MetricsServiceClient::EnableMetricsDefault +TestMetricsServiceClient::GetDefaultOptIn() { + return enable_default_; +} + +} // namespace metrics diff --git a/chromium/components/metrics/test_metrics_service_client.h b/chromium/components/metrics/test_metrics_service_client.h new file mode 100644 index 00000000000..8710355901c --- /dev/null +++ b/chromium/components/metrics/test_metrics_service_client.h @@ -0,0 +1,69 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_ +#define COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "components/metrics/metrics_service_client.h" + +namespace metrics { + +// A simple concrete implementation of the MetricsServiceClient interface, for +// use in tests. +class TestMetricsServiceClient : public MetricsServiceClient { + public: + static const char kBrandForTesting[]; + + TestMetricsServiceClient(); + ~TestMetricsServiceClient() override; + + // MetricsServiceClient: + metrics::MetricsService* GetMetricsService() override; + void SetMetricsClientId(const std::string& client_id) override; + void OnRecordingDisabled() override; + bool IsOffTheRecordSessionActive() override; + int32_t GetProduct() override; + std::string GetApplicationLocale() override; + bool GetBrand(std::string* brand_code) override; + SystemProfileProto::Channel GetChannel() override; + std::string GetVersionString() override; + void OnLogUploadComplete() override; + void InitializeSystemProfileMetrics( + const base::Closure& done_callback) override; + void CollectFinalMetricsForLog(const base::Closure& done_callback) override; + scoped_ptr<MetricsLogUploader> CreateUploader( + const base::Callback<void(int)>& on_upload_complete) override; + base::TimeDelta GetStandardUploadInterval() override; + bool IsReportingPolicyManaged() override; + MetricsServiceClient::EnableMetricsDefault GetDefaultOptIn() override; + + const std::string& get_client_id() const { return client_id_; } + void set_version_string(const std::string& str) { version_string_ = str; } + void set_product(int32_t product) { product_ = product; } + void set_reporting_is_managed(bool managed) { + reporting_is_managed_ = managed; + } + void set_enable_default( + MetricsServiceClient::EnableMetricsDefault enable_default) { + enable_default_ = enable_default; + } + + private: + std::string client_id_; + std::string version_string_; + int32_t product_; + bool reporting_is_managed_; + MetricsServiceClient::EnableMetricsDefault enable_default_; + + DISALLOW_COPY_AND_ASSIGN(TestMetricsServiceClient); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_ diff --git a/chromium/components/metrics/ui/DEPS b/chromium/components/metrics/ui/DEPS new file mode 100644 index 00000000000..b273ae3319c --- /dev/null +++ b/chromium/components/metrics/ui/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/gfx", +] diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider.cc b/chromium/components/metrics/ui/screen_info_metrics_provider.cc new file mode 100644 index 00000000000..3d31e988db9 --- /dev/null +++ b/chromium/components/metrics/ui/screen_info_metrics_provider.cc @@ -0,0 +1,92 @@ +// Copyright 2015 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 "components/metrics/ui/screen_info_metrics_provider.h" + +#include "build/build_config.h" +#include "components/metrics/proto/system_profile.pb.h" +#include "ui/gfx/screen.h" + +namespace metrics { + +#if defined(OS_WIN) + +#include <windows.h> + +namespace { + +struct ScreenDPIInformation { + double max_dpi_x; + double max_dpi_y; +}; + +// Called once for each connected monitor. +BOOL CALLBACK GetMonitorDPICallback(HMONITOR, HDC hdc, LPRECT, LPARAM dwData) { + const double kMillimetersPerInch = 25.4; + ScreenDPIInformation* screen_info = + reinterpret_cast<ScreenDPIInformation*>(dwData); + // Size of screen, in mm. + DWORD size_x = GetDeviceCaps(hdc, HORZSIZE); + DWORD size_y = GetDeviceCaps(hdc, VERTSIZE); + double dpi_x = (size_x > 0) ? + GetDeviceCaps(hdc, HORZRES) / (size_x / kMillimetersPerInch) : 0; + double dpi_y = (size_y > 0) ? + GetDeviceCaps(hdc, VERTRES) / (size_y / kMillimetersPerInch) : 0; + screen_info->max_dpi_x = std::max(dpi_x, screen_info->max_dpi_x); + screen_info->max_dpi_y = std::max(dpi_y, screen_info->max_dpi_y); + return TRUE; +} + +void WriteScreenDPIInformationProto(SystemProfileProto::Hardware* hardware) { + HDC desktop_dc = GetDC(NULL); + if (desktop_dc) { + ScreenDPIInformation si = {0, 0}; + if (EnumDisplayMonitors(desktop_dc, NULL, GetMonitorDPICallback, + reinterpret_cast<LPARAM>(&si))) { + hardware->set_max_dpi_x(si.max_dpi_x); + hardware->set_max_dpi_y(si.max_dpi_y); + } + ReleaseDC(GetDesktopWindow(), desktop_dc); + } +} + +} // namespace + +#endif // defined(OS_WIN) + +ScreenInfoMetricsProvider::ScreenInfoMetricsProvider() { +} + +ScreenInfoMetricsProvider::~ScreenInfoMetricsProvider() { +} + +void ScreenInfoMetricsProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) { + SystemProfileProto::Hardware* hardware = + system_profile_proto->mutable_hardware(); + + const gfx::Size display_size = GetScreenSize(); + hardware->set_primary_screen_width(display_size.width()); + hardware->set_primary_screen_height(display_size.height()); + hardware->set_primary_screen_scale_factor(GetScreenDeviceScaleFactor()); + hardware->set_screen_count(GetScreenCount()); + +#if defined(OS_WIN) + WriteScreenDPIInformationProto(hardware); +#endif +} + +gfx::Size ScreenInfoMetricsProvider::GetScreenSize() const { + return gfx::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel(); +} + +float ScreenInfoMetricsProvider::GetScreenDeviceScaleFactor() const { + return gfx::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor(); +} + +int ScreenInfoMetricsProvider::GetScreenCount() const { + return gfx::Screen::GetScreen()->GetNumDisplays(); +} + +} // namespace metrics diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider.h b/chromium/components/metrics/ui/screen_info_metrics_provider.h new file mode 100644 index 00000000000..51ef2b77ab1 --- /dev/null +++ b/chromium/components/metrics/ui/screen_info_metrics_provider.h @@ -0,0 +1,42 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_ + +#include "base/macros.h" +#include "components/metrics/metrics_provider.h" +#include "ui/gfx/geometry/size.h" + +namespace metrics { + +// ScreenInfoMetricsProvider provides metrics related to screen info. +class ScreenInfoMetricsProvider : public MetricsProvider { + public: + ScreenInfoMetricsProvider(); + ~ScreenInfoMetricsProvider() override; + + // MetricsProvider: + void ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) override; + + protected: + // Exposed for the sake of mocking in test code. + + // Returns the screen size for the primary monitor. + virtual gfx::Size GetScreenSize() const; + + // Returns the device scale factor for the primary monitor. + virtual float GetScreenDeviceScaleFactor() const; + + // Returns the number of monitors the user is using. + virtual int GetScreenCount() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_ diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc b/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc new file mode 100644 index 00000000000..9e0046bd60e --- /dev/null +++ b/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc @@ -0,0 +1,66 @@ +// Copyright 2015 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 "components/metrics/ui/screen_info_metrics_provider.h" + +#include "base/macros.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" + +namespace metrics { + +namespace { + +const int kScreenWidth = 1024; +const int kScreenHeight = 768; +const int kScreenCount = 3; +const float kScreenScaleFactor = 2; + +class TestScreenInfoMetricsProvider : public ScreenInfoMetricsProvider { + public: + TestScreenInfoMetricsProvider() {} + ~TestScreenInfoMetricsProvider() override {} + + private: + gfx::Size GetScreenSize() const override { + return gfx::Size(kScreenWidth, kScreenHeight); + } + + float GetScreenDeviceScaleFactor() const override { + return kScreenScaleFactor; + } + + int GetScreenCount() const override { return kScreenCount; } + + DISALLOW_COPY_AND_ASSIGN(TestScreenInfoMetricsProvider); +}; + +} // namespace + +class ScreenInfoMetricsProviderTest : public testing::Test { + public: + ScreenInfoMetricsProviderTest() {} + ~ScreenInfoMetricsProviderTest() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProviderTest); +}; + +TEST_F(ScreenInfoMetricsProviderTest, ProvideSystemProfileMetrics) { + TestScreenInfoMetricsProvider provider; + ChromeUserMetricsExtension uma_proto; + + provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile()); + + // Check that the system profile has the correct values set. + const SystemProfileProto::Hardware& hardware = + uma_proto.system_profile().hardware(); + EXPECT_EQ(kScreenWidth, hardware.primary_screen_width()); + EXPECT_EQ(kScreenHeight, hardware.primary_screen_height()); + EXPECT_EQ(kScreenScaleFactor, hardware.primary_screen_scale_factor()); + EXPECT_EQ(kScreenCount, hardware.screen_count()); +} + +} // namespace metrics diff --git a/chromium/components/metrics/url_constants.cc b/chromium/components/metrics/url_constants.cc new file mode 100644 index 00000000000..55d9e13d0e2 --- /dev/null +++ b/chromium/components/metrics/url_constants.cc @@ -0,0 +1,13 @@ +// Copyright 2015 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 "components/metrics/url_constants.h" + +namespace metrics { + +const char kDefaultMetricsServerUrl[] = "https://clients4.google.com/uma/v2"; +const char kDefaultMetricsMimeType[] = "application/vnd.chrome.uma"; + +} // namespace metrics + diff --git a/chromium/components/metrics/url_constants.h b/chromium/components/metrics/url_constants.h new file mode 100644 index 00000000000..b52ddef2ed9 --- /dev/null +++ b/chromium/components/metrics/url_constants.h @@ -0,0 +1,18 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_METRICS_URL_CONSTANTS_H_ +#define COMPONENTS_METRICS_URL_CONSTANTS_H_ + +namespace metrics { + +// The default metrics server's URL. +extern const char kDefaultMetricsServerUrl[]; + +// The default MIME type for the uploaded metrics data. +extern const char kDefaultMetricsMimeType[]; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_URL_CONSTANTS_H_ |