diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml index 72ad149a5faf8..7016f4b50cf0b 100644 --- a/.github/actions/build/action.yaml +++ b/.github/actions/build/action.yaml @@ -5,7 +5,7 @@ runs: steps: - name: Set up Cloud SDK if: startsWith(${{matrix.target_platform}}, 'android') - uses: google-github-actions/setup-gcloud@v1 + uses: isarkis/setup-gcloud@auth_warning - name: Set Android env vars if: startsWith(${{matrix.target_platform}}, 'android') run: | diff --git a/.github/actions/docker/action.yaml b/.github/actions/docker/action.yaml index c425a58c8b94a..15c7c73b080ec 100644 --- a/.github/actions/docker/action.yaml +++ b/.github/actions/docker/action.yaml @@ -55,6 +55,7 @@ runs: GITHUB_EVENT_NUMBER: ${{ github.event.number }} if: ${{ (steps.changed-files.outputs.any_changed == 'true') && (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.fork) }} run: | + set -x # Need to login to GCR to be able to push images created by fork based PR workflows. PROJECT_NAME=$(gcloud config get-value project) METADATA="http://metadata.google.internal./computeMetadata/v1" @@ -78,12 +79,12 @@ runs: - name: Build containers with docker-compose id: build-image if: env.need_to_build == 'true' - uses: smartlyio/docker-compose-action@83392e28664cc0cb5b3208e8d75697d01da8db18 # v1.7.1 - with: - serviceName: ${{ inputs.docker_service }} - build: false - push: "on:push" - composeArguments: "--no-start" + env: + SERVICE: ${{inputs.docker_service}} + shell: bash + run: | + set -xue + DOCKER_BUILDKIT=0 docker compose -f docker-compose.yml up --build --no-start "${SERVICE}" - name: Tag images id: tag-images if: env.need_to_build == 'true' diff --git a/.github/actions/gn/action.yaml b/.github/actions/gn/action.yaml index 60a3e70e9329e..07f4678b8b490 100644 --- a/.github/actions/gn/action.yaml +++ b/.github/actions/gn/action.yaml @@ -9,7 +9,11 @@ runs: echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV - name: Set up Cloud SDK if: startsWith(${{matrix.target_platform}}, 'android') - uses: google-github-actions/setup-gcloud@v1 + uses: isarkis/setup-gcloud@auth_warning + - name: Check Auth + shell: bash + run: | + gcloud auth list - name: Configure Android Environment shell: bash if: startsWith(${{matrix.target_platform}}, 'android') diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 772e96ad2d341..f04e99b7ee31f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -236,8 +236,9 @@ jobs: if: | fromJSON(needs.initialize.outputs.on_device_test).enabled == true && (( github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.name, 'on_device') ) - || ( inputs.nightly == 'true' && vars.RUN_ODT_TESTS_ON_NIGHTLY != 'False') || + contains(github.event.pull_request.labels.*.name, 'on_device') ) || (( + inputs.nightly == 'true' || github.event_name == 'schedule') && + vars.RUN_ODT_TESTS_ON_NIGHTLY != 'False') || ( github.event_name == 'push' && vars.RUN_ODT_TESTS_ON_POSTSUBMIT != 'False' ) ) runs-on: [self-hosted, linux, X64] name: ${{ matrix.name }}_on_device_${{ matrix.shard }} diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn new file mode 100644 index 0000000000000..3171e82e81548 --- /dev/null +++ b/components/metrics/BUILD.gn @@ -0,0 +1,442 @@ +# 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("//testing/test.gni") + +static_library("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", + "delegating_provider.cc", + "delegating_provider.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", + "enabled_state_provider.cc", + "enabled_state_provider.h", + "environment_recorder.cc", + "environment_recorder.h", + "execution_phase.cc", + "execution_phase.h", + "expired_histogram_util.cc", + "expired_histogram_util.h", + "expired_histograms_checker.cc", + "expired_histograms_checker.h", + "field_trials_provider.cc", + "field_trials_provider.h", + "file_metrics_provider.cc", + "file_metrics_provider.h", + "histogram_encoder.cc", + "histogram_encoder.h", + "log_decoder.cc", + "log_decoder.h", + "log_store.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_store.cc", + "metrics_log_store.h", + "metrics_log_uploader.h", + "metrics_pref_names.cc", + "metrics_pref_names.h", + "metrics_provider.cc", + "metrics_provider.h", + "metrics_reporting_default_state.cc", + "metrics_reporting_default_state.h", + "metrics_reporting_service.cc", + "metrics_reporting_service.h", + "metrics_rotation_scheduler.cc", + "metrics_rotation_scheduler.h", + "metrics_scheduler.cc", + "metrics_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", + "metrics_upload_scheduler.cc", + "metrics_upload_scheduler.h", + "persisted_logs.cc", + "persisted_logs.h", + "persisted_logs_metrics.h", + "persisted_logs_metrics_impl.cc", + "persisted_logs_metrics_impl.h", + "persistent_system_profile.cc", + "persistent_system_profile.h", + "reporting_service.cc", + "reporting_service.h", + "stability_metrics_helper.cc", + "stability_metrics_helper.h", + "stability_metrics_provider.cc", + "stability_metrics_provider.h", + "system_memory_stats_recorder.h", + "system_memory_stats_recorder_linux.cc", + "system_memory_stats_recorder_win.cc", + "system_session_analyzer_win.cc", + "system_session_analyzer_win.h", + "url_constants.cc", + "url_constants.h", + "version_utils.cc", + "version_utils.h", + ] + + public_deps = [ + "//third_party/metrics_proto", + ] + + deps = [ + "//base", + "//base:base_static", + "//components/prefs", + "//components/variations", + "//components/version_info:version_info", + "//extensions/buildflags", + "//third_party/zlib/google: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" ] + deps += [ "//components/browser_watcher:stability_client" ] + libs = [ "wevtapi.lib" ] + } + + if (is_fuchsia) { + sources += [ "drive_metrics_provider_fuchsia.cc" ] + } +} + +# The component metrics provider is a separate target because it depends upon +# (the large) component_updater code, and is not needed for some entities that +# depend on :metrics. +static_library("component_metrics") { + sources = [ + "component_metrics_provider.cc", + "component_metrics_provider.h", + ] + + public_deps = [ + "//third_party/metrics_proto", + ] + + deps = [ + ":metrics", + "//base", + "//components/component_updater", + ] +} + +if (!is_ios) { + static_library("gpu") { + sources = [ + "gpu/gpu_metrics_provider.cc", + "gpu/gpu_metrics_provider.h", + ] + + public_deps = [ + ":metrics", + ] + deps = [ + "//base", + "//content/public/browser", + "//gpu/config", + ] + } +} + +static_library("net") { + sources = [ + "net/cellular_logic_helper.cc", + "net/cellular_logic_helper.h", + "net/net_metrics_log_uploader.cc", + "net/net_metrics_log_uploader.h", + "net/network_metrics_provider.cc", + "net/network_metrics_provider.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/encrypted_messages:encrypted_message_proto", + "//components/encrypted_messages:encrypted_messages", + "//components/variations", + "//net", + "//services/network/public/cpp:cpp", + "//third_party/metrics_proto", + "//third_party/zlib/google:compression_utils", + "//url", + ] + + if (is_chromeos) { + sources += [ + "net/wifi_access_point_info_provider_chromeos.cc", + "net/wifi_access_point_info_provider_chromeos.h", + ] + deps += [ "//chromeos" ] + } +} + +static_library("ui") { + sources = [ + "ui/screen_info_metrics_provider.cc", + "ui/screen_info_metrics_provider.h", + ] + + public_deps = [ + ":metrics", + ] + deps = [ + "//base", + "//ui/display", + "//ui/gfx", + "//ui/gfx/geometry", + ] +} + +static_library("single_sample_metrics") { + sources = [ + "single_sample_metrics.cc", + "single_sample_metrics.h", + "single_sample_metrics_factory_impl.cc", + "single_sample_metrics_factory_impl.h", + ] + + deps = [ + "//mojo/public/cpp/bindings", + "//services/service_manager/public/cpp", + "//services/service_manager/public/mojom", + ] + + public_deps = [ + "//components/metrics/public/interfaces:single_sample_metrics_mojo_bindings", + ] +} + +source_set("call_stack_profile_params") { + public = [ + "call_stack_profile_encoding.h", + "call_stack_profile_params.h", + ] + sources = [ + "call_stack_profile_encoding.cc", + ] + + deps = [ + "//base:base", + "//third_party/metrics_proto", + ] +} + +# Dependency for child processes that use the CallStackProfileBuilder. +source_set("child_call_stack_profile_builder") { + public = [ + "call_stack_profile_builder.h", + "child_call_stack_profile_collector.h", + ] + sources = [ + "call_stack_profile_builder.cc", + "child_call_stack_profile_collector.cc", + ] + public_deps = [ + ":call_stack_profile_params", + ] + deps = [ + "//base", + "//components/metrics/public/interfaces:call_stack_mojo_bindings", + "//third_party/metrics_proto", + ] + + # This target must not depend on :metrics because that code is intended solely + # for use in the browser process. + assert_no_deps = [ ":metrics" ] +} + +# Dependency for browser process use of the CallStackProfileBuilder. +source_set("call_stack_profile_builder") { + deps = [ + ":metrics", + ] + public_deps = [ + ":child_call_stack_profile_builder", + ] +} + +# The browser process mojo service for collecting profiles from child +# processes. +source_set("call_stack_profile_collector") { + sources = [ + "call_stack_profile_collector.cc", + "call_stack_profile_collector.h", + ] + deps = [ + ":call_stack_profile_params", + ":metrics", + "//components/metrics/public/interfaces:call_stack_mojo_bindings", + ] +} + +static_library("test_support") { + testonly = true + sources = [ + "test_enabled_state_provider.cc", + "test_enabled_state_provider.h", + "test_metrics_log_uploader.cc", + "test_metrics_log_uploader.h", + "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) { + static_library("serialization") { + sources = [ + "serialization/metric_sample.cc", + "serialization/metric_sample.h", + "serialization/serialization_utils.cc", + "serialization/serialization_utils.h", + ] + deps = [ + "//base", + ] + } +} + +source_set("unit_tests") { + testonly = true + sources = [ + "call_stack_profile_builder_unittest.cc", + "call_stack_profile_metrics_provider_unittest.cc", + "child_call_stack_profile_collector_unittest.cc", + "cloned_install_detector_unittest.cc", + "component_metrics_provider_unittest.cc", + "daily_event_unittest.cc", + "data_use_tracker_unittest.cc", + "drive_metrics_provider_unittest.cc", + "environment_recorder_unittest.cc", + "expired_histograms_checker_unittest.cc", + "field_trials_provider_unittest.cc", + "file_metrics_provider_unittest.cc", + "histogram_encoder_unittest.cc", + "machine_id_provider_win_unittest.cc", + "metrics_log_manager_unittest.cc", + "metrics_log_store_unittest.cc", + "metrics_log_unittest.cc", + "metrics_service_unittest.cc", + "metrics_state_manager_unittest.cc", + "net/net_metrics_log_uploader_unittest.cc", + "net/network_metrics_provider_unittest.cc", + "persisted_logs_unittest.cc", + "persistent_system_profile_unittest.cc", + "reporting_service_unittest.cc", + "single_sample_metrics_factory_impl_unittest.cc", + "stability_metrics_helper_unittest.cc", + "stability_metrics_provider_unittest.cc", + "system_session_analyzer_win_unittest.cc", + "ui/screen_info_metrics_provider_unittest.cc", + ] + + deps = [ + ":call_stack_profile_builder", + ":component_metrics", + ":metrics", + ":net", + ":single_sample_metrics", + ":test_support", + ":ui", + "//base/test:test_support", + "//components/component_updater:test_support", + "//components/encrypted_messages:encrypted_message_proto", + "//components/metrics/public/cpp:call_stack_unit_tests", + "//components/prefs:test_support", + "//components/variations", + "//extensions/buildflags", + "//mojo/public/cpp/bindings", + "//net:test_support", + "//services/network:test_support", + "//services/network/public/cpp:cpp", + "//services/service_manager/public/cpp", + "//testing/gtest", + "//third_party/zlib/google:compression_utils", + "//ui/gfx/geometry", + ] + + if (is_linux) { + sources += [ "serialization/serialization_utils_unittest.cc" ] + deps += [ ":serialization" ] + } + + if (is_chromeos) { + deps += [ "//chromeos" ] + } + + # iOS is not supported by the profiler and the ios-simulator bot chokes on + # these tests. + if (is_ios) { + sources -= [ "child_call_stack_profile_collector_unittest.cc" ] + deps -= [ "//components/metrics/public/cpp:call_stack_unit_tests" ] + } +} + +# Convenience testing target +test("metrics_unittests") { + sources = [ + "//components/test/run_all_unittests.cc", + ] + deps = [ + ":unit_tests", + "//components/test:test_support", + ] +} diff --git a/components/metrics/DEPS b/components/metrics/DEPS new file mode 100644 index 0000000000000..1457333d3522b --- /dev/null +++ b/components/metrics/DEPS @@ -0,0 +1,19 @@ +# This component is shared with the Chrome OS build, so it's important to limit +# dependencies to a minimal set. +include_rules = [ + "-components", + "+components/browser_watcher", + "+components/component_updater", + "+components/compression", + "+components/metrics", + "+components/prefs", + "+components/variations", + "+components/version_info", + "+content/public/test", + "+extensions/buildflags", + "+mojo/public/cpp", + "+services/service_manager/public/cpp", + "+third_party/metrics_proto", + "+third_party/zlib/google", + "-net", +] diff --git a/components/metrics/OWNERS b/components/metrics/OWNERS new file mode 100644 index 0000000000000..68c3f9cbc000c --- /dev/null +++ b/components/metrics/OWNERS @@ -0,0 +1,5 @@ +file://base/metrics/OWNERS + +per-file *call_stack_profile*=wittman@chromium.org + +# COMPONENT: Internals>Metrics diff --git a/components/metrics/README b/components/metrics/README new file mode 100644 index 0000000000000..5a2abbb2f69dc --- /dev/null +++ b/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/components/metrics/call_stack_profile_builder.cc b/components/metrics/call_stack_profile_builder.cc new file mode 100644 index 0000000000000..7b2890f06a1ff --- /dev/null +++ b/components/metrics/call_stack_profile_builder.cc @@ -0,0 +1,346 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/call_stack_profile_builder.h" + +#include +#include + +#include "base/atomicops.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/metrics/metrics_hashes.h" +#include "base/no_destructor.h" +#include "base/stl_util.h" +#include "components/metrics/call_stack_profile_encoding.h" + +namespace metrics { + +namespace { + +// Only used by child processes. +base::LazyInstance::Leaky + g_child_call_stack_profile_collector = LAZY_INSTANCE_INITIALIZER; + +base::RepeatingCallback& +GetBrowserProcessReceiverCallbackInstance() { + static base::NoDestructor< + base::RepeatingCallback> + instance; + return *instance; +} + +// Identifies an unknown module. +const size_t kUnknownModuleIndex = static_cast(-1); + +// This global variables holds the current system state and is recorded with +// every captured sample, done on a separate thread which is why updates to +// this must be atomic. A PostTask to move the the updates to that thread +// would skew the timing and a lock could result in deadlock if the thread +// making a change was also being profiled and got stopped. +static base::subtle::Atomic32 g_process_milestones = 0; + +void ChangeAtomicFlags(base::subtle::Atomic32* flags, + base::subtle::Atomic32 set, + base::subtle::Atomic32 clear) { + DCHECK(set != 0 || clear != 0); + DCHECK_EQ(0, set & clear); + + base::subtle::Atomic32 bits = base::subtle::NoBarrier_Load(flags); + while (true) { + base::subtle::Atomic32 existing = base::subtle::NoBarrier_CompareAndSwap( + flags, bits, (bits | set) & ~clear); + if (existing == bits) + break; + bits = existing; + } +} + +// Provide a mapping from the C++ "enum" definition of various process mile- +// stones to the equivalent protobuf "enum" definition. This table-lookup +// conversion allows for the implementation to evolve and still be compatible +// with the protobuf -- even if there are ever more than 32 defined proto +// values, though never more than 32 could be in-use in a given C++ version +// of the code. +const ProcessPhase kProtoPhases[CallStackProfileBuilder::MILESTONES_MAX_VALUE] = + { + ProcessPhase::MAIN_LOOP_START, + ProcessPhase::MAIN_NAVIGATION_START, + ProcessPhase::MAIN_NAVIGATION_FINISHED, + ProcessPhase::FIRST_NONEMPTY_PAINT, + + ProcessPhase::SHUTDOWN_START, +}; + +// These functions are used 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 CallStackProfileBuilder::Sample& sample, + const std::vector& modules, + CallStackProfile::Sample* proto_sample) { + for (const auto& frame : sample.frames) { + CallStackProfile::Location* location = proto_sample->add_frame(); + // 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 == kUnknownModuleIndex) + continue; + int64_t module_offset = + reinterpret_cast(frame.instruction_pointer) - + reinterpret_cast(modules[frame.module_index].base_address); + DCHECK_GE(module_offset, 0); + location->set_address(static_cast(module_offset)); + location->set_module_id_index(frame.module_index); + } +} + +// Transcode Sample annotations into protobuf fields. The C++ code uses a +// bit- field with each bit corresponding to an entry in an enumeration +// while the protobuf uses a repeated field of individual values. Conversion +// tables allow for arbitrary mapping, though no more than 32 in any given +// version of the code. +void CopyAnnotationsToProto(uint32_t new_milestones, + CallStackProfile::Sample* sample_proto) { + for (size_t bit = 0; new_milestones != 0 && bit < sizeof(new_milestones) * 8; + ++bit) { + const uint32_t flag = 1U << bit; + if (new_milestones & flag) { + if (bit >= base::size(kProtoPhases)) { + NOTREACHED(); + continue; + } + sample_proto->add_process_phase(kProtoPhases[bit]); + new_milestones ^= flag; // Bit is set so XOR will clear it. + } + } +} + +} // namespace + +// CallStackProfileBuilder::Frame --------------------------------------------- + +CallStackProfileBuilder::Frame::Frame(uintptr_t instruction_pointer, + size_t module_index) + : instruction_pointer(instruction_pointer), module_index(module_index) {} + +CallStackProfileBuilder::Frame::~Frame() = default; + +CallStackProfileBuilder::Frame::Frame() + : instruction_pointer(0), module_index(kUnknownModuleIndex) {} + +// CallStackProfileBuilder::Sample -------------------------------------------- + +CallStackProfileBuilder::Sample::Sample() = default; + +CallStackProfileBuilder::Sample::Sample(const Sample& sample) = default; + +CallStackProfileBuilder::Sample::~Sample() = default; + +CallStackProfileBuilder::Sample::Sample(const Frame& frame) { + frames.push_back(std::move(frame)); +} + +CallStackProfileBuilder::Sample::Sample(const std::vector& frames) + : frames(frames) {} + +CallStackProfileBuilder::CallStackProfileBuilder( + const CallStackProfileParams& profile_params, + base::OnceClosure completed_callback) + : profile_params_(profile_params), + profile_start_time_(base::TimeTicks::Now()) { + completed_callback_ = std::move(completed_callback); +} + +CallStackProfileBuilder::~CallStackProfileBuilder() = default; + +void CallStackProfileBuilder::RecordAnnotations() { + // The code inside this method must not do anything that could acquire a + // mutex, including allocating memory (which includes LOG messages) because + // that mutex could be held by a stopped thread, thus resulting in deadlock. + sample_.process_milestones = + base::subtle::NoBarrier_Load(&g_process_milestones); +} + +void CallStackProfileBuilder::OnSampleCompleted( + std::vector frames) { + // Assemble sample_ from |frames| first. + for (const auto& frame : frames) { + const base::ModuleCache::Module& module(frame.module); + if (!module.is_valid) { + sample_.frames.emplace_back(frame.instruction_pointer, + kUnknownModuleIndex); + continue; + } + + // Dedup modules and cache them in modules_. + auto loc = module_index_.find(module.base_address); + if (loc == module_index_.end()) { + modules_.push_back(module); + size_t index = modules_.size() - 1; + loc = module_index_.insert(std::make_pair(module.base_address, index)) + .first; + } + sample_.frames.emplace_back(frame.instruction_pointer, loc->second); + } + + // Write CallStackProfile::Sample protocol buffer message based on sample_. + int existing_sample_index = -1; + auto location = sample_index_.find(sample_); + if (location != sample_index_.end()) + existing_sample_index = location->second; + + if (existing_sample_index != -1) { + CallStackProfile::Sample* sample_proto = + proto_profile_.mutable_deprecated_sample(existing_sample_index); + sample_proto->set_count(sample_proto->count() + 1); + return; + } + + CallStackProfile::Sample* sample_proto = + proto_profile_.add_deprecated_sample(); + CopySampleToProto(sample_, modules_, sample_proto); + sample_proto->set_count(1); + CopyAnnotationsToProto(sample_.process_milestones & ~milestones_, + sample_proto); + milestones_ = sample_.process_milestones; + + sample_index_.insert(std::make_pair( + sample_, static_cast(proto_profile_.deprecated_sample_size()) - 1)); + + sample_ = Sample(); +} + +// Build a SampledProfile in the protocol buffer message format from the +// collected sampling data. The message is then passed to +// CallStackProfileMetricsProvider or ChildCallStackProfileCollector. + +// A SampledProfile message (third_party/metrics_proto/sampled_profile.proto) +// contains a CallStackProfile message +// (third_party/metrics_proto/call_stack_profile.proto) and associated profile +// parameters (process/thread/trigger event). A CallStackProfile message +// contains a set of Sample messages and ModuleIdentifier messages, and other +// sampling information. One Sample corresponds to a single recorded stack, and +// the ModuleIdentifiers record those modules associated with the recorded stack +// frames. +void CallStackProfileBuilder::OnProfileCompleted( + base::TimeDelta profile_duration, + base::TimeDelta sampling_period) { + proto_profile_.set_profile_duration_ms(profile_duration.InMilliseconds()); + proto_profile_.set_sampling_period_ms(sampling_period.InMilliseconds()); + + for (const auto& module : 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)); + } + + // Clear the caches etc. + modules_.clear(); + module_index_.clear(); + sample_index_.clear(); + + // Assemble the SampledProfile protocol buffer message and run the associated + // callback to pass it. + SampledProfile sampled_profile; + CallStackProfile* proto_profile = + sampled_profile.mutable_call_stack_profile(); + *proto_profile = std::move(proto_profile_); + + sampled_profile.set_process( + ToExecutionContextProcess(profile_params_.process)); + sampled_profile.set_thread(ToExecutionContextThread(profile_params_.thread)); + sampled_profile.set_trigger_event( + ToSampledProfileTriggerEvent(profile_params_.trigger)); + + PassProfilesToMetricsProvider(std::move(sampled_profile)); + + // Run the completed callback if there is one. + if (!completed_callback_.is_null()) + std::move(completed_callback_).Run(); +} + +// static +void CallStackProfileBuilder::SetBrowserProcessReceiverCallback( + const base::RepeatingCallback& + callback) { + GetBrowserProcessReceiverCallbackInstance() = callback; +} + +void CallStackProfileBuilder::PassProfilesToMetricsProvider( + SampledProfile sampled_profile) { + if (profile_params_.process == CallStackProfileParams::BROWSER_PROCESS) { + GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time_, + std::move(sampled_profile)); + } else { + g_child_call_stack_profile_collector.Get() + .ChildCallStackProfileCollector::Collect(profile_start_time_, + std::move(sampled_profile)); + } +} + +// static +void CallStackProfileBuilder::SetProcessMilestone(int milestone) { + DCHECK_LE(0, milestone); + DCHECK_GT(static_cast(sizeof(g_process_milestones) * 8), milestone); + DCHECK_EQ(0, base::subtle::NoBarrier_Load(&g_process_milestones) & + (1 << milestone)); + ChangeAtomicFlags(&g_process_milestones, 1 << milestone, 0); +} + +// static +void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess( + metrics::mojom::CallStackProfileCollectorPtr browser_interface) { + g_child_call_stack_profile_collector.Get().SetParentProfileCollector( + std::move(browser_interface)); +} + +// These operators permit types to be compared and used in a map of Samples. + +bool operator==(const CallStackProfileBuilder::Sample& a, + const CallStackProfileBuilder::Sample& b) { + return a.process_milestones == b.process_milestones && a.frames == b.frames; +} + +bool operator!=(const CallStackProfileBuilder::Sample& a, + const CallStackProfileBuilder::Sample& b) { + return !(a == b); +} + +bool operator<(const CallStackProfileBuilder::Sample& a, + const CallStackProfileBuilder::Sample& b) { + if (a.process_milestones != b.process_milestones) + return a.process_milestones < b.process_milestones; + + return a.frames < b.frames; +} + +bool operator==(const CallStackProfileBuilder::Frame& a, + const CallStackProfileBuilder::Frame& b) { + return a.instruction_pointer == b.instruction_pointer && + a.module_index == b.module_index; +} + +bool operator<(const CallStackProfileBuilder::Frame& a, + const CallStackProfileBuilder::Frame& b) { + if (a.module_index != b.module_index) + return a.module_index < b.module_index; + + return a.instruction_pointer < b.instruction_pointer; +} + +} // namespace metrics diff --git a/components/metrics/call_stack_profile_builder.h b/components/metrics/call_stack_profile_builder.h new file mode 100644 index 0000000000000..deb2feafda572 --- /dev/null +++ b/components/metrics/call_stack_profile_builder.h @@ -0,0 +1,158 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_ +#define COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_ + +#include +#include + +#include "base/callback.h" +#include "base/profiler/stack_sampling_profiler.h" +#include "base/sampling_heap_profiler/module_cache.h" +#include "base/time/time.h" +#include "components/metrics/call_stack_profile_params.h" +#include "components/metrics/child_call_stack_profile_collector.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace metrics { + +class SampledProfile; + +// An instance of the class is meant to be passed to base::StackSamplingProfiler +// to collect profiles. The profiles collected are uploaded via the metrics log. +class CallStackProfileBuilder + : public base::StackSamplingProfiler::ProfileBuilder { + public: + // Frame represents an individual sampled stack frame with module information. + struct Frame { + Frame(uintptr_t instruction_pointer, size_t module_index); + ~Frame(); + + // Default constructor to satisfy IPC macros. Do not use explicitly. + Frame(); + + // The sampled instruction pointer within the function. + uintptr_t instruction_pointer; + + // Index of the module in the associated vector of mofules. We don't + // represent module state directly here to save space. + size_t module_index; + }; + + // Sample represents a set of stack frames with some extra information. + struct Sample { + Sample(); + Sample(const Sample& sample); + ~Sample(); + + // These constructors are used only during testing. + Sample(const Frame& frame); + Sample(const std::vector& frames); + + // The entire stack frame when the sample is taken. + std::vector frames; + + // A bit-field indicating which process milestones have passed. This can be + // used to tell where in the process lifetime the samples are taken. Just + // as a "lifetime" can only move forward, these bits mark the milestones of + // the processes life as they occur. Bits can be set but never reset. The + // actual definition of the individual bits is left to the user of this + // module. + uint32_t process_milestones = 0; + }; + + // These milestones of a process lifetime can be passed as process "mile- + // stones" to CallStackProfileBuilder::SetProcessMilestone(). Be sure to + // update the translation constants at the top of the .cc file when this is + // changed. + enum Milestones : int { + MAIN_LOOP_START, + MAIN_NAVIGATION_START, + MAIN_NAVIGATION_FINISHED, + FIRST_NONEMPTY_PAINT, + + SHUTDOWN_START, + + MILESTONES_MAX_VALUE + }; + + // |completed_callback| is made when sampling a profile completes. Other + // threads, including the UI thread, may block on callback completion so this + // should run as quickly as possible. + // + // IMPORTANT NOTE: The callback is invoked on a thread the profiler + // constructs, rather than on the thread used to construct the profiler, and + // thus the callback must be callable on any thread. + CallStackProfileBuilder( + const CallStackProfileParams& profile_params, + base::OnceClosure completed_callback = base::OnceClosure()); + + ~CallStackProfileBuilder() override; + + // base::StackSamplingProfiler::ProfileBuilder: + void RecordAnnotations() override; + void OnSampleCompleted( + std::vector frames) override; + void OnProfileCompleted(base::TimeDelta profile_duration, + base::TimeDelta sampling_period) override; + + // Sets the callback to use for reporting browser process profiles. This + // indirection is required to avoid a dependency on unnecessary metrics code + // in child processes. + static void SetBrowserProcessReceiverCallback( + const base::RepeatingCallback& + callback); + + // Sets the current system state that is recorded with each captured stack + // frame. This is thread-safe so can be called from anywhere. The parameter + // value should be from an enumeration of the appropriate type with values + // ranging from 0 to 31, inclusive. This sets bits within Sample field of + // |process_milestones|. The actual meanings of these bits are defined + // (globally) by the caller(s). + static void SetProcessMilestone(int milestone); + + // Sets the CallStackProfileCollector interface from |browser_interface|. + // This function must be called within child processes. + static void SetParentProfileCollectorForChildProcess( + metrics::mojom::CallStackProfileCollectorPtr browser_interface); + + protected: + // Test seam. + virtual void PassProfilesToMetricsProvider(SampledProfile sampled_profile); + + private: + // The collected stack samples in proto buffer message format. + CallStackProfile proto_profile_; + + // The current sample being recorded. + Sample sample_; + + // The indexes of samples, indexed by the sample. + std::map sample_index_; + + // The indexes of modules, indexed by module's base_address. + std::map module_index_; + + // The distinct modules in the current profile. + std::vector modules_; + + // The process milestones of a previous sample. + uint32_t milestones_ = 0; + + // Callback made when sampling a profile completes. + base::OnceClosure completed_callback_; + + // The parameters associated with the sampled profile. + const CallStackProfileParams profile_params_; + + // The start time of a profile collection. + const base::TimeTicks profile_start_time_; + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileBuilder); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_ diff --git a/components/metrics/call_stack_profile_builder_unittest.cc b/components/metrics/call_stack_profile_builder_unittest.cc new file mode 100644 index 0000000000000..89549c0a2504d --- /dev/null +++ b/components/metrics/call_stack_profile_builder_unittest.cc @@ -0,0 +1,363 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/call_stack_profile_builder.h" + +#include "base/files/file_path.h" +#include "base/sampling_heap_profiler/module_cache.h" +#include "base/test/bind_test_util.h" +#include "base/test/mock_callback.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/call_stack_profile_params.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +using Frame = base::StackSamplingProfiler::Frame; +using Module = base::ModuleCache::Module; + +namespace metrics { + +namespace { + +constexpr CallStackProfileParams kProfileParams = { + CallStackProfileParams::BROWSER_PROCESS, + CallStackProfileParams::MAIN_THREAD, + CallStackProfileParams::PROCESS_STARTUP}; + +class TestingCallStackProfileBuilder : public CallStackProfileBuilder { + public: + TestingCallStackProfileBuilder( + const CallStackProfileParams& profile_params, + base::OnceClosure completed_callback = base::OnceClosure()); + + ~TestingCallStackProfileBuilder() override; + + const SampledProfile& sampled_profile() { return sampled_profile_; } + + protected: + // Overridden for testing. + void PassProfilesToMetricsProvider(SampledProfile sampled_profile) override; + + private: + // The completed profile. + SampledProfile sampled_profile_; +}; + +TestingCallStackProfileBuilder::TestingCallStackProfileBuilder( + const CallStackProfileParams& profile_params, + base::OnceClosure completed_callback) + : CallStackProfileBuilder(profile_params, std::move(completed_callback)) {} + +TestingCallStackProfileBuilder::~TestingCallStackProfileBuilder() = default; + +void TestingCallStackProfileBuilder::PassProfilesToMetricsProvider( + SampledProfile sampled_profile) { + sampled_profile_ = std::move(sampled_profile); +} + +} // namespace + +TEST(CallStackProfileBuilderTest, SetProcessMilestone) { + auto profile_builder = + std::make_unique(kProfileParams); + + // The default milestone is 0. + profile_builder->RecordAnnotations(); + profile_builder->OnSampleCompleted(std::vector()); + + CallStackProfileBuilder::SetProcessMilestone(1); + profile_builder->RecordAnnotations(); + profile_builder->OnSampleCompleted(std::vector()); + + profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); + + const SampledProfile& proto = profile_builder->sampled_profile(); + + ASSERT_TRUE(proto.has_call_stack_profile()); + const CallStackProfile& profile = proto.call_stack_profile(); + + ASSERT_EQ(2, profile.deprecated_sample_size()); + + uint32_t process_milestones = 0; + for (int i = 0; i < profile.deprecated_sample(0).process_phase().size(); ++i) + process_milestones |= + 1U << profile.deprecated_sample(0).process_phase().Get(i); + EXPECT_EQ(0U, process_milestones); + + process_milestones = 0; + for (int i = 0; i < profile.deprecated_sample(1).process_phase().size(); ++i) + process_milestones |= + 1U << profile.deprecated_sample(1).process_phase().Get(i); + EXPECT_EQ(1U << 1, process_milestones); +} + +TEST(CallStackProfileBuilderTest, ProfilingCompleted) { + // Set up a mock completed callback which will be run once. + base::MockCallback mock_closure; + EXPECT_CALL(mock_closure, Run()).Times(1); + + auto profile_builder = std::make_unique( + kProfileParams, mock_closure.Get()); + +#if defined(OS_WIN) + uint64_t module_md5 = 0x46C3E4166659AC02ULL; + base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe"); +#else + uint64_t module_md5 = 0x554838A8451AC36CULL; + base::FilePath module_path("/some/path/to/chrome"); +#endif + + const uintptr_t module_base_address1 = 0x1000; + Module module1 = {module_base_address1, "1", module_path}; + Frame frame1 = {module_base_address1 + 0x10, module1}; + + const uintptr_t module_base_address2 = 0x1100; + Module module2 = {module_base_address2, "2", module_path}; + Frame frame2 = {module_base_address2 + 0x10, module2}; + + const uintptr_t module_base_address3 = 0x1010; + Module module3 = {module_base_address3, "3", module_path}; + Frame frame3 = {module_base_address3 + 0x10, module3}; + + std::vector frames1 = {frame1, frame2}; + std::vector frames2 = {frame3}; + + profile_builder->OnSampleCompleted(frames1); + profile_builder->OnSampleCompleted(frames2); + profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500), + base::TimeDelta::FromMilliseconds(100)); + + const SampledProfile& proto = profile_builder->sampled_profile(); + + ASSERT_TRUE(proto.has_process()); + ASSERT_EQ(BROWSER_PROCESS, proto.process()); + ASSERT_TRUE(proto.has_thread()); + ASSERT_EQ(MAIN_THREAD, proto.thread()); + ASSERT_TRUE(proto.has_trigger_event()); + ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event()); + + ASSERT_TRUE(proto.has_call_stack_profile()); + const CallStackProfile& profile = proto.call_stack_profile(); + + ASSERT_EQ(2, profile.deprecated_sample_size()); + ASSERT_EQ(2, profile.deprecated_sample(0).frame_size()); + ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index()); + EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index()); + ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index()); + EXPECT_EQ(1, profile.deprecated_sample(0).frame(1).module_id_index()); + ASSERT_EQ(1, profile.deprecated_sample(1).frame_size()); + ASSERT_TRUE(profile.deprecated_sample(1).frame(0).has_module_id_index()); + EXPECT_EQ(2, profile.deprecated_sample(1).frame(0).module_id_index()); + + ASSERT_EQ(3, profile.module_id().size()); + ASSERT_TRUE(profile.module_id(0).has_build_id()); + ASSERT_EQ("1", profile.module_id(0).build_id()); + ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix()); + ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix()); + ASSERT_TRUE(profile.module_id(1).has_build_id()); + ASSERT_EQ("2", profile.module_id(1).build_id()); + ASSERT_TRUE(profile.module_id(1).has_name_md5_prefix()); + ASSERT_EQ(module_md5, profile.module_id(1).name_md5_prefix()); + ASSERT_TRUE(profile.module_id(2).has_build_id()); + ASSERT_EQ("3", profile.module_id(2).build_id()); + ASSERT_TRUE(profile.module_id(2).has_name_md5_prefix()); + ASSERT_EQ(module_md5, profile.module_id(2).name_md5_prefix()); + + ASSERT_TRUE(profile.has_profile_duration_ms()); + EXPECT_EQ(500, profile.profile_duration_ms()); + ASSERT_TRUE(profile.has_sampling_period_ms()); + EXPECT_EQ(100, profile.sampling_period_ms()); +} + +TEST(CallStackProfileBuilderTest, SamplesDeduped) { + auto profile_builder = + std::make_unique(kProfileParams); + +#if defined(OS_WIN) + base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe"); +#else + base::FilePath module_path("/some/path/to/chrome"); +#endif + + const uintptr_t module_base_address1 = 0x1000; + Module module1 = {module_base_address1, "1", module_path}; + Frame frame1 = {module_base_address1 + 0x10, module1}; + + const uintptr_t module_base_address2 = 0x1100; + Module module2 = {module_base_address2, "2", module_path}; + Frame frame2 = {module_base_address2 + 0x10, module2}; + + std::vector frames = {frame1, frame2}; + + // Two samples are completed with the same frames. They also have the same + // process milestone therefore they are deduped to one. + CallStackProfileBuilder::SetProcessMilestone(0); + + profile_builder->RecordAnnotations(); + profile_builder->OnSampleCompleted(frames); + + profile_builder->RecordAnnotations(); + profile_builder->OnSampleCompleted(frames); + + profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); + + const SampledProfile& proto = profile_builder->sampled_profile(); + + ASSERT_TRUE(proto.has_process()); + ASSERT_EQ(BROWSER_PROCESS, proto.process()); + ASSERT_TRUE(proto.has_thread()); + ASSERT_EQ(MAIN_THREAD, proto.thread()); + ASSERT_TRUE(proto.has_trigger_event()); + ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event()); + + ASSERT_TRUE(proto.has_call_stack_profile()); + ASSERT_EQ(1, proto.call_stack_profile().deprecated_sample_size()); +} + +TEST(CallStackProfileBuilderTest, SamplesNotDeduped) { + auto profile_builder = + std::make_unique(kProfileParams); + +#if defined(OS_WIN) + base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe"); +#else + base::FilePath module_path("/some/path/to/chrome"); +#endif + + const uintptr_t module_base_address1 = 0x1000; + Module module1 = {module_base_address1, "1", module_path}; + Frame frame1 = {module_base_address1 + 0x10, module1}; + + const uintptr_t module_base_address2 = 0x1100; + Module module2 = {module_base_address2, "2", module_path}; + Frame frame2 = {module_base_address2 + 0x10, module2}; + + std::vector frames = {frame1, frame2}; + + // Two samples are completed with the same frames but different process + // milestones. They are considered as different samples threfore not deduped. + CallStackProfileBuilder::SetProcessMilestone(2); + profile_builder->RecordAnnotations(); + profile_builder->OnSampleCompleted(frames); + + CallStackProfileBuilder::SetProcessMilestone(4); + profile_builder->RecordAnnotations(); + profile_builder->OnSampleCompleted(frames); + + profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); + + const SampledProfile& proto = profile_builder->sampled_profile(); + + ASSERT_TRUE(proto.has_process()); + ASSERT_EQ(BROWSER_PROCESS, proto.process()); + ASSERT_TRUE(proto.has_thread()); + ASSERT_EQ(MAIN_THREAD, proto.thread()); + ASSERT_TRUE(proto.has_trigger_event()); + ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event()); + + ASSERT_TRUE(proto.has_call_stack_profile()); + ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample_size()); +} + +TEST(CallStackProfileBuilderTest, Modules) { + auto profile_builder = + std::make_unique(kProfileParams); + + const uintptr_t module_base_address1 = 0x1000; + Module module1; // module1 has no information hence invalid. + Frame frame1 = {module_base_address1 + 0x10, module1}; + + const uintptr_t module_base_address2 = 0x1100; +#if defined(OS_WIN) + uint64_t module_md5 = 0x46C3E4166659AC02ULL; + base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe"); +#else + uint64_t module_md5 = 0x554838A8451AC36CULL; + base::FilePath module_path("/some/path/to/chrome"); +#endif + Module module2 = {module_base_address2, "2", module_path}; + Frame frame2 = {module_base_address2 + 0x10, module2}; + + std::vector frames = {frame1, frame2}; + + profile_builder->OnSampleCompleted(frames); + profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); + + const SampledProfile& proto = profile_builder->sampled_profile(); + + ASSERT_TRUE(proto.has_call_stack_profile()); + const CallStackProfile& profile = proto.call_stack_profile(); + + ASSERT_EQ(1, profile.deprecated_sample_size()); + ASSERT_EQ(2, profile.deprecated_sample(0).frame_size()); + + ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_module_id_index()); + ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_address()); + + ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index()); + EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index()); + ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address()); + EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(1).address()); + + ASSERT_EQ(1, profile.module_id().size()); + ASSERT_TRUE(profile.module_id(0).has_build_id()); + ASSERT_EQ("2", profile.module_id(0).build_id()); + ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix()); + ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix()); +} + +TEST(CallStackProfileBuilderTest, DedupModules) { + auto profile_builder = + std::make_unique(kProfileParams); + + const uintptr_t module_base_address = 0x1000; + +#if defined(OS_WIN) + uint64_t module_md5 = 0x46C3E4166659AC02ULL; + base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe"); +#else + uint64_t module_md5 = 0x554838A8451AC36CULL; + base::FilePath module_path("/some/path/to/chrome"); +#endif + + Module module1 = {module_base_address, "1", module_path}; + Frame frame1 = {module_base_address + 0x10, module1}; + + Module module2 = {module_base_address, "1", module_path}; + Frame frame2 = {module_base_address + 0x20, module2}; + + std::vector frames = {frame1, frame2}; + + profile_builder->OnSampleCompleted(frames); + profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta()); + + const SampledProfile& proto = profile_builder->sampled_profile(); + + ASSERT_TRUE(proto.has_call_stack_profile()); + const CallStackProfile& profile = proto.call_stack_profile(); + + ASSERT_EQ(1, profile.deprecated_sample_size()); + ASSERT_EQ(2, profile.deprecated_sample(0).frame_size()); + + // Since module1 and module2 have the same base address, they are considered + // the same module and therefore deduped. + ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index()); + EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index()); + ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_address()); + EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(0).address()); + + ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index()); + EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index()); + ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address()); + EXPECT_EQ(0x20ULL, profile.deprecated_sample(0).frame(1).address()); + + ASSERT_EQ(1, profile.module_id().size()); + ASSERT_TRUE(profile.module_id(0).has_build_id()); + ASSERT_EQ("1", profile.module_id(0).build_id()); + ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix()); + ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix()); +} + +} // namespace metrics diff --git a/components/metrics/call_stack_profile_collector.cc b/components/metrics/call_stack_profile_collector.cc new file mode 100644 index 0000000000000..386333149189e --- /dev/null +++ b/components/metrics/call_stack_profile_collector.cc @@ -0,0 +1,40 @@ +// 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/call_stack_profile_collector.h" + +#include +#include + +#include "components/metrics/call_stack_profile_encoding.h" +#include "components/metrics/call_stack_profile_metrics_provider.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace metrics { + +CallStackProfileCollector::CallStackProfileCollector( + CallStackProfileParams::Process expected_process) + : expected_process_(expected_process) {} + +CallStackProfileCollector::~CallStackProfileCollector() {} + +// static +void CallStackProfileCollector::Create( + CallStackProfileParams::Process expected_process, + mojom::CallStackProfileCollectorRequest request) { + mojo::MakeStrongBinding( + std::make_unique(expected_process), + std::move(request)); +} + +void CallStackProfileCollector::Collect(base::TimeTicks start_timestamp, + SampledProfile profile) { + if (profile.process() != ToExecutionContextProcess(expected_process_)) + return; + + CallStackProfileMetricsProvider::ReceiveCompletedProfile(start_timestamp, + std::move(profile)); +} + +} // namespace metrics diff --git a/components/metrics/call_stack_profile_collector.h b/components/metrics/call_stack_profile_collector.h new file mode 100644 index 0000000000000..0ffb94d49cbd4 --- /dev/null +++ b/components/metrics/call_stack_profile_collector.h @@ -0,0 +1,40 @@ +// 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_CALL_STACK_PROFILE_COLLECTOR_H_ +#define COMPONENTS_METRICS_CALL_STACK_PROFILE_COLLECTOR_H_ + +#include "base/macros.h" +#include "components/metrics/call_stack_profile_params.h" +#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace metrics { + +class CallStackProfileCollector : public mojom::CallStackProfileCollector { + public: + explicit CallStackProfileCollector( + CallStackProfileParams::Process expected_process); + ~CallStackProfileCollector() override; + + // Create a collector to receive profiles from |expected_process|. + static void Create(CallStackProfileParams::Process expected_process, + mojom::CallStackProfileCollectorRequest request); + + // mojom::CallStackProfileCollector: + void Collect(base::TimeTicks start_timestamp, + SampledProfile profile) override; + + private: + // Profile params are validated to come from this process. Profiles with a + // different process declared in the params are considered untrustworthy and + // ignored. + const CallStackProfileParams::Process expected_process_; + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileCollector); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_COLLECTOR_H_ diff --git a/components/metrics/call_stack_profile_encoding.cc b/components/metrics/call_stack_profile_encoding.cc new file mode 100644 index 0000000000000..5d6a111b89d3c --- /dev/null +++ b/components/metrics/call_stack_profile_encoding.cc @@ -0,0 +1,67 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/call_stack_profile_encoding.h" + +namespace metrics { + +Process ToExecutionContextProcess(CallStackProfileParams::Process process) { + switch (process) { + case CallStackProfileParams::UNKNOWN_PROCESS: + return UNKNOWN_PROCESS; + case CallStackProfileParams::BROWSER_PROCESS: + return BROWSER_PROCESS; + case CallStackProfileParams::RENDERER_PROCESS: + return RENDERER_PROCESS; + case CallStackProfileParams::GPU_PROCESS: + return GPU_PROCESS; + case CallStackProfileParams::UTILITY_PROCESS: + return UTILITY_PROCESS; + case CallStackProfileParams::ZYGOTE_PROCESS: + return ZYGOTE_PROCESS; + case CallStackProfileParams::SANDBOX_HELPER_PROCESS: + return SANDBOX_HELPER_PROCESS; + case CallStackProfileParams::PPAPI_PLUGIN_PROCESS: + return PPAPI_PLUGIN_PROCESS; + case CallStackProfileParams::PPAPI_BROKER_PROCESS: + return PPAPI_BROKER_PROCESS; + } + NOTREACHED(); + return UNKNOWN_PROCESS; +} + +Thread ToExecutionContextThread(CallStackProfileParams::Thread thread) { + switch (thread) { + case CallStackProfileParams::UNKNOWN_THREAD: + return UNKNOWN_THREAD; + case CallStackProfileParams::MAIN_THREAD: + return MAIN_THREAD; + case CallStackProfileParams::IO_THREAD: + return IO_THREAD; + case CallStackProfileParams::COMPOSITOR_THREAD: + return COMPOSITOR_THREAD; + } + NOTREACHED(); + return UNKNOWN_THREAD; +} + +SampledProfile::TriggerEvent ToSampledProfileTriggerEvent( + CallStackProfileParams::Trigger trigger) { + switch (trigger) { + case CallStackProfileParams::UNKNOWN: + return SampledProfile::UNKNOWN_TRIGGER_EVENT; + case CallStackProfileParams::PROCESS_STARTUP: + return SampledProfile::PROCESS_STARTUP; + case CallStackProfileParams::JANKY_TASK: + return SampledProfile::JANKY_TASK; + case CallStackProfileParams::THREAD_HUNG: + return SampledProfile::THREAD_HUNG; + case CallStackProfileParams::PERIODIC_COLLECTION: + return SampledProfile::PERIODIC_COLLECTION; + } + NOTREACHED(); + return SampledProfile::UNKNOWN_TRIGGER_EVENT; +} + +} // namespace metrics diff --git a/components/metrics/call_stack_profile_encoding.h b/components/metrics/call_stack_profile_encoding.h new file mode 100644 index 0000000000000..c71ace7e636fd --- /dev/null +++ b/components/metrics/call_stack_profile_encoding.h @@ -0,0 +1,28 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_ENCODING_H_ +#define COMPONENTS_METRICS_CALL_STACK_PROFILE_ENCODING_H_ + +#include "components/metrics/call_stack_profile_params.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace metrics { + +// Translates CallStackProfileParams's process to the corresponding execution +// context Process. +Process ToExecutionContextProcess(CallStackProfileParams::Process process); + +// Translates CallStackProfileParams's thread to the corresponding +// SampledProfile Thread. +Thread ToExecutionContextThread(CallStackProfileParams::Thread thread); + +// Translates CallStackProfileParams's trigger to the corresponding +// SampledProfile TriggerEvent. +SampledProfile::TriggerEvent ToSampledProfileTriggerEvent( + CallStackProfileParams::Trigger trigger); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_ENCODING_H_ diff --git a/components/metrics/call_stack_profile_metrics_provider.cc b/components/metrics/call_stack_profile_metrics_provider.cc new file mode 100644 index 0000000000000..63160d1ee1b67 --- /dev/null +++ b/components/metrics/call_stack_profile_metrics_provider.cc @@ -0,0 +1,233 @@ +// 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 +#include + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" + +namespace metrics { + +namespace { + +// Cap the number of pending profiles to avoid excessive memory usage when +// profile uploads are delayed (e.g. due to being offline). 1250 profiles +// corresponds to 80MB of storage. Capping at this threshold loses approximately +// 0.5% of profiles on canary and dev. +// TODO(chengx): Remove this threshold after moving to a more memory-efficient +// profile representation. +const size_t kMaxPendingProfiles = 1250; + +// ProfileState -------------------------------------------------------------- + +// A set of profiles and the start time of the collection associated with them. +struct ProfileState { + ProfileState(base::TimeTicks start_timestamp, SampledProfile profile); + ProfileState(ProfileState&&); + ProfileState& operator=(ProfileState&&); + + // The time at which the profile collection was started. + base::TimeTicks start_timestamp; + + // The call stack profile message collected by the profiler. + SampledProfile profile; + + private: + DISALLOW_COPY_AND_ASSIGN(ProfileState); +}; + +ProfileState::ProfileState(base::TimeTicks start_timestamp, + SampledProfile profile) + : start_timestamp(start_timestamp), profile(std::move(profile)) {} + +ProfileState::ProfileState(ProfileState&&) = default; + +// Some versions of GCC need this for push_back to work with std::move. +ProfileState& ProfileState::operator=(ProfileState&&) = default; + +// PendingProfiles ------------------------------------------------------------ + +// Singleton class responsible for retaining profiles received from +// CallStackProfileBuilder. These are then sent to UMA on the invocation of +// CallStackProfileMetricsProvider::ProvideCurrentSessionData(). 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 Swap(std::vector* 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; it is + // not const& because it must be passed with std::move. + void CollectProfilesIfCollectionEnabled(ProfileState profile); + + // Allows testing against the initial state multiple times. + void ResetToDefaultStateForTesting(); + + private: + friend struct base::DefaultSingletonTraits; + + PendingProfiles(); + ~PendingProfiles() = default; + + 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 last time collection was enabled. Used to determine if collection was + // enabled at any point since a profile was started. + base::TimeTicks last_collection_enable_time_; + + // The set of completed profiles that should be reported. + std::vector profiles_; + + DISALLOW_COPY_AND_ASSIGN(PendingProfiles); +}; + +// static +PendingProfiles* PendingProfiles::GetInstance() { + // Leaky for performance rather than correctness reasons. + return base::Singleton>::get(); +} + +void PendingProfiles::Swap(std::vector* 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(); + } else { + last_collection_enable_time_ = base::TimeTicks::Now(); + } +} + +bool PendingProfiles::IsCollectionEnabled() const { + base::AutoLock scoped_lock(lock_); + return collection_enabled_; +} + +void PendingProfiles::CollectProfilesIfCollectionEnabled(ProfileState profile) { + base::AutoLock scoped_lock(lock_); + + // Scenario 1: stop collection if it is disabled. + if (!collection_enabled_) + return; + + // Scenario 2: stop collection if it is disabled after the start of collection + // for this profile. + if (!last_collection_disable_time_.is_null() && + last_collection_disable_time_ >= profile.start_timestamp) { + return; + } + + // Scenario 3: stop collection if it is disabled before the start of + // collection and re-enabled after the start. Note that this is different from + // scenario 1 where re-enabling never happens. + if (!last_collection_disable_time_.is_null() && + !last_collection_enable_time_.is_null() && + last_collection_enable_time_ >= profile.start_timestamp) { + return; + } + + if (profiles_.size() < kMaxPendingProfiles) + profiles_.push_back(std::move(profile)); +} + +void PendingProfiles::ResetToDefaultStateForTesting() { + base::AutoLock scoped_lock(lock_); + + collection_enabled_ = true; + last_collection_disable_time_ = base::TimeTicks(); + last_collection_enable_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) {} + +} // namespace + +// CallStackProfileMetricsProvider -------------------------------------------- + +const base::Feature CallStackProfileMetricsProvider::kEnableReporting = { + "SamplingProfilerReporting", base::FEATURE_DISABLED_BY_DEFAULT}; + +CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() {} + +CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {} + +// static +void CallStackProfileMetricsProvider::ReceiveCompletedProfile( + base::TimeTicks profile_start_time, + SampledProfile profile) { + PendingProfiles::GetInstance()->CollectProfilesIfCollectionEnabled( + ProfileState(profile_start_time, std::move(profile))); +} + +void CallStackProfileMetricsProvider::OnRecordingEnabled() { + PendingProfiles::GetInstance()->SetCollectionEnabled( + base::FeatureList::IsEnabled(kEnableReporting)); +} + +void CallStackProfileMetricsProvider::OnRecordingDisabled() { + PendingProfiles::GetInstance()->SetCollectionEnabled(false); +} + +void CallStackProfileMetricsProvider::ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) { + std::vector pending_profiles; + PendingProfiles::GetInstance()->Swap(&pending_profiles); + + DCHECK(base::FeatureList::IsEnabled(kEnableReporting) || + pending_profiles.empty()); + + for (const auto& profile_state : pending_profiles) { + SampledProfile* sampled_profile = uma_proto->add_sampled_profile(); + *sampled_profile = std::move(profile_state.profile); + } +} + +// static +void CallStackProfileMetricsProvider::ResetStaticStateForTesting() { + PendingProfiles::GetInstance()->ResetToDefaultStateForTesting(); +} + +} // namespace metrics diff --git a/components/metrics/call_stack_profile_metrics_provider.h b/components/metrics/call_stack_profile_metrics_provider.h new file mode 100644 index 0000000000000..e977040d51602 --- /dev/null +++ b/components/metrics/call_stack_profile_metrics_provider.h @@ -0,0 +1,49 @@ +// 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 "base/feature_list.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "components/metrics/metrics_provider.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace metrics { + +class ChromeUserMetricsExtension; + +// Performs metrics logging for the stack sampling profiler. +class CallStackProfileMetricsProvider : public MetricsProvider { + public: + CallStackProfileMetricsProvider(); + ~CallStackProfileMetricsProvider() override; + + // Will be invoked on either the main thread or the profiler's thread. + // Provides the profile to PendingProfiles to append, if the collecting state + // allows. |profile| is not const& because it must be passed with std::move. + static void ReceiveCompletedProfile(base::TimeTicks profile_start_time, + SampledProfile profile); + + // MetricsProvider: + void OnRecordingEnabled() override; + void OnRecordingDisabled() override; + void ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) override; + + protected: + // base::Feature for reporting profiles. Provided here for test use. + static const base::Feature kEnableReporting; + + // Reset the static state to the defaults after startup. + static void ResetStaticStateForTesting(); + + private: + DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_ diff --git a/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/components/metrics/call_stack_profile_metrics_provider_unittest.cc new file mode 100644 index 0000000000000..b326252727e5d --- /dev/null +++ b/components/metrics/call_stack_profile_metrics_provider_unittest.cc @@ -0,0 +1,122 @@ +// 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 + +#include "base/macros.h" +#include "base/test/scoped_feature_list.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" + +namespace metrics { + +// This test fixture enables the feature that +// CallStackProfileMetricsProvider depends on to report a profile. +class CallStackProfileMetricsProviderTest : public testing::Test { + public: + CallStackProfileMetricsProviderTest() { + scoped_feature_list_.InitAndEnableFeature(TestState::kEnableReporting); + TestState::ResetStaticStateForTesting(); + } + + ~CallStackProfileMetricsProviderTest() override {} + + private: + // Exposes the feature from the CallStackProfileMetricsProvider. + class TestState : public CallStackProfileMetricsProvider { + public: + using CallStackProfileMetricsProvider::kEnableReporting; + using CallStackProfileMetricsProvider::ResetStaticStateForTesting; + }; + + base::test::ScopedFeatureList scoped_feature_list_; + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProviderTest); +}; + +// Checks that the pending profile is passed to ProvideCurrentSessionData. +TEST_F(CallStackProfileMetricsProviderTest, ProvideCurrentSessionData) { + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + CallStackProfileMetricsProvider::ReceiveCompletedProfile( + base::TimeTicks::Now(), SampledProfile()); + ChromeUserMetricsExtension uma_proto; + provider.ProvideCurrentSessionData(&uma_proto); + ASSERT_EQ(1, uma_proto.sampled_profile().size()); +} + +// Checks that the pending profile is provided to ProvideCurrentSessionData +// when collected before CallStackProfileMetricsProvider is instantiated. +TEST_F(CallStackProfileMetricsProviderTest, + ProfileProvidedWhenCollectedBeforeInstantiation) { + CallStackProfileMetricsProvider::ReceiveCompletedProfile( + base::TimeTicks::Now(), SampledProfile()); + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + ChromeUserMetricsExtension uma_proto; + provider.ProvideCurrentSessionData(&uma_proto); + EXPECT_EQ(1, uma_proto.sampled_profile_size()); +} + +// Checks that the pending profile is not provided to ProvideCurrentSessionData +// while recording is disabled. +TEST_F(CallStackProfileMetricsProviderTest, ProfileNotProvidedWhileDisabled) { + CallStackProfileMetricsProvider provider; + provider.OnRecordingDisabled(); + CallStackProfileMetricsProvider::ReceiveCompletedProfile( + base::TimeTicks::Now(), SampledProfile()); + ChromeUserMetricsExtension uma_proto; + provider.ProvideCurrentSessionData(&uma_proto); + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +// Checks that the pending profile is not provided to ProvideCurrentSessionData +// if recording is disabled while profiling. +TEST_F(CallStackProfileMetricsProviderTest, + ProfileNotProvidedAfterChangeToDisabled) { + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + base::TimeTicks profile_start_time = base::TimeTicks::Now(); + provider.OnRecordingDisabled(); + CallStackProfileMetricsProvider::ReceiveCompletedProfile(profile_start_time, + SampledProfile()); + ChromeUserMetricsExtension uma_proto; + provider.ProvideCurrentSessionData(&uma_proto); + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +// Checks that the pending profile is not provided to ProvideCurrentSessionData +// if recording is enabled, but then disabled and reenabled while profiling. +TEST_F(CallStackProfileMetricsProviderTest, + ProfileNotProvidedAfterChangeToDisabledThenEnabled) { + CallStackProfileMetricsProvider provider; + provider.OnRecordingEnabled(); + base::TimeTicks profile_start_time = base::TimeTicks::Now(); + provider.OnRecordingDisabled(); + provider.OnRecordingEnabled(); + CallStackProfileMetricsProvider::ReceiveCompletedProfile(profile_start_time, + SampledProfile()); + ChromeUserMetricsExtension uma_proto; + provider.ProvideCurrentSessionData(&uma_proto); + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +// Checks that the pending profile is provided to ProvideCurrentSessionData +// if recording is disabled, but then enabled while profiling. +TEST_F(CallStackProfileMetricsProviderTest, + ProfileNotProvidedAfterChangeFromDisabled) { + CallStackProfileMetricsProvider provider; + provider.OnRecordingDisabled(); + base::TimeTicks profile_start_time = base::TimeTicks::Now(); + provider.OnRecordingEnabled(); + CallStackProfileMetricsProvider::ReceiveCompletedProfile(profile_start_time, + SampledProfile()); + ChromeUserMetricsExtension uma_proto; + provider.ProvideCurrentSessionData(&uma_proto); + EXPECT_EQ(0, uma_proto.sampled_profile_size()); +} + +} // namespace metrics diff --git a/components/metrics/call_stack_profile_params.h b/components/metrics/call_stack_profile_params.h new file mode 100644 index 0000000000000..5e78ef1dba6bb --- /dev/null +++ b/components/metrics/call_stack_profile_params.h @@ -0,0 +1,71 @@ +// 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_CALL_STACK_PROFILE_PARAMS_H_ +#define COMPONENTS_METRICS_CALL_STACK_PROFILE_PARAMS_H_ + +#include "base/time/time.h" + +namespace metrics { + +// Parameters to pass back to the metrics provider. +struct CallStackProfileParams { + // The process in which the collection occurred. + enum Process { + UNKNOWN_PROCESS, + BROWSER_PROCESS, + RENDERER_PROCESS, + GPU_PROCESS, + UTILITY_PROCESS, + ZYGOTE_PROCESS, + SANDBOX_HELPER_PROCESS, + PPAPI_PLUGIN_PROCESS, + PPAPI_BROKER_PROCESS + }; + + // The thread from which the collection occurred. + enum Thread { + UNKNOWN_THREAD, + + // Each process has a 'main thread'. In the Browser process, the 'main + // thread' is also often called the 'UI thread'. + MAIN_THREAD, + IO_THREAD, + + // Compositor thread (can be in both renderer and gpu processes). + COMPOSITOR_THREAD, + }; + + // The event that triggered the profile collection. + enum Trigger { + UNKNOWN, + PROCESS_STARTUP, + JANKY_TASK, + THREAD_HUNG, + PERIODIC_COLLECTION, + TRIGGER_LAST = PERIODIC_COLLECTION + }; + + // The default constructor is required for mojo and should not be used + // otherwise. A valid trigger should always be specified. + constexpr CallStackProfileParams() + : CallStackProfileParams(UNKNOWN_PROCESS, UNKNOWN_THREAD, UNKNOWN) {} + constexpr CallStackProfileParams(Process process, + Thread thread, + Trigger trigger) + : process(process), thread(thread), trigger(trigger) {} + + // The collection process. + Process process; + + // The collection thread. + Thread thread; + + // The triggering event. + Trigger trigger; +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_PARAMS_H_ diff --git a/components/metrics/child_call_stack_profile_collector.cc b/components/metrics/child_call_stack_profile_collector.cc new file mode 100644 index 0000000000000..0ae6d477f4a8a --- /dev/null +++ b/components/metrics/child_call_stack_profile_collector.cc @@ -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. + +#include "components/metrics/child_call_stack_profile_collector.h" + +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" + +namespace metrics { + +ChildCallStackProfileCollector::ProfileState::ProfileState() = default; +ChildCallStackProfileCollector::ProfileState::ProfileState(ProfileState&&) = + default; + +ChildCallStackProfileCollector::ProfileState::ProfileState( + base::TimeTicks start_timestamp, + SampledProfile profile) + : start_timestamp(start_timestamp), profile(std::move(profile)) {} + +ChildCallStackProfileCollector::ProfileState::~ProfileState() = default; + +// Some versions of GCC need this for push_back to work with std::move. +ChildCallStackProfileCollector::ProfileState& +ChildCallStackProfileCollector::ProfileState::operator=(ProfileState&&) = + default; + +ChildCallStackProfileCollector::ChildCallStackProfileCollector() {} + +ChildCallStackProfileCollector::~ChildCallStackProfileCollector() {} + +void ChildCallStackProfileCollector::SetParentProfileCollector( + metrics::mojom::CallStackProfileCollectorPtr parent_collector) { + base::AutoLock alock(lock_); + // This function should only invoked once, during the mode of operation when + // retaining profiles after construction. + DCHECK(retain_profiles_); + retain_profiles_ = false; + task_runner_ = base::ThreadTaskRunnerHandle::Get(); + // This should only be set one time per child process. + DCHECK(!parent_collector_); + parent_collector_ = std::move(parent_collector); + if (parent_collector_) { + for (ProfileState& state : profiles_) { + parent_collector_->Collect(state.start_timestamp, + std::move(state.profile)); + } + } + profiles_.clear(); +} + +void ChildCallStackProfileCollector::Collect(base::TimeTicks start_timestamp, + SampledProfile profile) { + base::AutoLock alock(lock_); + if (task_runner_ && + // The profiler thread does not have a task runner. Attempting to + // invoke Get() on it results in a DCHECK. + (!base::ThreadTaskRunnerHandle::IsSet() || + base::ThreadTaskRunnerHandle::Get() != task_runner_)) { + // Post back to the thread that owns the the parent interface. + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&ChildCallStackProfileCollector::Collect, + // This class has lazy instance lifetime. + base::Unretained(this), start_timestamp, + std::move(profile))); + return; + } + + if (parent_collector_) { + parent_collector_->Collect(start_timestamp, std::move(profile)); + } else if (retain_profiles_) { + profiles_.push_back(ProfileState(start_timestamp, std::move(profile))); + } +} + +} // namespace metrics diff --git a/components/metrics/child_call_stack_profile_collector.h b/components/metrics/child_call_stack_profile_collector.h new file mode 100644 index 0000000000000..27815d8d42f99 --- /dev/null +++ b/components/metrics/child_call_stack_profile_collector.h @@ -0,0 +1,115 @@ +// 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_CHILD_CALL_STACK_PROFILE_COLLECTOR_H_ +#define COMPONENTS_METRICS_CHILD_CALL_STACK_PROFILE_COLLECTOR_H_ + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace service_manager { +class InterfaceProvider; +} + +namespace metrics { + +// ChildCallStackProfileCollector collects stacks at startup, caching them +// internally until a CallStackProfileCollector interface is available. If a +// CallStackProfileCollector is provided via the InterfaceProvider supplied to +// SetParentProfileCollector, the cached stacks are sent via that interface. All +// future stacks received via callbacks supplied by GetProfilerCallback are sent +// via that interface as well. +// +// If no CallStackProfileCollector is provided via InterfaceProvider, any cached +// stacks and all future stacks received via callbacks supplied by +// GetProfilerCallback are flushed. In typical usage this should not happen +// because the browser is expected to always supply a CallStackProfileCollector. +// +// This class is only necessary if a CallStackProfileCollector is not available +// at the time the profiler is created. Otherwise the CallStackProfileCollector +// can be used directly. +// +// To use, create as a leaky lazy instance: +// +// base::LazyInstance::Leaky +// g_call_stack_profile_collector = LAZY_INSTANCE_INITIALIZER; +// +// Then, invoke Collect() in CallStackProfileBuilder::OnProfileCompleted() to +// collect a profile. +// +// When the mojo InterfaceProvider becomes available, provide it via +// SetParentProfileCollector(). +class ChildCallStackProfileCollector { + public: + ChildCallStackProfileCollector(); + ~ChildCallStackProfileCollector(); + + // Sets the CallStackProfileCollector interface from |parent_collector|. This + // function MUST be invoked exactly once, regardless of whether + // |parent_collector| is null, as it flushes pending data in either case. + void SetParentProfileCollector( + metrics::mojom::CallStackProfileCollectorPtr parent_collector); + + // Collects |profile| whose collection start time is |start_timestamp|. + void Collect(base::TimeTicks start_timestamp, SampledProfile profile); + + private: + friend class ChildCallStackProfileCollectorTest; + + // Bundles together a collected profile and the collection state for + // storage, pending availability of the parent mojo interface. |profile| + // is not const& because it must be passed with std::move. + struct ProfileState { + ProfileState(); + ProfileState(ProfileState&&); + ProfileState(base::TimeTicks start_timestamp, SampledProfile profile); + ~ProfileState(); + + ProfileState& operator=(ProfileState&&); + + base::TimeTicks start_timestamp; + + // The sampled profile. + SampledProfile profile; + + private: + DISALLOW_COPY_AND_ASSIGN(ProfileState); + }; + + // This object may be accessed on any thread, including the profiler + // thread. The expected use case for the object is to be created and have + // GetProfilerCallback before the message loop starts, which prevents the use + // of PostTask and the like for inter-thread communication. + base::Lock lock_; + + // Whether to retain the profile when the interface is not set. Remains true + // until the invocation of SetParentProfileCollector(), at which point it is + // false for the rest of the object lifetime. + bool retain_profiles_ = true; + + // The task runner associated with the parent interface. + scoped_refptr task_runner_; + + // The interface to use to collect the stack profiles provided to this + // object. Initially null until SetParentProfileCollector() is invoked, at + // which point it may either become set or remain null. If set, stacks are + // collected via the interface, otherwise they are ignored. + mojom::CallStackProfileCollectorPtr parent_collector_; + + // Profiles being cached by this object, pending a parent interface to be + // supplied. + std::vector profiles_; + + DISALLOW_COPY_AND_ASSIGN(ChildCallStackProfileCollector); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CHILD_CALL_STACK_PROFILE_COLLECTOR_H_ diff --git a/components/metrics/child_call_stack_profile_collector_unittest.cc b/components/metrics/child_call_stack_profile_collector_unittest.cc new file mode 100644 index 0000000000000..24cc30ea0cae5 --- /dev/null +++ b/components/metrics/child_call_stack_profile_collector_unittest.cc @@ -0,0 +1,112 @@ +// 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/child_call_stack_profile_collector.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +class ChildCallStackProfileCollectorTest : public testing::Test { + protected: + class Receiver : public mojom::CallStackProfileCollector { + public: + explicit Receiver(mojom::CallStackProfileCollectorRequest request) + : binding_(this, std::move(request)) {} + ~Receiver() override {} + + void Collect(base::TimeTicks start_timestamp, + SampledProfile profile) override { + this->profiles.push_back(ChildCallStackProfileCollector::ProfileState( + start_timestamp, std::move(profile))); + } + + std::vector profiles; + + private: + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(Receiver); + }; + + ChildCallStackProfileCollectorTest() + : receiver_impl_(new Receiver(MakeRequest(&receiver_))) {} + + void CollectEmptyProfile() { + child_collector_.Collect(base::TimeTicks::Now(), SampledProfile()); + } + + const std::vector& profiles() + const { + return child_collector_.profiles_; + } + + base::MessageLoop loop_; + mojom::CallStackProfileCollectorPtr receiver_; + std::unique_ptr receiver_impl_; + ChildCallStackProfileCollector child_collector_; + + private: + DISALLOW_COPY_AND_ASSIGN(ChildCallStackProfileCollectorTest); +}; + +// Test the behavior when an interface is provided. +TEST_F(ChildCallStackProfileCollectorTest, InterfaceProvided) { + EXPECT_EQ(0u, profiles().size()); + + // Add a profile before providing the interface. + CollectEmptyProfile(); + ASSERT_EQ(1u, profiles().size()); + base::TimeTicks start_timestamp = profiles()[0].start_timestamp; + EXPECT_GE(base::TimeDelta::FromMilliseconds(10), + base::TimeTicks::Now() - start_timestamp); + + // Set the interface. The profiles should be passed to it. + child_collector_.SetParentProfileCollector(std::move(receiver_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, profiles().size()); + ASSERT_EQ(1u, receiver_impl_->profiles.size()); + EXPECT_EQ(start_timestamp, receiver_impl_->profiles[0].start_timestamp); + + // Add a profile after providing the interface. It should also be passed. + receiver_impl_->profiles.clear(); + CollectEmptyProfile(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, profiles().size()); + ASSERT_EQ(1u, receiver_impl_->profiles.size()); + EXPECT_GE(base::TimeDelta::FromMilliseconds(10), + (base::TimeTicks::Now() - + receiver_impl_->profiles[0].start_timestamp)); +} + +TEST_F(ChildCallStackProfileCollectorTest, InterfaceNotProvided) { + EXPECT_EQ(0u, profiles().size()); + + // Add a profile before providing a null interface. + CollectEmptyProfile(); + ASSERT_EQ(1u, profiles().size()); + EXPECT_GE(base::TimeDelta::FromMilliseconds(10), + base::TimeTicks::Now() - profiles()[0].start_timestamp); + + // Set the null interface. The profile should be flushed. + child_collector_.SetParentProfileCollector( + mojom::CallStackProfileCollectorPtr()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, profiles().size()); + + // Add a profile after providing a null interface. They should also be + // flushed. + CollectEmptyProfile(); + EXPECT_EQ(0u, profiles().size()); +} + +} // namespace metrics diff --git a/components/metrics/clean_exit_beacon.cc b/components/metrics/clean_exit_beacon.cc new file mode 100644 index 0000000000000..74aefd897ba36 --- /dev/null +++ b/components/metrics/clean_exit_beacon.cc @@ -0,0 +1,98 @@ +// 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_registry_simple.h" +#include "components/prefs/pref_service.h" + +#if defined(OS_WIN) +#include +#include "base/metrics/histogram_macros.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)), + initial_browser_last_live_timestamp_( + local_state->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp)), + 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() { +} + +// static +void CleanExitBeacon::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, true); + + registry->RegisterTimePref(prefs::kStabilityBrowserLastLiveTimeStamp, + base::Time(), PrefRegistry::LOSSY_PREF); +} + +void CleanExitBeacon::WriteBeaconValue(bool value) { + UpdateLastLiveTimestamp(); + 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 +} + +void CleanExitBeacon::UpdateLastLiveTimestamp() { + local_state_->SetTime(prefs::kStabilityBrowserLastLiveTimeStamp, + base::Time::Now()); +} + +} // namespace metrics diff --git a/components/metrics/clean_exit_beacon.h b/components/metrics/clean_exit_beacon.h new file mode 100644 index 0000000000000..004359675872b --- /dev/null +++ b/components/metrics/clean_exit_beacon.h @@ -0,0 +1,63 @@ +// 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" +#include "base/time/time.h" + +class PrefRegistrySimple; +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_; } + + // Returns the original value of the last live timestamp. + base::Time browser_last_live_timestamp() const { + return initial_browser_last_live_timestamp_; + } + + // Writes the provided beacon value and updates the last live timestamp. + void WriteBeaconValue(bool exited_cleanly); + + // Updates the last live timestamp. + void UpdateLastLiveTimestamp(); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + PrefService* const local_state_; + const bool initial_value_; + + // This is the value of the last live timestamp from local state at the + // time of construction. It notes a timestamp from the previous browser + // session when the browser was known to be alive. + const base::Time initial_browser_last_live_timestamp_; + const base::string16 backup_registry_key_; + + DISALLOW_COPY_AND_ASSIGN(CleanExitBeacon); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_ diff --git a/components/metrics/client_info.cc b/components/metrics/client_info.cc new file mode 100644 index 0000000000000..57ad3947c1bd6 --- /dev/null +++ b/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/components/metrics/client_info.h b/components/metrics/client_info.h new file mode 100644 index 0000000000000..7dcf5d8de396e --- /dev/null +++ b/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 + +#include + +#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/components/metrics/cloned_install_detector.cc b/components/metrics/cloned_install_detector.cc new file mode 100644 index 0000000000000..38397447779a1 --- /dev/null +++ b/components/metrics/cloned_install_detector.cc @@ -0,0 +1,98 @@ +// 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 + +#include + +#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/post_task.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() : weak_ptr_factory_(this) {} + +ClonedInstallDetector::~ClonedInstallDetector() { +} + +void ClonedInstallDetector::CheckForClonedInstall(PrefService* local_state) { + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, + base::Bind(&MachineIdProvider::GetMachineId), + 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/components/metrics/cloned_install_detector.h b/components/metrics/cloned_install_detector.h new file mode 100644 index 0000000000000..6a728ae8a809c --- /dev/null +++ b/components/metrics/cloned_install_detector.h @@ -0,0 +1,50 @@ +// 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/weak_ptr.h" + +class PrefRegistrySimple; +class PrefService; + +namespace metrics { + +// A class for detecting if an install is cloned. It does this by detecting +// when the hardware running Chrome changes. +class ClonedInstallDetector { + public: + ClonedInstallDetector(); + 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); + + 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); + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ClonedInstallDetector); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_ diff --git a/components/metrics/cloned_install_detector_unittest.cc b/components/metrics/cloned_install_detector_unittest.cc new file mode 100644 index 0000000000000..2e2ebd74176fe --- /dev/null +++ b/components/metrics/cloned_install_detector_unittest.cc @@ -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. + +#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()); + + ClonedInstallDetector detector; + 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); + + ClonedInstallDetector detector; + detector.SaveMachineId(&prefs, kTestRawId); + + EXPECT_TRUE(prefs.GetBoolean(prefs::kMetricsResetIds)); +} + +} // namespace metrics diff --git a/components/metrics/component_metrics_provider.cc b/components/metrics/component_metrics_provider.cc new file mode 100644 index 0000000000000..5ec24e2bf73c0 --- /dev/null +++ b/components/metrics/component_metrics_provider.cc @@ -0,0 +1,109 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/component_metrics_provider.h" + +#include +#include +#include "base/strings/string_number_conversions.h" +#include "components/component_updater/component_updater_service.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace metrics { + +namespace { + +SystemProfileProto_ComponentId CrxIdToComponentId(const std::string& app_id) { + const static std::map + component_map = { + {"khaoiebndkojlmppeemjhbpbandiljpe", + SystemProfileProto_ComponentId_FILE_TYPE_POLICIES}, + {"kfoklmclfodeliojeaekpoflbkkhojea", + SystemProfileProto_ComponentId_ORIGIN_TRIALS}, + {"llkgjffcdpffmhiakmfcdcblohccpfmo", + SystemProfileProto_ComponentId_ORIGIN_TRIALS}, // Alternate ID + {"mimojjlkmoijpicakmndhoigimigcmbb", + SystemProfileProto_ComponentId_PEPPER_FLASH}, + {"ckjlcfmdbdglblbjglepgnoekdnkoklc", + SystemProfileProto_ComponentId_PEPPER_FLASH_CHROMEOS}, + {"hnimpnehoodheedghdeeijklkeaacbdc", + SystemProfileProto_ComponentId_PNACL}, + {"npdjjkjlcidkjlamlmmdelcjbcpdjocm", + SystemProfileProto_ComponentId_RECOVERY}, + {"giekcmmlnklenlaomppkphknjmnnpneh", + SystemProfileProto_ComponentId_SSL_ERROR_ASSISTANT}, + {"ojjgnpkioondelmggbekfhllhdaimnho", + SystemProfileProto_ComponentId_STH_SET}, + {"hfnkpimlhhgieaddgfemjhofmfblmnib", + SystemProfileProto_ComponentId_CRL_SET}, + {"gcmjkmgdlgnkkcocmoeiminaijmmjnii", + SystemProfileProto_ComponentId_SUBRESOURCE_FILTER}, + {"gkmgaooipdjhmangpemjhigmamcehddo", + SystemProfileProto_ComponentId_SW_REPORTER}, + {"oimompecagnajdejgnnjijobebaeigek", + SystemProfileProto_ComponentId_WIDEVINE_CDM}, + {"bjbdkfoakgmkndalgpadobhgbhhoanho", + SystemProfileProto_ComponentId_EPSON_INKJET_PRINTER_ESCPR}, + {"ojnjgapiepgciobpecnafnoeaegllfld", + SystemProfileProto_ComponentId_CROS_TERMINA}, + {"gncenodapghbnkfkoognegdnjoeegmkp", + SystemProfileProto_ComponentId_STAR_CUPS_DRIVER}, + {"gelhpeofhffbaeegmemklllhfdifagmb", + SystemProfileProto_ComponentId_SPEECH_SYNTHESIS_SV_SE}, + {"lmelglejhemejginpboagddgdfbepgmp", + SystemProfileProto_ComponentId_OPTIMIZATION_HINTS}, + {"fookoiellkocclipolgaceabajejjcnp", + SystemProfileProto_ComponentId_DOWNLOADABLE_STRINGS}, + {"cjfkbpdpjpdldhclahpfgnlhpodlpnba", + SystemProfileProto_ComponentId_VR_ASSETS}, + {"gjpajnddmedjmcklfflllocelehklffm", + SystemProfileProto_ComponentId_RTANALYTICS_LIGHT}, + {"mjdmdobabdmfcbaakcaadileafkmifen", + SystemProfileProto_ComponentId_RTANALYTICS_FULL}, + {"fhbeibbmaepakgdkkmjgldjajgpkkhfj", + SystemProfileProto_ComponentId_CELLULAR}}; + const auto result = component_map.find(app_id); + if (result == component_map.end()) + return SystemProfileProto_ComponentId_UNKNOWN; + return result->second; +} + +// Extract the first 32 bits of a fingerprint string, excluding the fingerprint +// format specifier - see the fingerprint format specification at +// https://github.com/google/omaha/blob/master/doc/ServerProtocolV3.md +uint32_t Trim(const std::string& fp) { + const auto len_prefix = fp.find("."); + if (len_prefix == std::string::npos) + return 0; + uint32_t result = 0; + if (base::HexStringToUInt(fp.substr(len_prefix + 1, 8), &result)) + return result; + return 0; +} + +} // namespace + +ComponentMetricsProvider::ComponentMetricsProvider( + component_updater::ComponentUpdateService* component_update_service) + : component_update_service_(component_update_service) {} + +ComponentMetricsProvider::~ComponentMetricsProvider() = default; + +void ComponentMetricsProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile) { + for (const auto& component : component_update_service_->GetComponents()) { + const auto id = CrxIdToComponentId(component.id); + // Ignore any unknown components - in practice these are the + // SupervisedUserWhitelists, which we do not want to transmit to UMA or + // Crash. + if (id == SystemProfileProto_ComponentId_UNKNOWN) + continue; + auto* proto = system_profile->add_chrome_component(); + proto->set_component_id(id); + proto->set_version(component.version.GetString()); + proto->set_omaha_fingerprint(Trim(component.fingerprint)); + } +} + +} // namespace metrics diff --git a/components/metrics/component_metrics_provider.h b/components/metrics/component_metrics_provider.h new file mode 100644 index 0000000000000..7899b97150c81 --- /dev/null +++ b/components/metrics/component_metrics_provider.h @@ -0,0 +1,37 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_COMPONENT_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_COMPONENT_METRICS_PROVIDER_H_ + +#include "components/metrics/metrics_provider.h" + +namespace component_updater { +class ComponentUpdateService; +} + +namespace metrics { + +class SystemProfileProto; + +// Stores and loads system information to prefs for stability logs. +class ComponentMetricsProvider : public MetricsProvider { + public: + explicit ComponentMetricsProvider( + component_updater::ComponentUpdateService* component_update_service); + ~ComponentMetricsProvider() override; + + // MetricsProvider: + void ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) override; + + private: + component_updater::ComponentUpdateService* component_update_service_; + + DISALLOW_COPY_AND_ASSIGN(ComponentMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_COMPONENT_METRICS_PROVIDER_H_ diff --git a/components/metrics/component_metrics_provider_unittest.cc b/components/metrics/component_metrics_provider_unittest.cc new file mode 100644 index 0000000000000..2fc9cae17e910 --- /dev/null +++ b/components/metrics/component_metrics_provider_unittest.cc @@ -0,0 +1,58 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/component_metrics_provider.h" + +#include "base/strings/utf_string_conversions.h" +#include "base/version.h" +#include "components/component_updater/mock_component_updater_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace metrics { + +class ComponentMetricsProviderTest : public testing::Test { + public: + ComponentMetricsProviderTest() {} + ~ComponentMetricsProviderTest() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(ComponentMetricsProviderTest); +}; + +using component_updater::ComponentInfo; + +TEST_F(ComponentMetricsProviderTest, ProvideComponentMetrics) { + std::vector components = { + ComponentInfo( + "hfnkpimlhhgieaddgfemjhofmfblmnib", + "1.0846414bf2025bbc067b6fa5b61b16eda2269d8712b8fec0973b4c71fdc65ca0", + base::ASCIIToUTF16("name1"), base::Version("1.2.3.4")), + ComponentInfo( + "oimompecagnajdejgnnjijobebaeigek", + "1.adc9207a4a88ee98bf9ddf0330f35818386f1adc006bc8eee94dc59d43c0f5d6", + base::ASCIIToUTF16("name2"), base::Version("5.6.7.8")), + ComponentInfo( + "thiscomponentfilteredfromresults", + "1.b5268dc93e08d68d0be26bd8fbbb15c7b7f805cc06b4abd9d49381bc178e78cf", + base::ASCIIToUTF16("name3"), base::Version("9.9.9.9"))}; + component_updater::MockComponentUpdateService service; + EXPECT_CALL(service, GetComponents()).WillOnce(testing::Return(components)); + ComponentMetricsProvider component_provider(&service); + SystemProfileProto system_profile; + component_provider.ProvideSystemProfileMetrics(&system_profile); + + EXPECT_EQ(2, system_profile.chrome_component_size()); + EXPECT_EQ(SystemProfileProto_ComponentId_CRL_SET, + system_profile.chrome_component(0).component_id()); + EXPECT_EQ("1.2.3.4", system_profile.chrome_component(0).version()); + EXPECT_EQ(138821963u, system_profile.chrome_component(0).omaha_fingerprint()); + EXPECT_EQ(SystemProfileProto_ComponentId_WIDEVINE_CDM, + system_profile.chrome_component(1).component_id()); + EXPECT_EQ("5.6.7.8", system_profile.chrome_component(1).version()); + EXPECT_EQ(2915639418u, + system_profile.chrome_component(1).omaha_fingerprint()); +} + +} // namespace metrics diff --git a/components/metrics/daily_event.cc b/components/metrics/daily_event.cc new file mode 100644 index 0000000000000..befc2eb9b3de3 --- /dev/null +++ b/components/metrics/daily_event.cc @@ -0,0 +1,96 @@ +// 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 + +#include "base/metrics/histogram.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace metrics { + +namespace { + +void RecordIntervalTypeHistogram(const std::string& histogram_name, + DailyEvent::IntervalType type) { + const int num_types = static_cast(DailyEvent::IntervalType::NUM_TYPES); + base::Histogram::FactoryGet(histogram_name, 1, num_types, num_types + 1, + base::HistogramBase::kUmaTargetedHistogramFlag) + ->Add(static_cast(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, 0); +} + +void DailyEvent::AddObserver(std::unique_ptr 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() + base::TimeDelta::FromMicroseconds( + 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_, IntervalType::FIRST_RUN); + OnInterval(now, IntervalType::FIRST_RUN); + return; + } + } + int days_elapsed = (now - last_fired_).InDays(); + if (days_elapsed >= 1) { + DVLOG(1) << "DailyEvent day elapsed."; + RecordIntervalTypeHistogram(histogram_name_, IntervalType::DAY_ELAPSED); + OnInterval(now, IntervalType::DAY_ELAPSED); + } 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_, IntervalType::CLOCK_CHANGED); + OnInterval(now, IntervalType::CLOCK_CHANGED); + } +} + +void DailyEvent::OnInterval(base::Time now, IntervalType type) { + DCHECK(!now.is_null()); + last_fired_ = now; + pref_service_->SetInt64(pref_name_, + last_fired_.since_origin().InMicroseconds()); + + for (auto it = observers_.begin(); it != observers_.end(); ++it) { + (*it)->OnDailyEvent(type); + } +} + +} // namespace metrics diff --git a/components/metrics/daily_event.h b/components/metrics/daily_event.h new file mode 100644 index 0000000000000..326cad4909944 --- /dev/null +++ b/components/metrics/daily_event.h @@ -0,0 +1,102 @@ +// 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 +#include + +#include "base/macros.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: + // Different reasons that Observer::OnDailyEvent() is called. + // This enum is used for histograms and must not be renumbered. + enum class IntervalType { + FIRST_RUN, + DAY_ELAPSED, + CLOCK_CHANGED, + NUM_TYPES, + }; + + // 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(IntervalType type) = 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(std::unique_ptr 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 because of |type|. + void OnInterval(base::Time now, IntervalType type); + + // 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. + std::vector> 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/components/metrics/daily_event_unittest.cc b/components/metrics/daily_event_unittest.cc new file mode 100644 index 0000000000000..f9a6f52c7f0bf --- /dev/null +++ b/components/metrics/daily_event_unittest.cc @@ -0,0 +1,98 @@ +// 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 "base/memory/ptr_util.h" +#include "base/optional.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() = default; + + bool fired() const { return type_.has_value(); } + DailyEvent::IntervalType type() const { return type_.value(); } + + void OnDailyEvent(DailyEvent::IntervalType type) override { type_ = type; } + + void Reset() { type_ = {}; } + + private: + // Last-received type, or unset if OnDailyEvent() hasn't been called. + base::Optional type_; + + 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(base::WrapUnique(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(); + ASSERT_TRUE(observer_->fired()); + EXPECT_EQ(DailyEvent::IntervalType::FIRST_RUN, observer_->type()); +} + +// 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.since_origin().InMicroseconds()); + event_.CheckInterval(); + ASSERT_TRUE(observer_->fired()); + EXPECT_EQ(DailyEvent::IntervalType::DAY_ELAPSED, observer_->type()); +} + +// 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.since_origin().InMicroseconds()); + event_.CheckInterval(); + ASSERT_TRUE(observer_->fired()); + EXPECT_EQ(DailyEvent::IntervalType::CLOCK_CHANGED, observer_->type()); +} + +// 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.since_origin().InMicroseconds()); + 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.since_origin().InMicroseconds()); + event_.CheckInterval(); + EXPECT_FALSE(observer_->fired()); +} + +} // namespace metrics diff --git a/components/metrics/data_use_tracker.cc b/components/metrics/data_use_tracker.cc new file mode 100644 index 0000000000000..61be02f5b9585 --- /dev/null +++ b/components/metrics/data_use_tracker.cc @@ -0,0 +1,185 @@ +// 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 + +#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 { + +// Default weekly quota and allowed UMA ratio for UMA log uploads for Android. +// These defaults will not be used for non-Android as |DataUseTracker| will not +// be initialized. Default values can be overridden by variation params. +const int kDefaultUMAWeeklyQuotaBytes = 204800; +const double kDefaultUMARatio = 0.05; + +} // namespace + +DataUseTracker::DataUseTracker(PrefService* local_state) + : local_state_(local_state) {} + +DataUseTracker::~DataUseTracker() {} + +// static +std::unique_ptr DataUseTracker::Create( + PrefService* local_state) { + std::unique_ptr data_use_tracker; +#if defined(OS_ANDROID) + data_use_tracker.reset(new DataUseTracker(local_state)); +#endif + return data_use_tracker; +} + +// static +void DataUseTracker::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse); + registry->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse); +} + +void DataUseTracker::UpdateMetricsUsagePrefs(const std::string& service_name, + int message_size, + bool is_cellular) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!is_cellular) + return; + + UpdateUsagePref(prefs::kUserCellDataUse, message_size); + // TODO(holte): Consider adding seperate tracking for UKM. + if (service_name == "UMA" || service_name == "UKM") + UpdateUsagePref(prefs::kUmaCellDataUse, message_size); +} + +bool DataUseTracker::ShouldUploadLogOnCellular(int log_bytes) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + 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(log_bytes + user_total_data_use) <= + uma_ratio; +} + +void DataUseTracker::UpdateUsagePref(const std::string& pref_name, + int message_size) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + 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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + RemoveExpiredEntriesForPref(prefs::kUmaCellDataUse); + RemoveExpiredEntriesForPref(prefs::kUserCellDataUse); +} + +void DataUseTracker::RemoveExpiredEntriesForPref(const std::string& pref_name) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + 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; + if (base::Time::FromUTCString(it.key().c_str(), &key_date) && + 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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + 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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + std::string param_value_str = variations::GetVariationParamValue( + "UMA_EnableCellularLogUpload", "Uma_Quota"); + if (param_value_str.empty()) + *uma_weekly_quota_bytes = kDefaultUMAWeeklyQuotaBytes; + else + base::StringToInt(param_value_str, uma_weekly_quota_bytes); + return true; +} + +bool DataUseTracker::GetUmaRatio(double* ratio) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + std::string param_value_str = variations::GetVariationParamValue( + "UMA_EnableCellularLogUpload", "Uma_Ratio"); + if (param_value_str.empty()) + *ratio = kDefaultUMARatio; + else + base::StringToDouble(param_value_str, ratio); + return true; +} + +base::Time DataUseTracker::GetCurrentMeasurementDate() const { + return base::Time::Now().LocalMidnight(); +} + +std::string DataUseTracker::GetCurrentMeasurementDateAsString() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + 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/components/metrics/data_use_tracker.h b/components/metrics/data_use_tracker.h new file mode 100644 index 0000000000000..104223e24efbd --- /dev/null +++ b/components/metrics/data_use_tracker.h @@ -0,0 +1,88 @@ +// 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 + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/sequence_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 + 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); + virtual ~DataUseTracker(); + + // Returns an instance of |DataUseTracker| with provided |local_state| if + // users data use should be tracked and null pointer otherwise. + static std::unique_ptr Create(PrefService* local_state); + + // Registers data use prefs using provided |registry|. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // Updates data usage tracking prefs with the specified values. + void UpdateMetricsUsagePrefs(const std::string& service_name, + int message_size, + bool is_cellular); + + // 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 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_; + + SEQUENCE_CHECKER(sequence_checker_); + + DISALLOW_COPY_AND_ASSIGN(DataUseTracker); +}; + +} // namespace metrics +#endif // COMPONENTS_METRICS_DATA_USE_TRACKER_H_ diff --git a/components/metrics/data_use_tracker_unittest.cc b/components/metrics/data_use_tracker_unittest.cc new file mode 100644 index 0000000000000..a271b5d4d250a --- /dev/null +++ b/components/metrics/data_use_tracker_unittest.cc @@ -0,0 +1,203 @@ +// 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() { DataUseTracker::RegisterPrefs(registry()); } + + 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; + EXPECT_TRUE(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.UpdateMetricsUsagePrefs("", 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.UpdateMetricsUsagePrefs("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/components/metrics/delegating_provider.cc b/components/metrics/delegating_provider.cc new file mode 100644 index 0000000000000..2bb20137e6904 --- /dev/null +++ b/components/metrics/delegating_provider.cc @@ -0,0 +1,112 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/delegating_provider.h" + +#include "base/barrier_closure.h" + +namespace metrics { + +DelegatingProvider::DelegatingProvider() = default; + +DelegatingProvider::~DelegatingProvider() = default; + +void DelegatingProvider::RegisterMetricsProvider( + std::unique_ptr provider) { + metrics_providers_.push_back(std::move(provider)); +} + +const std::vector>& +DelegatingProvider::GetProviders() { + return metrics_providers_; +} + +void DelegatingProvider::Init() { + for (auto& provider : metrics_providers_) + provider->Init(); +} + +void DelegatingProvider::AsyncInit(const base::Closure& done_callback) { + base::Closure barrier = + base::BarrierClosure(metrics_providers_.size(), done_callback); + for (auto& provider : metrics_providers_) { + provider->AsyncInit(barrier); + } +} + +void DelegatingProvider::OnDidCreateMetricsLog() { + for (auto& provider : metrics_providers_) + provider->OnDidCreateMetricsLog(); +} + +void DelegatingProvider::OnRecordingEnabled() { + for (auto& provider : metrics_providers_) + provider->OnRecordingEnabled(); +} + +void DelegatingProvider::OnRecordingDisabled() { + for (auto& provider : metrics_providers_) + provider->OnRecordingDisabled(); +} + +void DelegatingProvider::OnAppEnterBackground() { + for (auto& provider : metrics_providers_) + provider->OnAppEnterBackground(); +} + +bool DelegatingProvider::ProvideIndependentMetrics( + SystemProfileProto* system_profile_proto, + base::HistogramSnapshotManager* snapshot_manager) { + // These are collected seperately for each provider. + NOTREACHED(); + return false; +} + +void DelegatingProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) { + for (auto& provider : metrics_providers_) + provider->ProvideSystemProfileMetrics(system_profile_proto); +} + +bool DelegatingProvider::HasPreviousSessionData() { + // All providers are queried (rather than stopping after the first "true" + // response) in case they do any kind of setup work in preparation for + // the later call to RecordInitialHistogramSnapshots(). + bool has_stability_metrics = false; + for (auto& provider : metrics_providers_) + has_stability_metrics |= provider->HasPreviousSessionData(); + + return has_stability_metrics; +} + +void DelegatingProvider::ProvidePreviousSessionData( + ChromeUserMetricsExtension* uma_proto) { + for (const auto& provider : metrics_providers_) + provider->ProvidePreviousSessionData(uma_proto); +} + +void DelegatingProvider::ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) { + for (const auto& provider : metrics_providers_) + provider->ProvideCurrentSessionData(uma_proto); +} + +void DelegatingProvider::ClearSavedStabilityMetrics() { + for (auto& provider : metrics_providers_) + provider->ClearSavedStabilityMetrics(); +} + +void DelegatingProvider::RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { + for (auto& provider : metrics_providers_) + provider->RecordHistogramSnapshots(snapshot_manager); +} + +void DelegatingProvider::RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { + for (auto& provider : metrics_providers_) + provider->RecordInitialHistogramSnapshots(snapshot_manager); +} + +} // namespace metrics diff --git a/components/metrics/delegating_provider.h b/components/metrics/delegating_provider.h new file mode 100644 index 0000000000000..55c7144becc92 --- /dev/null +++ b/components/metrics/delegating_provider.h @@ -0,0 +1,60 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_DELEGATING_PROVIDER_H_ +#define COMPONENTS_METRICS_DELEGATING_PROVIDER_H_ + +#include +#include + +#include "components/metrics/metrics_provider.h" + +namespace metrics { + +// A MetricsProvider which manages a set of other MetricsProviders. +// Calls to this providers methods are forwarded to all of the registered +// metrics providers, allowing the group to be handled as a single provider. +class DelegatingProvider final : public MetricsProvider { + public: + DelegatingProvider(); + ~DelegatingProvider() override; + + // Registers an additional MetricsProvider to forward calls to. + void RegisterMetricsProvider(std::unique_ptr delegate); + + // Gets the list of registered providers. + const std::vector>& GetProviders(); + + // MetricsProvider: + void Init() override; + void AsyncInit(const base::Closure& done_callback) override; + void OnDidCreateMetricsLog() override; + void OnRecordingEnabled() override; + void OnRecordingDisabled() override; + void OnAppEnterBackground() override; + bool ProvideIndependentMetrics( + SystemProfileProto* system_profile_proto, + base::HistogramSnapshotManager* snapshot_manager) override; + void ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) override; + bool HasPreviousSessionData() override; + void ProvidePreviousSessionData( + ChromeUserMetricsExtension* uma_proto) override; + void ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) override; + void ClearSavedStabilityMetrics() override; + void RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) override; + void RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) override; + + private: + std::vector> metrics_providers_; + + DISALLOW_COPY_AND_ASSIGN(DelegatingProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_DELEGATING_PROVIDER_H_ diff --git a/components/metrics/drive_metrics_provider.cc b/components/metrics/drive_metrics_provider.cc new file mode 100644 index 0000000000000..bb92915a7139b --- /dev/null +++ b/components/metrics/drive_metrics_provider.cc @@ -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. + +#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/post_task.h" +#include "base/task/task_traits.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/time/time.h" + +namespace metrics { + +DriveMetricsProvider::DriveMetricsProvider(int local_state_path_key) + : 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::AsyncInit(const base::Closure& done_callback) { + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, + base::Bind(&DriveMetricsProvider::GetDriveMetricsOnBackgroundThread, + local_state_path_key_), + base::Bind(&DriveMetricsProvider::GotDriveMetrics, + weak_ptr_factory_.GetWeakPtr(), done_callback)); +} + +DriveMetricsProvider::SeekPenaltyResponse::SeekPenaltyResponse() + : success(false) {} + +// static +DriveMetricsProvider::DriveMetrics +DriveMetricsProvider::GetDriveMetricsOnBackgroundThread( + int local_state_path_key) { + base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::WILL_BLOCK); + + 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 (!base::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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + 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/components/metrics/drive_metrics_provider.h b/components/metrics/drive_metrics_provider.h new file mode 100644 index 0000000000000..1da73ce0b2e43 --- /dev/null +++ b/components/metrics/drive_metrics_provider.h @@ -0,0 +1,90 @@ +// 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/sequence_checker.h" +#include "components/metrics/metrics_provider.h" +#include "third_party/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). +class DriveMetricsProvider : public metrics::MetricsProvider { + public: + explicit DriveMetricsProvider(int local_state_path_key); + ~DriveMetricsProvider() override; + + // metrics::MetricsDataProvider: + void AsyncInit(const base::Closure& done_callback) override; + void ProvideSystemProfileMetrics( + metrics::SystemProfileProto* system_profile_proto) override; + + 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. Should be run on a background thread. + static DriveMetrics GetDriveMetricsOnBackgroundThread( + 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 asynchronously. + // |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 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_; + + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(DriveMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_ diff --git a/components/metrics/drive_metrics_provider_android.cc b/components/metrics/drive_metrics_provider_android.cc new file mode 100644 index 0000000000000..a653dd6f3ef24 --- /dev/null +++ b/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/components/metrics/drive_metrics_provider_fuchsia.cc b/components/metrics/drive_metrics_provider_fuchsia.cc new file mode 100644 index 0000000000000..165bc2d3909f6 --- /dev/null +++ b/components/metrics/drive_metrics_provider_fuchsia.cc @@ -0,0 +1,16 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "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/components/metrics/drive_metrics_provider_ios.mm b/components/metrics/drive_metrics_provider_ios.mm new file mode 100644 index 0000000000000..a653dd6f3ef24 --- /dev/null +++ b/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/components/metrics/drive_metrics_provider_linux.cc b/components/metrics/drive_metrics_provider_linux.cc new file mode 100644 index 0000000000000..149405e5f126a --- /dev/null +++ b/components/metrics/drive_metrics_provider_linux.cc @@ -0,0 +1,53 @@ +// 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 // For MAJOR()/MINOR(). +#include +#include + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" + +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) { + 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/components/metrics/drive_metrics_provider_mac.mm b/components/metrics/drive_metrics_provider_mac.mm new file mode 100644 index 0000000000000..84dbe68e827e6 --- /dev/null +++ b/components/metrics/drive_metrics_provider_mac.mm @@ -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/drive_metrics_provider.h" + +#include +#include +#import +#include +#include +#include +#include + +#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 session( + DASessionCreate(kCFAllocatorDefault)); + if (!session) + return false; + + base::ScopedCFTypeRef disk( + DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsd_name.c_str())); + if (!disk) + return false; + + base::mac::ScopedIOObject io_media(DADiskCopyIOMedia(disk)); + base::ScopedCFTypeRef characteristics( + static_cast(IORegistryEntrySearchCFProperty( + io_media, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), + kCFAllocatorDefault, + kIORegistryIterateRecursively | kIORegistryIterateParents))); + if (!characteristics) + return false; + + CFStringRef type_ref = base::mac::GetValueFromDictionary( + 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; + } + 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/components/metrics/drive_metrics_provider_unittest.cc b/components/metrics/drive_metrics_provider_unittest.cc new file mode 100644 index 0000000000000..142faf72be145 --- /dev/null +++ b/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/components/metrics/drive_metrics_provider_win.cc b/components/metrics/drive_metrics_provider_win.cc new file mode 100644 index 0000000000000..6c1334e217c9e --- /dev/null +++ b/components/metrics/drive_metrics_provider_win.cc @@ -0,0 +1,46 @@ +// 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 +#include +#include + +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/strings/stringprintf.h" + +namespace metrics { + +// static +bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path, + bool* has_seek_penalty) { + std::vector 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, nullptr); + + if (success == FALSE || bytes_returned < sizeof(result)) + return false; + + *has_seek_penalty = result.IncursSeekPenalty != FALSE; + return true; +} + +} // namespace metrics diff --git a/components/metrics/enabled_state_provider.cc b/components/metrics/enabled_state_provider.cc new file mode 100644 index 0000000000000..31f296437cd85 --- /dev/null +++ b/components/metrics/enabled_state_provider.cc @@ -0,0 +1,13 @@ +// 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/enabled_state_provider.h" + +namespace metrics { + +bool EnabledStateProvider::IsReportingEnabled() const { + return IsConsentGiven(); +} + +} // namespace metrics diff --git a/components/metrics/enabled_state_provider.h b/components/metrics/enabled_state_provider.h new file mode 100644 index 0000000000000..266194d778eef --- /dev/null +++ b/components/metrics/enabled_state_provider.h @@ -0,0 +1,25 @@ +// 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_ENABLED_STATE_PROVIDER_H_ +#define COMPONENTS_METRICS_ENABLED_STATE_PROVIDER_H_ + +namespace metrics { + +// An interface that provides whether metrics should be reported. +class EnabledStateProvider { + public: + virtual ~EnabledStateProvider() {} + + // Indicates that the user has provided consent to collect and report metrics. + virtual bool IsConsentGiven() const = 0; + + // Should collection and reporting be enabled. This should depend on consent + // being given. + virtual bool IsReportingEnabled() const; +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_ENABLED_STATE_PROVIDER_H_ diff --git a/components/metrics/environment_recorder.cc b/components/metrics/environment_recorder.cc new file mode 100644 index 0000000000000..6391f61665ab2 --- /dev/null +++ b/components/metrics/environment_recorder.cc @@ -0,0 +1,96 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/environment_recorder.h" + +#include "base/base64.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace metrics { + +namespace { + +// 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()); +} + +} // namespace + +EnvironmentRecorder::EnvironmentRecorder(PrefService* local_state) + : local_state_(local_state) {} + +EnvironmentRecorder::~EnvironmentRecorder() = default; + +std::string EnvironmentRecorder::SerializeAndRecordEnvironmentToPrefs( + const SystemProfileProto& system_profile) { + std::string serialized_system_profile; + std::string base64_system_profile; + if (system_profile.SerializeToString(&serialized_system_profile)) { + // Persist the system profile to disk. In the event of an unclean shutdown, + // it will be used as part of the initial stability report. + base::Base64Encode(serialized_system_profile, &base64_system_profile); + local_state_->SetString(prefs::kStabilitySavedSystemProfile, + base64_system_profile); + local_state_->SetString(prefs::kStabilitySavedSystemProfileHash, + ComputeSHA1(serialized_system_profile)); + } + + return serialized_system_profile; +} + +bool EnvironmentRecorder::LoadEnvironmentFromPrefs( + SystemProfileProto* system_profile) { + DCHECK(system_profile); + + 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); + + std::string serialized_system_profile; + return base::Base64Decode(base64_system_profile, + &serialized_system_profile) && + ComputeSHA1(serialized_system_profile) == system_profile_hash && + system_profile->ParseFromString(serialized_system_profile); +} + +void EnvironmentRecorder::ClearEnvironmentFromPrefs() { + local_state_->ClearPref(prefs::kStabilitySavedSystemProfile); + local_state_->ClearPref(prefs::kStabilitySavedSystemProfileHash); +} + +int64_t EnvironmentRecorder::GetLastBuildtime() { + return local_state_->GetInt64(prefs::kStabilityStatsBuildTime); +} + +std::string EnvironmentRecorder::GetLastVersion() { + return local_state_->GetString(prefs::kStabilityStatsVersion); +} + +void EnvironmentRecorder::SetBuildtimeAndVersion(int64_t buildtime, + const std::string& version) { + local_state_->SetInt64(prefs::kStabilityStatsBuildTime, buildtime); + local_state_->SetString(prefs::kStabilityStatsVersion, version); +} + +// static +void EnvironmentRecorder::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterStringPref(prefs::kStabilitySavedSystemProfile, + std::string()); + registry->RegisterStringPref(prefs::kStabilitySavedSystemProfileHash, + std::string()); + registry->RegisterStringPref(prefs::kStabilityStatsVersion, std::string()); + registry->RegisterInt64Pref(prefs::kStabilityStatsBuildTime, 0); +} + +} // namespace metrics diff --git a/components/metrics/environment_recorder.h b/components/metrics/environment_recorder.h new file mode 100644 index 0000000000000..0042d2e7d95b9 --- /dev/null +++ b/components/metrics/environment_recorder.h @@ -0,0 +1,59 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_ENVIRONMENT_RECORDER_H_ +#define COMPONENTS_METRICS_ENVIRONMENT_RECORDER_H_ + +#include + +#include "base/macros.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class SystemProfileProto; + +// Stores system profile information to prefs for creating stability logs +// in the next launch of chrome, and reads data from previous launches. +class EnvironmentRecorder { + public: + explicit EnvironmentRecorder(PrefService* local_state); + ~EnvironmentRecorder(); + + // Serializes the system profile and records it in prefs for the next + // session. Returns the uncompressed serialized proto for passing to crash + // reports, or the empty string if the proto can't be serialized. + std::string SerializeAndRecordEnvironmentToPrefs( + const SystemProfileProto& system_profile); + + // Loads the system_profile data stored in a previous chrome session, and + // stores it in the |system_profile| object. + // Returns true iff a system profile was successfully read. + bool LoadEnvironmentFromPrefs(SystemProfileProto* system_profile); + + // Deletes system profile data from prefs. + void ClearEnvironmentFromPrefs(); + + // Stores the buildtime of the current binary and version in prefs. + void SetBuildtimeAndVersion(int64_t buildtime, const std::string& version); + + // Gets the buildtime stored in prefs. + int64_t GetLastBuildtime(); + + // Gets the version stored in prefs. + std::string GetLastVersion(); + + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + PrefService* local_state_; + + DISALLOW_COPY_AND_ASSIGN(EnvironmentRecorder); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_ENVIRONMENT_RECORDER_H_ diff --git a/components/metrics/environment_recorder_unittest.cc b/components/metrics/environment_recorder_unittest.cc new file mode 100644 index 0000000000000..14c6cb7bb974e --- /dev/null +++ b/components/metrics/environment_recorder_unittest.cc @@ -0,0 +1,75 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/environment_recorder.h" + +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace metrics { + +class EnvironmentRecorderTest : public testing::Test { + public: + EnvironmentRecorderTest() { + EnvironmentRecorder::RegisterPrefs(prefs_.registry()); + } + + ~EnvironmentRecorderTest() override {} + + protected: + TestingPrefServiceSimple prefs_; + + private: + DISALLOW_COPY_AND_ASSIGN(EnvironmentRecorderTest); +}; + +TEST_F(EnvironmentRecorderTest, LoadEnvironmentFromPrefs) { + const char* kSystemProfilePref = prefs::kStabilitySavedSystemProfile; + const char* kSystemProfileHashPref = prefs::kStabilitySavedSystemProfileHash; + + // The pref value is empty, so loading it from prefs should fail. + { + EnvironmentRecorder recorder(&prefs_); + SystemProfileProto system_profile; + EXPECT_FALSE(recorder.LoadEnvironmentFromPrefs(&system_profile)); + EXPECT_FALSE(system_profile.has_app_version()); + } + + // Do a RecordEnvironment() call and check whether the pref is recorded. + { + EnvironmentRecorder recorder(&prefs_); + SystemProfileProto system_profile; + system_profile.set_app_version("bogus version"); + std::string serialized_profile = + recorder.SerializeAndRecordEnvironmentToPrefs(system_profile); + EXPECT_FALSE(serialized_profile.empty()); + EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty()); + EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty()); + } + + // Load it and check that it has the right value. + { + EnvironmentRecorder recorder(&prefs_); + SystemProfileProto system_profile; + EXPECT_TRUE(recorder.LoadEnvironmentFromPrefs(&system_profile)); + EXPECT_EQ("bogus version", system_profile.app_version()); + // Ensure that the call did not clear the prefs. + EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty()); + EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty()); + } + + // Ensure that a non-matching hash results in the pref being invalid. + { + // Set the hash to a bad value. + prefs_.SetString(kSystemProfileHashPref, "deadbeef"); + EnvironmentRecorder recorder(&prefs_); + SystemProfileProto system_profile; + EXPECT_FALSE(recorder.LoadEnvironmentFromPrefs(&system_profile)); + EXPECT_FALSE(system_profile.has_app_version()); + } +} + +} // namespace metrics diff --git a/components/metrics/execution_phase.cc b/components/metrics/execution_phase.cc new file mode 100644 index 0000000000000..992ef820e77c9 --- /dev/null +++ b/components/metrics/execution_phase.cc @@ -0,0 +1,69 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/execution_phase.h" + +#include "build/build_config.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +#if defined(OS_WIN) +#include "components/browser_watcher/stability_data_names.h" +#include "components/browser_watcher/stability_debugging.h" +#endif // defined(OS_WIN) + +namespace metrics { + +ExecutionPhaseManager::ExecutionPhaseManager(PrefService* local_state) + : local_state_(local_state) {} + +ExecutionPhaseManager::~ExecutionPhaseManager() {} + +// static +void ExecutionPhaseManager::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref( + prefs::kStabilityExecutionPhase, + static_cast(ExecutionPhase::UNINITIALIZED_PHASE)); +} + +// static +ExecutionPhase ExecutionPhaseManager::execution_phase_ = + ExecutionPhase::UNINITIALIZED_PHASE; + +void ExecutionPhaseManager::SetExecutionPhase(ExecutionPhase execution_phase) { + DCHECK(execution_phase != ExecutionPhase::START_METRICS_RECORDING || + execution_phase_ == ExecutionPhase::UNINITIALIZED_PHASE); + execution_phase_ = execution_phase; + local_state_->SetInteger(prefs::kStabilityExecutionPhase, + static_cast(execution_phase_)); +#if defined(OS_WIN) + browser_watcher::SetStabilityDataInt( + browser_watcher::kStabilityExecutionPhase, + static_cast(execution_phase_)); +#endif // defined(OS_WIN) +} + +ExecutionPhase ExecutionPhaseManager::GetExecutionPhase() { + // TODO(rtenneti): On windows, consider saving/getting execution_phase from + // the registry. + return static_cast( + local_state_->GetInteger(prefs::kStabilityExecutionPhase)); +} + +#if defined(OS_ANDROID) || defined(OS_IOS) +void ExecutionPhaseManager::OnAppEnterBackground() { + // Note: the in-memory ExecutionPhaseManager::execution_phase_ is not updated. + local_state_->SetInteger(prefs::kStabilityExecutionPhase, + static_cast(ExecutionPhase::SHUTDOWN_COMPLETE)); +} + +void ExecutionPhaseManager::OnAppEnterForeground() { + // Restore prefs value altered by OnEnterBackground. + local_state_->SetInteger(prefs::kStabilityExecutionPhase, + static_cast(execution_phase_)); +} +#endif // defined(OS_ANDROID) || defined(OS_IOS) + +} // namespace metrics diff --git a/components/metrics/execution_phase.h b/components/metrics/execution_phase.h new file mode 100644 index 0000000000000..b158923070518 --- /dev/null +++ b/components/metrics/execution_phase.h @@ -0,0 +1,55 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_EXECUTION_PHASE_H_ +#define COMPONENTS_METRICS_EXECUTION_PHASE_H_ + +#include "base/macros.h" +#include "build/build_config.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +enum class 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, +}; + +// Helper class for managing ExecutionPhase state in prefs and memory. +// It's safe to construct temporary objects to perform these operations. +class ExecutionPhaseManager { + public: + explicit ExecutionPhaseManager(PrefService* local_state); + ~ExecutionPhaseManager(); + + static void RegisterPrefs(PrefRegistrySimple* registry); + + void SetExecutionPhase(ExecutionPhase execution_phase); + ExecutionPhase GetExecutionPhase(); + +#if defined(OS_ANDROID) || defined(OS_IOS) + void OnAppEnterBackground(); + void OnAppEnterForeground(); +#endif // defined(OS_ANDROID) || defined(OS_IOS) + + private: + // Execution phase the browser is in. + static ExecutionPhase execution_phase_; + + PrefService* local_state_; + + DISALLOW_COPY_AND_ASSIGN(ExecutionPhaseManager); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_EXECUTION_PHASE_H_ diff --git a/components/metrics/expired_histogram_util.cc b/components/metrics/expired_histogram_util.cc new file mode 100644 index 0000000000000..17844e77213c4 --- /dev/null +++ b/components/metrics/expired_histogram_util.cc @@ -0,0 +1,34 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/expired_histogram_util.h" + +#include "base/feature_list.h" +#include "base/metrics/field_trial_params.h" +#include "base/metrics/statistics_recorder.h" +#include "components/metrics/expired_histograms_checker.h" + +namespace metrics { +namespace { + +const base::Feature kExpiredHistogramLogicFeature{ + "ExpiredHistogramLogic", base::FEATURE_DISABLED_BY_DEFAULT}; + +const base::FeatureParam kWhitelistParam{ + &kExpiredHistogramLogicFeature, "whitelist", ""}; + +} // namespace + +void EnableExpiryChecker(const uint64_t* expired_histograms_hashes, + size_t num_expired_histograms) { + DCHECK(base::FeatureList::GetInstance()); + if (base::FeatureList::IsEnabled(kExpiredHistogramLogicFeature)) { + base::StatisticsRecorder::SetRecordChecker( + std::make_unique(expired_histograms_hashes, + num_expired_histograms, + kWhitelistParam.Get())); + } +} + +} // namespace metrics \ No newline at end of file diff --git a/components/metrics/expired_histogram_util.h b/components/metrics/expired_histogram_util.h new file mode 100644 index 0000000000000..cf6c455ed93b6 --- /dev/null +++ b/components/metrics/expired_histogram_util.h @@ -0,0 +1,21 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_EXPIRED_HISTOGRAM_UTIL_H_ +#define COMPONENTS_METRICS_EXPIRED_HISTOGRAM_UTIL_H_ + +#include +#include + +namespace metrics { + +// Enables histogram expiry checker if it is enabled by field trial. Histogram +// expiry is disbaled by default so that unit tests don't fail unexpectedly when +// a histogram expires. +void EnableExpiryChecker(const uint64_t* expired_histograms_hashes, + size_t num_expired_histograms); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_EXPIRED_HISTOGRAM_UTIL_H_ \ No newline at end of file diff --git a/components/metrics/expired_histograms_checker.cc b/components/metrics/expired_histograms_checker.cc new file mode 100644 index 0000000000000..d1d3ecb67f5bc --- /dev/null +++ b/components/metrics/expired_histograms_checker.cc @@ -0,0 +1,41 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/expired_histograms_checker.h" + +#include +#include + +#include "base/metrics/metrics_hashes.h" +#include "base/metrics/statistics_recorder.h" +#include "base/stl_util.h" +#include "base/strings/string_split.h" + +namespace metrics { + +ExpiredHistogramsChecker::ExpiredHistogramsChecker( + const uint64_t* array, + size_t size, + const std::string& whitelist_str) + : array_(array), size_(size) { + InitWhitelist(whitelist_str); +} + +ExpiredHistogramsChecker::~ExpiredHistogramsChecker() {} + +bool ExpiredHistogramsChecker::ShouldRecord(uint64_t histogram_hash) const { + // If histogram is whitelisted then it should always be recorded. + if (base::ContainsKey(whitelist_, histogram_hash)) + return true; + return !std::binary_search(array_, array_ + size_, histogram_hash); +} + +void ExpiredHistogramsChecker::InitWhitelist(const std::string& whitelist_str) { + std::vector whitelist_names = base::SplitStringPiece( + whitelist_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + for (base::StringPiece name : whitelist_names) + whitelist_.insert(base::HashMetricName(name)); +} + +} // namespace metrics diff --git a/components/metrics/expired_histograms_checker.h b/components/metrics/expired_histograms_checker.h new file mode 100644 index 0000000000000..b3ca4a7612ac9 --- /dev/null +++ b/components/metrics/expired_histograms_checker.h @@ -0,0 +1,51 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_EXPIRED_HISTOGRAMS_CHECKER_H_ +#define COMPONENTS_METRICS_EXPIRED_HISTOGRAMS_CHECKER_H_ + +#include +#include + +#include "base/macros.h" +#include "base/metrics/record_histogram_checker.h" +#include "base/strings/string_piece.h" + +namespace metrics { + +// ExpiredHistogramsChecker implements RecordHistogramChecker interface +// to avoid recording expired metrics. +class ExpiredHistogramsChecker final : public base::RecordHistogramChecker { + public: + // Takes sorted in nondecreasing order array of histogram hashes, its size and + // list of whitelisted histogram names concatenated as a comma-separated + // string. + ExpiredHistogramsChecker(const uint64_t* array, + size_t size, + const std::string& whitelist_str); + ~ExpiredHistogramsChecker() override; + + // Checks if the given |histogram_hash| corresponds to an expired histogram. + bool ShouldRecord(uint64_t histogram_hash) const override; + + private: + // Initializes the |whitelist_| array of histogram hashes that should be + // recorded regardless of their expiration. + void InitWhitelist(const std::string& whitelist_str); + + // Array of expired histogram hashes. + const uint64_t* const array_; + + // Size of the |array_|. + const size_t size_; + + // List of expired histogram hashes that should be recorded. + std::set whitelist_; + + DISALLOW_COPY_AND_ASSIGN(ExpiredHistogramsChecker); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_EXPIRED_HISTOGRAMS_CHECKER_H_ diff --git a/components/metrics/expired_histograms_checker_unittest.cc b/components/metrics/expired_histograms_checker_unittest.cc new file mode 100644 index 0000000000000..13aa77d87f2ca --- /dev/null +++ b/components/metrics/expired_histograms_checker_unittest.cc @@ -0,0 +1,40 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/expired_histograms_checker.h" + +#include "base/metrics/metrics_hashes.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +TEST(ExpiredHistogramsCheckerTests, BasicTest) { + uint64_t expired_hashes[] = {1, 2, 3}; + size_t size = 3; + std::string whitelist_str = ""; + ExpiredHistogramsChecker checker(expired_hashes, size, whitelist_str); + + EXPECT_TRUE(checker.ShouldRecord(0)); + EXPECT_FALSE(checker.ShouldRecord(3)); +} + +TEST(ExpiredHistogramsCheckerTests, WhitelistTest) { + std::string hist1 = "hist1"; + std::string hist2 = "hist2"; + std::string hist3 = "hist3"; + std::string hist4 = "hist4"; + + uint64_t expired_hashes[] = {base::HashMetricName(hist1), + base::HashMetricName(hist2)}; + size_t size = 2; + std::string whitelist_str = hist2 + "," + hist4; + ExpiredHistogramsChecker checker(expired_hashes, size, whitelist_str); + + EXPECT_FALSE(checker.ShouldRecord(base::HashMetricName(hist1))); + EXPECT_TRUE(checker.ShouldRecord(base::HashMetricName(hist2))); + EXPECT_TRUE(checker.ShouldRecord(base::HashMetricName(hist3))); + EXPECT_TRUE(checker.ShouldRecord(base::HashMetricName(hist4))); +} + +} // namespace metrics \ No newline at end of file diff --git a/components/metrics/field_trials_provider.cc b/components/metrics/field_trials_provider.cc new file mode 100644 index 0000000000000..ea272f5d3baa7 --- /dev/null +++ b/components/metrics/field_trials_provider.cc @@ -0,0 +1,65 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/field_trials_provider.h" + +#include "base/strings/string_piece.h" +#include "components/variations/active_field_trials.h" +#include "components/variations/synthetic_trial_registry.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace variations { + +namespace { + +void WriteFieldTrials(const std::vector& field_trial_ids, + metrics::SystemProfileProto* system_profile) { + for (const ActiveGroupId& id : field_trial_ids) { + metrics::SystemProfileProto::FieldTrial* field_trial = + system_profile->add_field_trial(); + field_trial->set_name_id(id.name); + field_trial->set_group_id(id.group); + } +} + +} // namespace + +FieldTrialsProvider::FieldTrialsProvider(SyntheticTrialRegistry* registry, + base::StringPiece suffix) + : registry_(registry), suffix_(suffix) {} +FieldTrialsProvider::~FieldTrialsProvider() = default; + +void FieldTrialsProvider::GetFieldTrialIds( + std::vector* field_trial_ids) const { + // We use the default field trial suffixing (no suffix). + variations::GetFieldTrialActiveGroupIds(suffix_, field_trial_ids); +} + +void FieldTrialsProvider::OnDidCreateMetricsLog() { + if (registry_) { + creation_times_.push_back(base::TimeTicks::Now()); + } +} + +void FieldTrialsProvider::ProvideSystemProfileMetrics( + metrics::SystemProfileProto* system_profile_proto) { + std::vector field_trial_ids; + GetFieldTrialIds(&field_trial_ids); + WriteFieldTrials(field_trial_ids, system_profile_proto); + + if (registry_) { + base::TimeTicks creation_time; + // Should always be true, but don't crash even if there is a bug. + if (!creation_times_.empty()) { + creation_time = creation_times_.back(); + creation_times_.pop_back(); + } + std::vector synthetic_trials; + registry_->GetSyntheticFieldTrialsOlderThan(creation_time, + &synthetic_trials); + WriteFieldTrials(synthetic_trials, system_profile_proto); + } +} + +} // namespace variations diff --git a/components/metrics/field_trials_provider.h b/components/metrics/field_trials_provider.h new file mode 100644 index 0000000000000..d93988edc680b --- /dev/null +++ b/components/metrics/field_trials_provider.h @@ -0,0 +1,55 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_FIELD_TRIALS_PROVIDER_H_ +#define COMPONENTS_METRICS_FIELD_TRIALS_PROVIDER_H_ + +#include + +#include "base/strings/string_piece.h" +#include "base/time/time.h" +#include "components/metrics/metrics_provider.h" + +// TODO(crbug/507665): Once MetricsProvider/SystemProfileProto are moved into +// //services/metrics, then //components/variations can depend on them, and +// this should be moved there. +namespace variations { + +class SyntheticTrialRegistry; +struct ActiveGroupId; + +class FieldTrialsProvider : public metrics::MetricsProvider { + public: + // |registry| must outlive this metrics provider. + FieldTrialsProvider(SyntheticTrialRegistry* registry, + base::StringPiece suffix); + ~FieldTrialsProvider() override; + + // metrics::MetricsProvider: + void OnDidCreateMetricsLog() override; + void ProvideSystemProfileMetrics( + metrics::SystemProfileProto* system_profile_proto) override; + + private: + // Overrideable for testing. + virtual void GetFieldTrialIds( + std::vector* field_trial_ids) const; + + SyntheticTrialRegistry* registry_; + + // Suffix used for the field trial names before they are hashed for uploads. + std::string suffix_; + + // A stack of log creation times. + // While the initial metrics log exists, there will be two logs open. + // Use a stack so that we use the right creation time for the first ongoing + // log. + // TODO(crbug/746098): Simplify InitialMetricsLog logic so this is not + // necessary. + std::vector creation_times_; +}; + +} // namespace variations + +#endif // COMPONENTS_METRICS_FIELD_TRIALS_PROVIDER_H_ diff --git a/components/metrics/field_trials_provider_unittest.cc b/components/metrics/field_trials_provider_unittest.cc new file mode 100644 index 0000000000000..2f6c8eff5e7e6 --- /dev/null +++ b/components/metrics/field_trials_provider_unittest.cc @@ -0,0 +1,103 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/field_trials_provider.h" + +#include "components/variations/active_field_trials.h" +#include "components/variations/synthetic_trial_registry.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace variations { + +namespace { + +const ActiveGroupId kFieldTrialIds[] = {{37, 43}, {13, 47}, {23, 17}}; +const ActiveGroupId kSyntheticTrials[] = {{55, 15}, {66, 16}}; + +class TestProvider : public FieldTrialsProvider { + public: + TestProvider(SyntheticTrialRegistry* registry, base::StringPiece suffix) + : FieldTrialsProvider(registry, suffix) {} + ~TestProvider() override {} + + void GetFieldTrialIds( + std::vector* field_trial_ids) const override { + ASSERT_TRUE(field_trial_ids->empty()); + for (const ActiveGroupId& id : kFieldTrialIds) { + field_trial_ids->push_back(id); + } + } +}; + +// Check that the values in |system_values| correspond to the test data +// defined at the top of this file. +void CheckSystemProfile(const metrics::SystemProfileProto& system_profile) { + ASSERT_EQ(arraysize(kFieldTrialIds) + arraysize(kSyntheticTrials), + static_cast(system_profile.field_trial_size())); + for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) { + const metrics::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 metrics::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()); + } +} + +} // namespace + +class FieldTrialsProviderTest : public ::testing::Test { + public: + FieldTrialsProviderTest() {} + ~FieldTrialsProviderTest() override {} + + protected: + // Register trials which should get recorded. + void RegisterExpectedSyntheticTrials() { + for (const ActiveGroupId& id : kSyntheticTrials) { + registry_.RegisterSyntheticFieldTrial( + SyntheticTrialGroup(id.name, id.group)); + } + } + // Register trial which shouldn't get recorded. + void RegisterExtraSyntheticTrial() { + registry_.RegisterSyntheticFieldTrial(SyntheticTrialGroup(100, 1000)); + } + + // 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)); + } + } + + SyntheticTrialRegistry registry_; +}; + +TEST_F(FieldTrialsProviderTest, ProvideSyntheticTrials) { + TestProvider provider(®istry_, base::StringPiece()); + + RegisterExpectedSyntheticTrials(); + // Make sure these trials are older than the log. + WaitUntilTimeChanges(base::TimeTicks::Now()); + + provider.OnDidCreateMetricsLog(); + // Make sure that the log is older than the trials that should be excluded. + WaitUntilTimeChanges(base::TimeTicks::Now()); + + RegisterExtraSyntheticTrial(); + + metrics::SystemProfileProto proto; + provider.ProvideSystemProfileMetrics(&proto); + CheckSystemProfile(proto); +} + +} // namespace variations diff --git a/components/metrics/file_metrics_provider.cc b/components/metrics/file_metrics_provider.cc new file mode 100644 index 0000000000000..a94bb0787067e --- /dev/null +++ b/components/metrics/file_metrics_provider.cc @@ -0,0 +1,837 @@ +// 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 + +#include "base/command_line.h" +#include "base/containers/flat_map.h" +#include "base/files/file.h" +#include "base/files/file_enumerator.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/strings/string_piece.h" +#include "base/task/post_task.h" +#include "base/task/task_traits.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/metrics/persistent_system_profile.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace metrics { + +namespace { + +// These structures provide values used to define how files are opened and +// accessed. It obviates the need for multiple code-paths within several of +// the methods. +struct SourceOptions { + // The flags to be used to open a file on disk. + int file_open_flags; + + // The access mode to be used when mapping a file into memory. + base::MemoryMappedFile::Access memory_mapped_access; + + // Indicates if the file is to be accessed read-only. + bool is_read_only; +}; + +enum : int { + // Opening a file typically requires at least these flags. + STD_OPEN = base::File::FLAG_OPEN | base::File::FLAG_READ, +}; + +constexpr SourceOptions kSourceOptions[] = { + // SOURCE_HISTOGRAMS_ATOMIC_FILE + { + // Ensure that no other process reads this at the same time. + STD_OPEN | base::File::FLAG_EXCLUSIVE_READ, + base::MemoryMappedFile::READ_ONLY, + true + }, + // SOURCE_HISTOGRAMS_ATOMIC_DIR + { + // Ensure that no other process reads this at the same time. + STD_OPEN | base::File::FLAG_EXCLUSIVE_READ, + base::MemoryMappedFile::READ_ONLY, + true + }, + // SOURCE_HISTOGRAMS_ACTIVE_FILE + { + // Allow writing (updated "logged" values) to the file. + STD_OPEN | base::File::FLAG_WRITE, + base::MemoryMappedFile::READ_WRITE, + false + } +}; + +enum EmbeddedProfileResult : int { + EMBEDDED_PROFILE_ATTEMPT, + EMBEDDED_PROFILE_FOUND, + EMBEDDED_PROFILE_FALLBACK, + EMBEDDED_PROFILE_DROPPED, + EMBEDDED_PROFILE_ACTION_MAX +}; + +void RecordEmbeddedProfileResult(EmbeddedProfileResult result) { + UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.EmbeddedProfileResult", + result, EMBEDDED_PROFILE_ACTION_MAX); +} + +void DeleteFileWhenPossible(const base::FilePath& path) { + // Open (with delete) and then immediately close the file by going out of + // scope. This is the only cross-platform safe way to delete a file that may + // be open elsewhere, a distinct possibility given the asynchronous nature + // of the delete task. + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | + base::File::FLAG_DELETE_ON_CLOSE); +} + +// A task runner to use for testing. +base::TaskRunner* g_task_runner_for_testing = nullptr; + +// Returns a task runner appropriate for running background tasks that perform +// file I/O. +scoped_refptr CreateBackgroundTaskRunner() { + if (g_task_runner_for_testing) + return scoped_refptr(g_task_runner_for_testing); + + return base::CreateTaskRunnerWithTraits( + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); +} + +} // namespace + +// This structure stores all the information about the sources being monitored +// and their current reporting state. +struct FileMetricsProvider::SourceInfo { + SourceInfo(const Params& params) + : type(params.type), + association(params.association), + prefs_key(params.prefs_key), + filter(params.filter), + max_age(params.max_age), + max_dir_kib(params.max_dir_kib), + max_dir_files(params.max_dir_files) { + switch (type) { + case SOURCE_HISTOGRAMS_ACTIVE_FILE: + DCHECK(prefs_key.empty()); + FALLTHROUGH; + case SOURCE_HISTOGRAMS_ATOMIC_FILE: + path = params.path; + break; + case SOURCE_HISTOGRAMS_ATOMIC_DIR: + directory = params.path; + break; + } + } + ~SourceInfo() {} + + struct FoundFile { + base::FilePath path; + base::FileEnumerator::FileInfo info; + }; + using FoundFiles = base::flat_map; + + // How to access this source (file/dir, atomic/active). + const SourceType type; + + // With what run this source is associated. + const SourceAssociation association; + + // Where on disk the directory is located. This will only be populated when + // a directory is being monitored. + base::FilePath directory; + + // The files found in the above directory, ordered by last-modified. + std::unique_ptr found_files; + + // Where on disk the file is located. If a directory is being monitored, + // this will be updated for whatever file is being read. + base::FilePath path; + + // Name used inside prefs to persistent metadata. + std::string prefs_key; + + // The filter callback for determining what to do with found files. + FilterCallback filter; + + // The maximum allowed age of a file. + base::TimeDelta max_age; + + // The maximum allowed bytes in a directory. + size_t max_dir_kib; + + // The maximum allowed files in a directory. + size_t max_dir_files; + + // The last-seen time of this source to detect change. + base::Time last_seen; + + // Indicates if the data has been read out or not. + bool read_complete = false; + + // Once a file has been recognized as needing to be read, it is mapped + // into memory and assigned to an |allocator| object. + std::unique_ptr allocator; + + private: + DISALLOW_COPY_AND_ASSIGN(SourceInfo); +}; + +FileMetricsProvider::Params::Params(const base::FilePath& path, + SourceType type, + SourceAssociation association, + base::StringPiece prefs_key) + : path(path), type(type), association(association), prefs_key(prefs_key) {} + +FileMetricsProvider::Params::~Params() {} + +FileMetricsProvider::FileMetricsProvider(PrefService* local_state) + : task_runner_(CreateBackgroundTaskRunner()), + pref_service_(local_state), + weak_factory_(this) { + base::StatisticsRecorder::RegisterHistogramProvider( + weak_factory_.GetWeakPtr()); +} + +FileMetricsProvider::~FileMetricsProvider() {} + +void FileMetricsProvider::RegisterSource(const Params& params) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Ensure that kSourceOptions has been filled for this type. + DCHECK_GT(arraysize(kSourceOptions), static_cast(params.type)); + + std::unique_ptr source(new SourceInfo(params)); + + // |prefs_key| may be empty if the caller does not wish to persist the + // state across instances of the program. + if (pref_service_ && !params.prefs_key.empty()) { + source->last_seen = base::Time::FromInternalValue( + pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix + + source->prefs_key)); + } + + switch (params.association) { + case ASSOCIATE_CURRENT_RUN: + case ASSOCIATE_INTERNAL_PROFILE: + sources_to_check_.push_back(std::move(source)); + break; + case ASSOCIATE_PREVIOUS_RUN: + case ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN: + DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_FILE, source->type); + sources_for_previous_run_.push_back(std::move(source)); + break; + } +} + +// static +void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs, + const base::StringPiece prefs_key) { + prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix + + prefs_key.as_string(), 0); +} + +// static +void FileMetricsProvider::SetTaskRunnerForTesting( + const scoped_refptr& task_runner) { + DCHECK(!g_task_runner_for_testing || !task_runner); + g_task_runner_for_testing = task_runner.get(); +} + +// static +void FileMetricsProvider::RecordAccessResult(AccessResult result) { + UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.AccessResult", result, + ACCESS_RESULT_MAX); +} + +// static +bool FileMetricsProvider::LocateNextFileInDirectory(SourceInfo* source) { + DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_DIR, source->type); + DCHECK(!source->directory.empty()); + + // Cumulative directory stats. These will remain zero if the directory isn't + // scanned but that's okay since any work they would cause to be done below + // would have been done during the first call where the directory was fully + // scanned. + size_t total_size_kib = 0; // Using KiB allows 4TiB even on 32-bit builds. + size_t file_count = 0; + + base::Time now_time = base::Time::Now(); + if (!source->found_files) { + source->found_files = std::make_unique(); + base::FileEnumerator file_iter(source->directory, /*recursive=*/false, + base::FileEnumerator::FILES); + SourceInfo::FoundFile found_file; + + // Open the directory and find all the files, remembering the last-modified + // time of each. + for (found_file.path = file_iter.Next(); !found_file.path.empty(); + found_file.path = file_iter.Next()) { + found_file.info = file_iter.GetInfo(); + + // Ignore directories. + if (found_file.info.IsDirectory()) + continue; + + // Ignore temporary files. + base::FilePath::CharType first_character = + found_file.path.BaseName().value().front(); + if (first_character == FILE_PATH_LITERAL('.') || + first_character == FILE_PATH_LITERAL('_')) { + continue; + } + + // Ignore non-PMA (Persistent Memory Allocator) files. + if (found_file.path.Extension() != + base::PersistentMemoryAllocator::kFileExtension) { + continue; + } + + // Process real files. + total_size_kib += found_file.info.GetSize() >> 10; + base::Time modified = found_file.info.GetLastModifiedTime(); + if (modified > source->last_seen) { + // This file hasn't been read. Remember it (unless from the future). + if (modified <= now_time) + source->found_files->emplace(modified, std::move(found_file)); + ++file_count; + } else { + // This file has been read. Try to delete it. Ignore any errors because + // the file may be un-removeable by this process. It could, for example, + // have been created by a privileged process like setup.exe. Even if it + // is not removed, it will continue to be ignored bacuse of the older + // modification time. + base::DeleteFile(found_file.path, /*recursive=*/false); + } + } + } + + // Filter files from the front until one is found for processing. + bool have_file = false; + while (!source->found_files->empty()) { + SourceInfo::FoundFile found = + std::move(source->found_files->begin()->second); + source->found_files->erase(source->found_files->begin()); + + bool too_many = + source->max_dir_files > 0 && file_count > source->max_dir_files; + bool too_big = + source->max_dir_kib > 0 && total_size_kib > source->max_dir_kib; + bool too_old = + source->max_age != base::TimeDelta() && + now_time - found.info.GetLastModifiedTime() > source->max_age; + if (too_many || too_big || too_old) { + base::DeleteFile(found.path, /*recursive=*/false); + --file_count; + total_size_kib -= found.info.GetSize() >> 10; + RecordAccessResult(too_many ? ACCESS_RESULT_TOO_MANY_FILES + : too_big ? ACCESS_RESULT_TOO_MANY_BYTES + : ACCESS_RESULT_TOO_OLD); + continue; + } + + AccessResult result = HandleFilterSource(source, found.path); + if (result == ACCESS_RESULT_SUCCESS) { + source->path = std::move(found.path); + have_file = true; + break; + } + + // Record the result. Success will be recorded by the caller. + if (result != ACCESS_RESULT_THIS_PID) + RecordAccessResult(result); + } + + return have_file; +} + +// static +void FileMetricsProvider::FinishedWithSource(SourceInfo* source, + AccessResult result) { + // Different source types require different post-processing. + switch (source->type) { + case SOURCE_HISTOGRAMS_ATOMIC_FILE: + case SOURCE_HISTOGRAMS_ATOMIC_DIR: + // Done with this file so delete the allocator and its owned file. + source->allocator.reset(); + // Remove the file if has been recorded. This prevents them from + // accumulating or also being recorded by different instances of + // the browser. + if (result == ACCESS_RESULT_SUCCESS || + result == ACCESS_RESULT_NOT_MODIFIED || + result == ACCESS_RESULT_MEMORY_DELETED || + result == ACCESS_RESULT_TOO_OLD) { + DeleteFileWhenPossible(source->path); + } + break; + case SOURCE_HISTOGRAMS_ACTIVE_FILE: + // Keep the allocator open so it doesn't have to be re-mapped each + // time. This also allows the contents to be merged on-demand. + break; + } +} + +// static +void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner( + SourceInfoList* sources) { + // This method has all state information passed in |sources| and is intended + // to run on a worker thread rather than the UI thread. + for (std::unique_ptr& source : *sources) { + AccessResult result; + do { + result = CheckAndMapMetricSource(source.get()); + + // Some results are not reported in order to keep the dashboard clean. + if (result != ACCESS_RESULT_DOESNT_EXIST && + result != ACCESS_RESULT_NOT_MODIFIED && + result != ACCESS_RESULT_THIS_PID) { + RecordAccessResult(result); + } + + // If there are no files (or no more files) in this source, stop now. + if (result == ACCESS_RESULT_DOESNT_EXIST) + break; + + // Mapping was successful. Merge it. + if (result == ACCESS_RESULT_SUCCESS) { + // Metrics associated with internal profiles have to be fetched directly + // so just keep the mapping for use by the main thread. + if (source->association == ASSOCIATE_INTERNAL_PROFILE) + break; + + MergeHistogramDeltasFromSource(source.get()); + DCHECK(source->read_complete); + } + + // All done with this source. + FinishedWithSource(source.get(), result); + + // If it's a directory, keep trying until a file is successfully opened. + // When there are no more files, ACCESS_RESULT_DOESNT_EXIST will be + // returned and the loop will exit above. + } while (result != ACCESS_RESULT_SUCCESS && !source->directory.empty()); + + // If the set of known files is empty, clear the object so the next run + // will do a fresh scan of the directory. + if (source->found_files && source->found_files->empty()) + source->found_files.reset(); + } +} + +// This method has all state information passed in |source| and is intended +// to run on a worker thread rather than the UI thread. +// static +FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapMetricSource( + SourceInfo* source) { + // If source was read, clean up after it. + if (source->read_complete) + FinishedWithSource(source, ACCESS_RESULT_SUCCESS); + source->read_complete = false; + DCHECK(!source->allocator); + + // If the source is a directory, look for files within it. + if (!source->directory.empty() && !LocateNextFileInDirectory(source)) + return ACCESS_RESULT_DOESNT_EXIST; + + // Do basic validation on the file metadata. + base::File::Info info; + if (!base::GetFileInfo(source->path, &info)) + return ACCESS_RESULT_DOESNT_EXIST; + + if (info.is_directory || info.size == 0) + return ACCESS_RESULT_INVALID_FILE; + + if (source->last_seen >= info.last_modified) + return ACCESS_RESULT_NOT_MODIFIED; + if (source->max_age != base::TimeDelta() && + base::Time::Now() - info.last_modified > source->max_age) { + return ACCESS_RESULT_TOO_OLD; + } + + // Non-directory files still need to be filtered. + if (source->directory.empty()) { + AccessResult result = HandleFilterSource(source, source->path); + if (result != ACCESS_RESULT_SUCCESS) + return result; + } + + // A new file of metrics has been found. + base::File file(source->path, kSourceOptions[source->type].file_open_flags); + if (!file.IsValid()) + return ACCESS_RESULT_NO_OPEN; + + std::unique_ptr mapped(new base::MemoryMappedFile()); + if (!mapped->Initialize(std::move(file), + kSourceOptions[source->type].memory_mapped_access)) { + return ACCESS_RESULT_SYSTEM_MAP_FAILURE; + } + + // Ensure any problems below don't occur repeatedly. + source->last_seen = info.last_modified; + + // Test the validity of the file contents. + const bool read_only = kSourceOptions[source->type].is_read_only; + if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*mapped, + read_only)) { + return ACCESS_RESULT_INVALID_CONTENTS; + } + + // Map the file and validate it. + std::unique_ptr memory_allocator = + std::make_unique( + std::move(mapped), 0, 0, base::StringPiece(), read_only); + if (memory_allocator->GetMemoryState() == + base::PersistentMemoryAllocator::MEMORY_DELETED) { + return ACCESS_RESULT_MEMORY_DELETED; + } + if (memory_allocator->IsCorrupt()) + return ACCESS_RESULT_DATA_CORRUPTION; + + // Create an allocator for the mapped file. Ownership passes to the allocator. + source->allocator = std::make_unique( + std::move(memory_allocator)); + + // Check that an "independent" file has the necessary information present. + if (source->association == ASSOCIATE_INTERNAL_PROFILE && + !PersistentSystemProfile::GetSystemProfile( + *source->allocator->memory_allocator(), nullptr)) { + return ACCESS_RESULT_NO_PROFILE; + } + + return ACCESS_RESULT_SUCCESS; +} + +// static +void FileMetricsProvider::MergeHistogramDeltasFromSource(SourceInfo* source) { + DCHECK(source->allocator); + SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.SnapshotTime.File"); + base::PersistentHistogramAllocator::Iterator histogram_iter( + source->allocator.get()); + + const bool read_only = kSourceOptions[source->type].is_read_only; + int histogram_count = 0; + while (true) { + std::unique_ptr histogram = histogram_iter.GetNext(); + if (!histogram) + break; + if (read_only) { + source->allocator->MergeHistogramFinalDeltaToStatisticsRecorder( + histogram.get()); + } else { + source->allocator->MergeHistogramDeltaToStatisticsRecorder( + histogram.get()); + } + ++histogram_count; + } + + source->read_complete = true; + DVLOG(1) << "Reported " << histogram_count << " histograms from " + << source->path.value(); +} + +// static +void FileMetricsProvider::RecordHistogramSnapshotsFromSource( + base::HistogramSnapshotManager* snapshot_manager, + SourceInfo* source) { + DCHECK_NE(SOURCE_HISTOGRAMS_ACTIVE_FILE, source->type); + + base::PersistentHistogramAllocator::Iterator histogram_iter( + source->allocator.get()); + + int histogram_count = 0; + while (true) { + std::unique_ptr histogram = histogram_iter.GetNext(); + if (!histogram) + break; + snapshot_manager->PrepareFinalDelta(histogram.get()); + ++histogram_count; + } + + source->read_complete = true; + DVLOG(1) << "Reported " << histogram_count << " histograms from " + << source->path.value(); +} + +FileMetricsProvider::AccessResult FileMetricsProvider::HandleFilterSource( + SourceInfo* source, + const base::FilePath& path) { + if (!source->filter) + return ACCESS_RESULT_SUCCESS; + + // Alternatively, pass a Params object to the filter like what was originally + // used to configure the source. + // Params params(path, source->type, source->association, source->prefs_key); + FilterAction action = source->filter.Run(path); + switch (action) { + case FILTER_PROCESS_FILE: + // Process the file. + return ACCESS_RESULT_SUCCESS; + + case FILTER_ACTIVE_THIS_PID: + // Even the file for the current process has to be touched or its stamp + // will be less than "last processed" and thus skipped on future runs, + // even those done by new instances of the browser if a pref key is + // provided so that the last-uploaded stamp is recorded. + case FILTER_TRY_LATER: { + // Touch the file with the current timestamp making it (presumably) the + // newest file in the directory. + base::Time now = base::Time::Now(); + base::TouchFile(path, /*accessed=*/now, /*modified=*/now); + if (action == FILTER_ACTIVE_THIS_PID) + return ACCESS_RESULT_THIS_PID; + return ACCESS_RESULT_FILTER_TRY_LATER; + } + + case FILTER_SKIP_FILE: + switch (source->type) { + case SOURCE_HISTOGRAMS_ATOMIC_FILE: + case SOURCE_HISTOGRAMS_ATOMIC_DIR: + // Only "atomic" files are deleted (best-effort). + DeleteFileWhenPossible(path); + break; + case SOURCE_HISTOGRAMS_ACTIVE_FILE: + // File will presumably get modified elsewhere and thus tried again. + break; + } + return ACCESS_RESULT_FILTER_SKIP_FILE; + } + + // Code never gets here but some compilers don't realize that and so complain + // that "not all control paths return a value". + NOTREACHED(); + return ACCESS_RESULT_SUCCESS; +} + +void FileMetricsProvider::ScheduleSourcesCheck() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (sources_to_check_.empty()) + return; + + // Create an independent list of sources 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. + SourceInfoList* check_list = new SourceInfoList(); + std::swap(sources_to_check_, *check_list); + task_runner_->PostTaskAndReply( + FROM_HERE, + base::BindOnce( + &FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner, + base::Unretained(check_list)), + base::BindOnce(&FileMetricsProvider::RecordSourcesChecked, + weak_factory_.GetWeakPtr(), base::Owned(check_list))); +} + +void FileMetricsProvider::RecordSourcesChecked(SourceInfoList* checked) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Sources that still have an allocator at this point are read/write "active" + // files that may need their contents merged on-demand. If there is no + // allocator (not a read/write file) but a read was done on the task-runner, + // try again immediately to see if more is available (in a directory of + // files). Otherwise, remember the source for checking again at a later time. + bool did_read = false; + for (auto iter = checked->begin(); iter != checked->end();) { + auto temp = iter++; + SourceInfo* source = temp->get(); + if (source->read_complete) { + RecordSourceAsRead(source); + did_read = true; + } + if (source->allocator) { + if (source->association == ASSOCIATE_INTERNAL_PROFILE) { + sources_with_profile_.splice(sources_with_profile_.end(), *checked, + temp); + } else { + sources_mapped_.splice(sources_mapped_.end(), *checked, temp); + } + } else { + sources_to_check_.splice(sources_to_check_.end(), *checked, temp); + } + } + + // If a read was done, schedule another one immediately. In the case of a + // directory of files, this ensures that all entries get processed. It's + // done here instead of as a loop in CheckAndMergeMetricSourcesOnTaskRunner + // so that (a) it gives the disk a rest and (b) testing of individual reads + // is possible. + if (did_read) + ScheduleSourcesCheck(); +} + +void FileMetricsProvider::DeleteFileAsync(const base::FilePath& path) { + task_runner_->PostTask(FROM_HERE, + base::BindOnce(DeleteFileWhenPossible, path)); +} + +void FileMetricsProvider::RecordSourceAsRead(SourceInfo* source) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Persistently record the "last seen" timestamp of the source file to + // ensure that the file is never read again unless it is modified again. + if (pref_service_ && !source->prefs_key.empty()) { + pref_service_->SetInt64( + metrics::prefs::kMetricsLastSeenPrefix + source->prefs_key, + source->last_seen.ToInternalValue()); + } +} + +void FileMetricsProvider::OnDidCreateMetricsLog() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // 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 a MayBlock() TaskRunner so as to not cause delays on the main UI + // thread (which is currently where metric collection is done). + ScheduleSourcesCheck(); + + // Clear any data for initial metrics since they're always reported + // before the first call to this method. It couldn't be released after + // being reported in RecordInitialHistogramSnapshots because the data + // will continue to be used by the caller after that method returns. Once + // here, though, all actions to be done on the data have been completed. + for (const std::unique_ptr& source : sources_for_previous_run_) + DeleteFileAsync(source->path); + sources_for_previous_run_.clear(); +} + +bool FileMetricsProvider::ProvideIndependentMetrics( + SystemProfileProto* system_profile_proto, + base::HistogramSnapshotManager* snapshot_manager) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + while (!sources_with_profile_.empty()) { + SourceInfo* source = sources_with_profile_.begin()->get(); + DCHECK(source->allocator); + + bool success = false; + RecordEmbeddedProfileResult(EMBEDDED_PROFILE_ATTEMPT); + if (PersistentSystemProfile::GetSystemProfile( + *source->allocator->memory_allocator(), system_profile_proto)) { + RecordHistogramSnapshotsFromSource(snapshot_manager, source); + success = true; + RecordEmbeddedProfileResult(EMBEDDED_PROFILE_FOUND); + } else { + RecordEmbeddedProfileResult(EMBEDDED_PROFILE_DROPPED); + + // TODO(bcwhite): Remove these once crbug/695880 is resolved. + + int histogram_count = 0; + base::PersistentHistogramAllocator::Iterator histogram_iter( + source->allocator.get()); + while (histogram_iter.GetNext()) { + ++histogram_count; + } + UMA_HISTOGRAM_COUNTS_10000( + "UMA.FileMetricsProvider.EmbeddedProfile.DroppedHistogramCount", + histogram_count); + } + + // Regardless of whether this source was successfully recorded, it is never + // read again. + source->read_complete = true; + RecordSourceAsRead(source); + sources_to_check_.splice(sources_to_check_.end(), sources_with_profile_, + sources_with_profile_.begin()); + ScheduleSourcesCheck(); + + if (success) + return true; + } + + return false; +} + +bool FileMetricsProvider::HasPreviousSessionData() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Check all sources for previous run to see if they need to be read. + for (auto iter = sources_for_previous_run_.begin(); + iter != sources_for_previous_run_.end();) { + SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.InitialCheckTime.File"); + + auto temp = iter++; + SourceInfo* source = temp->get(); + + // This would normally be done on a background I/O thread but there + // hasn't been a chance to run any at the time this method is called. + // Do the check in-line. + AccessResult result = CheckAndMapMetricSource(source); + UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.InitialAccessResult", + result, ACCESS_RESULT_MAX); + + // If it couldn't be accessed, remove it from the list. There is only ever + // one chance to record it so no point keeping it around for later. Also + // mark it as having been read since uploading it with a future browser + // run would associate it with the then-previous run which would no longer + // be the run from which it came. + if (result != ACCESS_RESULT_SUCCESS) { + DCHECK(!source->allocator); + RecordSourceAsRead(source); + DeleteFileAsync(source->path); + sources_for_previous_run_.erase(temp); + continue; + } + + DCHECK(source->allocator); + + // If the source should be associated with an existing internal profile, + // move it to |sources_with_profile_| for later upload. + if (source->association == ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN) { + if (PersistentSystemProfile::HasSystemProfile( + *source->allocator->memory_allocator())) { + RecordEmbeddedProfileResult(EMBEDDED_PROFILE_ATTEMPT); + RecordEmbeddedProfileResult(EMBEDDED_PROFILE_FALLBACK); + sources_with_profile_.splice(sources_with_profile_.end(), + sources_for_previous_run_, temp); + } + } + } + + return !sources_for_previous_run_.empty(); +} + +void FileMetricsProvider::RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + for (const std::unique_ptr& source : sources_for_previous_run_) { + SCOPED_UMA_HISTOGRAM_TIMER( + "UMA.FileMetricsProvider.InitialSnapshotTime.File"); + + // The source needs to have an allocator attached to it in order to read + // histograms out of it. + DCHECK(!source->read_complete); + DCHECK(source->allocator); + + // Dump all histograms contained within the source to the snapshot-manager. + RecordHistogramSnapshotsFromSource(snapshot_manager, source.get()); + + // Update the last-seen time so it isn't read again unless it changes. + RecordSourceAsRead(source.get()); + } +} + +void FileMetricsProvider::MergeHistogramDeltas() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + for (std::unique_ptr& source : sources_mapped_) { + MergeHistogramDeltasFromSource(source.get()); + } +} + +} // namespace metrics diff --git a/components/metrics/file_metrics_provider.h b/components/metrics/file_metrics_provider.h new file mode 100644 index 0000000000000..7465d93d8734a --- /dev/null +++ b/components/metrics/file_metrics_provider.h @@ -0,0 +1,323 @@ +// 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 +#include +#include + +#include "base/callback_forward.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/weak_ptr.h" +#include "base/metrics/statistics_recorder.h" +#include "base/sequence_checker.h" +#include "base/time/time.h" +#include "components/metrics/metrics_provider.h" + +class PrefRegistrySimple; +class PrefService; + +namespace base { +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 MetricsProvider, + public base::StatisticsRecorder::HistogramProvider { + public: + struct Params; + + enum SourceType { + // "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. Once the file has been read, an attempt will be made to + // delete it thus providing some measure of safety should different + // instantiations (such as by different users of a system-level install) + // try to read it. In case the delete operation fails, this class + // persistently tracks the last-modified time of the file so it will + // not be read a second time. + SOURCE_HISTOGRAMS_ATOMIC_FILE, + + // A directory of atomic PMA files. This handles a directory in which + // files of metrics are atomically added. Only files ending with ".pma" + // will be read. They are read according to their last-modified time and + // never read more that once (unless they change). Only one file will + // be read per reporting cycle. Filenames that start with a dot (.) or + // an underscore (_) are ignored so temporary files (perhaps created by + // the ImportantFileWriter) will not get read. Files that have been + // read will be attempted to be deleted; should those files not be + // deletable by this process, it is the reponsibility of the producer + // to keep the directory pruned in some manner. Added files must have a + // timestamp later (not the same or earlier) than the newest file that + // already exists or it may be assumed to have been already uploaded. + SOURCE_HISTOGRAMS_ATOMIC_DIR, + + // "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. + SOURCE_HISTOGRAMS_ACTIVE_FILE, + }; + + enum SourceAssociation { + // Associates the metrics in the file with the current run of the browser. + // The reporting will take place as part of the normal logging of + // histograms. + ASSOCIATE_CURRENT_RUN, + + // Associates the metrics in the file with the previous run of the browesr. + // The reporting will take place as part of the "stability" histograms. + // This is important when metrics are dumped as part of a crash of the + // previous run. This can only be used with FILE_HISTOGRAMS_ATOMIC. + ASSOCIATE_PREVIOUS_RUN, + + // Associates the metrics in the file with the a profile embedded in the + // same file. The reporting will take place at a convenient time after + // startup when the browser is otherwise idle. If there is no embedded + // system profile, these metrics will be lost. + ASSOCIATE_INTERNAL_PROFILE, + + // Like above but fall back to ASSOCIATE_PREVIOUS_RUN if there is no + // embedded profile. This has a small cost during startup as that is + // when previous-run metrics are sent so the file has be checked at + // that time even though actual transfer will be delayed if an + // embedded profile is found. + ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN, + }; + + enum FilterAction { + // Process this file normally. + FILTER_PROCESS_FILE, + + // This file is the active metrics file for the current process. Don't + // do anything with it. This is effectively "try later" but isn't + // added to the results histogram because the file has to be ignored + // throughout the life of the browser and that skews the distribution. + FILTER_ACTIVE_THIS_PID, + + // Try again. This could happen within milliseconds or minutes but no other + // files from the same source will get processed in between. The process + // must have permission to "touch" the file and alter its last-modified + // time because files are always processed in order of those stamps. + FILTER_TRY_LATER, + + // Skip this file. This file will not be processed until it has changed + // (i.e. had its last-modifided time updated). If it is "atomic", an + // attempt will be made to delete it. + FILTER_SKIP_FILE, + }; + + // A "filter" can be defined to determine what to do on a per-file basis. + // This is called only after a file has been found to be the next one to + // be processed so it's okay if filter calls are relatively expensive. + // Calls are made on a background thread of low-priority and capable of + // doing I/O. + using FilterCallback = + base::RepeatingCallback; + + // Parameters for RegisterSource, defined as a structure to allow new + // ones to be added (with default values) that doesn't require changes + // to all call sites. + struct Params { + Params(const base::FilePath& path, + SourceType type, + SourceAssociation association, + base::StringPiece prefs_key = base::StringPiece()); + + ~Params(); + + // The standard parameters, set during construction. + const base::FilePath path; + const SourceType type; + const SourceAssociation association; + const base::StringPiece prefs_key; + + // Other parameters that can be set after construction. + FilterCallback filter; // Run-time check for what to do with file. + base::TimeDelta max_age; // Maximum age of a file (0=unlimited). + size_t max_dir_kib = 0; // Maximum bytes in a directory (0=inf). + size_t max_dir_files = 100; // Maximum files in a directory (0=inf). + }; + + explicit FileMetricsProvider(PrefService* local_state); + ~FileMetricsProvider() override; + + // Indicates a file or directory to be monitored and how the file or files + // within that directory are used. Because some metadata may need to 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 (nullptr will work) if + // no persistence is required. ACTIVE files shouldn't have a pref key as + // they update internal state about what has been previously sent. + void RegisterSource(const Params& params); + + // 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); + + // Sets the task runner to use for testing. + static void SetTaskRunnerForTesting( + const scoped_refptr& task_runner); + + private: + friend class FileMetricsProviderTest; + + // 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, + + // File could not be opened. + ACCESS_RESULT_NO_OPEN, + + // File contents were internally deleted. + ACCESS_RESULT_MEMORY_DELETED, + + // File is scheduled to be tried again later. + ACCESS_RESULT_FILTER_TRY_LATER, + + // File was skipped according to filtering rules. + ACCESS_RESULT_FILTER_SKIP_FILE, + + // File was skipped because it exceeds the maximum age. + ACCESS_RESULT_TOO_OLD, + + // File was skipped because too many files in directory. + ACCESS_RESULT_TOO_MANY_FILES, + + // File was skipped because too many bytes in directory. + ACCESS_RESULT_TOO_MANY_BYTES, + + // The file was skipped because it's being written by this process. + ACCESS_RESULT_THIS_PID, + + // The file had no embedded system profile. + ACCESS_RESULT_NO_PROFILE, + + // The file had internal data corruption. + ACCESS_RESULT_DATA_CORRUPTION, + + ACCESS_RESULT_MAX + }; + + // Information about sources being monitored; defined and used exclusively + // inside the .cc file. + struct SourceInfo; + using SourceInfoList = std::list>; + + // Records an access result in a histogram. + static void RecordAccessResult(AccessResult result); + + // Looks for the next file to read within a directory. Returns true if a + // file was found. This is part of CheckAndMapNewMetricSourcesOnTaskRunner + // and so runs on an thread capable of I/O. The |source| structure will + // be internally updated to indicate the next file to be read. + static bool LocateNextFileInDirectory(SourceInfo* source); + + // Handles the completion of a source. + static void FinishedWithSource(SourceInfo* source, AccessResult result); + + // Checks a list of sources (on a task-runner allowed to do I/O) and merge + // any data found within them. + static void CheckAndMergeMetricSourcesOnTaskRunner(SourceInfoList* sources); + + // Checks a single source and maps it into memory. + static AccessResult CheckAndMapMetricSource(SourceInfo* source); + + // Merges all of the histograms from a |source| to the StatisticsRecorder. + static void MergeHistogramDeltasFromSource(SourceInfo* source); + + // Records all histograms from a given source via a snapshot-manager. + static void RecordHistogramSnapshotsFromSource( + base::HistogramSnapshotManager* snapshot_manager, + SourceInfo* source); + + // Calls source filter (if any) and returns the desired action. + static AccessResult HandleFilterSource(SourceInfo* source, + const base::FilePath& path); + + // Creates a task to check all monitored sources for updates. + void ScheduleSourcesCheck(); + + // Takes a list of sources checked by an external task and determines what + // to do with each. + void RecordSourcesChecked(SourceInfoList* checked); + + // Schedules the deletion of a file in the background using the task-runner. + void DeleteFileAsync(const base::FilePath& path); + + // Updates the persistent state information to show a source as being read. + void RecordSourceAsRead(SourceInfo* source); + + // metrics::MetricsProvider: + void OnDidCreateMetricsLog() override; + bool ProvideIndependentMetrics( + SystemProfileProto* system_profile_proto, + base::HistogramSnapshotManager* snapshot_manager) override; + bool HasPreviousSessionData() override; + void RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) override; + + // base::StatisticsRecorder::HistogramProvider: + void MergeHistogramDeltas() override; + + // A task-runner capable of performing I/O. + scoped_refptr task_runner_; + + // A list of sources not currently active that need to be checked for changes. + SourceInfoList sources_to_check_; + + // A list of currently active sources to be merged when required. + SourceInfoList sources_mapped_; + + // A list of currently active sources to be merged when required. + SourceInfoList sources_with_profile_; + + // A list of sources for a previous run. These are held separately because + // they are not subject to the periodic background checking that handles + // metrics for the current run. + SourceInfoList sources_for_previous_run_; + + // The preferences-service used to store persistent state about sources. + PrefService* pref_service_; + + SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_ diff --git a/components/metrics/file_metrics_provider_unittest.cc b/components/metrics/file_metrics_provider_unittest.cc new file mode 100644 index 0000000000000..ca5ca9ae35787 --- /dev/null +++ b/components/metrics/file_metrics_provider_unittest.cc @@ -0,0 +1,1069 @@ +// 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 + +#include "base/files/file_util.h" +#include "base/files/memory_mapped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.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/sparse_histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/strings/stringprintf.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/persistent_system_profile.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.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 { + // Only remember locally created histograms; they have exactly 2 chars. + if (strlen(histogram.histogram_name()) == 2) + recorded_delta_histogram_names_.push_back(histogram.histogram_name()); + } + + std::vector GetRecordedDeltaHistogramNames() { + return recorded_delta_histogram_names_; + } + + private: + std::vector recorded_delta_histogram_names_; + + DISALLOW_COPY_AND_ASSIGN(HistogramFlattenerDeltaRecorder); +}; + + +class FileMetricsProviderTest : public testing::TestWithParam { + protected: + const size_t kSmallFileSize = 64 << 10; // 64 KiB + const size_t kLargeFileSize = 2 << 20; // 2 MiB + + enum : int { kMaxCreateHistograms = 10 }; + + FileMetricsProviderTest() + : create_large_files_(GetParam()), + task_runner_(new base::TestSimpleTaskRunner()), + thread_task_runner_handle_(task_runner_), + statistics_recorder_( + base::StatisticsRecorder::CreateTemporaryForTesting()), + prefs_(new TestingPrefServiceSimple) { + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + FileMetricsProvider::RegisterPrefs(prefs_->registry(), kMetricsName); + FileMetricsProvider::SetTaskRunnerForTesting(task_runner_); + } + + ~FileMetricsProviderTest() override { + // Clear out any final remaining tasks. + task_runner_->RunUntilIdle(); + FileMetricsProvider::SetTaskRunnerForTesting(nullptr); + DCHECK_EQ(0U, filter_actions_remaining_); + // If a global histogram allocator exists at this point then it likely + // acquired histograms that will continue to point to the released + // memory and potentially cause use-after-free memory corruption. + DCHECK(!base::GlobalHistogramAllocator::Get()); + } + + TestingPrefServiceSimple* prefs() { return prefs_.get(); } + base::FilePath temp_dir() { return temp_dir_.GetPath(); } + base::FilePath metrics_file() { + return temp_dir_.GetPath().AppendASCII(kMetricsFilename); + } + + FileMetricsProvider* provider() { + if (!provider_) + provider_.reset(new FileMetricsProvider(prefs())); + return provider_.get(); + } + + void OnDidCreateMetricsLog() { + provider()->OnDidCreateMetricsLog(); + } + + bool HasPreviousSessionData() { return provider()->HasPreviousSessionData(); } + + void MergeHistogramDeltas() { + provider()->MergeHistogramDeltas(); + } + + bool ProvideIndependentMetrics( + SystemProfileProto* profile_proto, + base::HistogramSnapshotManager* snapshot_manager) { + return provider()->ProvideIndependentMetrics(profile_proto, + snapshot_manager); + } + + void RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { + provider()->RecordInitialHistogramSnapshots(snapshot_manager); + } + + size_t GetSnapshotHistogramCount() { + // Merge the data from the allocator into the StatisticsRecorder. + provider()->MergeHistogramDeltas(); + + // Flatten what is known to see what has changed since the last time. + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + // "true" to the begin() includes histograms held in persistent storage. + base::StatisticsRecorder::PrepareDeltas(true, base::Histogram::kNoFlags, + base::Histogram::kNoFlags, + &snapshot_manager); + return flattener.GetRecordedDeltaHistogramNames().size(); + } + + size_t GetIndependentHistogramCount() { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + SystemProfileProto profile_proto; + if (!provider()->ProvideIndependentMetrics(&profile_proto, + &snapshot_manager)) { + return 0; + } + return flattener.GetRecordedDeltaHistogramNames().size(); + } + + void CreateGlobalHistograms(int histogram_count) { + DCHECK_GT(kMaxCreateHistograms, histogram_count); + + // Create both sparse and normal histograms in the allocator. + created_histograms_[0] = base::SparseHistogram::FactoryGet("h0", 0); + created_histograms_[0]->Add(0); + for (int i = 1; i < histogram_count; ++i) { + created_histograms_[i] = base::Histogram::FactoryGet( + base::StringPrintf("h%d", i), 1, 100, 10, 0); + created_histograms_[i]->Add(i); + } + } + + void RunTasks() { + // Run pending tasks twice: Once for IPC calls, once for replies. Don't + // use RunUntilIdle() because that can do more work than desired. + task_runner_->RunPendingTasks(); + task_runner_->RunPendingTasks(); + } + + void WriteMetricsFile(const base::FilePath& path, + base::PersistentHistogramAllocator* metrics) { + base::File writer(path, + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); + // Use DCHECK so the stack-trace will indicate where this was called. + DCHECK(writer.IsValid()) << path.value(); + size_t file_size = create_large_files_ ? metrics->size() : metrics->used(); + int written = writer.Write(0, (const char*)metrics->data(), file_size); + DCHECK_EQ(static_cast(file_size), written); + } + + void WriteMetricsFileAtTime(const base::FilePath& path, + base::PersistentHistogramAllocator* metrics, + base::Time write_time) { + WriteMetricsFile(path, metrics); + base::TouchFile(path, write_time, write_time); + } + + std::unique_ptr + CreateMetricsFileWithHistograms( + const base::FilePath& file_path, + base::Time write_time, + int histogram_count, + const std::function& + callback) { + base::GlobalHistogramAllocator::CreateWithLocalMemory( + create_large_files_ ? kLargeFileSize : kSmallFileSize, + 0, kMetricsName); + + CreateGlobalHistograms(histogram_count); + + std::unique_ptr histogram_allocator = + base::GlobalHistogramAllocator::ReleaseForTesting(); + callback(histogram_allocator.get()); + + WriteMetricsFileAtTime(file_path, histogram_allocator.get(), write_time); + return histogram_allocator; + } + + std::unique_ptr + CreateMetricsFileWithHistograms(int histogram_count) { + return CreateMetricsFileWithHistograms( + metrics_file(), base::Time::Now(), histogram_count, + [](base::PersistentHistogramAllocator* allocator) {}); + } + + base::HistogramBase* GetCreatedHistogram(int index) { + DCHECK_GT(kMaxCreateHistograms, index); + return created_histograms_[index]; + } + + void SetFilterActions(FileMetricsProvider::Params* params, + const FileMetricsProvider::FilterAction* actions, + size_t count) { + filter_actions_ = actions; + filter_actions_remaining_ = count; + params->filter = base::Bind(&FileMetricsProviderTest::FilterSourcePath, + base::Unretained(this)); + } + + const bool create_large_files_; + + private: + FileMetricsProvider::FilterAction FilterSourcePath( + const base::FilePath& path) { + DCHECK_LT(0U, filter_actions_remaining_); + --filter_actions_remaining_; + return *filter_actions_++; + } + + scoped_refptr task_runner_; + base::ThreadTaskRunnerHandle thread_task_runner_handle_; + + std::unique_ptr statistics_recorder_; + base::ScopedTempDir temp_dir_; + std::unique_ptr prefs_; + std::unique_ptr provider_; + base::HistogramBase* created_histograms_[kMaxCreateHistograms]; + + const FileMetricsProvider::FilterAction* filter_actions_ = nullptr; + size_t filter_actions_remaining_ = 0; + + DISALLOW_COPY_AND_ASSIGN(FileMetricsProviderTest); +}; + +// Run all test cases with both small and large files. +INSTANTIATE_TEST_CASE_P(SmallAndLargeFiles, + FileMetricsProviderTest, + testing::Bool()); + + +TEST_P(FileMetricsProviderTest, AccessMetrics) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::Time metrics_time = base::Time::Now() - base::TimeDelta::FromMinutes(5); + std::unique_ptr histogram_allocator = + CreateMetricsFileWithHistograms(2); + ASSERT_TRUE(PathExists(metrics_file())); + base::TouchFile(metrics_file(), metrics_time, metrics_time); + + // Register the file and allow the "checker" task to run. + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName)); + + // Record embedded snapshots via snapshot-manager. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_file())); + + // Make sure a second call to the snapshot-recorder doesn't break anything. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + + // File should have been deleted but recreate it to test behavior should + // the file not be deleteable by this process. + WriteMetricsFileAtTime(metrics_file(), histogram_allocator.get(), + metrics_time); + + // Second full run on the same file should produce nothing. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_file())); + + // Recreate the file to indicate that it is "new" and must be recorded. + metrics_time = metrics_time + base::TimeDelta::FromMinutes(1); + WriteMetricsFileAtTime(metrics_file(), histogram_allocator.get(), + metrics_time); + + // This run should again have "new" histograms. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessTimeLimitedFile) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::Time metrics_time = base::Time::Now() - base::TimeDelta::FromHours(5); + std::unique_ptr histogram_allocator = + CreateMetricsFileWithHistograms(2); + ASSERT_TRUE(PathExists(metrics_file())); + base::TouchFile(metrics_file(), metrics_time, metrics_time); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + params.max_age = base::TimeDelta::FromHours(1); + provider()->RegisterSource(params); + + // Attempt to access the file should return nothing. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, FilterDelaysFile) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::Time now_time = base::Time::Now(); + base::Time metrics_time = now_time - base::TimeDelta::FromMinutes(5); + std::unique_ptr histogram_allocator = + CreateMetricsFileWithHistograms(2); + ASSERT_TRUE(PathExists(metrics_file())); + base::TouchFile(metrics_file(), metrics_time, metrics_time); + base::File::Info fileinfo; + ASSERT_TRUE(base::GetFileInfo(metrics_file(), &fileinfo)); + EXPECT_GT(base::Time::Now(), fileinfo.last_modified); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + const FileMetricsProvider::FilterAction actions[] = { + FileMetricsProvider::FILTER_TRY_LATER, + FileMetricsProvider::FILTER_PROCESS_FILE}; + SetFilterActions(¶ms, actions, arraysize(actions)); + provider()->RegisterSource(params); + + // Processing the file should touch it but yield no results. File timestamp + // accuracy is limited so compare the touched time to a couple seconds past. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_file())); + ASSERT_TRUE(base::GetFileInfo(metrics_file(), &fileinfo)); + EXPECT_LT(metrics_time, fileinfo.last_modified); + EXPECT_LE(now_time - base::TimeDelta::FromSeconds(2), fileinfo.last_modified); + + // Second full run on the same file should process the file. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, FilterSkipsFile) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::Time now_time = base::Time::Now(); + base::Time metrics_time = now_time - base::TimeDelta::FromMinutes(5); + std::unique_ptr histogram_allocator = + CreateMetricsFileWithHistograms(2); + ASSERT_TRUE(PathExists(metrics_file())); + base::TouchFile(metrics_file(), metrics_time, metrics_time); + base::File::Info fileinfo; + ASSERT_TRUE(base::GetFileInfo(metrics_file(), &fileinfo)); + EXPECT_GT(base::Time::Now(), fileinfo.last_modified); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + const FileMetricsProvider::FilterAction actions[] = { + FileMetricsProvider::FILTER_SKIP_FILE}; + SetFilterActions(¶ms, actions, arraysize(actions)); + provider()->RegisterSource(params); + + // Processing the file should delete it. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessDirectory) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::GlobalHistogramAllocator::CreateWithLocalMemory( + 64 << 10, 0, kMetricsName); + base::GlobalHistogramAllocator* allocator = + base::GlobalHistogramAllocator::Get(); + base::HistogramBase* histogram; + + // Create files starting with a timestamp a few minutes back. + base::Time base_time = base::Time::Now() - base::TimeDelta::FromMinutes(10); + + // Create some files in an odd order. The files are "touched" back in time to + // ensure that each file has a later timestamp on disk than the previous one. + base::ScopedTempDir metrics_files; + EXPECT_TRUE(metrics_files.CreateUniqueTempDir()); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII(".foo.pma"), + allocator, base_time); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("_bar.pma"), + allocator, base_time); + + histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0); + histogram->Add(1); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(1)); + + histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0); + histogram->Add(2); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("c2.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(2)); + + histogram = base::Histogram::FactoryGet("h3", 1, 100, 10, 0); + histogram->Add(3); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b3.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(3)); + + histogram = base::Histogram::FactoryGet("h4", 1, 100, 10, 0); + histogram->Add(3); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("d4.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(4)); + + base::TouchFile(metrics_files.GetPath().AppendASCII("b3.pma"), + base_time + base::TimeDelta::FromMinutes(5), + base_time + base::TimeDelta::FromMinutes(5)); + + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("baz"), allocator, + base_time + base::TimeDelta::FromMinutes(6)); + + // The global allocator has to be detached here so that no metrics created + // by code called below get stored in it as that would make for potential + // use-after-free operations if that code is called again. + base::GlobalHistogramAllocator::ReleaseForTesting(); + + // Register the file and allow the "checker" task to run. + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_files.GetPath(), + FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName)); + + // Files could come out in the order: a1, c2, d4, b3. They are recognizeable + // by the number of histograms contained within each. + const uint32_t expect_order[] = {1, 2, 4, 3, 0}; + for (size_t i = 0; i < arraysize(expect_order); ++i) { + // Record embedded snapshots via snapshot-manager. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(expect_order[i], GetSnapshotHistogramCount()) << i; + } + + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("c2.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b3.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("d4.pma"))); + EXPECT_TRUE( + base::PathExists(metrics_files.GetPath().AppendASCII(".foo.pma"))); + EXPECT_TRUE( + base::PathExists(metrics_files.GetPath().AppendASCII("_bar.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("baz"))); +} + +TEST_P(FileMetricsProviderTest, AccessDirectoryWithInvalidFiles) { + ASSERT_FALSE(PathExists(metrics_file())); + + // Create files starting with a timestamp a few minutes back. + base::Time base_time = base::Time::Now() - base::TimeDelta::FromMinutes(10); + + base::ScopedTempDir metrics_files; + EXPECT_TRUE(metrics_files.CreateUniqueTempDir()); + + CreateMetricsFileWithHistograms( + metrics_files.GetPath().AppendASCII("h1.pma"), + base_time + base::TimeDelta::FromMinutes(1), 1, + [](base::PersistentHistogramAllocator* allocator) { + allocator->memory_allocator()->SetMemoryState( + base::PersistentMemoryAllocator::MEMORY_DELETED); + }); + + CreateMetricsFileWithHistograms( + metrics_files.GetPath().AppendASCII("h2.pma"), + base_time + base::TimeDelta::FromMinutes(2), 2, + [](base::PersistentHistogramAllocator* allocator) { + SystemProfileProto profile_proto; + SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial(); + trial->set_name_id(123); + trial->set_group_id(456); + + PersistentSystemProfile persistent_profile; + persistent_profile.RegisterPersistentAllocator( + allocator->memory_allocator()); + persistent_profile.SetSystemProfile(profile_proto, true); + }); + + CreateMetricsFileWithHistograms( + metrics_files.GetPath().AppendASCII("h3.pma"), + base_time + base::TimeDelta::FromMinutes(3), 3, + [](base::PersistentHistogramAllocator* allocator) { + allocator->memory_allocator()->SetMemoryState( + base::PersistentMemoryAllocator::MEMORY_DELETED); + }); + + { + base::File empty(metrics_files.GetPath().AppendASCII("h4.pma"), + base::File::FLAG_CREATE | base::File::FLAG_WRITE); + } + base::TouchFile(metrics_files.GetPath().AppendASCII("h4.pma"), + base_time + base::TimeDelta::FromMinutes(4), + base_time + base::TimeDelta::FromMinutes(4)); + + // Register the file and allow the "checker" task to run. + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_files.GetPath(), + FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName)); + + // No files yet. + EXPECT_EQ(0U, GetIndependentHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h1.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma"))); + + // H1 should be skipped and H2 available. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetIndependentHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h1.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma"))); + + // Nothing else should be found but the last (valid but empty) file will + // stick around to be processed later (should it get expanded). + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetIndependentHistogramCount()); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma"))); + EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma"))); +} + +TEST_P(FileMetricsProviderTest, AccessTimeLimitedDirectory) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::GlobalHistogramAllocator::CreateWithLocalMemory(64 << 10, 0, + kMetricsName); + base::GlobalHistogramAllocator* allocator = + base::GlobalHistogramAllocator::Get(); + base::HistogramBase* histogram; + + // Create one old file and one new file. + base::ScopedTempDir metrics_files; + EXPECT_TRUE(metrics_files.CreateUniqueTempDir()); + histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0); + histogram->Add(1); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"), + allocator, + base::Time::Now() - base::TimeDelta::FromHours(1)); + + histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0); + histogram->Add(2); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b2.pma"), + allocator, base::Time::Now()); + + // The global allocator has to be detached here so that no metrics created + // by code called below get stored in it as that would make for potential + // use-after-free operations if that code is called again. + base::GlobalHistogramAllocator::ReleaseForTesting(); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_files.GetPath(), + FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + params.max_age = base::TimeDelta::FromMinutes(30); + provider()->RegisterSource(params); + + // Only b2, with 2 histograms, should be read. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b2.pma"))); +} + +TEST_P(FileMetricsProviderTest, AccessCountLimitedDirectory) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::GlobalHistogramAllocator::CreateWithLocalMemory(64 << 10, 0, + kMetricsName); + base::GlobalHistogramAllocator* allocator = + base::GlobalHistogramAllocator::Get(); + base::HistogramBase* histogram; + + // Create one old file and one new file. + base::ScopedTempDir metrics_files; + EXPECT_TRUE(metrics_files.CreateUniqueTempDir()); + histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0); + histogram->Add(1); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"), + allocator, + base::Time::Now() - base::TimeDelta::FromHours(1)); + + histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0); + histogram->Add(2); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b2.pma"), + allocator, base::Time::Now()); + + // The global allocator has to be detached here so that no metrics created + // by code called below get stored in it as that would make for potential + // use-after-free operations if that code is called again. + base::GlobalHistogramAllocator::ReleaseForTesting(); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_files.GetPath(), + FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + params.max_dir_files = 1; + provider()->RegisterSource(params); + + // Only b2, with 2 histograms, should be read. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b2.pma"))); +} + +TEST_P(FileMetricsProviderTest, AccessSizeLimitedDirectory) { + // This only works with large files that are big enough to count. + if (!create_large_files_) + return; + + ASSERT_FALSE(PathExists(metrics_file())); + + size_t file_size_kib = 64; + base::GlobalHistogramAllocator::CreateWithLocalMemory(file_size_kib << 10, 0, + kMetricsName); + base::GlobalHistogramAllocator* allocator = + base::GlobalHistogramAllocator::Get(); + base::HistogramBase* histogram; + + // Create one old file and one new file. + base::ScopedTempDir metrics_files; + EXPECT_TRUE(metrics_files.CreateUniqueTempDir()); + histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0); + histogram->Add(1); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"), + allocator, + base::Time::Now() - base::TimeDelta::FromHours(1)); + + histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0); + histogram->Add(2); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b2.pma"), + allocator, base::Time::Now()); + + // The global allocator has to be detached here so that no metrics created + // by code called below get stored in it as that would make for potential + // use-after-free operations if that code is called again. + base::GlobalHistogramAllocator::ReleaseForTesting(); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_files.GetPath(), + FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + params.max_dir_kib = file_size_kib + 1; + provider()->RegisterSource(params); + + // Only b2, with 2 histograms, should be read. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b2.pma"))); +} + +TEST_P(FileMetricsProviderTest, AccessFilteredDirectory) { + ASSERT_FALSE(PathExists(metrics_file())); + + base::GlobalHistogramAllocator::CreateWithLocalMemory(64 << 10, 0, + kMetricsName); + base::GlobalHistogramAllocator* allocator = + base::GlobalHistogramAllocator::Get(); + base::HistogramBase* histogram; + + // Create files starting with a timestamp a few minutes back. + base::Time base_time = base::Time::Now() - base::TimeDelta::FromMinutes(10); + + // Create some files in an odd order. The files are "touched" back in time to + // ensure that each file has a later timestamp on disk than the previous one. + base::ScopedTempDir metrics_files; + EXPECT_TRUE(metrics_files.CreateUniqueTempDir()); + + histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0); + histogram->Add(1); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(1)); + + histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0); + histogram->Add(2); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("c2.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(2)); + + histogram = base::Histogram::FactoryGet("h3", 1, 100, 10, 0); + histogram->Add(3); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b3.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(3)); + + histogram = base::Histogram::FactoryGet("h4", 1, 100, 10, 0); + histogram->Add(3); + WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("d4.pma"), + allocator, + base_time + base::TimeDelta::FromMinutes(4)); + + base::TouchFile(metrics_files.GetPath().AppendASCII("b3.pma"), + base_time + base::TimeDelta::FromMinutes(5), + base_time + base::TimeDelta::FromMinutes(5)); + + // The global allocator has to be detached here so that no metrics created + // by code called below get stored in it as that would make for potential + // use-after-free operations if that code is called again. + base::GlobalHistogramAllocator::ReleaseForTesting(); + + // Register the file and allow the "checker" task to run. + FileMetricsProvider::Params params( + metrics_files.GetPath(), + FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName); + const FileMetricsProvider::FilterAction actions[] = { + FileMetricsProvider::FILTER_PROCESS_FILE, // a1 + FileMetricsProvider::FILTER_TRY_LATER, // c2 + FileMetricsProvider::FILTER_SKIP_FILE, // d4 + FileMetricsProvider::FILTER_PROCESS_FILE, // b3 + FileMetricsProvider::FILTER_PROCESS_FILE}; // c2 (again) + SetFilterActions(¶ms, actions, arraysize(actions)); + provider()->RegisterSource(params); + + // Files could come out in the order: a1, b3, c2. They are recognizeable + // by the number of histograms contained within each. + const uint32_t expect_order[] = {1, 3, 2, 0}; + for (size_t i = 0; i < arraysize(expect_order); ++i) { + // Record embedded snapshots via snapshot-manager. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(expect_order[i], GetSnapshotHistogramCount()) << i; + } + + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("c2.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b3.pma"))); + EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("d4.pma"))); +} + +TEST_P(FileMetricsProviderTest, AccessReadWriteMetrics) { + // Create a global histogram allocator that maps to a file. + ASSERT_FALSE(PathExists(metrics_file())); + base::GlobalHistogramAllocator::CreateWithFile( + metrics_file(), + create_large_files_ ? kLargeFileSize : kSmallFileSize, + 0, kMetricsName); + CreateGlobalHistograms(2); + ASSERT_TRUE(PathExists(metrics_file())); + base::HistogramBase* h0 = GetCreatedHistogram(0); + base::HistogramBase* h1 = GetCreatedHistogram(1); + DCHECK(h0); + DCHECK(h1); + std::unique_ptr histogram_allocator = + base::GlobalHistogramAllocator::ReleaseForTesting(); + + // Register the file and allow the "checker" task to run. + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ACTIVE_FILE, + FileMetricsProvider::ASSOCIATE_CURRENT_RUN)); + + // Record embedded snapshots via snapshot-manager. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(2U, GetSnapshotHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_file())); + + // Make sure a second call to the snapshot-recorder doesn't break anything. + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_file())); + + // Change a histogram and ensure that it's counted. + h0->Add(0); + EXPECT_EQ(1U, GetSnapshotHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_file())); + + // Change the other histogram and verify. + h1->Add(11); + EXPECT_EQ(1U, GetSnapshotHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessInitialMetrics) { + ASSERT_FALSE(PathExists(metrics_file())); + CreateMetricsFileWithHistograms(2); + + // Register the file and allow the "checker" task to run. + ASSERT_TRUE(PathExists(metrics_file())); + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_PREVIOUS_RUN, kMetricsName)); + + // Record embedded snapshots via snapshot-manager. + ASSERT_TRUE(HasPreviousSessionData()); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + RecordInitialHistogramSnapshots(&snapshot_manager); + EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size()); + } + EXPECT_TRUE(base::PathExists(metrics_file())); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_FALSE(base::PathExists(metrics_file())); + + // A run for normal histograms should produce nothing. + CreateMetricsFileWithHistograms(2); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_EQ(0U, GetSnapshotHistogramCount()); + EXPECT_TRUE(base::PathExists(metrics_file())); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_TRUE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsWithoutProfile) { + ASSERT_FALSE(PathExists(metrics_file())); + CreateMetricsFileWithHistograms(2); + + // Register the file and allow the "checker" task to run. + ASSERT_TRUE(PathExists(metrics_file())); + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName)); + + // Record embedded snapshots via snapshot-manager. + OnDidCreateMetricsLog(); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + SystemProfileProto profile; + + // A read of metrics with internal profiles should return nothing. + EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + } + EXPECT_TRUE(base::PathExists(metrics_file())); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsWithProfile) { + ASSERT_FALSE(PathExists(metrics_file())); + CreateMetricsFileWithHistograms( + metrics_file(), base::Time::Now(), 2, + [](base::PersistentHistogramAllocator* allocator) { + SystemProfileProto profile_proto; + SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial(); + trial->set_name_id(123); + trial->set_group_id(456); + + PersistentSystemProfile persistent_profile; + persistent_profile.RegisterPersistentAllocator( + allocator->memory_allocator()); + persistent_profile.SetSystemProfile(profile_proto, true); + }); + + // Register the file and allow the "checker" task to run. + ASSERT_TRUE(PathExists(metrics_file())); + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName)); + + // Record embedded snapshots via snapshot-manager. + OnDidCreateMetricsLog(); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + RecordInitialHistogramSnapshots(&snapshot_manager); + EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size()); + + // A read of metrics with internal profiles should return one result. + SystemProfileProto profile; + EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + } + EXPECT_TRUE(base::PathExists(metrics_file())); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessEmbeddedFallbackMetricsWithoutProfile) { + ASSERT_FALSE(PathExists(metrics_file())); + CreateMetricsFileWithHistograms(2); + + // Register the file and allow the "checker" task to run. + ASSERT_TRUE(PathExists(metrics_file())); + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN, + kMetricsName)); + + // Record embedded snapshots via snapshot-manager. + ASSERT_TRUE(HasPreviousSessionData()); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + RecordInitialHistogramSnapshots(&snapshot_manager); + EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size()); + + // A read of metrics with internal profiles should return nothing. + SystemProfileProto profile; + EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + } + EXPECT_TRUE(base::PathExists(metrics_file())); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessEmbeddedFallbackMetricsWithProfile) { + ASSERT_FALSE(PathExists(metrics_file())); + CreateMetricsFileWithHistograms( + metrics_file(), base::Time::Now(), 2, + [](base::PersistentHistogramAllocator* allocator) { + SystemProfileProto profile_proto; + SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial(); + trial->set_name_id(123); + trial->set_group_id(456); + + PersistentSystemProfile persistent_profile; + persistent_profile.RegisterPersistentAllocator( + allocator->memory_allocator()); + persistent_profile.SetSystemProfile(profile_proto, true); + }); + + // Register the file and allow the "checker" task to run. + ASSERT_TRUE(PathExists(metrics_file())); + provider()->RegisterSource(FileMetricsProvider::Params( + metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE, + FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN, + kMetricsName)); + + // Record embedded snapshots via snapshot-manager. + EXPECT_FALSE(HasPreviousSessionData()); + RunTasks(); + { + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + RecordInitialHistogramSnapshots(&snapshot_manager); + EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size()); + + // A read of metrics with internal profiles should return one result. + SystemProfileProto profile; + EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + } + EXPECT_TRUE(base::PathExists(metrics_file())); + OnDidCreateMetricsLog(); + RunTasks(); + EXPECT_FALSE(base::PathExists(metrics_file())); +} + +TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsFromDir) { + const int file_count = 3; + base::Time file_base_time = base::Time::Now(); + std::vector file_names; + for (int i = 0; i < file_count; ++i) { + CreateMetricsFileWithHistograms( + metrics_file(), base::Time::Now(), 2, + [](base::PersistentHistogramAllocator* allocator) { + SystemProfileProto profile_proto; + SystemProfileProto::FieldTrial* trial = + profile_proto.add_field_trial(); + trial->set_name_id(123); + trial->set_group_id(456); + + PersistentSystemProfile persistent_profile; + persistent_profile.RegisterPersistentAllocator( + allocator->memory_allocator()); + persistent_profile.SetSystemProfile(profile_proto, true); + }); + ASSERT_TRUE(PathExists(metrics_file())); + char new_name[] = "hX"; + new_name[1] = '1' + i; + base::FilePath file_name = temp_dir().AppendASCII(new_name).AddExtension( + base::PersistentMemoryAllocator::kFileExtension); + base::Time file_time = + file_base_time - base::TimeDelta::FromMinutes(file_count - i); + base::TouchFile(metrics_file(), file_time, file_time); + base::Move(metrics_file(), file_name); + file_names.push_back(std::move(file_name)); + } + + // Register the file and allow the "checker" task to run. + provider()->RegisterSource(FileMetricsProvider::Params( + temp_dir(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR, + FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE)); + + OnDidCreateMetricsLog(); + RunTasks(); + + // A read of metrics with internal profiles should return one result. + HistogramFlattenerDeltaRecorder flattener; + base::HistogramSnapshotManager snapshot_manager(&flattener); + SystemProfileProto profile; + for (int i = 0; i < file_count; ++i) { + EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager)) << i; + RunTasks(); + } + EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager)); + + OnDidCreateMetricsLog(); + RunTasks(); + for (const auto& file_name : file_names) + EXPECT_FALSE(base::PathExists(file_name)); +} + +} // namespace metrics diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni new file mode 100644 index 0000000000000..b4522465325b5 --- /dev/null +++ b/components/metrics/generate_expired_histograms_array.gni @@ -0,0 +1,52 @@ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Runs the resources map generation script other the given header files to +# produce an output file and a source_set to build it. +# +# Parameters: +# inputs: +# List of file name to read. Each file should be a .xml file with +# histogram descriptions. +# +# namespace (optional): +# Namespace in which the generated code should be scoped. If left empty, +# the code will be in the global namespace. +# +# header_filename: +# Name of the generated header file. +# +# major_branch_date_filepath: +# A path to the file with the base date. +# +# milestone_filepath: +# A path to the file with the milestone information. +# +template("generate_expired_histograms_array") { + action(target_name) { + header_filename = "$target_gen_dir/" + invoker.header_filename + + script = "//tools/metrics/histograms/generate_expired_histograms_array.py" + outputs = [ + header_filename, + ] + + inputs = invoker.inputs + major_branch_date_filepath = invoker.major_branch_date_filepath + milestone_filepath = invoker.milestone_filepath + + args = [] + + if (defined(invoker.namespace) && invoker.namespace != "") { + args += [ "-n" + invoker.namespace ] + } + + args += [ + "-o" + rebase_path(root_gen_dir, root_build_dir), + "-H" + rebase_path(header_filename, root_gen_dir), + "-d" + rebase_path(major_branch_date_filepath, root_build_dir), + "-m" + rebase_path(milestone_filepath, root_build_dir), + ] + rebase_path(inputs, root_build_dir) + } +} diff --git a/components/metrics/gpu/DEPS b/components/metrics/gpu/DEPS new file mode 100644 index 0000000000000..c2ff8a0af679b --- /dev/null +++ b/components/metrics/gpu/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/browser", + "+gpu/config", +] diff --git a/components/metrics/gpu/gpu_metrics_provider.cc b/components/metrics/gpu/gpu_metrics_provider.cc new file mode 100644 index 0000000000000..53c15fb7c878e --- /dev/null +++ b/components/metrics/gpu/gpu_metrics_provider.cc @@ -0,0 +1,37 @@ +// 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 "content/public/browser/gpu_data_manager.h" +#include "gpu/config/gpu_info.h" +#include "third_party/metrics_proto/system_profile.pb.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(); + const gpu::GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu(); + SystemProfileProto::Hardware::Graphics* gpu = + hardware->mutable_gpu(); + gpu->set_vendor_id(active_gpu.vendor_id); + gpu->set_device_id(active_gpu.device_id); + gpu->set_driver_version(active_gpu.driver_version); + gpu->set_driver_date(active_gpu.driver_date); + gpu->set_gl_vendor(gpu_info.gl_vendor); + gpu->set_gl_renderer(gpu_info.gl_renderer); +} + +} // namespace metrics diff --git a/components/metrics/gpu/gpu_metrics_provider.h b/components/metrics/gpu/gpu_metrics_provider.h new file mode 100644 index 0000000000000..581c7651ce63a --- /dev/null +++ b/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/components/metrics/histogram_encoder.cc b/components/metrics/histogram_encoder.cc new file mode 100644 index 0000000000000..4d7d945314175 --- /dev/null +++ b/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 +#include + +#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 (std::unique_ptr it = snapshot.Iterator(); + !it->Done(); it->Next()) { + base::Histogram::Sample min; + int64_t 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/components/metrics/histogram_encoder.h b/components/metrics/histogram_encoder.h new file mode 100644 index 0000000000000..332e907dc195e --- /dev/null +++ b/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 + +#include "third_party/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/components/metrics/histogram_encoder_unittest.cc b/components/metrics/histogram_encoder_unittest.cc new file mode 100644 index 0000000000000..dfe7f847d5dae --- /dev/null +++ b/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 + +#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/components/metrics/log_decoder.cc b/components/metrics/log_decoder.cc new file mode 100644 index 0000000000000..1754c2717df28 --- /dev/null +++ b/components/metrics/log_decoder.cc @@ -0,0 +1,16 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/log_decoder.h" + +#include "third_party/zlib/google/compression_utils.h" + +namespace metrics { + +bool DecodeLogData(const std::string& compressed_log_data, + std::string* log_data) { + return compression::GzipUncompress(compressed_log_data, log_data); +} + +} // namespace metrics diff --git a/components/metrics/log_decoder.h b/components/metrics/log_decoder.h new file mode 100644 index 0000000000000..c85037f932c13 --- /dev/null +++ b/components/metrics/log_decoder.h @@ -0,0 +1,21 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_LOG_DECODER_H_ +#define COMPONENTS_METRICS_LOG_DECODER_H_ + +#include + +namespace metrics { + +// Other modules can call this function instead of directly calling gzip. This +// prevents other modules from having to depend on zlib, or being aware of +// metrics' use of gzip compression, which is a metrics implementation detail. +// Returns true on success, false on failure. +bool DecodeLogData(const std::string& compressed_log_data, + std::string* log_data); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_LOG_DECODER_H_ diff --git a/components/metrics/log_store.h b/components/metrics/log_store.h new file mode 100644 index 0000000000000..9dd1a62c605ac --- /dev/null +++ b/components/metrics/log_store.h @@ -0,0 +1,49 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_LOG_STORE_H_ +#define COMPONENTS_METRICS_LOG_STORE_H_ + +#include + +namespace metrics { + +// Interface for local storage of serialized logs to be reported. +// It allows consumers to check if there are logs to consume, consume them one +// at a time by staging and discarding logs, and persist/load the whole set. +class LogStore { + public: + // Returns true if there are any logs waiting to be uploaded. + virtual bool has_unsent_logs() const = 0; + + // Returns true if there is a log that needs to be, or is being, uploaded. + virtual bool has_staged_log() const = 0; + + // The text of the staged log, as a serialized protobuf. + // Will trigger a DCHECK if there is no staged log. + virtual const std::string& staged_log() const = 0; + + // The SHA1 hash of the staged log. + // Will trigger a DCHECK if there is no staged log. + virtual const std::string& staged_log_hash() const = 0; + + // Populates staged_log() with the next stored log to send. + // The order in which logs are staged is up to the implementor. + // The staged_log must remain the same even if additional logs are added. + // Should only be called if has_unsent_logs() is true. + virtual void StageNextLog() = 0; + + // Discards the staged log. + virtual void DiscardStagedLog() = 0; + + // Saves any unsent logs to persistent storage. + virtual void PersistUnsentLogs() const = 0; + + // Loads unsent logs from persistent storage. + virtual void LoadPersistedUnsentLogs() = 0; +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_LOG_STORE_H_ diff --git a/components/metrics/machine_id_provider.h b/components/metrics/machine_id_provider.h new file mode 100644 index 0000000000000..b7a270438293f --- /dev/null +++ b/components/metrics/machine_id_provider.h @@ -0,0 +1,37 @@ +// 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 + +#include "base/macros.h" + +namespace metrics { + +// Provides machine characteristics used as a machine id. The implementation is +// platform specific. GetMachineId() must be called on a thread which allows +// I/O. GetMachineId() must not be called if HasId() returns false on this +// platform. +class MachineIdProvider { + public: + // Returns true if this platform provides a non-empty GetMachineId(). This is + // useful to avoid an async call to GetMachineId() on platforms with no + // implementation. + static bool HasId(); + + // 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. + static std::string GetMachineId(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MachineIdProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_ diff --git a/components/metrics/machine_id_provider_stub.cc b/components/metrics/machine_id_provider_stub.cc new file mode 100644 index 0000000000000..d74720987459b --- /dev/null +++ b/components/metrics/machine_id_provider_stub.cc @@ -0,0 +1,22 @@ +// 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/logging.h" + +namespace metrics { + +// static +bool MachineIdProvider::HasId() { + return false; +} + +// static +std::string MachineIdProvider::GetMachineId() { + NOTREACHED(); + return std::string(); +} + +} // namespace metrics diff --git a/components/metrics/machine_id_provider_win.cc b/components/metrics/machine_id_provider_win.cc new file mode 100644 index 0000000000000..eb7b0d7e8fdea --- /dev/null +++ b/components/metrics/machine_id_provider_win.cc @@ -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. + +#include "components/metrics/machine_id_provider.h" + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/threading/scoped_blocking_call.h" +#include "base/win/scoped_handle.h" + +namespace metrics { + +// static +bool MachineIdProvider::HasId() { + return true; +} + +// On windows, the machine id is based on the serial number of the drive Chrome +// is running from. +// static +std::string MachineIdProvider::GetMachineId() { + base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK); + + // 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 (!base::PathService::Get(base::FILE_EXE, &executable_path)) { + NOTREACHED(); + return std::string(); + } + + std::vector 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, + nullptr, OPEN_EXISTING, 0, nullptr)); + + 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, nullptr); + + if (!status) + return std::string(); + + // Query for the actual serial number. + std::vector 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, nullptr); + + if (!status) + return std::string(); + + const STORAGE_DEVICE_DESCRIPTOR* device_descriptor = + reinterpret_cast(&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::iterator serial_number_begin = + output_buf.begin() + offset; + const std::vector::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(&output_buf[offset]); + + return std::string(serial_number); +} +} // namespace metrics diff --git a/components/metrics/machine_id_provider_win_unittest.cc b/components/metrics/machine_id_provider_win_unittest.cc new file mode 100644 index 0000000000000..6bcd0c41e5668 --- /dev/null +++ b/components/metrics/machine_id_provider_win_unittest.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/machine_id_provider.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +TEST(MachineIdProviderTest, GetId) { + EXPECT_TRUE(MachineIdProvider::HasId()); + + const std::string id1 = MachineIdProvider::GetMachineId(); + EXPECT_NE(std::string(), id1); + + const std::string id2 = MachineIdProvider::GetMachineId(); + EXPECT_EQ(id1, id2); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc new file mode 100644 index 0000000000000..e7504d351c8bb --- /dev/null +++ b/components/metrics/metrics_log.cc @@ -0,0 +1,344 @@ +// 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 + +#include +#include + +#include "base/build_time.h" +#include "base/cpu.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/histogram_snapshot_manager.h" +#include "base/metrics/metrics_hashes.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/sys_info.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/delegating_provider.h" +#include "components/metrics/environment_recorder.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/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "third_party/metrics_proto/histogram_event.pb.h" +#include "third_party/metrics_proto/system_profile.pb.h" +#include "third_party/metrics_proto/user_action_event.pb.h" + +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#endif + +#if defined(OS_WIN) +#include +#include "base/win/current_module.h" +#endif + +using base::SampleCountIterator; + +namespace metrics { + +namespace { + +// A simple class to write histogram data to a log. +class IndependentFlattener : public base::HistogramFlattener { + public: + explicit IndependentFlattener(MetricsLog* log) : log_(log) {} + + // base::HistogramFlattener: + void RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) override { + log_->RecordHistogramDelta(histogram.histogram_name(), snapshot); + } + + private: + MetricsLog* const log_; + + DISALLOW_COPY_AND_ASSIGN(IndependentFlattener); +}; + +// Any id less than 16 bytes is considered to be a testing id. +bool IsTestingID(const std::string& id) { + return id.size() < 16; +} + +} // namespace + +MetricsLog::MetricsLog(const std::string& client_id, + int session_id, + LogType log_type, + MetricsServiceClient* client) + : closed_(false), + log_type_(log_type), + client_(client), + creation_time_(base::TimeTicks::Now()), + has_environment_(false) { + 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(); + RecordCoreSystemProfile(client_, system_profile); +} + +MetricsLog::~MetricsLog() { +} + +// static +void MetricsLog::RegisterPrefs(PrefRegistrySimple* registry) { + EnvironmentRecorder::RegisterPrefs(registry); +} + +// 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(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_sec(GetCurrentTime()); +} + +void MetricsLog::RecordCoreSystemProfile(MetricsServiceClient* client, + SystemProfileProto* system_profile) { + system_profile->set_build_timestamp(metrics::MetricsLog::GetBuildTime()); + system_profile->set_app_version(client->GetVersionString()); + system_profile->set_channel(client->GetChannel()); + system_profile->set_application_locale(client->GetApplicationLocale()); + +#if defined(ADDRESS_SANITIZER) + system_profile->set_is_asan_build(true); +#endif + + metrics::SystemProfileProto::Hardware* hardware = + system_profile->mutable_hardware(); +#if !defined(OS_IOS) + // On iOS, OperatingSystemArchitecture() returns values like iPad4,4 which is + // not the actual CPU architecture. Don't set it until the API is fixed. See + // crbug.com/370104 for details. + hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture()); +#endif + hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB()); + hardware->set_hardware_class(base::SysInfo::HardwareModelName()); +#if defined(OS_WIN) + hardware->set_dll_base(reinterpret_cast(CURRENT_MODULE())); +#endif + + metrics::SystemProfileProto::OS* os = system_profile->mutable_os(); + os->set_name(base::SysInfo::OperatingSystemName()); + os->set_version(base::SysInfo::OperatingSystemVersion()); +#if defined(OS_CHROMEOS) + os->set_kernel_version(base::SysInfo::KernelVersion()); +#elif defined(OS_ANDROID) + os->set_build_fingerprint( + base::android::BuildInfo::GetInstance()->android_build_fp()); + std::string package_name = client->GetAppPackageName(); + if (!package_name.empty() && package_name != "com.android.chrome") + system_profile->set_app_package_name(package_name); +#endif +} + +void MetricsLog::RecordHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot) { + DCHECK(!closed_); + EncodeHistogramDelta(histogram_name, snapshot, &uma_proto_); +} + +void MetricsLog::RecordPreviousSessionData( + DelegatingProvider* delegating_provider) { + delegating_provider->ProvidePreviousSessionData(uma_proto()); +} + +void MetricsLog::RecordCurrentSessionData( + DelegatingProvider* delegating_provider, + base::TimeDelta incremental_uptime, + base::TimeDelta uptime) { + DCHECK(!closed_); + DCHECK(has_environment_); + + // 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(incremental_uptime, uptime); + + delegating_provider->ProvideCurrentSessionData(uma_proto()); +} + +void MetricsLog::WriteMetricsEnableDefault(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 EnableMetricsDefault::DEFAULT_UNKNOWN: + // Don't set the field if it's unknown. + break; + case EnableMetricsDefault::OPT_IN: + system_profile->set_uma_default_state( + SystemProfileProto_UmaDefaultState_OPT_IN); + break; + case EnableMetricsDefault::OPT_OUT: + system_profile->set_uma_default_state( + SystemProfileProto_UmaDefaultState_OPT_OUT); + } +} + +void MetricsLog::WriteRealtimeStabilityAttributes( + 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); +} + +const SystemProfileProto& MetricsLog::RecordEnvironment( + DelegatingProvider* delegating_provider) { + DCHECK(!has_environment_); + has_environment_ = true; + + SystemProfileProto* system_profile = uma_proto()->mutable_system_profile(); + + WriteMetricsEnableDefault(client_->GetMetricsReportingDefaultState(), + system_profile); + + std::string brand_code; + if (client_->GetBrand(&brand_code)) + system_profile->set_brand_code(brand_code); + + SystemProfileProto::Hardware::CPU* cpu = + system_profile->mutable_hardware()->mutable_cpu(); + base::CPU cpu_info; + cpu->set_vendor_name(cpu_info.vendor_name()); + cpu->set_signature(cpu_info.signature()); + cpu->set_num_cores(base::SysInfo::NumberOfProcessors()); + + delegating_provider->ProvideSystemProfileMetrics(system_profile); + + return *system_profile; +} + +bool MetricsLog::LoadIndependentMetrics(MetricsProvider* metrics_provider) { + SystemProfileProto* system_profile = uma_proto()->mutable_system_profile(); + IndependentFlattener flattener(this); + base::HistogramSnapshotManager snapshot_manager(&flattener); + + return metrics_provider->ProvideIndependentMetrics(system_profile, + &snapshot_manager); +} + +bool MetricsLog::LoadSavedEnvironmentFromPrefs(PrefService* local_state, + std::string* app_version) { + DCHECK(!has_environment_); + has_environment_ = true; + app_version->clear(); + + SystemProfileProto* system_profile = uma_proto()->mutable_system_profile(); + EnvironmentRecorder recorder(local_state); + bool success = recorder.LoadEnvironmentFromPrefs(system_profile); + if (success) + *app_version = system_profile->app_version(); + return success; +} + +void MetricsLog::CloseLog() { + DCHECK(!closed_); + closed_ = true; +} + +void MetricsLog::TruncateEvents() { + DCHECK(!closed_); + if (uma_proto_.user_action_event_size() > internal::kUserActionEventLimit) { + UMA_HISTOGRAM_COUNTS_100000("UMA.TruncatedEvents.UserAction", + uma_proto_.user_action_event_size()); + for (int i = internal::kUserActionEventLimit; + i < uma_proto_.user_action_event_size(); ++i) { + // No histograms.xml entry is added for this histogram because it uses an + // enum that is generated from actions.xml in our processing pipelines. + // Instead, a histogram description will also be produced in our + // pipelines. + base::UmaHistogramSparse( + "UMA.TruncatedEvents.UserAction.Type", + // Truncate the unsigned 64-bit hash to 31 bits, to make it a suitable + // histogram sample. + uma_proto_.user_action_event(i).name_hash() & 0x7fffffff); + } + uma_proto_.mutable_user_action_event()->DeleteSubrange( + internal::kUserActionEventLimit, + uma_proto_.user_action_event_size() - internal::kUserActionEventLimit); + } + + if (uma_proto_.omnibox_event_size() > internal::kOmniboxEventLimit) { + UMA_HISTOGRAM_COUNTS_100000("UMA.TruncatedEvents.Omnibox", + uma_proto_.omnibox_event_size()); + uma_proto_.mutable_omnibox_event()->DeleteSubrange( + internal::kOmniboxEventLimit, + uma_proto_.omnibox_event_size() - internal::kOmniboxEventLimit); + } +} + +void MetricsLog::GetEncodedLog(std::string* encoded_log) { + DCHECK(closed_); + uma_proto_.SerializeToString(encoded_log); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log.h b/components/metrics/metrics_log.h new file mode 100644 index 0000000000000..4cedd6635002c --- /dev/null +++ b/components/metrics/metrics_log.h @@ -0,0 +1,186 @@ +// 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 + +#include +#include +#include + +#include "base/macros.h" +#include "base/time/time.h" +#include "components/metrics/metrics_service_client.h" +#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" + +class PrefService; + +namespace base { +class HistogramSamples; +} + +namespace metrics { + +class MetricsProvider; +class MetricsServiceClient; +class DelegatingProvider; + +namespace internal { +// Maximum number of events before truncation. +constexpr int kOmniboxEventLimit = 5000; +constexpr int kUserActionEventLimit = 5000; +} // namespace internal + +class MetricsLog { + public: + enum LogType { + INITIAL_STABILITY_LOG, // The initial log containing stability stats. + ONGOING_LOG, // Subsequent logs in a session. + INDEPENDENT_LOG, // An independent log from a previous 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); + 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(); + + // Record core profile settings into the SystemProfileProto. + static void RecordCoreSystemProfile(MetricsServiceClient* client, + SystemProfileProto* system_profile); + + // 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); + + // TODO(rkaplow): I think this can be a little refactored as it currently + // records a pretty arbitrary set of things. + // Records the current operating environment, including metrics provided by + // the specified |delegating_provider|. The current environment is + // returned as a SystemProfileProto. + const SystemProfileProto& RecordEnvironment( + DelegatingProvider* delegating_provider); + + // Loads a saved system profile and the associated metrics into the log. + // Returns true on success. Keep calling it with fresh logs until it returns + // false. + bool LoadIndependentMetrics(MetricsProvider* metrics_provider); + + // Loads the environment proto that was saved by the last RecordEnvironment() + // call from prefs. On success, returns true and |app_version| contains the + // recovered version. Otherwise (if there was no saved environment in prefs + // or it could not be decoded), returns false and |app_version| is empty. + bool LoadSavedEnvironmentFromPrefs(PrefService* local_state, + std::string* app_version); + + // Record data from providers about the previous session into the log. + void RecordPreviousSessionData(DelegatingProvider* delegating_provider); + + // Record data from providers about the current session into the log. + void RecordCurrentSessionData(DelegatingProvider* delegating_provider, + base::TimeDelta incremental_uptime, + base::TimeDelta uptime); + + // Stop writing to this record and generate the encoded representation. + // None of the Record* methods can be called after this is called. + void CloseLog(); + + // Truncate some of the fields within the log that we want to restrict in + // size due to bandwidth concerns. + void TruncateEvents(); + + // 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_; + } + + LogType log_type() const { return log_type_; } + + protected: + // Exposed for the sake of mocking/accessing in test code. + + 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: + // Write the default state of the enable metrics checkbox. + void WriteMetricsEnableDefault(EnableMetricsDefault metrics_default, + SystemProfileProto* system_profile); + + // 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(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_; + + // True if the environment has already been filled in by a call to + // RecordEnvironment() or LoadSavedEnvironmentFromPrefs(). + bool has_environment_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLog); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_H_ diff --git a/components/metrics/metrics_log_manager.cc b/components/metrics/metrics_log_manager.cc new file mode 100644 index 0000000000000..d90aa7b3dc4e0 --- /dev/null +++ b/components/metrics/metrics_log_manager.cc @@ -0,0 +1,51 @@ +// 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 +#include + +#include "base/strings/string_util.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_log_store.h" +#include "components/metrics/metrics_pref_names.h" + +namespace metrics { + +MetricsLogManager::MetricsLogManager() {} + +MetricsLogManager::~MetricsLogManager() {} + +void MetricsLogManager::BeginLoggingWithLog(std::unique_ptr log) { + DCHECK(!current_log_); + current_log_ = std::move(log); +} + +void MetricsLogManager::FinishCurrentLog(MetricsLogStore* log_store) { + DCHECK(current_log_); + current_log_->CloseLog(); + std::string log_data; + current_log_->GetEncodedLog(&log_data); + if (!log_data.empty()) + log_store->StoreLog(log_data, current_log_->log_type()); + current_log_.reset(); +} + +void MetricsLogManager::DiscardCurrentLog() { + current_log_->CloseLog(); + current_log_.reset(); +} + +void MetricsLogManager::PauseCurrentLog() { + DCHECK(!paused_log_); + paused_log_ = std::move(current_log_); +} + +void MetricsLogManager::ResumePausedLog() { + DCHECK(!current_log_); + current_log_ = std::move(paused_log_); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_manager.h b/components/metrics/metrics_log_manager.h new file mode 100644 index 0000000000000..3227617acfc12 --- /dev/null +++ b/components/metrics/metrics_log_manager.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_METRICS_LOG_MANAGER_H_ +#define COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ + +#include + +#include +#include +#include + +#include "base/macros.h" +#include "components/metrics/metrics_log.h" + +namespace metrics { + +class MetricsLogStore; + +// Manages all the log objects used by a MetricsService implementation. Keeps +// track of an in-progress log and a paused log. +class MetricsLogManager { + public: + MetricsLogManager(); + ~MetricsLogManager(); + + // Makes |log| the current_log. This should only be called if there is not a + // current log. + void BeginLoggingWithLog(std::unique_ptr log); + + // Returns the in-progress log. + MetricsLog* current_log() { return current_log_.get(); } + + // Closes |current_log_|, compresses it, and stores it in the |log_store| for + // later, leaving |current_log_| NULL. + void FinishCurrentLog(MetricsLogStore* log_store); + + // 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(); + + private: + // The log that we are still appending to. + std::unique_ptr current_log_; + + // A paused, previously-current log. + std::unique_ptr paused_log_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogManager); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ diff --git a/components/metrics/metrics_log_manager_unittest.cc b/components/metrics/metrics_log_manager_unittest.cc new file mode 100644 index 0000000000000..4e3004e8163bf --- /dev/null +++ b/components/metrics/metrics_log_manager_unittest.cc @@ -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. + +#include "components/metrics/metrics_log_manager.h" + +#include + +#include +#include +#include + +#include "base/memory/ptr_util.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_log_store.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/persisted_logs_metrics_impl.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 { + +class MetricsLogManagerTest : public testing::Test { + public: + MetricsLogManagerTest() : log_store_(&pref_service_, 0) { + MetricsLogStore::RegisterPrefs(pref_service_.registry()); + log_store()->LoadPersistedUnsentLogs(); + } + ~MetricsLogManagerTest() override {} + + MetricsLogStore* log_store() { return &log_store_; } + + MetricsLog* CreateLog(MetricsLog::LogType log_type) { + return new MetricsLog("id", 0, log_type, &client_); + } + + private: + TestMetricsServiceClient client_; + TestingPrefServiceSimple pref_service_; + MetricsLogStore log_store_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogManagerTest); +}; + +} // namespace + +TEST_F(MetricsLogManagerTest, StandardFlow) { + MetricsLogManager log_manager; + + // Make sure a new manager has a clean slate. + EXPECT_EQ(nullptr, log_manager.current_log()); + + // Check that the normal flow works. + MetricsLog* initial_log = CreateLog(MetricsLog::INITIAL_STABILITY_LOG); + log_manager.BeginLoggingWithLog(base::WrapUnique(initial_log)); + EXPECT_EQ(initial_log, log_manager.current_log()); + + EXPECT_FALSE(log_store()->has_unsent_logs()); + log_manager.FinishCurrentLog(log_store()); + EXPECT_EQ(nullptr, log_manager.current_log()); + EXPECT_TRUE(log_store()->has_unsent_logs()); + + MetricsLog* second_log = CreateLog(MetricsLog::ONGOING_LOG); + log_manager.BeginLoggingWithLog(base::WrapUnique(second_log)); + EXPECT_EQ(second_log, log_manager.current_log()); +} + +TEST_F(MetricsLogManagerTest, AbandonedLog) { + MetricsLogManager log_manager; + + MetricsLog* dummy_log = CreateLog(MetricsLog::INITIAL_STABILITY_LOG); + log_manager.BeginLoggingWithLog(base::WrapUnique(dummy_log)); + EXPECT_EQ(dummy_log, log_manager.current_log()); + + log_manager.DiscardCurrentLog(); + EXPECT_EQ(nullptr, log_manager.current_log()); +} + +// Make sure that interjecting logs updates the "current" log correctly. +TEST_F(MetricsLogManagerTest, InterjectedLog) { + MetricsLogManager log_manager; + + MetricsLog* ongoing_log = CreateLog(MetricsLog::ONGOING_LOG); + MetricsLog* temp_log = CreateLog(MetricsLog::INITIAL_STABILITY_LOG); + + log_manager.BeginLoggingWithLog(base::WrapUnique(ongoing_log)); + EXPECT_EQ(ongoing_log, log_manager.current_log()); + + log_manager.PauseCurrentLog(); + EXPECT_EQ(nullptr, log_manager.current_log()); + + log_manager.BeginLoggingWithLog(base::WrapUnique(temp_log)); + EXPECT_EQ(temp_log, log_manager.current_log()); + log_manager.FinishCurrentLog(log_store()); + EXPECT_EQ(nullptr, log_manager.current_log()); + + log_manager.ResumePausedLog(); + EXPECT_EQ(ongoing_log, log_manager.current_log()); +} + +// Make sure that when one log is interjected by another, that finishing them +// creates logs of the correct type. +TEST_F(MetricsLogManagerTest, InterjectedLogPreservesType) { + MetricsLogManager log_manager; + + log_manager.BeginLoggingWithLog( + base::WrapUnique(CreateLog(MetricsLog::ONGOING_LOG))); + log_manager.PauseCurrentLog(); + log_manager.BeginLoggingWithLog( + base::WrapUnique(CreateLog(MetricsLog::INITIAL_STABILITY_LOG))); + log_manager.FinishCurrentLog(log_store()); + log_manager.ResumePausedLog(); + // Finishing the interjecting inital log should have stored an initial log. + EXPECT_EQ(1U, log_store()->initial_log_count()); + EXPECT_EQ(0U, log_store()->ongoing_log_count()); + + // Finishing the interjected ongoing log should store an ongoing log. + log_manager.FinishCurrentLog(log_store()); + EXPECT_EQ(1U, log_store()->initial_log_count()); + EXPECT_EQ(1U, log_store()->ongoing_log_count()); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_store.cc b/components/metrics/metrics_log_store.cc new file mode 100644 index 0000000000000..30cbe6cfb97de --- /dev/null +++ b/components/metrics/metrics_log_store.cc @@ -0,0 +1,129 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/metrics_log_store.h" + +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/persisted_logs_metrics_impl.h" +#include "components/prefs/pref_registry_simple.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 of logs to save of each type (initial/ongoing). +// 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 = 300 * 1000; // ~300kB + +} // namespace + +// static +void MetricsLogStore::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterListPref(prefs::kMetricsInitialLogs); + registry->RegisterListPref(prefs::kMetricsOngoingLogs); +} + +MetricsLogStore::MetricsLogStore(PrefService* local_state, + size_t max_ongoing_log_size) + : unsent_logs_loaded_(false), + initial_log_queue_(std::unique_ptr( + new PersistedLogsMetricsImpl()), + local_state, + prefs::kMetricsInitialLogs, + kInitialLogsPersistLimit, + kStorageByteLimitPerLogType, + 0), + ongoing_log_queue_(std::unique_ptr( + new PersistedLogsMetricsImpl()), + local_state, + prefs::kMetricsOngoingLogs, + kOngoingLogsPersistLimit, + kStorageByteLimitPerLogType, + max_ongoing_log_size) {} + +MetricsLogStore::~MetricsLogStore() {} + +void MetricsLogStore::LoadPersistedUnsentLogs() { + initial_log_queue_.LoadPersistedUnsentLogs(); + ongoing_log_queue_.LoadPersistedUnsentLogs(); + unsent_logs_loaded_ = true; +} + +void MetricsLogStore::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: + case MetricsLog::INDEPENDENT_LOG: + ongoing_log_queue_.StoreLog(log_data); + break; + } +} + +bool MetricsLogStore::has_unsent_logs() const { + return initial_log_queue_.has_unsent_logs() || + ongoing_log_queue_.has_unsent_logs(); +} + +bool MetricsLogStore::has_staged_log() const { + return initial_log_queue_.has_staged_log() || + ongoing_log_queue_.has_staged_log(); +} + +const std::string& MetricsLogStore::staged_log() const { + return initial_log_queue_.has_staged_log() ? initial_log_queue_.staged_log() + : ongoing_log_queue_.staged_log(); +} + +const std::string& MetricsLogStore::staged_log_hash() const { + return initial_log_queue_.has_staged_log() + ? initial_log_queue_.staged_log_hash() + : ongoing_log_queue_.staged_log_hash(); +} + +void MetricsLogStore::StageNextLog() { + DCHECK(!has_staged_log()); + if (initial_log_queue_.has_unsent_logs()) + initial_log_queue_.StageNextLog(); + else + ongoing_log_queue_.StageNextLog(); +} + +void MetricsLogStore::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 MetricsLogStore::PersistUnsentLogs() const { + DCHECK(unsent_logs_loaded_); + if (!unsent_logs_loaded_) + return; + + initial_log_queue_.PersistUnsentLogs(); + ongoing_log_queue_.PersistUnsentLogs(); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_store.h b/components/metrics/metrics_log_store.h new file mode 100644 index 0000000000000..b9658d67e6d38 --- /dev/null +++ b/components/metrics/metrics_log_store.h @@ -0,0 +1,67 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_METRICS_LOG_STORE_H_ +#define COMPONENTS_METRICS_METRICS_LOG_STORE_H_ + +#include + +#include "base/macros.h" +#include "components/metrics/log_store.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/persisted_logs.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +// A LogStore implementation for storing UMA logs. +// This implementation keeps track of two types of logs, initial and ongoing, +// each stored in PersistedLogs. It prioritizes staging initial logs over +// ongoing logs. +class MetricsLogStore : public LogStore { + public: + // Constructs a MetricsLogStore that persists data into |local_state|. + // If max_log_size is non-zero, it will not persist ongoing logs larger than + // |max_ongoing_log_size| bytes. + MetricsLogStore(PrefService* local_state, size_t max_ongoing_log_size); + ~MetricsLogStore(); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // Saves |log_data| as the given type. + void StoreLog(const std::string& log_data, MetricsLog::LogType log_type); + + // LogStore: + bool has_unsent_logs() const override; + bool has_staged_log() const override; + const std::string& staged_log() const override; + const std::string& staged_log_hash() const override; + void StageNextLog() override; + void DiscardStagedLog() override; + void PersistUnsentLogs() const override; + void LoadPersistedUnsentLogs() override; + + // Inspection methods for tests. + size_t ongoing_log_count() const { return ongoing_log_queue_.size(); } + size_t initial_log_count() const { return initial_log_queue_.size(); } + + private: + // Tracks whether unsent logs (if any) have been loaded from the serializer. + bool unsent_logs_loaded_; + + // Logs stored with the INITIAL_STABILITY_LOG type that haven't been sent yet. + // These logs will be staged first when staging new logs. + PersistedLogs initial_log_queue_; + // Logs stored with the ONGOING_LOG type that haven't been sent yet. + PersistedLogs ongoing_log_queue_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogStore); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_STORE_H_ diff --git a/components/metrics/metrics_log_store_unittest.cc b/components/metrics/metrics_log_store_unittest.cc new file mode 100644 index 0000000000000..50b78c7cd88e9 --- /dev/null +++ b/components/metrics/metrics_log_store_unittest.cc @@ -0,0 +1,194 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/metrics_log_store.h" + +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/test_metrics_service_client.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +class MetricsLogStoreTest : public testing::Test { + public: + MetricsLogStoreTest() { + MetricsLogStore::RegisterPrefs(pref_service_.registry()); + } + ~MetricsLogStoreTest() override {} + + MetricsLog* CreateLog(MetricsLog::LogType log_type) { + return new MetricsLog("id", 0, log_type, &client_); + } + + // Returns the stored number of logs of the given type. + size_t TypeCount(MetricsLog::LogType log_type) { + const char* pref = log_type == MetricsLog::INITIAL_STABILITY_LOG + ? prefs::kMetricsInitialLogs + : prefs::kMetricsOngoingLogs; + return pref_service_.GetList(pref)->GetSize(); + } + + TestMetricsServiceClient client_; + TestingPrefServiceSimple pref_service_; + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsLogStoreTest); +}; + +} // namespace + +TEST_F(MetricsLogStoreTest, StandardFlow) { + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + + // Make sure a new manager has a clean slate. + EXPECT_FALSE(log_store.has_staged_log()); + EXPECT_FALSE(log_store.has_unsent_logs()); + + log_store.StoreLog("a", MetricsLog::ONGOING_LOG); + EXPECT_TRUE(log_store.has_unsent_logs()); + EXPECT_FALSE(log_store.has_staged_log()); + + log_store.StageNextLog(); + EXPECT_TRUE(log_store.has_staged_log()); + EXPECT_FALSE(log_store.staged_log().empty()); + + log_store.DiscardStagedLog(); + EXPECT_FALSE(log_store.has_staged_log()); + EXPECT_FALSE(log_store.has_unsent_logs()); +} + +TEST_F(MetricsLogStoreTest, StoreAndLoad) { + // Set up some in-progress logging in a scoped log manager simulating the + // leadup to quitting, then persist as would be done on quit. + { + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + EXPECT_FALSE(log_store.has_unsent_logs()); + log_store.StoreLog("a", MetricsLog::ONGOING_LOG); + log_store.PersistUnsentLogs(); + EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG)); + } + + // Relaunch load and store more logs. + { + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + EXPECT_TRUE(log_store.has_unsent_logs()); + EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG)); + log_store.StoreLog("x", MetricsLog::INITIAL_STABILITY_LOG); + log_store.StageNextLog(); + log_store.StoreLog("b", MetricsLog::ONGOING_LOG); + + EXPECT_TRUE(log_store.has_unsent_logs()); + EXPECT_TRUE(log_store.has_staged_log()); + EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG)); + + log_store.PersistUnsentLogs(); + EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG)); + } + + // Relaunch and verify that once logs are handled they are not re-persisted. + { + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + EXPECT_TRUE(log_store.has_unsent_logs()); + + log_store.StageNextLog(); + log_store.DiscardStagedLog(); + // The initial log should be sent first; update the persisted storage to + // verify. + log_store.PersistUnsentLogs(); + EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG)); + + // Handle the first ongoing log. + log_store.StageNextLog(); + log_store.DiscardStagedLog(); + EXPECT_TRUE(log_store.has_unsent_logs()); + + // Handle the last log. + log_store.StageNextLog(); + log_store.DiscardStagedLog(); + EXPECT_FALSE(log_store.has_unsent_logs()); + + // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been + // called again. + EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG)); + // Persist, and make sure nothing is left. + log_store.PersistUnsentLogs(); + EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG)); + } +} + +TEST_F(MetricsLogStoreTest, StoreStagedOngoingLog) { + // Ensure that types are preserved when storing staged logs. + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + log_store.StoreLog("a", MetricsLog::ONGOING_LOG); + log_store.StageNextLog(); + log_store.PersistUnsentLogs(); + + EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG)); +} + +TEST_F(MetricsLogStoreTest, StoreStagedInitialLog) { + // Ensure that types are preserved when storing staged logs. + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + log_store.StoreLog("b", MetricsLog::INITIAL_STABILITY_LOG); + log_store.StageNextLog(); + log_store.PersistUnsentLogs(); + + EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG)); +} + +TEST_F(MetricsLogStoreTest, LargeLogDiscarding) { + // Set the size threshold very low, to verify that it's honored. + MetricsLogStore log_store(&pref_service_, 1); + log_store.LoadPersistedUnsentLogs(); + + log_store.StoreLog("persisted", MetricsLog::INITIAL_STABILITY_LOG); + log_store.StoreLog("not_persisted", MetricsLog::ONGOING_LOG); + + // Only the stability log should be written out, due to the threshold. + log_store.PersistUnsentLogs(); + EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG)); +} + +TEST_F(MetricsLogStoreTest, DiscardOrder) { + // Ensure that the correct log is discarded if new logs are pushed while + // a log is staged. + MetricsLogStore log_store(&pref_service_, 0); + log_store.LoadPersistedUnsentLogs(); + + log_store.StoreLog("a", MetricsLog::ONGOING_LOG); + log_store.StoreLog("b", MetricsLog::ONGOING_LOG); + log_store.StageNextLog(); + log_store.StoreLog("c", MetricsLog::INITIAL_STABILITY_LOG); + EXPECT_EQ(2U, log_store.ongoing_log_count()); + EXPECT_EQ(1U, log_store.initial_log_count()); + // Should discard the ongoing log staged earlier. + log_store.DiscardStagedLog(); + EXPECT_EQ(1U, log_store.ongoing_log_count()); + EXPECT_EQ(1U, log_store.initial_log_count()); + // Initial log should be staged next. + log_store.StageNextLog(); + log_store.DiscardStagedLog(); + EXPECT_EQ(1U, log_store.ongoing_log_count()); + EXPECT_EQ(0U, log_store.initial_log_count()); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_unittest.cc b/components/metrics/metrics_log_unittest.cc new file mode 100644 index 0000000000000..8af49a8cbbcfd --- /dev/null +++ b/components/metrics/metrics_log_unittest.cc @@ -0,0 +1,360 @@ +// 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 +#include + +#include + +#include "base/base64.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/sample_vector.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/sys_info.h" +#include "base/time/time.h" +#include "components/metrics/delegating_provider.h" +#include "components/metrics/environment_recorder.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/pref_service.h" +#include "components/prefs/testing_pref_service.h" +#include "components/variations/active_field_trials.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" + +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#endif + +#if defined(OS_WIN) +#include "base/win/current_module.h" +#endif + +namespace metrics { + +namespace { + +const char kClientId[] = "bogus client ID"; +const int kSessionId = 127; + +class TestMetricsLog : public MetricsLog { + public: + TestMetricsLog(const std::string& client_id, + int session_id, + LogType log_type, + MetricsServiceClient* client) + : MetricsLog(client_id, session_id, log_type, client) {} + + ~TestMetricsLog() override {} + + const ChromeUserMetricsExtension& uma_proto() const { + return *MetricsLog::uma_proto(); + } + + ChromeUserMetricsExtension* mutable_uma_proto() { + return MetricsLog::uma_proto(); + } + + const SystemProfileProto& system_profile() const { + return uma_proto().system_profile(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsLog); +}; + +} // namespace + +class MetricsLogTest : public testing::Test { + public: + MetricsLogTest() {} + ~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(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. + } + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsLogTest); +}; + +TEST_F(MetricsLogTest, LogType) { + TestMetricsServiceClient client; + TestingPrefServiceSimple prefs; + + MetricsLog log1("id", 0, MetricsLog::ONGOING_LOG, &client); + EXPECT_EQ(MetricsLog::ONGOING_LOG, log1.log_type()); + + MetricsLog log2("id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client); + EXPECT_EQ(MetricsLog::INITIAL_STABILITY_LOG, log2.log_type()); +} + +TEST_F(MetricsLogTest, BasicRecord) { + TestMetricsServiceClient client; + client.set_version_string("bogus version"); + TestingPrefServiceSimple prefs; + MetricsLog log("totally bogus client ID", 137, MetricsLog::ONGOING_LOG, + &client); + 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); + + SystemProfileProto* system_profile = expected.mutable_system_profile(); + system_profile->set_app_version("bogus version"); + system_profile->set_channel(client.GetChannel()); + system_profile->set_application_locale(client.GetApplicationLocale()); + +#if defined(ADDRESS_SANITIZER) + system_profile->set_is_asan_build(true); +#endif + metrics::SystemProfileProto::Hardware* hardware = + system_profile->mutable_hardware(); +#if !defined(OS_IOS) + hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture()); +#endif + hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB()); + hardware->set_hardware_class(base::SysInfo::HardwareModelName()); +#if defined(OS_WIN) + hardware->set_dll_base(reinterpret_cast(CURRENT_MODULE())); +#endif + + system_profile->mutable_os()->set_name(base::SysInfo::OperatingSystemName()); + system_profile->mutable_os()->set_version( + base::SysInfo::OperatingSystemVersion()); +#if defined(OS_CHROMEOS) + system_profile->mutable_os()->set_kernel_version( + base::SysInfo::KernelVersion()); +#elif defined(OS_ANDROID) + system_profile->mutable_os()->set_build_fingerprint( + base::android::BuildInfo::GetInstance()->android_build_fp()); + system_profile->set_app_package_name("test app"); +#endif + + // Hard to mock. + system_profile->set_build_timestamp( + parsed.system_profile().build_timestamp()); + + 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); + 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); + + DelegatingProvider delegating_provider; + log.RecordEnvironment(&delegating_provider); + // Check that the system profile on the log has the correct values set. + CheckSystemProfile(log.system_profile()); +} + +TEST_F(MetricsLogTest, RecordEnvironmentEnableDefault) { + TestMetricsServiceClient client; + TestMetricsLog log_unknown(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client); + + DelegatingProvider delegating_provider; + log_unknown.RecordEnvironment(&delegating_provider); + EXPECT_FALSE(log_unknown.system_profile().has_uma_default_state()); + + client.set_enable_default(EnableMetricsDefault::OPT_IN); + TestMetricsLog log_opt_in(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client); + log_opt_in.RecordEnvironment(&delegating_provider); + 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(EnableMetricsDefault::OPT_OUT); + TestMetricsLog log_opt_out(kClientId, kSessionId, MetricsLog::ONGOING_LOG, + &client); + log_opt_out.RecordEnvironment(&delegating_provider); + 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); + log_managed.RecordEnvironment(&delegating_provider); + 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); + TestMetricsProvider* test_provider = new TestMetricsProvider(); + DelegatingProvider delegating_provider; + delegating_provider.RegisterMetricsProvider( + base::WrapUnique(test_provider)); + log.RecordEnvironment(&delegating_provider); + log.RecordPreviousSessionData(&delegating_provider); + + // 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); + TestMetricsProvider* test_provider = new TestMetricsProvider(); + DelegatingProvider delegating_provider; + delegating_provider.RegisterMetricsProvider( + base::WrapUnique(test_provider)); + log.RecordEnvironment(&delegating_provider); + log.RecordCurrentSessionData(&delegating_provider, base::TimeDelta(), + base::TimeDelta()); + + // 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); + 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); + // 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); + // Check that the product is set to |kTestProduct|. + EXPECT_TRUE(log.uma_proto().has_product()); + EXPECT_EQ(kTestProduct, log.uma_proto().product()); +} + +TEST_F(MetricsLogTest, TruncateEvents) { + TestMetricsServiceClient client; + TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client); + + for (int i = 0; i < internal::kUserActionEventLimit * 2; ++i) { + log.RecordUserAction("BasicAction"); + EXPECT_EQ(i + 1, log.uma_proto().user_action_event_size()); + } + for (int i = 0; i < internal::kOmniboxEventLimit * 2; ++i) { + // Add an empty omnibox event. Not fully realistic since these are normally + // supplied by a metrics provider. + log.mutable_uma_proto()->add_omnibox_event(); + EXPECT_EQ(i + 1, log.uma_proto().omnibox_event_size()); + } + + // Truncate, and check that the current size is the limit. + log.TruncateEvents(); + EXPECT_EQ(internal::kUserActionEventLimit, + log.uma_proto().user_action_event_size()); + EXPECT_EQ(internal::kOmniboxEventLimit, log.uma_proto().omnibox_event_size()); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_uploader.h b/components/metrics/metrics_log_uploader.h new file mode 100644 index 0000000000000..882bab028a26a --- /dev/null +++ b/components/metrics/metrics_log_uploader.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_METRICS_LOG_UPLOADER_H_ +#define COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/strings/string_piece.h" + +namespace metrics { + +class ReportingInfo; + +// MetricsLogUploader is an abstract base class for uploading UMA logs on behalf +// of MetricsService. +class MetricsLogUploader { + public: + // Type for OnUploadComplete callbacks. These callbacks will receive three + // parameters: A response code, a net error code, and a boolean specifying + // if the connection was secure (over HTTPS). + typedef base::Callback UploadCallback; + + // Possible service types. This should correspond to a type from + // DataUseUserData. + enum MetricServiceType { + UMA, + UKM, + }; + + 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, + const ReportingInfo& reporting_info) = 0; +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_ diff --git a/components/metrics/metrics_pref_names.cc b/components/metrics/metrics_pref_names.cc new file mode 100644 index 0000000000000..5dcb253d711c6 --- /dev/null +++ b/components/metrics/metrics_pref_names.cc @@ -0,0 +1,228 @@ +// 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"; + +// An enum value indicating the default value of the enable metrics reporting +// checkbox shown during first-run. If it's opt-in, then the checkbox defaulted +// to unchecked, if it's opt-out, then it defaulted to checked. This value is +// only recorded during first-run, so older clients will not set it. The enum +// used for the value is metrics::MetricsServiceClient::EnableMetricsDefault. +const char kMetricsDefaultOptIn[] = "user_experience_metrics.default_opt_in"; + +// Array of dictionaries 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_logs2"; + +// 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 dictionaries 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_logs2"; + +// 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 most +// recently (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"; + +// A time stamp at which time the browser was known to be alive. Used to +// evaluate whether the browser crash was due to a whole system crash. +// At minimum this is updated each time the "exited_cleanly" preference is +// modified, but can also be optionally updated on a slow schedule. +const char kStabilityBrowserLastLiveTimeStamp[] = + "user_experience_metrics.stability.browser_last_live_timestamp"; + +// 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 application exited uncleanly since the last report +// without gms core update. +const char kStabilityCrashCountWithoutGmsCoreUpdate[] = + "user_experience_metrics.stability.crash_count_without_gms_core_update"; + +// Number of times the initial stability log upload was deferred to the next +// startup. +const char kStabilityDeferredCount[] = + "user_experience_metrics.stability.deferred_count"; + +// Number of times stability data was discarded. This is accumulated since the +// last report, even across versions. +const char kStabilityDiscardCount[] = + "user_experience_metrics.stability.discard_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 an extension renderer process successfully launched since the +// last report. +const char kStabilityExtensionRendererLaunchCount[] = + "user_experience_metrics.stability.extension_renderer_launch_count"; + +// The GMS core version used in Chrome. +const char kStabilityGmsCoreVersion[] = + "user_experience_metrics.stability.gms_core_version"; + +// Number of times the session end did not complete. +const char kStabilityIncompleteSessionEndCount[] = + "user_experience_metrics.stability.incomplete_session_end_count"; + +// Number of times the application was launched since last report. +const char kStabilityLaunchCount[] = + "user_experience_metrics.stability.launch_count"; + +// 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"; + +// Number of times a renderer process successfully launched since the last +// report. +const char kStabilityRendererLaunchCount[] = + "user_experience_metrics.stability.renderer_launch_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"; + +// Number of times the application exited uncleanly and the system session +// embedding the browser session ended abnormally since the last report. +// Windows only. +const char kStabilitySystemCrashCount[] = + "user_experience_metrics.stability.system_crash_count"; + +// Number of times the version number stored in prefs did not match the +// serialized system profile version number. +const char kStabilityVersionMismatchCount[] = + "user_experience_metrics.stability.version_mismatch_count"; + +// 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 UKM service during last 7 +// days. +const char kUkmCellDataUse[] = "user_experience_metrics.ukm_cell_datause"; + +// 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/components/metrics/metrics_pref_names.h b/components/metrics/metrics_pref_names.h new file mode 100644 index 0000000000000..2024c49022f6c --- /dev/null +++ b/components/metrics/metrics_pref_names.h @@ -0,0 +1,79 @@ +// 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 kDeprecatedMetricsInitialLogs[]; +extern const char kDeprecatedMetricsOngoingLogs[]; +extern const char kInstallDate[]; +extern const char kMetricsClientID[]; +extern const char kMetricsDefaultOptIn[]; +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[]; + +// Preferences for recording stability logs. +extern const char kStabilityBreakpadRegistrationFail[]; +extern const char kStabilityBreakpadRegistrationSuccess[]; +extern const char kStabilityBrowserLastLiveTimeStamp[]; +extern const char kStabilityChildProcessCrashCount[]; +extern const char kStabilityCrashCount[]; +extern const char kStabilityCrashCountWithoutGmsCoreUpdate[]; +extern const char kStabilityDebuggerNotPresent[]; +extern const char kStabilityDebuggerPresent[]; +extern const char kStabilityDeferredCount[]; +extern const char kStabilityDiscardCount[]; +extern const char kStabilityExecutionPhase[]; +extern const char kStabilityExitedCleanly[]; +extern const char kStabilityExtensionRendererCrashCount[]; +extern const char kStabilityExtensionRendererFailedLaunchCount[]; +extern const char kStabilityExtensionRendererLaunchCount[]; +extern const char kStabilityGmsCoreVersion[]; +extern const char kStabilityIncompleteSessionEndCount[]; +extern const char kStabilityLaunchCount[]; +extern const char kStabilityPageLoadCount[]; +extern const char kStabilityRendererCrashCount[]; +extern const char kStabilityRendererFailedLaunchCount[]; +extern const char kStabilityRendererHangCount[]; +extern const char kStabilityRendererLaunchCount[]; +extern const char kStabilitySavedSystemProfile[]; +extern const char kStabilitySavedSystemProfileHash[]; +extern const char kStabilitySessionEndCompleted[]; +extern const char kStabilityStatsBuildTime[]; +extern const char kStabilityStatsVersion[]; +extern const char kStabilitySystemCrashCount[]; +extern const char kStabilityVersionMismatchCount[]; + +// Preferences for generating metrics at uninstall time. +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 kUkmCellDataUse[]; +extern const char kUmaCellDataUse[]; +extern const char kUserCellDataUse[]; + +} // namespace prefs +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ diff --git a/components/metrics/metrics_provider.cc b/components/metrics/metrics_provider.cc new file mode 100644 index 0000000000000..d5408bb16b190 --- /dev/null +++ b/components/metrics/metrics_provider.cc @@ -0,0 +1,75 @@ +// 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" + +#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" + +namespace metrics { + +MetricsProvider::MetricsProvider() { +} + +MetricsProvider::~MetricsProvider() { +} + +void MetricsProvider::Init() { +} + +void MetricsProvider::AsyncInit(const base::Closure& done_callback) { + done_callback.Run(); +} + +void MetricsProvider::OnDidCreateMetricsLog() { +} + +void MetricsProvider::OnRecordingEnabled() { +} + +void MetricsProvider::OnRecordingDisabled() { +} + +void MetricsProvider::OnAppEnterBackground() { +} + +bool MetricsProvider::ProvideIndependentMetrics( + SystemProfileProto* system_profile_proto, + base::HistogramSnapshotManager* snapshot_manager) { + return false; +} + +void MetricsProvider::ProvideSystemProfileMetrics( + SystemProfileProto* system_profile_proto) { +} + +bool MetricsProvider::HasPreviousSessionData() { + return false; +} + +void MetricsProvider::ProvidePreviousSessionData( + ChromeUserMetricsExtension* uma_proto) { + ProvideStabilityMetrics(uma_proto->mutable_system_profile()); +} + +void MetricsProvider::ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) { + ProvideStabilityMetrics(uma_proto->mutable_system_profile()); +} + +void MetricsProvider::ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto) { +} + +void MetricsProvider::ClearSavedStabilityMetrics() { +} + +void MetricsProvider::RecordHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { +} + +void MetricsProvider::RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager) { +} + +} // namespace metrics diff --git a/components/metrics/metrics_provider.h b/components/metrics/metrics_provider.h new file mode 100644 index 0000000000000..591c63ff7cc1b --- /dev/null +++ b/components/metrics/metrics_provider.h @@ -0,0 +1,111 @@ +// 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/callback.h" +#include "base/macros.h" + +namespace base { +class HistogramSnapshotManager; +} // namespace base + +namespace metrics { + +class ChromeUserMetricsExtension; +class SystemProfileProto; + +// 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 during service initialization to allow the provider to start any + // async initialization tasks. The service will wait for the provider to + // call |done_callback| before generating logs for the current session. + virtual void AsyncInit(const base::Closure& done_callback); + + // 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(); + + // Called when the application is going into background mode, on platforms + // where applications may be killed when going into the background (Android, + // iOS). Providers that buffer histogram data in memory should persist + // histograms in this callback, as the application may be killed without + // further notification after this callback. + virtual void OnAppEnterBackground(); + + // Provides a complete and independent system profile + metrics for uploading. + // Any histograms added to the |snapshot_manager| will also be included. A + // return of false indicates there are none. Will be called repeatedly until + // there is nothing else. + virtual bool ProvideIndependentMetrics( + SystemProfileProto* system_profile_proto, + base::HistogramSnapshotManager* snapshot_manager); + + // 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 data + // to provide about the previous session. + // Returning true will trigger ProvidePreviousSessionData on all other + // registered metrics providers. + // Default implementation always returns false. + virtual bool HasPreviousSessionData(); + + // Called when building a log about the previous session, so the provider + // can provide data about it. Stability metrics can be provided + // directly into |stability_proto| fields or by logging stability histograms + // via the UMA_STABILITY_HISTOGRAM_ENUMERATION() macro. + virtual void ProvidePreviousSessionData( + ChromeUserMetricsExtension* uma_proto); + + // Called when building a log about the current session, so the provider + // can provide data about it. + virtual void ProvideCurrentSessionData(ChromeUserMetricsExtension* uma_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(); + + // Called during regular 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); + + // Called during collection of initial metrics 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 RecordInitialHistogramSnapshots( + base::HistogramSnapshotManager* snapshot_manager); + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_PROVIDER_H_ diff --git a/components/metrics/metrics_reporting_default_state.cc b/components/metrics/metrics_reporting_default_state.cc new file mode 100644 index 0000000000000..a166600bbb999 --- /dev/null +++ b/components/metrics/metrics_reporting_default_state.cc @@ -0,0 +1,36 @@ +// 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/metrics_reporting_default_state.h" + +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" + +namespace metrics { + +void RegisterMetricsReportingStatePrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(prefs::kMetricsDefaultOptIn, + EnableMetricsDefault::DEFAULT_UNKNOWN); +} + +void RecordMetricsReportingDefaultState(PrefService* local_state, + EnableMetricsDefault default_state) { + DCHECK_EQ(GetMetricsReportingDefaultState(local_state), + EnableMetricsDefault::DEFAULT_UNKNOWN); + local_state->SetInteger(prefs::kMetricsDefaultOptIn, default_state); +} + +void ForceRecordMetricsReportingDefaultState( + PrefService* local_state, + EnableMetricsDefault default_state) { + local_state->SetInteger(prefs::kMetricsDefaultOptIn, default_state); +} + +EnableMetricsDefault GetMetricsReportingDefaultState(PrefService* local_state) { + return static_cast( + local_state->GetInteger(prefs::kMetricsDefaultOptIn)); +} + +} // namespace metrics diff --git a/components/metrics/metrics_reporting_default_state.h b/components/metrics/metrics_reporting_default_state.h new file mode 100644 index 0000000000000..ad81e99c3e0f7 --- /dev/null +++ b/components/metrics/metrics_reporting_default_state.h @@ -0,0 +1,48 @@ +// 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_METRICS_REPORTING_DEFAULT_STATE_H_ +#define COMPONENTS_METRICS_METRICS_REPORTING_DEFAULT_STATE_H_ + +class PrefRegistrySimple; +class PrefService; + +namespace metrics { + +// Metrics reporting default state. 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, +}; + +// Register prefs relating to metrics reporting state. Currently only registers +// a pref for metrics reporting default opt-in state. +void RegisterMetricsReportingStatePrefs(PrefRegistrySimple* registry); + +// Sets whether metrics reporting was opt-in or not. If it was opt-in, then the +// enable checkbox on first-run was default unchecked. If it was opt-out, then +// the checkbox was default checked. This should only be set once, and only +// during first-run. +void RecordMetricsReportingDefaultState(PrefService* local_state, + EnableMetricsDefault default_state); + +// Same as above, but does not verify the current state is UNKNOWN. +void ForceRecordMetricsReportingDefaultState( + PrefService* local_state, + EnableMetricsDefault default_state); + +// Gets information about the default value for the enable metrics reporting +// checkbox shown during first-run. +EnableMetricsDefault GetMetricsReportingDefaultState(PrefService* local_state); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_REPORTING_DEFAULT_STATE_H_ diff --git a/components/metrics/metrics_reporting_service.cc b/components/metrics/metrics_reporting_service.cc new file mode 100644 index 0000000000000..4ab08d71a3e22 --- /dev/null +++ b/components/metrics/metrics_reporting_service.cc @@ -0,0 +1,97 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ReportingService specialized to report UMA metrics. + +#include "components/metrics/metrics_reporting_service.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/persisted_logs_metrics_impl.h" +#include "components/metrics/url_constants.h" +#include "components/prefs/pref_registry_simple.h" + +namespace metrics { + +namespace { + +// 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; + +} // namespace + +// static +void MetricsReportingService::RegisterPrefs(PrefRegistrySimple* registry) { + ReportingService::RegisterPrefs(registry); + MetricsLogStore::RegisterPrefs(registry); +} + +MetricsReportingService::MetricsReportingService(MetricsServiceClient* client, + PrefService* local_state) + : ReportingService(client, local_state, kUploadLogAvoidRetransmitSize), + metrics_log_store_(local_state, kUploadLogAvoidRetransmitSize) {} + +MetricsReportingService::~MetricsReportingService() {} + +LogStore* MetricsReportingService::log_store() { + return &metrics_log_store_; +} + +std::string MetricsReportingService::GetUploadUrl() const { + return client()->GetMetricsServerUrl(); +} + +std::string MetricsReportingService::GetInsecureUploadUrl() const { + return client()->GetInsecureMetricsServerUrl(); +} + +base::StringPiece MetricsReportingService::upload_mime_type() const { + return kDefaultMetricsMimeType; +} + +MetricsLogUploader::MetricServiceType MetricsReportingService::service_type() + const { + return MetricsLogUploader::UMA; +} + +void MetricsReportingService::LogActualUploadInterval( + base::TimeDelta interval) { + UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.ActualLogUploadInterval", + interval.InMinutes(), 1, + base::TimeDelta::FromHours(12).InMinutes(), 50); +} + +void MetricsReportingService::LogCellularConstraint(bool upload_canceled) { + UMA_HISTOGRAM_BOOLEAN("UMA.LogUpload.Canceled.CellularConstraint", + upload_canceled); +} + +void MetricsReportingService::LogResponseOrErrorCode(int response_code, + int error_code, + bool was_https) { + if (was_https) { + base::UmaHistogramSparse("UMA.LogUpload.ResponseOrErrorCode", + response_code >= 0 ? response_code : error_code); + } else { + base::UmaHistogramSparse("UMA.LogUpload.ResponseOrErrorCode.HTTP", + response_code >= 0 ? response_code : error_code); + } +} + +void MetricsReportingService::LogSuccess(size_t log_size) { + UMA_HISTOGRAM_COUNTS_10000("UMA.LogSize.OnSuccess", log_size / 1024); +} + +void MetricsReportingService::LogLargeRejection(size_t log_size) { + UMA_HISTOGRAM_COUNTS_1M("UMA.Large Rejected Log was Discarded", + static_cast(log_size)); +} + +} // namespace metrics diff --git a/components/metrics/metrics_reporting_service.h b/components/metrics/metrics_reporting_service.h new file mode 100644 index 0000000000000..38961b27a39ae --- /dev/null +++ b/components/metrics/metrics_reporting_service.h @@ -0,0 +1,68 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines a service that sends metrics logs to a server. + +#ifndef COMPONENTS_METRICS_METRICS_REPORTING_SERVICE_H_ +#define COMPONENTS_METRICS_METRICS_REPORTING_SERVICE_H_ + +#include + +#include + +#include "base/macros.h" +#include "components/metrics/metrics_log_store.h" +#include "components/metrics/reporting_service.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class MetricsServiceClient; + +// MetricsReportingService is concrete implementation of ReportingService for +// UMA logs. It uses a MetricsLogStore as its LogStore, reports to the UMA +// endpoint, and logs some histograms with the UMA prefix. +class MetricsReportingService : public ReportingService { + public: + // Creates a ReportingService with the given |client|, |local_state|. + // Does not take ownership of the parameters; instead it stores a weak + // pointer to each. Caller should ensure that the parameters are valid for + // the lifetime of this class. + MetricsReportingService(MetricsServiceClient* client, + PrefService* local_state); + ~MetricsReportingService() override; + + MetricsLogStore* metrics_log_store() { return &metrics_log_store_; } + const MetricsLogStore* metrics_log_store() const { + return &metrics_log_store_; + } + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + private: + // ReportingService: + LogStore* log_store() override; + std::string GetUploadUrl() const override; + std::string GetInsecureUploadUrl() const override; + base::StringPiece upload_mime_type() const override; + MetricsLogUploader::MetricServiceType service_type() const override; + void LogActualUploadInterval(base::TimeDelta interval) override; + void LogCellularConstraint(bool upload_canceled) override; + void LogResponseOrErrorCode(int response_code, + int error_code, + bool was_https) override; + void LogSuccess(size_t log_size) override; + void LogLargeRejection(size_t log_size) override; + + MetricsLogStore metrics_log_store_; + + DISALLOW_COPY_AND_ASSIGN(MetricsReportingService); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_REPORTING_SERVICE_H_ diff --git a/components/metrics/metrics_rotation_scheduler.cc b/components/metrics/metrics_rotation_scheduler.cc new file mode 100644 index 0000000000000..c760b4a525a58 --- /dev/null +++ b/components/metrics/metrics_rotation_scheduler.cc @@ -0,0 +1,53 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/metrics_rotation_scheduler.h" + +#include "base/metrics/histogram_macros.h" +#include "build/build_config.h" + +namespace metrics { + +MetricsRotationScheduler::MetricsRotationScheduler( + const base::Closure& upload_callback, + const base::Callback& upload_interval_callback) + : MetricsScheduler(upload_callback), + init_task_complete_(false), + waiting_for_init_task_complete_(false), + upload_interval_callback_(upload_interval_callback) {} + +MetricsRotationScheduler::~MetricsRotationScheduler() {} + +void MetricsRotationScheduler::InitTaskComplete() { + DCHECK(!init_task_complete_); + init_task_complete_ = true; + if (waiting_for_init_task_complete_) { + waiting_for_init_task_complete_ = false; + TriggerTask(); + } else { + LogMetricsInitSequence(INIT_TASK_COMPLETED_FIRST); + } +} + +void MetricsRotationScheduler::RotationFinished() { + TaskDone(upload_interval_callback_.Run()); +} + +void MetricsRotationScheduler::LogMetricsInitSequence(InitSequence sequence) { + UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence, + INIT_SEQUENCE_ENUM_SIZE); +} + +void MetricsRotationScheduler::TriggerTask() { + // 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; + } + MetricsScheduler::TriggerTask(); +} + +} // namespace metrics diff --git a/components/metrics/metrics_rotation_scheduler.h b/components/metrics/metrics_rotation_scheduler.h new file mode 100644 index 0000000000000..20a2a4499fc3b --- /dev/null +++ b/components/metrics/metrics_rotation_scheduler.h @@ -0,0 +1,62 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_METRICS_ROTATION_SCHEDULER_H_ +#define COMPONENTS_METRICS_METRICS_ROTATION_SCHEDULER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "components/metrics/metrics_scheduler.h" + +namespace metrics { + +// Scheduler task to drive a MetricsService object's uploading. +class MetricsRotationScheduler : public MetricsScheduler { + public: + // Creates MetricsRotationScheduler object with the given |rotation_callback| + // callback to call when log rotation should happen and |interval_callback| + // to determine the interval between rotations in steady state. + // |rotation_callback| must arrange to call RotationFinished on completion. + MetricsRotationScheduler( + const base::Closure& rotation_callback, + const base::Callback& interval_callback); + ~MetricsRotationScheduler() override; + + // Callback from MetricsService when the startup init task has completed. + void InitTaskComplete(); + + // Callback from MetricsService when a triggered rotation finishes. + void RotationFinished(); + + protected: + enum InitSequence { + TIMER_FIRED_FIRST, + INIT_TASK_COMPLETED_FIRST, + INIT_SEQUENCE_ENUM_SIZE, + }; + + private: + // Record the init sequence order histogram. + virtual void LogMetricsInitSequence(InitSequence sequence); + + // MetricsScheduler: + void TriggerTask() override; + + // 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 upload_interval_callback_; + + DISALLOW_COPY_AND_ASSIGN(MetricsRotationScheduler); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_ROTATION_SCHEDULER_H_ diff --git a/components/metrics/metrics_scheduler.cc b/components/metrics/metrics_scheduler.cc new file mode 100644 index 0000000000000..86d11579c9188 --- /dev/null +++ b/components/metrics/metrics_scheduler.cc @@ -0,0 +1,64 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/metrics_scheduler.h" + +#include "build/build_config.h" + +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 kInitialIntervalSeconds = 15; +#else +const int kInitialIntervalSeconds = 60; +#endif + +} // namespace + +MetricsScheduler::MetricsScheduler(const base::Closure& task_callback) + : task_callback_(task_callback), + interval_(base::TimeDelta::FromSeconds(kInitialIntervalSeconds)), + running_(false), + callback_pending_(false) {} + +MetricsScheduler::~MetricsScheduler() {} + +void MetricsScheduler::Start() { + running_ = true; + ScheduleNextTask(); +} + +void MetricsScheduler::Stop() { + running_ = false; + if (timer_.IsRunning()) + timer_.Stop(); +} + +void MetricsScheduler::TaskDone(base::TimeDelta next_interval) { + DCHECK(callback_pending_); + callback_pending_ = false; + interval_ = next_interval; + if (running_) + ScheduleNextTask(); +} + +void MetricsScheduler::TriggerTask() { + callback_pending_ = true; + task_callback_.Run(); +} + +void MetricsScheduler::ScheduleNextTask() { + DCHECK(running_); + if (timer_.IsRunning() || callback_pending_) + return; + + timer_.Start(FROM_HERE, interval_, this, &MetricsScheduler::TriggerTask); +} + +} // namespace metrics diff --git a/components/metrics/metrics_scheduler.h b/components/metrics/metrics_scheduler.h new file mode 100644 index 0000000000000..fc031fb8ddc09 --- /dev/null +++ b/components/metrics/metrics_scheduler.h @@ -0,0 +1,65 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_METRICS_SCHEDULER_H_ +#define COMPONENTS_METRICS_METRICS_SCHEDULER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "base/timer/timer.h" + +namespace metrics { + +// Scheduler task to drive a MetricsService object's uploading. +class MetricsScheduler { + public: + // Creates MetricsScheduler object with the given |task_callback| + // callback to call when a task should happen. + explicit MetricsScheduler(const base::Closure& task_callback); + virtual ~MetricsScheduler(); + + // 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(); + + protected: + // Subclasses should provide task_callback with a wrapper to call this with. + // This indicates the triggered task was completed/cancelled and the next + // call can be scheduled. + void TaskDone(base::TimeDelta next_interval); + + // Called by the Timer when it's time to run the task. + virtual void TriggerTask(); + + private: + // Schedules a future call to TriggerTask if one isn't already pending. + void ScheduleNextTask(); + + // The method to call when task should happen. + const base::Closure task_callback_; + + // Uses a one-shot timer rather than a repeating one because the task may be + // async, and the length of the interval may change. + base::OneShotTimer timer_; + + // The interval between being told an task is done and starting the next task. + base::TimeDelta interval_; + + // Indicates that the scheduler is running (i.e., that Start has been called + // more recently than Stop). + bool running_; + + // Indicates that the last triggered task hasn't resolved yet. + bool callback_pending_; + + DISALLOW_COPY_AND_ASSIGN(MetricsScheduler); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SCHEDULER_H_ diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc new file mode 100644 index 0000000000000..bfa17fe93b369 --- /dev/null +++ b/components/metrics/metrics_service.cc @@ -0,0 +1,921 @@ +// 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 + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/persistent_histogram_allocator.h" +#include "base/metrics/statistics_recorder.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_piece.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/environment_recorder.h" +#include "components/metrics/field_trials_provider.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_rotation_scheduler.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/metrics/persistent_system_profile.h" +#include "components/metrics/stability_metrics_provider.h" +#include "components/metrics/url_constants.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/variations/entropy_provider.h" + +namespace metrics { + +namespace { + +// 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 browser last live timestamp is updated every 15 minutes. +const int kUpdateAliveTimestampSeconds = 15 * 60; + +#if defined(OS_ANDROID) || defined(OS_IOS) +void MarkAppCleanShutdownAndCommit(CleanExitBeacon* clean_exit_beacon, + PrefService* local_state) { + clean_exit_beacon->WriteBeaconValue(true); + ExecutionPhaseManager(local_state).OnAppEnterBackground(); + // Start writing right away (write happens on a different thread). + local_state->CommitPendingWrite(); +} +#endif // defined(OS_ANDROID) || defined(OS_IOS) + +} // namespace + +// static +MetricsService::ShutdownCleanliness MetricsService::clean_shutdown_status_ = + MetricsService::CLEANLY_SHUTDOWN; + +// static +void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) { + CleanExitBeacon::RegisterPrefs(registry); + MetricsStateManager::RegisterPrefs(registry); + MetricsLog::RegisterPrefs(registry); + StabilityMetricsProvider::RegisterPrefs(registry); + ExecutionPhaseManager::RegisterPrefs(registry); + MetricsReportingService::RegisterPrefs(registry); + + registry->RegisterIntegerPref(prefs::kMetricsSessionID, -1); + + registry->RegisterInt64Pref(prefs::kUninstallLaunchCount, 0); + registry->RegisterInt64Pref(prefs::kUninstallMetricsUptimeSec, 0); +} + +MetricsService::MetricsService(MetricsStateManager* state_manager, + MetricsServiceClient* client, + PrefService* local_state) + : reporting_service_(client, local_state), + histogram_snapshot_manager_(this), + state_manager_(state_manager), + client_(client), + local_state_(local_state), + recording_state_(UNSET), + test_mode_active_(false), + state_(INITIALIZED), + idle_since_last_transmission_(false), + session_id_(-1), + self_ptr_factory_(this) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(state_manager_); + DCHECK(client_); + DCHECK(local_state_); + + RegisterMetricsProvider( + std::make_unique(local_state_)); + + RegisterMetricsProvider(state_manager_->GetProvider()); + + RegisterMetricsProvider(std::make_unique( + &synthetic_trial_registry_, base::StringPiece())); +} + +MetricsService::~MetricsService() { + DisableRecording(); +} + +void MetricsService::InitializeMetricsRecordingState() { + reporting_service_.Initialize(); + InitializeMetricsState(); + + base::Closure upload_callback = + base::Bind(&MetricsService::StartScheduledUpload, + self_ptr_factory_.GetWeakPtr()); + + rotation_scheduler_.reset(new MetricsRotationScheduler( + upload_callback, + // MetricsServiceClient outlives MetricsService, and + // MetricsRotationScheduler is tied to the lifetime of |this|. + base::Bind(&MetricsServiceClient::GetStandardUploadInterval, + base::Unretained(client_)))); + + // Init() has to be called after LogCrash() in order for LogCrash() to work. + delegating_provider_.Init(); +} + +void MetricsService::Start() { + HandleIdleSinceLastTransmission(false); + EnableRecording(); + EnableReporting(); +} + +void MetricsService::StartRecordingForTests() { + test_mode_active_ = true; + EnableRecording(); + DisableReporting(); +} + +void MetricsService::StartUpdatingLastLiveTimestamp() { + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&MetricsService::UpdateLastLiveTimestampTask, + self_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromSeconds(kUpdateAliveTimestampSeconds)); +} + +void MetricsService::Stop() { + HandleIdleSinceLastTransmission(false); + DisableReporting(); + DisableRecording(); +} + +void MetricsService::EnableReporting() { + if (reporting_service_.reporting_active()) + return; + reporting_service_.EnableReporting(); + StartSchedulerIfNecessary(); +} + +void MetricsService::DisableReporting() { + reporting_service_.DisableReporting(); +} + +std::string MetricsService::GetClientId() { + return state_manager_->client_id(); +} + +int64_t MetricsService::GetInstallDate() { + return state_manager_->GetInstallDate(); +} + +bool MetricsService::WasLastShutdownClean() const { + return state_manager_->clean_exit_beacon()->exited_cleanly(); +} + +void MetricsService::EnableRecording() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (recording_state_ == ACTIVE) + return; + recording_state_ = ACTIVE; + + state_manager_->ForceClientIdCreation(); + client_->SetMetricsClientId(state_manager_->client_id()); + + SystemProfileProto system_profile; + MetricsLog::RecordCoreSystemProfile(client_, &system_profile); + GlobalPersistentSystemProfile::GetInstance()->SetSystemProfile( + system_profile, /*complete=*/false); + + if (!log_manager_.current_log()) + OpenNewLog(); + + delegating_provider_.OnRecordingEnabled(); + + base::RemoveActionCallback(action_callback_); + action_callback_ = base::Bind(&MetricsService::OnUserAction, + base::Unretained(this)); + base::AddActionCallback(action_callback_); +} + +void MetricsService::DisableRecording() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (recording_state_ == INACTIVE) + return; + recording_state_ = INACTIVE; + + base::RemoveActionCallback(action_callback_); + + delegating_provider_.OnRecordingDisabled(); + + PushPendingLogsToPersistentStorage(); +} + +bool MetricsService::recording_active() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return recording_state_ == ACTIVE; +} + +bool MetricsService::reporting_active() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return reporting_service_.reporting_active(); +} + +bool MetricsService::has_unsent_logs() const { + return reporting_service_.metrics_log_store()->has_unsent_logs(); +} + +void MetricsService::RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) { + log_manager_.current_log()->RecordHistogramDelta(histogram.histogram_name(), + snapshot); +} + +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(false); +} + +void MetricsService::RecordCompletedSessionEnd() { + LogCleanShutdown(true); +} + +#if defined(OS_ANDROID) || defined(OS_IOS) +void MetricsService::OnAppEnterBackground() { + rotation_scheduler_->Stop(); + reporting_service_.Stop(); + + MarkAppCleanShutdownAndCommit(state_manager_->clean_exit_beacon(), + local_state_); + + // Give providers a chance to persist histograms as part of being + // backgrounded. + delegating_provider_.OnAppEnterBackground(); + + // 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() { + state_manager_->clean_exit_beacon()->WriteBeaconValue(false); + ExecutionPhaseManager(local_state_).OnAppEnterForeground(); + StartSchedulerIfNecessary(); +} +#else +void MetricsService::LogNeedForCleanShutdown() { + state_manager_->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) { + ExecutionPhaseManager(local_state).SetExecutionPhase(execution_phase); +} + +void MetricsService::RecordBreakpadRegistration(bool success) { + StabilityMetricsProvider(local_state_).RecordBreakpadRegistration(success); +} + +void MetricsService::RecordBreakpadHasDebugger(bool has_debugger) { + StabilityMetricsProvider(local_state_) + .RecordBreakpadHasDebugger(has_debugger); +} + +void MetricsService::ClearSavedStabilityMetrics() { + delegating_provider_.ClearSavedStabilityMetrics(); +} + +void MetricsService::PushExternalLog(const std::string& log) { + log_store()->StoreLog(log, MetricsLog::ONGOING_LOG); +} + +void MetricsService::UpdateMetricsUsagePrefs(const std::string& service_name, + int message_size, + bool is_cellular) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + reporting_service_.UpdateMetricsUsagePrefs(service_name, message_size, + is_cellular); +} + +//------------------------------------------------------------------------------ +// private methods +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Initialization methods + +void MetricsService::InitializeMetricsState() { + const int64_t buildtime = MetricsLog::GetBuildTime(); + const std::string version = client_->GetVersionString(); + + bool version_changed = false; + EnvironmentRecorder recorder(local_state_); + int64_t previous_buildtime = recorder.GetLastBuildtime(); + std::string previous_version = recorder.GetLastVersion(); + if (previous_buildtime != buildtime || previous_version != version) { + recorder.SetBuildtimeAndVersion(buildtime, version); + version_changed = true; + } + + session_id_ = local_state_->GetInteger(prefs::kMetricsSessionID); + + StabilityMetricsProvider provider(local_state_); + if (!state_manager_->clean_exit_beacon()->exited_cleanly()) { + provider.LogCrash( + state_manager_->clean_exit_beacon()->browser_last_live_timestamp()); + // Reset flag, and wait until we call LogNeedForCleanShutdown() before + // monitoring. + state_manager_->clean_exit_beacon()->WriteBeaconValue(true); + ExecutionPhaseManager manager(local_state_); + base::UmaHistogramSparse("Chrome.Browser.CrashedExecutionPhase", + static_cast(manager.GetExecutionPhase())); + manager.SetExecutionPhase(ExecutionPhase::UNINITIALIZED_PHASE); + } + + // HasPreviousSessionData is called first to ensure it is never bypassed. + const bool is_initial_stability_log_required = + delegating_provider_.HasPreviousSessionData() || + !state_manager_->clean_exit_beacon()->exited_cleanly(); + bool has_initial_stability_log = false; + if (is_initial_stability_log_required) { + // 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(previous_version); + if (!has_initial_stability_log) + provider.LogStabilityLogDeferred(); + } + } + + // If the version changed, but no initial stability log was generated, 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 (version_changed && !has_initial_stability_log) { + ClearSavedStabilityMetrics(); + provider.LogStabilityDataDiscarded(); + } + + // If the version changed, the system profile is obsolete and needs to be + // cleared. This is to avoid the stability data misattribution that could + // occur if the current version crashed before saving its own system profile. + // Note however this clearing occurs only after preparing the initial + // stability log, an operation that requires the previous version's system + // profile. At this point, stability metrics pertaining to the previous + // version have been cleared. + if (version_changed) + recorder.ClearEnvironmentFromPrefs(); + + // Update session ID. + ++session_id_; + local_state_->SetInteger(prefs::kMetricsSessionID, session_id_); + + // Notify stability metrics providers about the launch. + provider.LogLaunch(); + SetExecutionPhase(ExecutionPhase::START_METRICS_RECORDING, local_state_); + provider.CheckLastSessionEndCompleted(); + + // 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()); + + // Bookkeeping for the uninstall metrics. + IncrementLongPrefsValue(prefs::kUninstallLaunchCount); +} + +void MetricsService::OnUserAction(const std::string& action) { + 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_) { + initial_metrics_log_ = CreateLog(MetricsLog::ONGOING_LOG); + delegating_provider_.OnDidCreateMetricsLog(); + } + + rotation_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); + } +} + +//------------------------------------------------------------------------------ +// Recording control methods + +void MetricsService::OpenNewLog() { + DCHECK(!log_manager_.current_log()); + + log_manager_.BeginLoggingWithLog(CreateLog(MetricsLog::ONGOING_LOG)); + delegating_provider_.OnDidCreateMetricsLog(); + if (state_ == INITIALIZED) { + // We only need to schedule that run once. + state_ = INIT_TASK_SCHEDULED; + + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&MetricsService::StartInitTask, + self_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromSeconds(kInitializationDelaySeconds)); + + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&MetricsService::PrepareProviderMetricsTask, + self_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromSeconds(2 * kInitializationDelaySeconds)); + } +} + +void MetricsService::StartInitTask() { + delegating_provider_.AsyncInit(base::Bind(&MetricsService::FinishedInitTask, + self_ptr_factory_.GetWeakPtr())); +} + +void MetricsService::CloseCurrentLog() { + if (!log_manager_.current_log()) + return; + + // 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->RecordCurrentSessionData(&delegating_provider_, + incremental_uptime, uptime); + RecordCurrentHistograms(); + current_log->TruncateEvents(); + DVLOG(1) << "Generated an ongoing log."; + log_manager_.FinishCurrentLog(log_store()); +} + +void MetricsService::PushPendingLogsToPersistentStorage() { + if (state_ < SENDING_LOGS) + return; // We didn't and still don't have time to get plugin list etc. + + CloseCurrentLog(); + log_store()->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)) { + rotation_scheduler_->Start(); + reporting_service_.Start(); + } +} + +void MetricsService::StartScheduledUpload() { + DVLOG(1) << "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)) { + rotation_scheduler_->Stop(); + rotation_scheduler_->RotationFinished(); + 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 && has_unsent_logs()) { + reporting_service_.Start(); + rotation_scheduler_->RotationFinished(); + } 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() { + DVLOG(1) << "OnFinalLogInfoCollectionDone"; + + // Abort if metrics were turned off during the final info gathering. + if (!recording_active()) { + rotation_scheduler_->Stop(); + rotation_scheduler_->RotationFinished(); + return; + } + + if (state_ == INIT_TASK_DONE) { + PrepareInitialMetricsLog(); + } else { + DCHECK_EQ(SENDING_LOGS, state_); + CloseCurrentLog(); + OpenNewLog(); + } + reporting_service_.Start(); + rotation_scheduler_->RotationFinished(); + HandleIdleSinceLastTransmission(true); +} + +bool MetricsService::PrepareInitialStabilityLog( + const std::string& prefs_previous_version) { + DCHECK_EQ(INITIALIZED, state_); + + std::unique_ptr initial_stability_log( + CreateLog(MetricsLog::INITIAL_STABILITY_LOG)); + + // Do not call OnDidCreateMetricsLog here because the stability + // log describes stats from the _previous_ session. + std::string system_profile_app_version; + if (!initial_stability_log->LoadSavedEnvironmentFromPrefs( + local_state_, &system_profile_app_version)) { + return false; + } + if (system_profile_app_version != prefs_previous_version) + StabilityMetricsProvider(local_state_).LogStabilityVersionMismatch(); + + 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()->RecordPreviousSessionData(&delegating_provider_); + RecordCurrentStabilityHistograms(); + + DVLOG(1) << "Generated an stability log."; + log_manager_.FinishCurrentLog(log_store()); + 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_store()->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(). + log_manager_.current_log()->RecordCurrentSessionData( + &delegating_provider_, base::TimeDelta(), base::TimeDelta()); + RecordCurrentHistograms(); + + DVLOG(1) << "Generated an initial log."; + log_manager_.FinishCurrentLog(log_store()); + 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_store()->PersistUnsentLogs(); + + state_ = SENDING_LOGS; +} + +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::RegisterMetricsProvider( + std::unique_ptr provider) { + DCHECK_EQ(INITIALIZED, state_); + delegating_provider_.RegisterMetricsProvider(std::move(provider)); +} + +void MetricsService::CheckForClonedInstall() { + state_manager_->CheckForClonedInstall(); +} + +std::unique_ptr MetricsService::CreateLog( + MetricsLog::LogType log_type) { + return std::make_unique(state_manager_->client_id(), session_id_, + log_type, client_); +} + +std::string MetricsService::RecordCurrentEnvironmentHelper( + MetricsLog* log, + PrefService* local_state, + DelegatingProvider* delegating_provider) { + const SystemProfileProto& system_profile = + log->RecordEnvironment(delegating_provider); + EnvironmentRecorder recorder(local_state); + return recorder.SerializeAndRecordEnvironmentToPrefs(system_profile); +} + +void MetricsService::RecordCurrentEnvironment(MetricsLog* log) { + DCHECK(client_); + std::string serialized_proto = + RecordCurrentEnvironmentHelper(log, local_state_, &delegating_provider_); + GlobalPersistentSystemProfile::GetInstance()->SetSystemProfile( + serialized_proto, /*complete=*/true); + client_->OnEnvironmentUpdate(&serialized_proto); +} + +void MetricsService::RecordCurrentHistograms() { + DCHECK(log_manager_.current_log()); + + // "true" indicates that StatisticsRecorder should include histograms held in + // persistent storage. + base::StatisticsRecorder::PrepareDeltas( + true, base::Histogram::kNoFlags, + base::Histogram::kUmaTargetedHistogramFlag, &histogram_snapshot_manager_); + delegating_provider_.RecordHistogramSnapshots(&histogram_snapshot_manager_); +} + +void MetricsService::RecordCurrentStabilityHistograms() { + DCHECK(log_manager_.current_log()); + // "true" indicates that StatisticsRecorder should include histograms held in + // persistent storage. + base::StatisticsRecorder::PrepareDeltas( + true, base::Histogram::kNoFlags, + base::Histogram::kUmaStabilityHistogramFlag, + &histogram_snapshot_manager_); + delegating_provider_.RecordInitialHistogramSnapshots( + &histogram_snapshot_manager_); +} + +bool MetricsService::PrepareProviderMetricsLog() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Create a new log. This will have some defaut values injected in it but + // those will be overwritten when an embedded profile is extracted. + std::unique_ptr log = CreateLog(MetricsLog::INDEPENDENT_LOG); + + for (auto& provider : delegating_provider_.GetProviders()) { + if (log->LoadIndependentMetrics(provider.get())) { + log_manager_.PauseCurrentLog(); + log_manager_.BeginLoggingWithLog(std::move(log)); + log_manager_.FinishCurrentLog(log_store()); + log_manager_.ResumePausedLog(); + return true; + } + } + return false; +} + +void MetricsService::PrepareProviderMetricsTask() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + bool found = PrepareProviderMetricsLog(); + base::TimeDelta next_check = found ? base::TimeDelta::FromSeconds(5) + : base::TimeDelta::FromMinutes(15); + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&MetricsService::PrepareProviderMetricsTask, + self_ptr_factory_.GetWeakPtr()), + next_check); +} + +void MetricsService::LogCleanShutdown(bool end_completed) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // 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; + client_->OnLogCleanShutdown(); + state_manager_->clean_exit_beacon()->WriteBeaconValue(true); + SetExecutionPhase(ExecutionPhase::SHUTDOWN_COMPLETE, local_state_); + StabilityMetricsProvider(local_state_).MarkSessionEndCompleted(end_completed); +} + +void MetricsService::UpdateLastLiveTimestampTask() { + state_manager_->clean_exit_beacon()->UpdateLastLiveTimestamp(); + + // Schecule the next update. + StartUpdatingLastLiveTimestamp(); +} + +} // namespace metrics diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h new file mode 100644 index 0000000000000..647131422b2d5 --- /dev/null +++ b/components/metrics/metrics_service.h @@ -0,0 +1,402 @@ +// 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 + +#include +#include +#include +#include + +#include "base/gtest_prod_util.h" +#include "base/macros.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/sequence_checker.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/clean_exit_beacon.h" +#include "components/metrics/delegating_provider.h" +#include "components/metrics/execution_phase.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_log_manager.h" +#include "components/metrics/metrics_log_store.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/metrics_reporting_service.h" +#include "components/metrics/net/network_metrics_provider.h" +#include "components/variations/synthetic_trial_registry.h" + +class PrefService; +class PrefRegistrySimple; + +namespace base { +class HistogramSamples; +class PrefService; +} + +namespace metrics { + +class MetricsRotationScheduler; +class MetricsServiceClient; +class MetricsStateManager; + +// See metrics_service.cc for a detailed description. +class MetricsService : public base::HistogramFlattener { + public: + // 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(); + + // Starts updating the "last live" browser timestamp. + void StartUpdatingLastLiveTimestamp(); + + // 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; + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // HistogramFlattener: + void RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) 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; + bool has_unsent_logs() 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(); + + // Register the specified |provider| to provide additional metrics into the + // UMA log. Should be called during MetricsService initialization only. + void RegisterMetricsProvider(std::unique_ptr 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(); + + // 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); + + // Updates data usage tracking prefs with the specified values. + void UpdateMetricsUsagePrefs(const std::string& service_name, + int message_size, + bool is_cellular); + + variations::SyntheticTrialRegistry* synthetic_trial_registry() { + return &synthetic_trial_registry_; + } + + protected: + // Exposed for testing. + MetricsLogManager* log_manager() { return &log_manager_; } + MetricsLogStore* log_store() { + return reporting_service_.metrics_log_store(); + } + + // Records the current environment (system profile) in |log|, and persists + // the results in prefs. + // Exposed for testing. + static std::string RecordCurrentEnvironmentHelper( + MetricsLog* log, + PrefService* local_state, + DelegatingProvider* delegating_provider); + + private: + // 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 + }; + + // 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 + // their 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(); + + // 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(); + + // 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. |prefs_previous_version| is used + // to validate the version number recovered from the system profile. Returns + // true if a log was created. + bool PrepareInitialStabilityLog(const std::string& prefs_previous_version); + + // Prepares the initial metrics log, which includes startup histograms and + // profiler data, as well as incremental stability-related metrics. + void PrepareInitialMetricsLog(); + + // 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(bool end_completed); + + // Creates a new MetricsLog instance with the given |log_type|. + std::unique_ptr CreateLog(MetricsLog::LogType log_type); + + // Records the current environment (system profile) in |log|, and persists + // the results in prefs and GlobalPersistentSystemProfile. + // Exposed for testing. + 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(); + + // Record a single independent profile and associated histogram from + // metrics providers. If this returns true, one was found and there may + // be more. + bool PrepareProviderMetricsLog(); + + // Records one independent histogram log and then reschedules itself to + // check for others. The interval is so as to not adversely impact the UI. + void PrepareProviderMetricsTask(); + + // Updates the "last live" browser timestamp and schedules the next update. + void UpdateLastLiveTimestampTask(); + + // Sub-service for uploading logs. + MetricsReportingService reporting_service_; + + // 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. + DelegatingProvider delegating_provider_; + + PrefService* local_state_; + + 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_; + + // 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. + std::unique_ptr initial_metrics_log_; + + // 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 log rotations should happen. + std::unique_ptr rotation_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_; + + variations::SyntheticTrialRegistry synthetic_trial_registry_; + + // 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); + + SEQUENCE_CHECKER(sequence_checker_); + + // 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 self_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(MetricsService); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SERVICE_H_ diff --git a/components/metrics/metrics_service_accessor.cc b/components/metrics/metrics_service_accessor.cc new file mode 100644 index 0000000000000..f00d2e8523f6f --- /dev/null +++ b/components/metrics/metrics_service_accessor.cc @@ -0,0 +1,96 @@ +// 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/hashing.h" + +namespace metrics { +namespace { + +bool g_force_official_enabled_test = false; + +bool IsMetricsReportingEnabledForOfficialBuild(PrefService* pref_service) { + // 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) && + pref_service->GetBoolean(prefs::kMetricsReportingEnabled); +} + +} // namespace + +// static +bool MetricsServiceAccessor::IsMetricsReportingEnabled( + PrefService* pref_service) { +#if defined(GOOGLE_CHROME_BUILD) + return IsMetricsReportingEnabledForOfficialBuild(pref_service); +#else + // In non-official builds, disable metrics reporting completely. + return g_force_official_enabled_test + ? IsMetricsReportingEnabledForOfficialBuild(pref_service) + : false; +#endif // defined(GOOGLE_CHROME_BUILD) +} + +// static +bool MetricsServiceAccessor::RegisterSyntheticFieldTrial( + MetricsService* metrics_service, + base::StringPiece trial_name, + base::StringPiece group_name) { + return RegisterSyntheticFieldTrialWithNameAndGroupHash( + metrics_service, variations::HashName(trial_name), + variations::HashName(group_name)); +} + +// static +bool MetricsServiceAccessor::RegisterSyntheticMultiGroupFieldTrial( + MetricsService* metrics_service, + base::StringPiece trial_name, + const std::vector& group_name_hashes) { + if (!metrics_service) + return false; + + metrics_service->synthetic_trial_registry() + ->RegisterSyntheticMultiGroupFieldTrial(variations::HashName(trial_name), + group_name_hashes); + return true; +} + +// static +bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameHash( + MetricsService* metrics_service, + uint32_t trial_name_hash, + base::StringPiece group_name) { + return RegisterSyntheticFieldTrialWithNameAndGroupHash( + metrics_service, trial_name_hash, variations::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->synthetic_trial_registry()->RegisterSyntheticFieldTrial( + trial_group); + return true; +} + +// static +void MetricsServiceAccessor::SetForceIsMetricsReportingEnabledPrefLookup( + bool value) { + g_force_official_enabled_test = value; +} + +} // namespace metrics diff --git a/components/metrics/metrics_service_accessor.h b/components/metrics/metrics_service_accessor.h new file mode 100644 index 0000000000000..3a36278213c67 --- /dev/null +++ b/components/metrics/metrics_service_accessor.h @@ -0,0 +1,81 @@ +// 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 +#include + +#include "base/macros.h" +#include "base/strings/string_piece.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. + static bool IsMetricsReportingEnabled(PrefService* pref_service); + + + // 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. Returns true on success. + // See the comment on MetricsService::RegisterSyntheticFieldTrial() for + // details. + static bool RegisterSyntheticFieldTrial(MetricsService* metrics_service, + base::StringPiece trial_name, + base::StringPiece group_name); + + // Registers a field trial name and set of groups with |metrics_service| (if + // not null), to be used to annotate a UMA report with a particular + // configuration state. Returns true on success. + // See the comment on MetricsService::RegisterSyntheticMultiGroupFieldTrial() + // for details. + static bool RegisterSyntheticMultiGroupFieldTrial( + MetricsService* metrics_service, + base::StringPiece trial_name, + const std::vector& group_name_hashes); + + // 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, + base::StringPiece 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); + + // IsMetricsReportingEnabled() in non-official builds unconditionally returns + // false. This results in different behavior for tests running in official vs + // non-official builds. To get consistent behavior call this with true, which + // forces non-official builds to look at the prefs value official builds look + // at. + static void SetForceIsMetricsReportingEnabledPrefLookup(bool value); + + private: + DISALLOW_COPY_AND_ASSIGN(MetricsServiceAccessor); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_ diff --git a/components/metrics/metrics_service_client.cc b/components/metrics/metrics_service_client.cc new file mode 100644 index 0000000000000..a80dc8ad84836 --- /dev/null +++ b/components/metrics/metrics_service_client.cc @@ -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. + +#include "components/metrics/metrics_service_client.h" + +#include "components/metrics/url_constants.h" + +namespace metrics { + +MetricsServiceClient::MetricsServiceClient() {} + +MetricsServiceClient::~MetricsServiceClient() {} + +ukm::UkmService* MetricsServiceClient::GetUkmService() { + return nullptr; +} + +bool MetricsServiceClient::IsReportingPolicyManaged() { + return false; +} + +EnableMetricsDefault MetricsServiceClient::GetMetricsReportingDefaultState() { + return EnableMetricsDefault::DEFAULT_UNKNOWN; +} + +bool MetricsServiceClient::IsUMACellularUploadLogicEnabled() { + return false; +} + +std::string MetricsServiceClient::GetMetricsServerUrl() { + return kNewMetricsServerUrl; +} + +std::string MetricsServiceClient::GetInsecureMetricsServerUrl() { + return kNewMetricsServerUrlInsecure; +} + +bool MetricsServiceClient::SyncStateAllowsUkm() { + return false; +} + +bool MetricsServiceClient::SyncStateAllowsExtensionUkm() { + return false; +} + +bool MetricsServiceClient::AreNotificationListenersEnabledOnAllProfiles() { + return false; +} + +void MetricsServiceClient::SetUpdateRunningServicesCallback( + const base::Closure& callback) { + update_running_services_ = callback; +} + +void MetricsServiceClient::UpdateRunningServices() { + if (update_running_services_) + update_running_services_.Run(); +} + +std::string MetricsServiceClient::GetAppPackageName() { + return std::string(); +} + +} // namespace metrics diff --git a/components/metrics/metrics_service_client.h b/components/metrics/metrics_service_client.h new file mode 100644 index 0000000000000..c1bc828553124 --- /dev/null +++ b/components/metrics/metrics_service_client.h @@ -0,0 +1,151 @@ +// 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 + +#include +#include + +#include "base/callback.h" +#include "base/strings/string16.h" +#include "base/time/time.h" +#include "components/metrics/metrics_log_uploader.h" +#include "components/metrics/metrics_reporting_default_state.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace base { +class FilePath; +} + +namespace ukm { +class UkmService; +} + +namespace metrics { + +class MetricsLogUploader; +class MetricsService; + +// An abstraction of operations that depend on the embedder's (e.g. Chrome) +// environment. +class MetricsServiceClient { + public: + MetricsServiceClient(); + 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; + + // Returns the UkmService instance that this client is associated with. + virtual ukm::UkmService* GetUkmService(); + + // 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; + + // 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 new environment has been recorded. + // Takes the serialized environment as a parameter. The contents of + // |serialized_environment| are consumed by the call, but the caller maintains + // ownership. + virtual void OnEnvironmentUpdate(std::string* serialized_environment) {} + + // Called by the metrics service to record a clean shutdown. + virtual void OnLogCleanShutdown() {} + + // 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; + + // Get the URL of the metrics server. + virtual std::string GetMetricsServerUrl(); + + // Get the fallback HTTP URL of the metrics server. + virtual std::string GetInsecureMetricsServerUrl(); + + // Creates a MetricsLogUploader with the specified parameters (see comments on + // MetricsLogUploader for details). + virtual std::unique_ptr CreateUploader( + base::StringPiece server_url, + base::StringPiece insecure_server_url, + base::StringPiece mime_type, + metrics::MetricsLogUploader::MetricServiceType service_type, + const MetricsLogUploader::UploadCallback& on_upload_complete) = 0; + + // Returns the standard interval between upload attempts. + virtual base::TimeDelta GetStandardUploadInterval() = 0; + + // 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 metrics reporting checkbox + // shown during first-run. + virtual EnableMetricsDefault GetMetricsReportingDefaultState(); + + // Returns whether cellular logic is enabled for metrics reporting. + virtual bool IsUMACellularUploadLogicEnabled(); + + // Returns true iff sync is in a state that allows UKM to be enabled. + // See //components/ukm/observers/sync_disable_observer.h for details. + virtual bool SyncStateAllowsUkm(); + + // Returns true iff sync is in a state that allows UKM to capture extensions. + // See //components/ukm/observers/sync_disable_observer.h for details. + virtual bool SyncStateAllowsExtensionUkm(); + + // Returns whether UKM notification listeners were attached to all profiles. + virtual bool AreNotificationListenersEnabledOnAllProfiles(); + + // Gets the Chrome package name for Android. Returns empty string for other + // platforms. + virtual std::string GetAppPackageName(); + + // Sets the callback to run MetricsServiceManager::UpdateRunningServices. + void SetUpdateRunningServicesCallback(const base::Closure& callback); + + // Notify MetricsServiceManager to UpdateRunningServices using callback. + void UpdateRunningServices(); + + private: + base::Closure update_running_services_; + + DISALLOW_COPY_AND_ASSIGN(MetricsServiceClient); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_ diff --git a/components/metrics/metrics_service_unittest.cc b/components/metrics/metrics_service_unittest.cc new file mode 100644 index 0000000000000..4350238dcb816 --- /dev/null +++ b/components/metrics/metrics_service_unittest.cc @@ -0,0 +1,443 @@ +// 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 + +#include +#include +#include + +#include "base/bind.h" +#include "base/macros.h" +#include "base/metrics/metrics_hashes.h" +#include "base/metrics/statistics_recorder.h" +#include "base/metrics/user_metrics.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/metrics/client_info.h" +#include "components/metrics/environment_recorder.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/metrics/metrics_upload_scheduler.h" +#include "components/metrics/test_enabled_state_provider.h" +#include "components/metrics/test_metrics_provider.h" +#include "components/metrics/test_metrics_service_client.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 { + +void YieldUntil(base::Time when) { + while (base::Time::Now() <= when) + base::PlatformThread::YieldCurrentThread(); +} + +void StoreNoClientInfoBackup(const ClientInfo& /* client_info */) { +} + +std::unique_ptr ReturnNoBackup() { + return std::unique_ptr(); +} + +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; + using MetricsService::log_store; + using MetricsService::RecordCurrentEnvironmentHelper; + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsService); +}; + +class TestMetricsLog : public MetricsLog { + public: + TestMetricsLog(const std::string& client_id, + int session_id, + MetricsServiceClient* client) + : MetricsLog(client_id, session_id, MetricsLog::ONGOING_LOG, client) {} + + ~TestMetricsLog() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsLog); +}; + +class MetricsServiceTest : public testing::Test { + public: + MetricsServiceTest() + : task_runner_(new base::TestSimpleTaskRunner), + task_runner_handle_(task_runner_), + enabled_state_provider_(new TestEnabledStateProvider(false, false)) { + base::SetRecordActionTaskRunner(task_runner_); + MetricsService::RegisterPrefs(testing_local_state_.registry()); + } + + ~MetricsServiceTest() override { + MetricsService::SetExecutionPhase(ExecutionPhase::UNINITIALIZED_PHASE, + GetLocalState()); + } + + MetricsStateManager* GetMetricsStateManager() { + // Lazy-initialize the metrics_state_manager so that it correctly reads the + // stability state from prefs after tests have a chance to initialize it. + if (!metrics_state_manager_) { + metrics_state_manager_ = MetricsStateManager::Create( + GetLocalState(), enabled_state_provider_.get(), base::string16(), + base::Bind(&StoreNoClientInfoBackup), base::Bind(&ReturnNoBackup)); + } + return metrics_state_manager_.get(); + } + + PrefService* GetLocalState() { return &testing_local_state_; } + + // Sets metrics reporting as enabled for testing. + void EnableMetricsReporting() { + enabled_state_provider_->set_consent(true); + enabled_state_provider_->set_enabled(true); + } + + // 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; + const base::StatisticsRecorder::Histograms histograms = + base::StatisticsRecorder::GetHistograms(); + 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; + } + } + + protected: + scoped_refptr task_runner_; + base::ThreadTaskRunnerHandle task_runner_handle_; + base::test::ScopedFeatureList feature_list_; + + private: + std::unique_ptr enabled_state_provider_; + TestingPrefServiceSimple testing_local_state_; + std::unique_ptr metrics_state_manager_; + + 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( + std::unique_ptr(test_provider)); + + service.InitializeMetricsRecordingState(); + + // No initial stability log should be generated. + EXPECT_FALSE(service.has_unsent_logs()); + + // Ensure that HasPreviousSessionData() is always called on providers, + // for consistency, even if other conditions already indicate their presence. + EXPECT_TRUE(test_provider->has_initial_stability_metrics_called()); + + // 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); + DelegatingProvider delegating_provider; + TestMetricsService::RecordCurrentEnvironmentHelper(&log, GetLocalState(), + &delegating_provider); + + // Record stability build time and version from previous session, so that + // stability metrics (including exited cleanly flag) won't be cleared. + EnvironmentRecorder(GetLocalState()) + .SetBuildtimeAndVersion(MetricsLog::GetBuildTime(), + 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( + std::unique_ptr(test_provider)); + + service.InitializeMetricsRecordingState(); + + // The initial stability log should be generated and persisted in unsent logs. + MetricsLogStore* log_store = service.log_store(); + EXPECT_TRUE(log_store->has_unsent_logs()); + EXPECT_FALSE(log_store->has_staged_log()); + + // Ensure that HasPreviousSessionData() is always called on providers, + // for consistency, even if other conditions already indicate their presence. + EXPECT_TRUE(test_provider->has_initial_stability_metrics_called()); + + // 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_store->StageNextLog(); + EXPECT_TRUE(log_store->has_staged_log()); + + std::string uncompressed_log; + EXPECT_TRUE( + compression::GzipUncompress(log_store->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.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); + DelegatingProvider delegating_provider; + TestMetricsService::RecordCurrentEnvironmentHelper(&log, GetLocalState(), + &delegating_provider); + + // Record stability build time and version from previous session, so that + // stability metrics (including exited cleanly flag) won't be cleared. + EnvironmentRecorder(GetLocalState()) + .SetBuildtimeAndVersion(MetricsLog::GetBuildTime(), + client.GetVersionString()); + + GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, false); + + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + // Add a provider. + TestMetricsProvider* test_provider = new TestMetricsProvider(); + service.RegisterMetricsProvider( + std::unique_ptr(test_provider)); + service.InitializeMetricsRecordingState(); + + // The initial stability log should be generated and persisted in unsent logs. + MetricsLogStore* log_store = service.log_store(); + EXPECT_TRUE(log_store->has_unsent_logs()); + EXPECT_FALSE(log_store->has_staged_log()); + + // Ensure that HasPreviousSessionData() is always called on providers, + // for consistency, even if other conditions already indicate their presence. + EXPECT_TRUE(test_provider->has_initial_stability_metrics_called()); + + // 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_store->StageNextLog(); + EXPECT_TRUE(log_store->has_staged_log()); + + std::string uncompressed_log; + EXPECT_TRUE( + compression::GzipUncompress(log_store->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.perf_data_size()); + CheckForNonStabilityHistograms(uma_log); + + EXPECT_EQ(1, uma_log.system_profile().stability().crash_count()); +} + +TEST_F(MetricsServiceTest, + MetricsProviderOnRecordingDisabledCalledOnInitialStop) { + TestMetricsServiceClient client; + TestMetricsService service( + GetMetricsStateManager(), &client, GetLocalState()); + + TestMetricsProvider* test_provider = new TestMetricsProvider(); + service.RegisterMetricsProvider( + std::unique_ptr(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( + std::unique_ptr(test_provider)); + + service.InitializeMetricsRecordingState(); + + EXPECT_TRUE(test_provider->init_called()); +} + +TEST_F(MetricsServiceTest, SplitRotation) { + TestMetricsServiceClient client; + TestMetricsService service(GetMetricsStateManager(), &client, + GetLocalState()); + service.InitializeMetricsRecordingState(); + service.Start(); + // Rotation loop should create a log and mark state as idle. + // Upload loop should start upload or be restarted. + // The independent-metrics upload job will be started and always be a task. + task_runner_->RunPendingTasks(); + // Rotation loop should terminated due to being idle. + // Upload loop should start uploading if it isn't already. + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client.uploader()->is_uploading()); + EXPECT_EQ(1U, task_runner_->NumPendingTasks()); + service.OnApplicationNotIdle(); + EXPECT_TRUE(client.uploader()->is_uploading()); + EXPECT_EQ(2U, task_runner_->NumPendingTasks()); + // Log generation should be suppressed due to unsent log. + // Idle state should not be reset. + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client.uploader()->is_uploading()); + EXPECT_EQ(2U, task_runner_->NumPendingTasks()); + // Make sure idle state was not reset. + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client.uploader()->is_uploading()); + EXPECT_EQ(2U, task_runner_->NumPendingTasks()); + // Upload should not be rescheduled, since there are no other logs. + client.uploader()->CompleteUpload(200); + EXPECT_FALSE(client.uploader()->is_uploading()); + EXPECT_EQ(2U, task_runner_->NumPendingTasks()); + // Running should generate a log, restart upload loop, and mark idle. + task_runner_->RunPendingTasks(); + EXPECT_FALSE(client.uploader()->is_uploading()); + EXPECT_EQ(3U, task_runner_->NumPendingTasks()); + // Upload should start, and rotation loop should idle out. + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client.uploader()->is_uploading()); + EXPECT_EQ(1U, task_runner_->NumPendingTasks()); + // Uploader should reschedule when there is another log available. + service.PushExternalLog("Blah"); + client.uploader()->CompleteUpload(200); + EXPECT_FALSE(client.uploader()->is_uploading()); + EXPECT_EQ(2U, task_runner_->NumPendingTasks()); + // Upload should start. + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client.uploader()->is_uploading()); + EXPECT_EQ(1U, task_runner_->NumPendingTasks()); +} + +TEST_F(MetricsServiceTest, LastLiveTimestamp) { + TestMetricsServiceClient client; + TestMetricsService service(GetMetricsStateManager(), &client, + GetLocalState()); + + base::Time initial_last_live_time = + GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp); + + service.InitializeMetricsRecordingState(); + service.Start(); + + task_runner_->RunPendingTasks(); + size_t num_pending_tasks = task_runner_->NumPendingTasks(); + + service.StartUpdatingLastLiveTimestamp(); + + // Starting the update sequence should not write anything, but should + // set up for a later write. + EXPECT_EQ( + initial_last_live_time, + GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp)); + EXPECT_EQ(num_pending_tasks + 1, task_runner_->NumPendingTasks()); + + // To avoid flakiness, yield until we're over a microsecond threshold. + YieldUntil(initial_last_live_time + base::TimeDelta::FromMicroseconds(2)); + + task_runner_->RunPendingTasks(); + + // Verify that the time has updated in local state. + base::Time updated_last_live_time = + GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp); + EXPECT_LT(initial_last_live_time, updated_last_live_time); + + // Double check that an update schedules again... + YieldUntil(updated_last_live_time + base::TimeDelta::FromMicroseconds(2)); + + task_runner_->RunPendingTasks(); + EXPECT_LT( + updated_last_live_time, + GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp)); +} + +} // namespace metrics diff --git a/components/metrics/metrics_state_manager.cc b/components/metrics/metrics_state_manager.cc new file mode 100644 index 0000000000000..f4515c932e5d8 --- /dev/null +++ b/components/metrics/metrics_state_manager.cc @@ -0,0 +1,397 @@ +// 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 +#include + +#include + +#include "base/command_line.h" +#include "base/guid.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.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/enabled_state_provider.h" +#include "components/metrics/machine_id_provider.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_provider.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" +#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h" +#include "third_party/metrics_proto/system_profile.pb.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); +} + +// Records the given |low_entorpy_source_value| in a histogram. +void LogLowEntropyValue(int low_entropy_source_value) { + base::UmaHistogramSparse("UMA.LowEntropySourceValue", + low_entropy_source_value); +} + +int64_t ReadEnabledDate(PrefService* local_state) { + return local_state->GetInt64(prefs::kMetricsReportingEnabledTimestamp); +} + +int64_t ReadInstallDate(PrefService* local_state) { + return local_state->GetInt64(prefs::kInstallDate); +} + +// 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); +} + +// Records the cloned install histogram. +void LogClonedInstall() { + // Equivalent to UMA_HISTOGRAM_BOOLEAN with the stability flag set. + UMA_STABILITY_HISTOGRAM_ENUMERATION("UMA.IsClonedInstall", 1, 2); +} + +class MetricsStateMetricsProvider : public MetricsProvider { + public: + MetricsStateMetricsProvider(PrefService* local_state, + bool metrics_ids_were_reset, + std::string previous_client_id) + : local_state_(local_state), + metrics_ids_were_reset_(metrics_ids_were_reset), + previous_client_id_(std::move(previous_client_id)) {} + + // MetricsProvider: + void ProvideSystemProfileMetrics( + SystemProfileProto* system_profile) override { + system_profile->set_uma_enabled_date( + RoundSecondsToHour(ReadEnabledDate(local_state_))); + system_profile->set_install_date( + RoundSecondsToHour(ReadInstallDate(local_state_))); + } + + void ProvidePreviousSessionData( + ChromeUserMetricsExtension* uma_proto) override { + if (metrics_ids_were_reset_) { + LogClonedInstall(); + if (!previous_client_id_.empty()) { + // If we know the previous client id, overwrite the client id for the + // previous session log so the log contains the client id at the time + // of the previous session. This allows better attribution of crashes + // to earlier behavior. If the previous client id is unknown, leave + // the current client id. + uma_proto->set_client_id(MetricsLog::Hash(previous_client_id_)); + } + } + } + + void ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) override { + if (local_state_->GetBoolean(prefs::kMetricsResetIds)) + LogClonedInstall(); + } + + private: + PrefService* const local_state_; + const bool metrics_ids_were_reset_; + // |previous_client_id_| is set only (if known) when |metrics_ids_were_reset_| + const std::string previous_client_id_; + + DISALLOW_COPY_AND_ASSIGN(MetricsStateMetricsProvider); +}; + +} // namespace + +// static +bool MetricsStateManager::instance_exists_ = false; + +MetricsStateManager::MetricsStateManager( + PrefService* local_state, + EnabledStateProvider* enabled_state_provider, + const base::string16& backup_registry_key, + const StoreClientInfoCallback& store_client_info, + const LoadClientInfoCallback& retrieve_client_info) + : local_state_(local_state), + enabled_state_provider_(enabled_state_provider), + store_client_info_(store_client_info), + load_client_info_(retrieve_client_info), + clean_exit_beacon_(backup_registry_key, local_state), + low_entropy_source_(kLowEntropySourceNotSet), + entropy_source_returned_(ENTROPY_SOURCE_NONE), + metrics_ids_were_reset_(false) { + ResetMetricsIDsIfNecessary(); + if (enabled_state_provider_->IsConsentGiven()) + ForceClientIdCreation(); + + // 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()); + + DCHECK(!instance_exists_); + instance_exists_ = true; +} + +MetricsStateManager::~MetricsStateManager() { + DCHECK(instance_exists_); + instance_exists_ = false; +} + +std::unique_ptr MetricsStateManager::GetProvider() { + return std::make_unique( + local_state_, metrics_ids_were_reset_, previous_client_id_); +} + +bool MetricsStateManager::IsMetricsReportingEnabled() { + return enabled_state_provider_->IsReportingEnabled(); +} + +int64_t MetricsStateManager::GetInstallDate() const { + return ReadInstallDate(local_state_); +} + +void MetricsStateManager::ForceClientIdCreation() { + { + std::string client_id_from_prefs = + local_state_->GetString(prefs::kMetricsClientID); + // If client id in prefs matches the cached copy, return early. + if (!client_id_from_prefs.empty() && client_id_from_prefs == client_id_) + return; + client_id_.swap(client_id_from_prefs); + } + + if (!client_id_.empty()) + return; + + const std::unique_ptr client_info_backup = LoadClientInfo(); + 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() { + DCHECK(!cloned_install_detector_); + + if (!MachineIdProvider::HasId()) + return; + + cloned_install_detector_ = std::make_unique(); + cloned_install_detector_->CheckForClonedInstall(local_state_); +} + +std::unique_ptr +MetricsStateManager::CreateDefaultEntropyProvider() { + if (enabled_state_provider_->IsConsentGiven()) { + // 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(); + + UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH); + const std::string high_entropy_source = + client_id_ + base::IntToString(low_entropy_source_value); + return std::unique_ptr( + new variations::SHA1EntropyProvider(high_entropy_source)); + } + + UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW); + return CreateLowEntropyProvider(); +} + +std::unique_ptr +MetricsStateManager::CreateLowEntropyProvider() { + const int low_entropy_source_value = GetLowEntropySource(); + +#if defined(OS_ANDROID) || defined(OS_IOS) + return std::unique_ptr( + new variations::CachingPermutedEntropyProvider( + local_state_, low_entropy_source_value, kMaxLowEntropySize)); +#else + return std::unique_ptr( + new variations::PermutedEntropyProvider(low_entropy_source_value, + kMaxLowEntropySize)); +#endif +} + +// static +std::unique_ptr MetricsStateManager::Create( + PrefService* local_state, + EnabledStateProvider* enabled_state_provider, + const base::string16& backup_registry_key, + const StoreClientInfoCallback& store_client_info, + const LoadClientInfoCallback& retrieve_client_info) { + std::unique_ptr result; + // Note: |instance_exists_| is updated in the constructor and destructor. + if (!instance_exists_) { + result.reset(new MetricsStateManager(local_state, enabled_state_provider, + backup_registry_key, store_client_info, + retrieve_client_info)); + } + return result; +} + +// static +void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(prefs::kMetricsResetIds, false); + registry->RegisterStringPref(prefs::kMetricsClientID, std::string()); + registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0); + registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource, + kLowEntropySourceNotSet); + registry->RegisterInt64Pref(prefs::kInstallDate, 0); + + ClonedInstallDetector::RegisterPrefs(registry); + variations::CachingPermutedEntropyProvider::RegisterPrefs(registry); +} + +void MetricsStateManager::BackUpCurrentClientInfo() { + ClientInfo client_info; + client_info.client_id = client_id_; + client_info.installation_date = ReadInstallDate(local_state_); + client_info.reporting_enabled_date = ReadEnabledDate(local_state_); + store_client_info_.Run(client_info); +} + +std::unique_ptr MetricsStateManager::LoadClientInfo() { + std::unique_ptr client_info = load_client_info_.Run(); + + // The GUID retrieved should be valid unless retrieval failed. + // If not, return nullptr. This will result in a new GUID being generated by + // the calling function ForceClientIdCreation(). + if (client_info && !base::IsValidGUID(client_info->client_id)) + return nullptr; + + 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; + LogLowEntropyValue(low_entropy_source_); + return; + } + } + + low_entropy_source_ = GenerateLowEntropySource(); + LogLowEntropyValue(low_entropy_source_); + local_state_->SetInteger(prefs::kMetricsLowEntropySource, + low_entropy_source_); + variations::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; + metrics_ids_were_reset_ = true; + previous_client_id_ = local_state_->GetString(prefs::kMetricsClientID); + + 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/components/metrics/metrics_state_manager.h b/components/metrics/metrics_state_manager.h new file mode 100644 index 0000000000000..a1a15b34b7fd4 --- /dev/null +++ b/components/metrics/metrics_state_manager.h @@ -0,0 +1,217 @@ +// 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 +#include + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/metrics/field_trial.h" +#include "base/strings/string16.h" +#include "components/metrics/clean_exit_beacon.h" +#include "components/metrics/client_info.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class ClonedInstallDetector; +class EnabledStateProvider; +class MetricsProvider; + +// 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 final { + 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 + StoreClientInfoCallback; + + // A callback that can be invoked to load client info stored through the + // StoreClientInfoCallback. + typedef base::Callback(void)> + LoadClientInfoCallback; + + ~MetricsStateManager(); + + std::unique_ptr GetProvider(); + + // Returns true if the user has consented to sending metric reports, and there + // is no other reason to disable reporting. One such reason is client + // sampling, and this client isn't in the sample. + bool IsMetricsReportingEnabled(); + + // Returns the install date of the application, in seconds since the epoch. + int64_t GetInstallDate() const; + + // 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_; } + + // The CleanExitBeacon, used to determine whether the previous Chrome browser + // session terminated gracefully. + CleanExitBeacon* clean_exit_beacon() { return &clean_exit_beacon_; } + const CleanExitBeacon* clean_exit_beacon() const { + return &clean_exit_beacon_; + } + + // 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(); + + // Returns the preferred entropy provider used to seed persistent activities + // based on whether or not metrics reporting is permitted on this client. + // + // If there's consent to report metrics, 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. + std::unique_ptr + CreateDefaultEntropyProvider(); + + // Returns an entropy provider that is based on a low entropy source. This + // provider is the same type of provider returned by + // CreateDefaultEntropyProvider when there's no consent to report metrics, but + // will be a new instance. + std::unique_ptr + CreateLowEntropyProvider(); + + // Creates the MetricsStateManager, enforcing that only a single instance + // of the class exists at a time. Returns NULL if an instance exists already. + // On Windows, |backup_registry_key| is used to store a backup of the clean + // exit beacon. It is ignored on other platforms. + static std::unique_ptr Create( + PrefService* local_state, + EnabledStateProvider* enabled_state_provider, + const base::string16& backup_registry_key, + 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, CheckProviderResetIds); + 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, ResetBackup); + 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|. Uses + // |enabled_state_provider| to query whether there is consent for metrics + // reporting, and if it 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, + EnabledStateProvider* enabled_state_provider, + const base::string16& backup_registry_key, + 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_|. + std::unique_ptr LoadClientInfo(); + + // 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_; + + // Weak pointer to an enabled state provider. Used to know whether the user + // has consented to reporting, and if reporting should be done. + EnabledStateProvider* enabled_state_provider_; + + // 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_; + + // A beacon used to determine whether the previous Chrome browser session + // terminated gracefully. + CleanExitBeacon clean_exit_beacon_; + + // 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_; + + // The value of prefs::kMetricsResetIds seen upon startup, i.e., the value + // that was appropriate in the previous session. Used when reporting previous + // session (stability) data. + bool metrics_ids_were_reset_; + + // The value of the metrics id before reseting. Only possibly valid if the + // metrics id was reset. May be blank if the metrics id was reset but Chrome + // has no record of what the previous metrics id was. + std::string previous_client_id_; + + std::unique_ptr cloned_install_detector_; + + DISALLOW_COPY_AND_ASSIGN(MetricsStateManager); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_ diff --git a/components/metrics/metrics_state_manager_unittest.cc b/components/metrics/metrics_state_manager_unittest.cc new file mode 100644 index 0000000000000..0e1f1481c641c --- /dev/null +++ b/components/metrics/metrics_state_manager_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_state_manager.h" + +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/macros.h" +#include "base/strings/string16.h" +#include "base/test/metrics/histogram_tester.h" +#include "components/metrics/client_info.h" +#include "components/metrics/metrics_log.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/metrics/metrics_service.h" +#include "components/metrics/metrics_switches.h" +#include "components/metrics/test_enabled_state_provider.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() + : test_begin_time_(base::Time::Now().ToTimeT()), + enabled_state_provider_(new TestEnabledStateProvider(false, false)) { + MetricsService::RegisterPrefs(prefs_.registry()); + } + + std::unique_ptr CreateStateManager() { + return MetricsStateManager::Create( + &prefs_, enabled_state_provider_.get(), base::string16(), + base::Bind(&MetricsStateManagerTest::MockStoreClientInfoBackup, + base::Unretained(this)), + base::Bind(&MetricsStateManagerTest::LoadFakeClientInfoBackup, + base::Unretained(this))); + } + + // Sets metrics reporting as enabled for testing. + void EnableMetricsReporting() { + enabled_state_provider_->set_consent(true); + enabled_state_provider_->set_enabled(true); + } + + void SetClientInfoPrefs(const ClientInfo& client_info) { + prefs_.SetString(prefs::kMetricsClientID, client_info.client_id); + prefs_.SetInt64(prefs::kInstallDate, client_info.installation_date); + prefs_.SetInt64(prefs::kMetricsReportingEnabledTimestamp, + client_info.reporting_enabled_date); + } + + void SetFakeClientInfoBackup(const ClientInfo& client_info) { + fake_client_info_backup_.reset(new ClientInfo); + fake_client_info_backup_->client_id = client_info.client_id; + fake_client_info_backup_->installation_date = client_info.installation_date; + fake_client_info_backup_->reporting_enabled_date = + client_info.reporting_enabled_date; + } + + protected: + TestingPrefServiceSimple prefs_; + + // Last ClientInfo stored by the MetricsStateManager via + // MockStoreClientInfoBackup. + std::unique_ptr stored_client_info_backup_; + + // If set, will be returned via LoadFakeClientInfoBackup if requested by the + // MetricsStateManager. + std::unique_ptr fake_client_info_backup_; + + const int64_t test_begin_time_; + + private: + // 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. + std::unique_ptr LoadFakeClientInfoBackup() { + if (!fake_client_info_backup_) + return std::unique_ptr(); + + std::unique_ptr 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; + } + + std::unique_ptr enabled_state_provider_; + + DISALLOW_COPY_AND_ASSIGN(MetricsStateManagerTest); +}; + +// Ensure the ClientId is formatted as expected. +TEST_F(MetricsStateManagerTest, ClientIdCorrectlyFormatted) { + std::unique_ptr 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) { + std::unique_ptr state_manager(CreateStateManager()); + state_manager->CreateDefaultEntropyProvider(); + EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW, + state_manager->entropy_source_returned()); +} + +TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) { + EnableMetricsReporting(); + std::unique_ptr state_manager(CreateStateManager()); + state_manager->CreateDefaultEntropyProvider(); + EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH, + state_manager->entropy_source_returned()); +} + +TEST_F(MetricsStateManagerTest, LowEntropySource0NotReset) { + std::unique_ptr 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()); + + std::unique_ptr 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"); + + std::unique_ptr 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); + + std::unique_ptr 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. + { + std::unique_ptr state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + EXPECT_EQ(kInitialClientId, state_manager->client_id()); + EXPECT_FALSE(state_manager->metrics_ids_were_reset_); + } + + // Set the reset pref to cause the IDs to be reset. + prefs_.SetBoolean(prefs::kMetricsResetIds, true); + + // Cause the actual reset to happen. + { + std::unique_ptr state_manager(CreateStateManager()); + state_manager->ForceClientIdCreation(); + EXPECT_NE(kInitialClientId, state_manager->client_id()); + EXPECT_TRUE(state_manager->metrics_ids_were_reset_); + EXPECT_EQ(kInitialClientId, state_manager->previous_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); + + { + std::unique_ptr 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); + } +} + +TEST_F(MetricsStateManagerTest, LoadPrefs) { + ClientInfo client_info; + client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEF"; + client_info.installation_date = 1112; + client_info.reporting_enabled_date = 2223; + SetClientInfoPrefs(client_info); + + EnableMetricsReporting(); + { + EXPECT_FALSE(fake_client_info_backup_); + EXPECT_FALSE(stored_client_info_backup_); + + std::unique_ptr state_manager(CreateStateManager()); + + // client_id should be auto-obtained from the constructor when metrics + // reporting is enabled. + EXPECT_EQ(client_info.client_id, state_manager->client_id()); + + // The backup should not be modified. + ASSERT_FALSE(stored_client_info_backup_); + + // Re-forcing client id creation shouldn't cause another backup and + // shouldn't affect the existing client id. + state_manager->ForceClientIdCreation(); + EXPECT_FALSE(stored_client_info_backup_); + EXPECT_EQ(client_info.client_id, state_manager->client_id()); + } +} + +TEST_F(MetricsStateManagerTest, PreferPrefs) { + ClientInfo client_info; + client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEF"; + client_info.installation_date = 1112; + client_info.reporting_enabled_date = 2223; + SetClientInfoPrefs(client_info); + + ClientInfo client_info2; + client_info2.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; + client_info2.installation_date = 1111; + client_info2.reporting_enabled_date = 2222; + SetFakeClientInfoBackup(client_info2); + + EnableMetricsReporting(); + { + // The backup should be ignored if we already have a client id. + + EXPECT_FALSE(stored_client_info_backup_); + + std::unique_ptr state_manager(CreateStateManager()); + EXPECT_EQ(client_info.client_id, state_manager->client_id()); + + // The backup should not be modified. + ASSERT_FALSE(stored_client_info_backup_); + } +} + +TEST_F(MetricsStateManagerTest, RestoreBackup) { + ClientInfo client_info; + client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEF"; + client_info.installation_date = 1112; + client_info.reporting_enabled_date = 2223; + SetClientInfoPrefs(client_info); + + ClientInfo client_info2; + client_info2.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; + client_info2.installation_date = 1111; + client_info2.reporting_enabled_date = 2222; + SetFakeClientInfoBackup(client_info2); + + prefs_.ClearPref(prefs::kMetricsClientID); + prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp); + + EnableMetricsReporting(); + { + // 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_); + + std::unique_ptr state_manager(CreateStateManager()); + EXPECT_EQ(client_info2.client_id, state_manager->client_id()); + EXPECT_EQ(client_info2.installation_date, + prefs_.GetInt64(prefs::kInstallDate)); + EXPECT_EQ(client_info2.reporting_enabled_date, + prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp)); + + EXPECT_TRUE(stored_client_info_backup_); + } +} + +TEST_F(MetricsStateManagerTest, ResetBackup) { + ClientInfo client_info; + client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; + client_info.installation_date = 1111; + client_info.reporting_enabled_date = 2222; + + SetFakeClientInfoBackup(client_info); + SetClientInfoPrefs(client_info); + + prefs_.SetBoolean(prefs::kMetricsResetIds, true); + + EnableMetricsReporting(); + { + // Upon request to reset metrics ids, the existing backup should not be + // restored. + + std::unique_ptr state_manager(CreateStateManager()); + + // A brand new client id should have been generated. + EXPECT_NE(std::string(), state_manager->client_id()); + EXPECT_NE(client_info.client_id, state_manager->client_id()); + EXPECT_TRUE(state_manager->metrics_ids_were_reset_); + EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_); + EXPECT_TRUE(stored_client_info_backup_); + + // The installation date should not have been affected. + EXPECT_EQ(client_info.installation_date, + prefs_.GetInt64(prefs::kInstallDate)); + + // The metrics-reporting-enabled date will be reset to Now(). + EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp), + test_begin_time_); + } +} + +TEST_F(MetricsStateManagerTest, CheckProvider) { + int64_t kInstallDate = 1373051956; + int64_t kInstallDateExpected = 1373050800; // Computed from kInstallDate. + int64_t kEnabledDate = 1373001211; + int64_t kEnabledDateExpected = 1373000400; // Computed from kEnabledDate. + + ClientInfo client_info; + client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; + client_info.installation_date = kInstallDate; + client_info.reporting_enabled_date = kEnabledDate; + + SetFakeClientInfoBackup(client_info); + SetClientInfoPrefs(client_info); + + std::unique_ptr state_manager(CreateStateManager()); + std::unique_ptr provider = state_manager->GetProvider(); + SystemProfileProto system_profile; + provider->ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(system_profile.install_date(), kInstallDateExpected); + EXPECT_EQ(system_profile.uma_enabled_date(), kEnabledDateExpected); + + base::HistogramTester histogram_tester; + ChromeUserMetricsExtension uma_proto; + provider->ProvidePreviousSessionData(&uma_proto); + // The client_id field in the proto should not be overwritten. + EXPECT_FALSE(uma_proto.has_client_id()); + // Nothing should have been emitted to the cloned install histogram. + histogram_tester.ExpectTotalCount("UMA.IsClonedInstall", 0); +} + +TEST_F(MetricsStateManagerTest, CheckProviderResetIds) { + int64_t kInstallDate = 1373051956; + int64_t kInstallDateExpected = 1373050800; // Computed from kInstallDate. + int64_t kEnabledDate = 1373001211; + int64_t kEnabledDateExpected = 1373000400; // Computed from kEnabledDate. + + ClientInfo client_info; + client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"; + client_info.installation_date = kInstallDate; + client_info.reporting_enabled_date = kEnabledDate; + + SetFakeClientInfoBackup(client_info); + SetClientInfoPrefs(client_info); + + // Set the reset pref to cause the IDs to be reset. + prefs_.SetBoolean(prefs::kMetricsResetIds, true); + std::unique_ptr state_manager(CreateStateManager()); + EXPECT_NE(client_info.client_id, state_manager->client_id()); + EXPECT_TRUE(state_manager->metrics_ids_were_reset_); + EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_); + + std::unique_ptr provider = state_manager->GetProvider(); + SystemProfileProto system_profile; + provider->ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(system_profile.install_date(), kInstallDateExpected); + EXPECT_EQ(system_profile.uma_enabled_date(), kEnabledDateExpected); + + base::HistogramTester histogram_tester; + ChromeUserMetricsExtension uma_proto; + provider->ProvidePreviousSessionData(&uma_proto); + EXPECT_EQ(MetricsLog::Hash(state_manager->previous_client_id_), + uma_proto.client_id()); + histogram_tester.ExpectUniqueSample("UMA.IsClonedInstall", 1, 1); +} + +} // namespace metrics diff --git a/components/metrics/metrics_switches.cc b/components/metrics/metrics_switches.cc new file mode 100644 index 0000000000000..38f65aef15a7e --- /dev/null +++ b/components/metrics/metrics_switches.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/metrics_switches.h" + +namespace metrics { +namespace switches { + +// Enables the recording of metrics reports but disables reporting. In contrast +// to kDisableMetrics, this executes all the code that a normal client would +// use for reporting, except the report is dropped rather than sent to the +// server. This is useful for finding issues in the metrics code during UI and +// performance tests. +const char kMetricsRecordingOnly[] = "metrics-recording-only"; + +// 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"; + +// Forces metrics reporting to be enabled. +const char kForceEnableMetricsReporting[] = "force-enable-metrics-reporting"; + +} // namespace switches +} // namespace metrics diff --git a/components/metrics/metrics_switches.h b/components/metrics/metrics_switches.h new file mode 100644 index 0000000000000..10e41e94c0462 --- /dev/null +++ b/components/metrics/metrics_switches.h @@ -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. + +#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 kMetricsRecordingOnly[]; +extern const char kResetVariationState[]; +extern const char kForceEnableMetricsReporting[]; + +} // namespace switches +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_SWITCHES_H_ diff --git a/components/metrics/metrics_upload_scheduler.cc b/components/metrics/metrics_upload_scheduler.cc new file mode 100644 index 0000000000000..b7b7364452a93 --- /dev/null +++ b/components/metrics/metrics_upload_scheduler.cc @@ -0,0 +1,92 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/metrics_upload_scheduler.h" + +#include + +#include "base/feature_list.h" +#include "base/metrics/field_trial_params.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "components/metrics/metrics_scheduler.h" + +namespace metrics { + +namespace { + +// 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 = 2; + +// The maximum backoff interval in hours. +const int kMaxBackoffIntervalHours = 24; + +// Minutes to wait if we are unable to upload due to data usage cap. +const int kOverDataUsageIntervalMinutes = 5; + +// Increases the upload interval each time it's called, to handle the case +// where the server is having issues. +base::TimeDelta BackOffUploadInterval(base::TimeDelta interval) { + DCHECK_GT(kBackoffMultiplier, 1.0); + interval = base::TimeDelta::FromMicroseconds(static_cast( + kBackoffMultiplier * interval.InMicroseconds())); + + base::TimeDelta max_interval = + base::TimeDelta::FromHours(kMaxBackoffIntervalHours); + if (interval > max_interval || interval.InSeconds() < 0) { + interval = max_interval; + } + return interval; +} + +// Time delay after a log is uploaded successfully before attempting another. +// On mobile, keeping the radio on is very expensive, so prefer to keep this +// short and send in bursts. +base::TimeDelta GetUnsentLogsInterval() { + return base::TimeDelta::FromSeconds(3); +} + +// Initial time delay after a log uploaded fails before retrying it. +base::TimeDelta GetInitialBackoffInterval() { + return base::TimeDelta::FromMinutes(5); +} + +} // namespace + +MetricsUploadScheduler::MetricsUploadScheduler( + const base::Closure& upload_callback) + : MetricsScheduler(upload_callback), + unsent_logs_interval_(GetUnsentLogsInterval()), + initial_backoff_interval_(GetInitialBackoffInterval()), + backoff_interval_(initial_backoff_interval_) {} + +MetricsUploadScheduler::~MetricsUploadScheduler() {} + +void MetricsUploadScheduler::UploadFinished(bool server_is_healthy) { + // 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) { + TaskDone(backoff_interval_); + backoff_interval_ = BackOffUploadInterval(backoff_interval_); + } else { + backoff_interval_ = initial_backoff_interval_; + TaskDone(unsent_logs_interval_); + } +} + +void MetricsUploadScheduler::StopAndUploadCancelled() { + Stop(); + TaskDone(unsent_logs_interval_); +} + +void MetricsUploadScheduler::UploadOverDataUsageCap() { + TaskDone(base::TimeDelta::FromMinutes(kOverDataUsageIntervalMinutes)); +} + +} // namespace metrics diff --git a/components/metrics/metrics_upload_scheduler.h b/components/metrics/metrics_upload_scheduler.h new file mode 100644 index 0000000000000..a97b7cb1f1ad3 --- /dev/null +++ b/components/metrics/metrics_upload_scheduler.h @@ -0,0 +1,51 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_METRICS_UPLOAD_SCHEDULER_H_ +#define COMPONENTS_METRICS_METRICS_UPLOAD_SCHEDULER_H_ + +#include "base/callback.h" +#include "base/feature_list.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "components/metrics/metrics_scheduler.h" + +namespace metrics { + +// Scheduler task to drive a ReportingService object's uploading. +class MetricsUploadScheduler : public MetricsScheduler { + public: + // Creates MetricsUploadScheduler object with the given |upload_callback| + // callback to call when uploading should happen. The callback must + // arrange to call either UploadFinished or UploadCancelled on completion. + explicit MetricsUploadScheduler(const base::Closure& upload_callback); + ~MetricsUploadScheduler() override; + + // Callback from MetricsService when a triggered upload finishes. + void UploadFinished(bool server_is_healthy); + + // Callback from MetricsService when an upload is cancelled. + // Also stops the scheduler. + void StopAndUploadCancelled(); + + // Callback from MetricsService when an upload is cancelled because it would + // be over the allowed data usage cap. + void UploadOverDataUsageCap(); + + private: + // Time to wait between uploads on success. + const base::TimeDelta unsent_logs_interval_; + + // Initial time to wait between upload retry attempts. + const base::TimeDelta initial_backoff_interval_; + + // Time to wait for the next upload attempt if the next one fails. + base::TimeDelta backoff_interval_; + + DISALLOW_COPY_AND_ASSIGN(MetricsUploadScheduler); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_UPLOAD_SCHEDULER_H_ diff --git a/components/metrics/net/DEPS b/components/metrics/net/DEPS new file mode 100644 index 0000000000000..633e4afca5642 --- /dev/null +++ b/components/metrics/net/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+chromeos/dbus", + "+chromeos/network", + "+components/data_use_measurement/core", + "+components/encrypted_messages", + "+components/variations", + "+net", + "+services/network/public/cpp", + "+services/network/test", + "+third_party/cros_system_api", +] diff --git a/components/metrics/net/cellular_logic_helper.cc b/components/metrics/net/cellular_logic_helper.cc new file mode 100644 index 0000000000000..33b351e0a4d49 --- /dev/null +++ b/components/metrics/net/cellular_logic_helper.cc @@ -0,0 +1,47 @@ +// 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/net/cellular_logic_helper.h" + +#include "net/base/network_change_notifier.h" + +namespace metrics { + +namespace { + +// Standard interval between log uploads, in seconds. +#if defined(OS_ANDROID) || defined(OS_IOS) +const int kStandardUploadIntervalSeconds = 5 * 60; // Five minutes. +const int kStandardUploadIntervalCellularSeconds = 15 * 60; // Fifteen minutes. +#else +const int kStandardUploadIntervalSeconds = 30 * 60; // Thirty minutes. +#endif + +#if defined(OS_ANDROID) +const bool kDefaultCellularLogicEnabled = true; +#else +const bool kDefaultCellularLogicEnabled = false; +#endif + +} // namespace + +base::TimeDelta GetUploadInterval() { +#if defined(OS_ANDROID) || defined(OS_IOS) + if (IsCellularLogicEnabled()) + return base::TimeDelta::FromSeconds(kStandardUploadIntervalCellularSeconds); +#endif + return base::TimeDelta::FromSeconds(kStandardUploadIntervalSeconds); +} + +// Returns true if current connection type is cellular and cellular logic is +// enabled. +bool IsCellularLogicEnabled() { + if (!kDefaultCellularLogicEnabled) + return false; + + return net::NetworkChangeNotifier::IsConnectionCellular( + net::NetworkChangeNotifier::GetConnectionType()); +} + +} // namespace metrics diff --git a/components/metrics/net/cellular_logic_helper.h b/components/metrics/net/cellular_logic_helper.h new file mode 100644 index 0000000000000..b5c7061cf2d9a --- /dev/null +++ b/components/metrics/net/cellular_logic_helper.h @@ -0,0 +1,21 @@ +// 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_NET_CELLULAR_LOGIC_HELPER_H_ +#define COMPONENTS_METRICS_NET_CELLULAR_LOGIC_HELPER_H_ + +#include "base/time/time.h" + +namespace metrics { + +// Returns UMA log upload interval based on OS and ongoing cellular experiment. +base::TimeDelta GetUploadInterval(); + +// Returns true if current connection type is cellular and user is assigned to +// experimental group for enabled cellular uploads. +bool IsCellularLogicEnabled(); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_CELLULAR_LOGIC_HELPER_H_ diff --git a/components/metrics/net/net_metrics_log_uploader.cc b/components/metrics/net/net_metrics_log_uploader.cc new file mode 100644 index 0000000000000..0d937ce6dedff --- /dev/null +++ b/components/metrics/net/net_metrics_log_uploader.cc @@ -0,0 +1,295 @@ +// 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/base64.h" +#include "base/feature_list.h" +#include "base/metrics/histogram_macros.h" +#include "components/data_use_measurement/core/data_use_user_data.h" +#include "components/encrypted_messages/encrypted_message.pb.h" +#include "components/encrypted_messages/message_encrypter.h" +#include "components/metrics/metrics_log_uploader.h" +#include "net/base/load_flags.h" +#include "net/base/url_util.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "net/url_request/url_fetcher.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "third_party/metrics_proto/reporting_info.pb.h" +#include "url/gurl.h" + +namespace { + +const base::Feature kHttpRetryFeature{"UMAHttpRetry", + base::FEATURE_ENABLED_BY_DEFAULT}; + +// Constants used for encrypting logs that are sent over HTTP. The +// corresponding private key is used by the metrics server to decrypt logs. +const char kEncryptedMessageLabel[] = "metrics log"; + +const uint8_t kServerPublicKey[] = { + 0x51, 0xcc, 0x52, 0x67, 0x42, 0x47, 0x3b, 0x10, 0xe8, 0x63, 0x18, + 0x3c, 0x61, 0xa7, 0x96, 0x76, 0x86, 0x91, 0x40, 0x71, 0x39, 0x5f, + 0x31, 0x1a, 0x39, 0x5b, 0x76, 0xb1, 0x6b, 0x3d, 0x6a, 0x2b}; + +const uint32_t kServerPublicKeyVersion = 1; + +net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotation( + const metrics::MetricsLogUploader::MetricServiceType& service_type) { + // The code in this function should remain so that we won't need a default + // case that does not have meaningful annotation. + if (service_type == metrics::MetricsLogUploader::UMA) { + return net::DefineNetworkTrafficAnnotation("metrics_report_uma", R"( + semantics { + sender: "Metrics UMA Log Uploader" + description: + "Report of usage statistics and crash-related data about Chromium. " + "Usage statistics contain information such as preferences, button " + "clicks, and memory usage and do not include web page URLs or " + "personal information. See more at " + "https://www.google.com/chrome/browser/privacy/ under 'Usage " + "statistics and crash reports'. Usage statistics are tied to a " + "pseudonymous machine identifier and not to your email address." + trigger: + "Reports are automatically generated on startup and at intervals " + "while Chromium is running." + data: + "A protocol buffer with usage statistics and crash related data." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: NO + setting: + "Users can enable or disable this feature by disabling " + "'Automatically send usage statistics and crash reports to Google' " + "in Chromium's settings under Advanced Settings, Privacy. The " + "feature is enabled by default." + chrome_policy { + MetricsReportingEnabled { + policy_options {mode: MANDATORY} + MetricsReportingEnabled: false + } + } + })"); + } + DCHECK_EQ(service_type, metrics::MetricsLogUploader::UKM); + return net::DefineNetworkTrafficAnnotation("metrics_report_ukm", R"( + semantics { + sender: "Metrics UKM Log Uploader" + description: + "Report of usage statistics that are keyed by URLs to Chromium, " + "sent only if the profile has History Sync. This includes " + "information about the web pages you visit and your usage of them, " + "such as page load speed. This will also include URLs and " + "statistics related to downloaded files. If Extension Sync is " + "enabled, these statistics will also include information about " + "the extensions that have been installed from Chrome Web Store. " + "Google only stores usage statistics associated with published " + "extensions, and URLs that are known by Google’s search index. " + "Usage statistics are tied to a pseudonymous machine identifier " + "and not to your email address." + trigger: + "Reports are automatically generated on startup and at intervals " + "while Chromium is running with Sync enabled." + data: + "A protocol buffer with usage statistics and associated URLs." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: NO + setting: + "Users can enable or disable this feature by disabling " + "'Automatically send usage statistics and crash reports to Google' " + "in Chromium's settings under Advanced Settings, Privacy. This is " + "only enabled if all active profiles have History/Extension Sync " + "enabled without a Sync passphrase." + chrome_policy { + MetricsReportingEnabled { + policy_options {mode: MANDATORY} + MetricsReportingEnabled: false + } + } + })"); +} + +std::string SerializeReportingInfo( + const metrics::ReportingInfo& reporting_info) { + std::string result; + std::string bytes; + bool success = reporting_info.SerializeToString(&bytes); + DCHECK(success); + base::Base64Encode(bytes, &result); + return result; +} + +void RecordUploadSizeForServiceTypeHistograms( + int64_t content_length, + metrics::MetricsLogUploader::MetricServiceType service_type) { + switch (service_type) { + case metrics::MetricsLogUploader::UMA: + UMA_HISTOGRAM_COUNTS_1M("UMA.LogUploader.UploadSize", content_length); + break; + case metrics::MetricsLogUploader::UKM: + UMA_HISTOGRAM_COUNTS_1M("UKM.LogUploader.UploadSize", content_length); + break; + } +} + +} // namespace + +namespace metrics { + +NetMetricsLogUploader::NetMetricsLogUploader( + scoped_refptr url_loader_factory, + base::StringPiece server_url, + base::StringPiece mime_type, + MetricsLogUploader::MetricServiceType service_type, + const MetricsLogUploader::UploadCallback& on_upload_complete) + : url_loader_factory_(std::move(url_loader_factory)), + server_url_(server_url), + mime_type_(mime_type.data(), mime_type.size()), + service_type_(service_type), + on_upload_complete_(on_upload_complete) {} + +NetMetricsLogUploader::NetMetricsLogUploader( + scoped_refptr url_loader_factory, + base::StringPiece server_url, + base::StringPiece insecure_server_url, + base::StringPiece mime_type, + MetricsLogUploader::MetricServiceType service_type, + const MetricsLogUploader::UploadCallback& on_upload_complete) + : url_loader_factory_(std::move(url_loader_factory)), + server_url_(server_url), + insecure_server_url_(insecure_server_url), + mime_type_(mime_type.data(), mime_type.size()), + service_type_(service_type), + on_upload_complete_(on_upload_complete) {} + +NetMetricsLogUploader::~NetMetricsLogUploader() { +} + +void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data, + const std::string& log_hash, + const ReportingInfo& reporting_info) { + // If this attempt is a retry, there was a network error, the last attempt was + // over https, and there is an insecure url set, attempt this upload over + // HTTP. + // Currently we only retry over HTTP if the retry-uma-over-http flag is set. + if (!insecure_server_url_.is_empty() && reporting_info.attempt_count() > 1 && + reporting_info.last_error_code() != 0 && + reporting_info.last_attempt_was_https() && + base::FeatureList::IsEnabled(kHttpRetryFeature)) { + UploadLogToURL(compressed_log_data, log_hash, reporting_info, + insecure_server_url_); + return; + } + UploadLogToURL(compressed_log_data, log_hash, reporting_info, server_url_); +} + +void NetMetricsLogUploader::UploadLogToURL( + const std::string& compressed_log_data, + const std::string& log_hash, + const ReportingInfo& reporting_info, + const GURL& url) { + DCHECK(!log_hash.empty()); + + // TODO(crbug.com/808498): Restore the data use measurement when bug is fixed. + + auto resource_request = std::make_unique(); + resource_request->url = url; + // Drop cookies and auth data. + resource_request->allow_credentials = false; + resource_request->method = "POST"; + + std::string reporting_info_string = SerializeReportingInfo(reporting_info); + // If we are not using HTTPS for this upload, encrypt it. We do not encrypt + // requests to localhost to allow testing with a local collector that doesn't + // have decryption enabled. + bool should_encrypt = + !url.SchemeIs(url::kHttpsScheme) && !net::IsLocalhost(url); + if (should_encrypt) { + std::string encrypted_hash; + std::string base64_encoded_hash; + if (!EncryptString(log_hash, &encrypted_hash)) { + on_upload_complete_.Run(0, net::ERR_FAILED, false); + return; + } + base::Base64Encode(encrypted_hash, &base64_encoded_hash); + resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1", + base64_encoded_hash); + + std::string encrypted_reporting_info; + std::string base64_reporting_info; + if (!EncryptString(reporting_info_string, &encrypted_reporting_info)) { + on_upload_complete_.Run(0, net::ERR_FAILED, false); + return; + } + base::Base64Encode(encrypted_reporting_info, &base64_reporting_info); + resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo", + base64_reporting_info); + } else { + resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1", log_hash); + resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo", + reporting_info_string); + // Tell the server that we're uploading gzipped protobufs only if we are not + // encrypting, since encrypted messages have to be decrypted server side + // after decryption, not before. + resource_request->headers.SetHeader("content-encoding", "gzip"); + } + + url_loader_ = network::SimpleURLLoader::Create( + std::move(resource_request), GetNetworkTrafficAnnotation(service_type_)); + + if (should_encrypt) { + std::string encrypted_message; + if (!EncryptString(compressed_log_data, &encrypted_message)) { + url_loader_.reset(); + on_upload_complete_.Run(0, net::ERR_FAILED, false); + return; + } + url_loader_->AttachStringForUpload(encrypted_message, mime_type_); + RecordUploadSizeForServiceTypeHistograms(encrypted_message.size(), + service_type_); + } else { + url_loader_->AttachStringForUpload(compressed_log_data, mime_type_); + RecordUploadSizeForServiceTypeHistograms(compressed_log_data.size(), + service_type_); + } + + // It's safe to use |base::Unretained(this)| here, because |this| owns + // the |url_loader_|, and the callback will be cancelled if the |url_loader_| + // is destroyed. + url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( + url_loader_factory_.get(), + base::BindOnce(&NetMetricsLogUploader::OnURLLoadComplete, + base::Unretained(this))); +} + +// The callback is only invoked if |url_loader_| it was bound against is alive. +void NetMetricsLogUploader::OnURLLoadComplete( + std::unique_ptr response_body) { + int response_code = -1; + if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers) + response_code = url_loader_->ResponseInfo()->headers->response_code(); + + int error_code = url_loader_->NetError(); + + bool was_https = url_loader_->GetFinalURL().SchemeIs(url::kHttpsScheme); + url_loader_.reset(); + on_upload_complete_.Run(response_code, error_code, was_https); +} + +bool NetMetricsLogUploader::EncryptString(const std::string& plaintext, + std::string* encrypted) { + encrypted_messages::EncryptedMessage encrypted_message; + if (!encrypted_messages::EncryptSerializedMessage( + kServerPublicKey, kServerPublicKeyVersion, kEncryptedMessageLabel, + plaintext, &encrypted_message) || + !encrypted_message.SerializeToString(encrypted)) { + return false; + } + return true; +} +} // namespace metrics diff --git a/components/metrics/net/net_metrics_log_uploader.h b/components/metrics/net/net_metrics_log_uploader.h new file mode 100644 index 0000000000000..99fce26afce8b --- /dev/null +++ b/components/metrics/net/net_metrics_log_uploader.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_NET_NET_METRICS_LOG_UPLOADER_H_ +#define COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_ + +#include +#include + +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "components/metrics/metrics_log_uploader.h" +#include "third_party/metrics_proto/reporting_info.pb.h" +#include "url/gurl.h" + +namespace network { +class SharedURLLoaderFactory; +class SimpleURLLoader; +} // namespace network + +namespace metrics { + +// Implementation of MetricsLogUploader using the Chrome network stack. +class NetMetricsLogUploader : public MetricsLogUploader { + public: + // Constructs a NetMetricsLogUploader which uploads data to |server_url| with + // the specified |mime_type|. The |service_type| marks which service the + // data usage should be attributed to. The |on_upload_complete| callback will + // be called with the HTTP response code of the upload or with -1 on an error. + NetMetricsLogUploader( + scoped_refptr url_loader_factory, + base::StringPiece server_url, + base::StringPiece mime_type, + MetricsLogUploader::MetricServiceType service_type, + const MetricsLogUploader::UploadCallback& on_upload_complete); + + // This constructor allows a secondary non-HTTPS URL to be passed in as + // |insecure_server_url|. That URL is used as a fallback if a connection + // to |server_url| fails, requests are encrypted when sent to an HTTP URL. + NetMetricsLogUploader( + scoped_refptr url_loader_factory, + base::StringPiece server_url, + base::StringPiece insecure_server_url, + base::StringPiece mime_type, + MetricsLogUploader::MetricServiceType service_type, + const MetricsLogUploader::UploadCallback& on_upload_complete); + + ~NetMetricsLogUploader() override; + + // MetricsLogUploader: + // Uploads a log to the server_url specified in the constructor. + void UploadLog(const std::string& compressed_log_data, + const std::string& log_hash, + const ReportingInfo& reporting_info) override; + + private: + // Uploads a log to a URL passed as a parameter. + void UploadLogToURL(const std::string& compressed_log_data, + const std::string& log_hash, + const ReportingInfo& reporting_info, + const GURL& url); + + void OnURLLoadComplete(std::unique_ptr response_body); + + // Encrypts a |plaintext| string, using the encrypted_messages component, + // returns |encrypted| which is a serialized EncryptedMessage object. + bool EncryptString(const std::string& plaintext, std::string* encrypted); + + // The URLLoader factory for loads done using the network stack. + scoped_refptr url_loader_factory_; + + const GURL server_url_; + const GURL insecure_server_url_; + const std::string mime_type_; + const MetricsLogUploader ::MetricServiceType service_type_; + const MetricsLogUploader::UploadCallback on_upload_complete_; + // The outstanding transmission appears as a URL Fetch operation. + std::unique_ptr url_loader_; + + DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploader); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_ diff --git a/components/metrics/net/net_metrics_log_uploader_unittest.cc b/components/metrics/net/net_metrics_log_uploader_unittest.cc new file mode 100644 index 0000000000000..f6aecdc954931 --- /dev/null +++ b/components/metrics/net/net_metrics_log_uploader_unittest.cc @@ -0,0 +1,187 @@ +// 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/base64.h" +#include "base/bind.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/test/bind_test_util.h" +#include "base/test/scoped_task_environment.h" +#include "components/encrypted_messages/encrypted_message.pb.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "services/network/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/reporting_info.pb.h" +#include "third_party/zlib/google/compression_utils.h" +#include "url/gurl.h" + +namespace metrics { + +class NetMetricsLogUploaderTest : public testing::Test { + public: + NetMetricsLogUploaderTest() + : on_upload_complete_count_(0), + test_shared_url_loader_factory_( + base::MakeRefCounted( + &test_url_loader_factory_)) { + test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting( + [&](const network::ResourceRequest& request) { + upload_data_ = network::GetUploadData(request); + headers_ = request.headers; + loop_.Quit(); + })); + } + + void CreateAndOnUploadCompleteReuseUploader() { + ReportingInfo reporting_info; + reporting_info.set_attempt_count(10); + uploader_.reset(new NetMetricsLogUploader( + test_shared_url_loader_factory_, "https://dummy_server", "dummy_mime", + MetricsLogUploader::UMA, + base::Bind(&NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader, + base::Unretained(this)))); + uploader_->UploadLog("initial_dummy_data", "initial_dummy_hash", + reporting_info); + } + + void CreateUploaderAndUploadToSecureURL(const std::string& url) { + ReportingInfo dummy_reporting_info; + uploader_.reset(new NetMetricsLogUploader( + test_shared_url_loader_factory_, url, "dummy_mime", + MetricsLogUploader::UMA, + base::Bind(&NetMetricsLogUploaderTest::DummyOnUploadComplete, + base::Unretained(this)))); + uploader_->UploadLog("dummy_data", "dummy_hash", dummy_reporting_info); + } + + void CreateUploaderAndUploadToInsecureURL() { + ReportingInfo dummy_reporting_info; + uploader_.reset(new NetMetricsLogUploader( + test_shared_url_loader_factory_, "http://dummy_insecure_server", + "dummy_mime", MetricsLogUploader::UMA, + base::Bind(&NetMetricsLogUploaderTest::DummyOnUploadComplete, + base::Unretained(this)))); + std::string compressed_message; + // Compress the data since the encryption code expects a compressed log, + // and tries to decompress it before encrypting it. + compression::GzipCompress("dummy_data", &compressed_message); + uploader_->UploadLog(compressed_message, "dummy_hash", + dummy_reporting_info); + } + + void DummyOnUploadComplete(int response_code, + int error_code, + bool was_https) {} + + void OnUploadCompleteReuseUploader(int response_code, + int error_code, + bool was_https) { + ++on_upload_complete_count_; + if (on_upload_complete_count_ == 1) { + ReportingInfo reporting_info; + reporting_info.set_attempt_count(20); + uploader_->UploadLog("dummy_data", "dummy_hash", reporting_info); + } + } + + network::TestURLLoaderFactory::PendingRequest* GetPendingRequest( + size_t index) { + if (index >= test_url_loader_factory_.pending_requests()->size()) + return nullptr; + auto* request = &(*test_url_loader_factory_.pending_requests())[index]; + DCHECK(request); + return request; + } + + int on_upload_complete_count() const { + return on_upload_complete_count_; + } + + network::TestURLLoaderFactory* test_url_loader_factory() { + return &test_url_loader_factory_; + } + + const net::HttpRequestHeaders& last_request_headers() { return headers_; } + + const std::string& last_upload_data() { return upload_data_; } + + void WaitForRequest() { loop_.Run(); } + + private: + std::unique_ptr uploader_; + int on_upload_complete_count_; + + network::TestURLLoaderFactory test_url_loader_factory_; + scoped_refptr + test_shared_url_loader_factory_; + + base::test::ScopedTaskEnvironment scoped_task_environment_; + + base::RunLoop loop_; + std::string upload_data_; + net::HttpRequestHeaders headers_; + + DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploaderTest); +}; + +void CheckReportingInfoHeader(net::HttpRequestHeaders headers, + int expected_attempt_count) { + std::string reporting_info_base64; + EXPECT_TRUE( + headers.GetHeader("X-Chrome-UMA-ReportingInfo", &reporting_info_base64)); + std::string reporting_info_string; + EXPECT_TRUE( + base::Base64Decode(reporting_info_base64, &reporting_info_string)); + ReportingInfo reporting_info; + EXPECT_TRUE(reporting_info.ParseFromString(reporting_info_string)); + EXPECT_EQ(reporting_info.attempt_count(), expected_attempt_count); +} + +TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) { + CreateAndOnUploadCompleteReuseUploader(); + WaitForRequest(); + + // Mimic the initial fetcher callback. + CheckReportingInfoHeader(last_request_headers(), 10); + test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList( + GetPendingRequest(0), ""); + + // Mimic the second fetcher callback. + CheckReportingInfoHeader(last_request_headers(), 20); + test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList( + GetPendingRequest(1), ""); + + EXPECT_EQ(on_upload_complete_count(), 2); +} + +// Test that attempting to upload to an HTTP URL results in an encrypted +// message. +TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPIsEncrypted) { + CreateUploaderAndUploadToInsecureURL(); + WaitForRequest(); + encrypted_messages::EncryptedMessage message; + EXPECT_TRUE(message.ParseFromString(last_upload_data())); +} + +// Test that attempting to upload to an HTTPS URL results in an unencrypted +// message. +TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPSIsNotEncrypted) { + CreateUploaderAndUploadToSecureURL("https://dummy_secure_server"); + WaitForRequest(); + EXPECT_EQ(last_upload_data(), "dummy_data"); +} + +// Test that attempting to upload to localhost over http results in an +// unencrypted message. +TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPLocalhostIsNotEncrypted) { + CreateUploaderAndUploadToSecureURL("http://localhost"); + WaitForRequest(); + EXPECT_EQ(last_upload_data(), "dummy_data"); +} + +} // namespace metrics diff --git a/components/metrics/net/network_metrics_provider.cc b/components/metrics/net/network_metrics_provider.cc new file mode 100644 index 0000000000000..d1756486e9224 --- /dev/null +++ b/components/metrics/net/network_metrics_provider.cc @@ -0,0 +1,483 @@ +// 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 + +#include +#include + +#include "base/bind_helpers.h" +#include "base/callback_forward.h" +#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/post_task.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "net/base/net_errors.h" +#include "net/nqe/effective_connection_type_observer.h" +#include "net/nqe/network_quality_estimator.h" + +#if defined(OS_CHROMEOS) +#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h" +#endif // OS_CHROMEOS + +namespace metrics { + +SystemProfileProto::Network::EffectiveConnectionType +ConvertEffectiveConnectionType( + net::EffectiveConnectionType effective_connection_type) { + switch (effective_connection_type) { + case net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN: + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; + case net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G: + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; + case net::EFFECTIVE_CONNECTION_TYPE_2G: + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G; + case net::EFFECTIVE_CONNECTION_TYPE_3G: + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_3G; + case net::EFFECTIVE_CONNECTION_TYPE_4G: + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G; + case net::EFFECTIVE_CONNECTION_TYPE_OFFLINE: + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_OFFLINE; + case net::EFFECTIVE_CONNECTION_TYPE_LAST: + NOTREACHED(); + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; + } + NOTREACHED(); + return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; +} + +// Listens to the changes in the effective conection type. +class NetworkMetricsProvider::EffectiveConnectionTypeObserver + : public net::EffectiveConnectionTypeObserver { + public: + // |network_quality_estimator| is used to provide the network quality + // estimates. Guaranteed to be non-null. |callback| is run on + // |callback_task_runner|, and provides notifications about the changes in the + // effective connection type. + EffectiveConnectionTypeObserver( + base::Callback callback, + const scoped_refptr& callback_task_runner) + : network_quality_estimator_(nullptr), + callback_(callback), + callback_task_runner_(callback_task_runner) { + DCHECK(callback_); + DCHECK(callback_task_runner_); + // |this| is initialized and used on the IO thread using + // |network_quality_task_runner_|. + thread_checker_.DetachFromThread(); + } + + ~EffectiveConnectionTypeObserver() override { + DCHECK(thread_checker_.CalledOnValidThread()); + if (network_quality_estimator_) + network_quality_estimator_->RemoveEffectiveConnectionTypeObserver(this); + } + + // Initializes |this| on IO thread using |network_quality_task_runner_|. This + // is the same thread on which |network_quality_estimator| lives. + void Init(net::NetworkQualityEstimator* network_quality_estimator) { + network_quality_estimator_ = network_quality_estimator; + if (network_quality_estimator_) + network_quality_estimator_->AddEffectiveConnectionTypeObserver(this); + } + + private: + // net::EffectiveConnectionTypeObserver: + void OnEffectiveConnectionTypeChanged( + net::EffectiveConnectionType type) override { + DCHECK(thread_checker_.CalledOnValidThread()); + callback_task_runner_->PostTask(FROM_HERE, base::BindOnce(callback_, type)); + } + + // Notifies |this| when there is a change in the effective connection type. + net::NetworkQualityEstimator* network_quality_estimator_; + + // Called when the effective connection type is changed. + base::Callback callback_; + + // Task runner on which |callback_| is run. + scoped_refptr callback_task_runner_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(EffectiveConnectionTypeObserver); +}; + +NetworkMetricsProvider::NetworkMetricsProvider( + std::unique_ptr + network_quality_estimator_provider) + : connection_type_is_ambiguous_(false), + network_change_notifier_initialized_(false), + wifi_phy_layer_protocol_is_ambiguous_(false), + wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN), + total_aborts_(0), + total_codes_(0), + network_quality_estimator_provider_( + std::move(network_quality_estimator_provider)), + effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN), + min_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN), + max_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN), + weak_ptr_factory_(this) { + net::NetworkChangeNotifier::AddNetworkChangeObserver(this); + connection_type_ = net::NetworkChangeNotifier::GetConnectionType(); + if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_UNKNOWN) + network_change_notifier_initialized_ = true; + + ProbeWifiPHYLayerProtocol(); + + if (network_quality_estimator_provider_) { + effective_connection_type_observer_.reset( + new EffectiveConnectionTypeObserver( + base::Bind( + &NetworkMetricsProvider::OnEffectiveConnectionTypeChanged, + base::Unretained(this)), + base::ThreadTaskRunnerHandle::Get())); + + // Get the network quality estimator and initialize + // |effective_connection_type_observer_| on the same task runner on which + // the network quality estimator lives. It is safe to use base::Unretained + // here since both |network_quality_estimator_provider_| and + // |effective_connection_type_observer_| are owned by |this|, and + // |network_quality_estimator_provider_| is deleted before + // |effective_connection_type_observer_|. + network_quality_estimator_provider_->PostReplyNetworkQualityEstimator( + base::Bind( + &EffectiveConnectionTypeObserver::Init, + base::Unretained(effective_connection_type_observer_.get()))); + } +} + +NetworkMetricsProvider::~NetworkMetricsProvider() { + DCHECK(thread_checker_.CalledOnValidThread()); + net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); + + if (network_quality_estimator_provider_) { + scoped_refptr network_quality_task_runner = + network_quality_estimator_provider_->GetTaskRunner(); + + // |network_quality_estimator_provider_| must be deleted before + // |effective_connection_type_observer_| since + // |effective_connection_type_observer_| may callback into + // |effective_connection_type_observer_|. + network_quality_estimator_provider_.reset(); + + if (network_quality_task_runner && + !network_quality_task_runner->DeleteSoon( + FROM_HERE, effective_connection_type_observer_.release())) { + NOTREACHED() << " ECT observer was not deleted successfully"; + } + } +} + +void NetworkMetricsProvider::ProvideCurrentSessionData( + ChromeUserMetricsExtension*) { + DCHECK(thread_checker_.CalledOnValidThread()); + // ProvideCurrentSessionData 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) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!connection_type_is_ambiguous_ || + network_change_notifier_initialized_); + 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()); + + network->set_min_effective_connection_type( + ConvertEffectiveConnectionType(min_effective_connection_type_)); + network->set_max_effective_connection_type( + ConvertEffectiveConnectionType(max_effective_connection_type_)); + + // 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(); + if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_UNKNOWN) + network_change_notifier_initialized_ = true; + // Reset the "ambiguous" flags, since a new metrics log session has started. + connection_type_is_ambiguous_ = false; + wifi_phy_layer_protocol_is_ambiguous_ = false; + min_effective_connection_type_ = effective_connection_type_; + max_effective_connection_type_ = effective_connection_type_; + + if (!wifi_access_point_info_provider_) { +#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::OnNetworkChanged( + net::NetworkChangeNotifier::ConnectionType type) { + DCHECK(thread_checker_.CalledOnValidThread()); + // 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) { + network_change_notifier_initialized_ = true; + return; + } + + DCHECK(network_change_notifier_initialized_ || + connection_type_ == net::NetworkChangeNotifier::CONNECTION_UNKNOWN); + + if (type != connection_type_ && + connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE && + network_change_notifier_initialized_) { + // If |network_change_notifier_initialized_| is false, it implies that this + // is the first connection change callback received from network change + // notifier, and the previous connection type was CONNECTION_UNKNOWN. In + // that case, connection type should not be marked as ambiguous since there + // was no actual change in the connection type. + connection_type_is_ambiguous_ = true; + } + + network_change_notifier_initialized_ = true; + connection_type_ = type; + + ProbeWifiPHYLayerProtocol(); +} + +SystemProfileProto::Network::ConnectionType +NetworkMetricsProvider::GetConnectionType() const { + DCHECK(thread_checker_.CalledOnValidThread()); + 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 { + DCHECK(thread_checker_.CalledOnValidThread()); + 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() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, + base::BindOnce(&net::GetWifiPHYLayerProtocol), + base::BindOnce(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult, + weak_ptr_factory_.GetWeakPtr())); +} + +void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult( + net::WifiPHYLayerProtocol mode) { + DCHECK(thread_checker_.CalledOnValidThread()); + 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) { + DCHECK(thread_checker_.CalledOnValidThread()); + 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 { + DLOG(WARNING) << "Error when parsing OUI list of the WiFi access point"; + } + } +} + +void NetworkMetricsProvider::LogAggregatedMetrics() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::HistogramBase* error_codes = base::SparseHistogram::FactoryGet( + "Net.ErrorCodesForMainFrame4", + base::HistogramBase::kUmaTargetedHistogramFlag); + std::unique_ptr 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_CUSTOM_COUNTS("Net.ErrAborted.CountPerUpload2", new_aborts, 1, + 100000000, 50); + UMA_HISTOGRAM_PERCENTAGE("Net.ErrAborted.ProportionPerUpload", + (100 * new_aborts) / new_codes); + total_codes_ += new_codes; + total_aborts_ += new_aborts; + } +} + +void NetworkMetricsProvider::OnEffectiveConnectionTypeChanged( + net::EffectiveConnectionType type) { + DCHECK(thread_checker_.CalledOnValidThread()); + effective_connection_type_ = type; + + if (effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN || + effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) { + // The effective connection type may be reported as Unknown if there is a + // change in the connection type. Disregard it since network requests can't + // be send during the changes in connection type. Similarly, disregard + // offline as the type since it may be reported as the effective connection + // type for a short period when there is a change in the connection type. + return; + } + + if (min_effective_connection_type_ == + net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN && + max_effective_connection_type_ == + net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) { + min_effective_connection_type_ = type; + max_effective_connection_type_ = type; + return; + } + + if (min_effective_connection_type_ == + net::EFFECTIVE_CONNECTION_TYPE_OFFLINE && + max_effective_connection_type_ == + net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) { + min_effective_connection_type_ = type; + max_effective_connection_type_ = type; + return; + } + + min_effective_connection_type_ = + std::min(min_effective_connection_type_, effective_connection_type_); + max_effective_connection_type_ = + std::max(max_effective_connection_type_, effective_connection_type_); + + DCHECK_EQ( + min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN); + DCHECK_EQ( + min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE, + max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE); +} + +} // namespace metrics diff --git a/components/metrics/net/network_metrics_provider.h b/components/metrics/net/network_metrics_provider.h new file mode 100644 index 0000000000000..3b9932f9cbd74 --- /dev/null +++ b/components/metrics/net/network_metrics_provider.h @@ -0,0 +1,159 @@ +// 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 + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/metrics/histogram_base.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_checker.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/net/wifi_access_point_info_provider.h" +#include "net/base/network_change_notifier.h" +#include "net/base/network_interfaces.h" +#include "net/nqe/effective_connection_type.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace net { +class NetworkQualityEstimator; +} + +namespace metrics { + +SystemProfileProto::Network::EffectiveConnectionType +ConvertEffectiveConnectionType( + net::EffectiveConnectionType effective_connection_type); + +// Registers as observer with net::NetworkChangeNotifier and keeps track of +// the network environment. +class NetworkMetricsProvider + : public MetricsProvider, + public net::NetworkChangeNotifier::NetworkChangeObserver { + public: + // Class that provides |this| with the network quality estimator. + class NetworkQualityEstimatorProvider { + public: + virtual ~NetworkQualityEstimatorProvider() {} + + // Returns the network quality estimator by calling |io_callback|. The + // returned network quality estimator may be nullptr. |io_callback| must be + // called on the IO thread. |io_callback| can be destroyed on IO thread only + // after |this| is destroyed. + virtual void PostReplyNetworkQualityEstimator( + base::Callback io_callback) = 0; + + // Returns the task runner on which |this| should be used and destroyed. + virtual scoped_refptr GetTaskRunner() = 0; + + protected: + NetworkQualityEstimatorProvider() {} + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkQualityEstimatorProvider); + }; + + // Creates a NetworkMetricsProvider, where + // |network_quality_estimator_provider| should be set if it is useful to + // attach the quality of the network to the metrics report. + explicit NetworkMetricsProvider( + std::unique_ptr + network_quality_estimator_provider = nullptr); + ~NetworkMetricsProvider() override; + + private: + FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest, EffectiveConnectionType); + FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest, + ECTAmbiguousOnConnectionTypeChange); + FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest, + ECTNotAmbiguousOnOffline); + FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest, + ConnectionTypeIsAmbiguous); + + // Listens to the changes in the effective conection type. + class EffectiveConnectionTypeObserver; + + // MetricsProvider: + void ProvideCurrentSessionData( + ChromeUserMetricsExtension* uma_proto) override; + void ProvideSystemProfileMetrics(SystemProfileProto* system_profile) override; + + // NetworkChangeObserver: + void OnNetworkChanged( + 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(); + + // Notifies |this| that the effective connection type of the current network + // has changed to |type|. + void OnEffectiveConnectionTypeChanged(net::EffectiveConnectionType type); + + // 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 the network change notifier has been initialized. + bool network_change_notifier_initialized_; + + // 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. + std::unique_ptr wifi_access_point_info_provider_; + + // These metrics track histogram totals for the Net.ErrorCodesForMainFrame4 + // histogram. They are used to compute deltas at upload time. + base::HistogramBase::Count total_aborts_; + base::HistogramBase::Count total_codes_; + + // Provides the network quality estimator. May be null. + std::unique_ptr + network_quality_estimator_provider_; + + // Listens to the changes in the effective connection type. Initialized and + // destroyed on the IO thread. May be null. + std::unique_ptr + effective_connection_type_observer_; + + // Last known effective connection type. + net::EffectiveConnectionType effective_connection_type_; + + // Minimum and maximum effective connection type since the metrics were last + // provided. + net::EffectiveConnectionType min_effective_connection_type_; + net::EffectiveConnectionType max_effective_connection_type_; + + base::ThreadChecker thread_checker_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NetworkMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_ diff --git a/components/metrics/net/network_metrics_provider_unittest.cc b/components/metrics/net/network_metrics_provider_unittest.cc new file mode 100644 index 0000000000000..b5e650766382e --- /dev/null +++ b/components/metrics/net/network_metrics_provider_unittest.cc @@ -0,0 +1,290 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/net/network_metrics_provider.h" + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "net/base/network_change_notifier.h" +#include "net/nqe/network_quality_estimator_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +#if defined(OS_CHROMEOS) +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/network/network_handler.h" +#endif // OS_CHROMEOS + +namespace metrics { + +namespace { + +class TestNetworkQualityEstimatorProvider + : public NetworkMetricsProvider::NetworkQualityEstimatorProvider { + public: + explicit TestNetworkQualityEstimatorProvider( + net::TestNetworkQualityEstimator* estimator) + : estimator_(estimator) {} + ~TestNetworkQualityEstimatorProvider() override {} + + private: + // NetworkMetricsProvider::NetworkQualityEstimatorProvider: + scoped_refptr GetTaskRunner() override { + return base::ThreadTaskRunnerHandle::Get(); + } + + void PostReplyNetworkQualityEstimator( + base::Callback callback) override { + callback.Run(estimator_); + } + + net::TestNetworkQualityEstimator* estimator_; + DISALLOW_COPY_AND_ASSIGN(TestNetworkQualityEstimatorProvider); +}; + +} // namespace + +class NetworkMetricsProviderTest : public testing::Test { + protected: + NetworkMetricsProviderTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO) { +#if defined(OS_CHROMEOS) + chromeos::DBusThreadManager::Initialize(); + chromeos::NetworkHandler::Initialize(); +#endif // OS_CHROMEOS + } + + private: + base::test::ScopedTaskEnvironment scoped_task_environment_; +}; + +// Verifies that the effective connection type is correctly set. +TEST_F(NetworkMetricsProviderTest, EffectiveConnectionType) { + net::TestNetworkQualityEstimator estimator; + std::unique_ptr + estimator_provider(base::WrapUnique( + new TestNetworkQualityEstimatorProvider(&estimator))); + SystemProfileProto system_profile; + NetworkMetricsProvider network_metrics_provider( + std::move(estimator_provider)); + + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.min_effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.max_effective_connection_type_); + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + system_profile.network().max_effective_connection_type()); + + // Set RTT so that the effective connection type is computed as 2G. + estimator.set_recent_http_rtt(base::TimeDelta::FromMilliseconds(1500)); + estimator.SetStartTimeNullHttpRtt(base::TimeDelta::FromMilliseconds(1500)); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.min_effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.max_effective_connection_type_); + // Running a request would cause the effective connection type to be computed + // as 2G, and observers to be notified. + estimator.RunOneRequest(); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.min_effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.max_effective_connection_type_); + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().max_effective_connection_type()); + + // Set RTT so that the effective connection type is computed as SLOW_2G. + estimator.set_recent_http_rtt(base::TimeDelta::FromMilliseconds(3000)); + estimator.SetStartTimeNullHttpRtt(base::TimeDelta::FromMilliseconds(3000)); + // Running a request would cause the effective connection type to be computed + // as SLOW_2G, and observers to be notified. + estimator.RunOneRequest(); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + network_metrics_provider.effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + network_metrics_provider.min_effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.max_effective_connection_type_); + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + // Effective connection type changed from 2G to SLOW_2G during the lifetime of + // the log. Minimum value of ECT must be different from the maximum value. + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().max_effective_connection_type()); + + // Getting the system profile again should return the current effective + // connection type. + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + system_profile.network().max_effective_connection_type()); +} + +// Verifies that the effective connection type is not set to UNKNOWN when there +// is a change in the connection type. +TEST_F(NetworkMetricsProviderTest, ECTAmbiguousOnConnectionTypeChange) { + net::TestNetworkQualityEstimator estimator; + std::unique_ptr + estimator_provider(base::WrapUnique( + new TestNetworkQualityEstimatorProvider(&estimator))); + SystemProfileProto system_profile; + NetworkMetricsProvider network_metrics_provider( + std::move(estimator_provider)); + + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.min_effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + network_metrics_provider.max_effective_connection_type_); + + // Set RTT so that the effective connection type is computed as 2G. + estimator.set_recent_http_rtt(base::TimeDelta::FromMilliseconds(1500)); + estimator.SetStartTimeNullHttpRtt(base::TimeDelta::FromMilliseconds(1500)); + // Running a request would cause the effective connection type to be computed + // as 2G, and observers to be notified. + estimator.RunOneRequest(); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.min_effective_connection_type_); + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G, + network_metrics_provider.max_effective_connection_type_); + + // There is no change in the connection type. Effective connection types + // should be reported as 2G. + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().max_effective_connection_type()); + + // Even with change in the connection type, effective connection types + // should be reported as 2G. + network_metrics_provider.OnNetworkChanged( + net::NetworkChangeNotifier::CONNECTION_2G); + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().max_effective_connection_type()); +} + +// Verifies that the effective connection type is not set to UNKNOWN when the +// connection type is OFFLINE. +TEST_F(NetworkMetricsProviderTest, ECTNotAmbiguousOnOffline) { + for (net::EffectiveConnectionType force_ect : + {net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN, + net::EFFECTIVE_CONNECTION_TYPE_OFFLINE}) { + std::unique_ptr params = + std::make_unique( + std::map()); + net::NetworkQualityEstimatorParams* params_ptr = params.get(); + net::TestNetworkQualityEstimator estimator(std::move(params)); + + std::unique_ptr + estimator_provider(base::WrapUnique( + new TestNetworkQualityEstimatorProvider(&estimator))); + SystemProfileProto system_profile; + NetworkMetricsProvider network_metrics_provider( + std::move(estimator_provider)); + + params_ptr->SetForcedEffectiveConnectionType( + net::EFFECTIVE_CONNECTION_TYPE_2G); + estimator.RunOneRequest(); + + params_ptr->SetForcedEffectiveConnectionType(force_ect); + estimator.RunOneRequest(); + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G, + system_profile.network().max_effective_connection_type()); + + params_ptr->SetForcedEffectiveConnectionType( + net::EFFECTIVE_CONNECTION_TYPE_4G); + estimator.RunOneRequest(); + + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G, + system_profile.network().min_effective_connection_type()); + EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G, + system_profile.network().max_effective_connection_type()); + } +} + +// Verifies that the connection type is ambiguous boolean is correctly set. +TEST_F(NetworkMetricsProviderTest, ConnectionTypeIsAmbiguous) { + net::TestNetworkQualityEstimator estimator; + std::unique_ptr + estimator_provider(base::WrapUnique( + new TestNetworkQualityEstimatorProvider(&estimator))); + SystemProfileProto system_profile; + NetworkMetricsProvider network_metrics_provider( + std::move(estimator_provider)); + estimator.RunOneRequest(); + + EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_UNKNOWN, + network_metrics_provider.connection_type_); + EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_); + EXPECT_FALSE(network_metrics_provider.network_change_notifier_initialized_); + + // When a connection type change callback is received, network change notifier + // should be marked as initialized. + network_metrics_provider.OnNetworkChanged( + net::NetworkChangeNotifier::CONNECTION_2G); + EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_2G, + network_metrics_provider.connection_type_); + // Connection type should not be marked as ambiguous when a delayed connection + // type change callback is received due to delayed initialization of the + // network change notifier. + EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_); + EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_); + + // On collection of the system profile, |connection_type_is_ambiguous_| should + // stay false, and |network_change_notifier_initialized_| should remain true. + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_); + EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_); + EXPECT_FALSE(system_profile.network().connection_type_is_ambiguous()); + EXPECT_EQ(SystemProfileProto::Network::CONNECTION_2G, + system_profile.network().connection_type()); + + network_metrics_provider.OnNetworkChanged( + net::NetworkChangeNotifier::CONNECTION_3G); + EXPECT_TRUE(network_metrics_provider.connection_type_is_ambiguous_); + EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_); + + // On collection of the system profile, |connection_type_is_ambiguous_| should + // be reset to false, and |network_change_notifier_initialized_| should remain + // true. + network_metrics_provider.ProvideSystemProfileMetrics(&system_profile); + EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_); + EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_); + EXPECT_TRUE(system_profile.network().connection_type_is_ambiguous()); + EXPECT_EQ(SystemProfileProto::Network::CONNECTION_3G, + system_profile.network().connection_type()); +} + +} // namespace metrics diff --git a/components/metrics/net/wifi_access_point_info_provider.cc b/components/metrics/net/wifi_access_point_info_provider.cc new file mode 100644 index 0000000000000..21e04b181d25f --- /dev/null +++ b/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/components/metrics/net/wifi_access_point_info_provider.h b/components/metrics/net/wifi_access_point_info_provider.h new file mode 100644 index 0000000000000..3a761da1b12fa --- /dev/null +++ b/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 + +#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/components/metrics/net/wifi_access_point_info_provider_chromeos.cc b/components/metrics/net/wifi_access_point_info_provider_chromeos.cc new file mode 100644 index 0000000000000..3654e93ef4fc7 --- /dev/null +++ b/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 + +#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/components/metrics/net/wifi_access_point_info_provider_chromeos.h b/components/metrics/net/wifi_access_point_info_provider_chromeos.h new file mode 100644 index 0000000000000..d310239774160 --- /dev/null +++ b/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 + +#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 { + 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/components/metrics/persisted_logs.cc b/components/metrics/persisted_logs.cc new file mode 100644 index 0000000000000..74d1ca71d4ccf --- /dev/null +++ b/components/metrics/persisted_logs.cc @@ -0,0 +1,227 @@ +// 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 +#include +#include + +#include "base/base64.h" +#include "base/md5.h" +#include "base/metrics/histogram_macros.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "base/timer/elapsed_timer.h" +#include "components/metrics/persisted_logs_metrics.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 { + +const char kLogHashKey[] = "hash"; +const char kLogTimestampKey[] = "timestamp"; +const char kLogDataKey[] = "data"; + +std::string EncodeToBase64(const std::string& to_convert) { + // CHECK to diagnose crbug.com/695433 + CHECK(to_convert.data()); + std::string base64_result; + base::Base64Encode(to_convert, &base64_result); + return base64_result; +} + +std::string DecodeFromBase64(const std::string& to_convert) { + std::string result; + base::Base64Decode(to_convert, &result); + return result; +} + +} // namespace + +void PersistedLogs::LogInfo::Init(PersistedLogsMetrics* metrics, + const std::string& log_data, + const std::string& log_timestamp) { + DCHECK(!log_data.empty()); + + if (!compression::GzipCompress(log_data, &compressed_log_data)) { + NOTREACHED(); + return; + } + + metrics->RecordCompressionRatio(compressed_log_data.size(), log_data.size()); + + hash = base::SHA1HashString(log_data); + timestamp = log_timestamp; +} + +PersistedLogs::PersistedLogs(std::unique_ptr metrics, + PrefService* local_state, + const char* pref_name, + size_t min_log_count, + size_t min_log_bytes, + size_t max_log_size) + : metrics_(std::move(metrics)), + 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(-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() {} + +bool PersistedLogs::has_unsent_logs() const { + return !!size(); +} + +// True if a log has been staged. +bool PersistedLogs::has_staged_log() const { + return staged_log_index_ != -1; +} + +// Returns the element in the front of the list. +const std::string& PersistedLogs::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& PersistedLogs::staged_log_hash() const { + DCHECK(has_staged_log()); + return list_[staged_log_index_].hash; +} + +// Returns the timestamp of the element in the front of the list. +const std::string& PersistedLogs::staged_log_timestamp() const { + DCHECK(has_staged_log()); + return list_[staged_log_index_].timestamp; +} + +void PersistedLogs::StageNextLog() { + // 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() { + // CHECK, rather than DCHECK, to diagnose cause of crashes from the field, + // for crbug.com/695433. + CHECK(has_staged_log()); + DCHECK_LT(static_cast(staged_log_index_), list_.size()); + list_.erase(list_.begin() + staged_log_index_); + staged_log_index_ = -1; +} + +void PersistedLogs::PersistUnsentLogs() const { + ListPrefUpdate update(local_state_, pref_name_); + WriteLogsToPrefList(update.Get()); +} + +void PersistedLogs::LoadPersistedUnsentLogs() { + ReadLogsFromPrefList(*local_state_->GetList(pref_name_)); +} + +void PersistedLogs::StoreLog(const std::string& log_data) { + list_.push_back(LogInfo()); + list_.back().Init(metrics_.get(), log_data, + base::Int64ToString(base::Time::Now().ToTimeT())); +} + +void PersistedLogs::Purge() { + if (has_staged_log()) { + DiscardStagedLog(); + } + list_.clear(); + local_state_->ClearPref(pref_name_); +} + +void PersistedLogs::ReadLogsFromPrefList(const base::ListValue& list_value) { + if (list_value.empty()) { + metrics_->RecordLogReadStatus(PersistedLogsMetrics::LIST_EMPTY); + return; + } + + const size_t log_count = list_value.GetSize(); + + DCHECK(list_.empty()); + list_.resize(log_count); + + for (size_t i = 0; i < log_count; ++i) { + const base::DictionaryValue* dict; + if (!list_value.GetDictionary(i, &dict) || + !dict->GetString(kLogDataKey, &list_[i].compressed_log_data) || + !dict->GetString(kLogHashKey, &list_[i].hash)) { + list_.clear(); + metrics_->RecordLogReadStatus( + PersistedLogsMetrics::LOG_STRING_CORRUPTION); + return; + } + + list_[i].compressed_log_data = + DecodeFromBase64(list_[i].compressed_log_data); + list_[i].hash = DecodeFromBase64(list_[i].hash); + // Ignoring the success of this step as timestamp might not be there for + // older logs. + // NOTE: Should be added to the check with other fields once migration is + // over. + dict->GetString(kLogTimestampKey, &list_[i].timestamp); + } + + metrics_->RecordLogReadStatus(PersistedLogsMetrics::RECALL_SUCCESS); +} + +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; + } + int dropped_logs_num = start - 1; + + 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_) { + metrics_->RecordDroppedLogSize(log_size); + dropped_logs_num++; + continue; + } + std::unique_ptr dict_value( + new base::DictionaryValue); + dict_value->SetString(kLogHashKey, EncodeToBase64(list_[i].hash)); + dict_value->SetString(kLogDataKey, + EncodeToBase64(list_[i].compressed_log_data)); + dict_value->SetString(kLogTimestampKey, list_[i].timestamp); + list_value->Append(std::move(dict_value)); + } + if (dropped_logs_num > 0) + metrics_->RecordDroppedLogsNum(dropped_logs_num); +} + +} // namespace metrics diff --git a/components/metrics/persisted_logs.h b/components/metrics/persisted_logs.h new file mode 100644 index 0000000000000..f95b8201750ce --- /dev/null +++ b/components/metrics/persisted_logs.h @@ -0,0 +1,125 @@ +// 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 + +#include +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/values.h" +#include "components/metrics/log_store.h" + +class PrefService; + +namespace metrics { + +class PersistedLogsMetrics; + +// Maintains a list of unsent logs that are written and restored from disk. +class PersistedLogs : public LogStore { + public: + // 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(std::unique_ptr metrics, + PrefService* local_state, + const char* pref_name, + size_t min_log_count, + size_t min_log_bytes, + size_t max_log_size); + ~PersistedLogs(); + + // LogStore: + bool has_unsent_logs() const override; + bool has_staged_log() const override; + const std::string& staged_log() const override; + const std::string& staged_log_hash() const override; + void StageNextLog() override; + void DiscardStagedLog() override; + void PersistUnsentLogs() const override; + void LoadPersistedUnsentLogs() override; + + // Adds a log to the list. + void StoreLog(const std::string& log_data); + + // Delete all logs, in memory and on disk. + void Purge(); + + // Returns the timestamp of the element in the front of the list. + const std::string& staged_log_timestamp() const; + + // The number of elements currently stored. + size_t size() const { return list_.size(); } + + private: + // Writes the list to the ListValue. + void WriteLogsToPrefList(base::ListValue* list) const; + + // Reads the list from the ListValue. + void ReadLogsFromPrefList(const base::ListValue& list); + + // An object for recording UMA metrics. + std::unique_ptr metrics_; + + // 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 LogInfo { + // Initializes the members based on uncompressed |log_data| and + // |log_timestamp|. + // |metrics| is the parent's metrics_ object, and should not be held. + void Init(PersistedLogsMetrics* metrics, + const std::string& log_data, + const std::string& log_timestamp); + + // 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; + + // The timestamp of when the log was created as a time_t value. + std::string timestamp; + }; + // A list of all of the stored logs, stored with SHA1 hashes to check for + // corruption while they are stored in memory. + std::vector 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/components/metrics/persisted_logs_metrics.h b/components/metrics/persisted_logs_metrics.h new file mode 100644 index 0000000000000..e3c58dd1c56b5 --- /dev/null +++ b/components/metrics/persisted_logs_metrics.h @@ -0,0 +1,52 @@ +// 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_PERSISTED_LOGS_METRICS_H_ +#define COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_H_ + +#include "base/macros.h" +#include "components/metrics/persisted_logs.h" + +namespace metrics { + +// Interface for recording metrics from PersistedLogs. +class PersistedLogsMetrics { + 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. + }; + + PersistedLogsMetrics() {} + virtual ~PersistedLogsMetrics() {} + + virtual void RecordLogReadStatus(LogReadStatus status){}; + + virtual void RecordCompressionRatio( + size_t compressed_size, size_t original_size) {} + + virtual void RecordDroppedLogSize(size_t size) {} + + virtual void RecordDroppedLogsNum(int dropped_logs_num) {} + + private: + DISALLOW_COPY_AND_ASSIGN(PersistedLogsMetrics); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_H_ diff --git a/components/metrics/persisted_logs_metrics_impl.cc b/components/metrics/persisted_logs_metrics_impl.cc new file mode 100644 index 0000000000000..7142ba5103356 --- /dev/null +++ b/components/metrics/persisted_logs_metrics_impl.cc @@ -0,0 +1,33 @@ +// 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/persisted_logs_metrics_impl.h" + +#include "base/metrics/histogram_macros.h" + +namespace metrics { + +void PersistedLogsMetricsImpl::RecordLogReadStatus( + PersistedLogsMetrics::LogReadStatus status) { + UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", status, + PersistedLogsMetrics::END_RECALL_STATUS); +} + +void PersistedLogsMetricsImpl::RecordCompressionRatio( + size_t compressed_size, size_t original_size) { + UMA_HISTOGRAM_PERCENTAGE( + "UMA.ProtoCompressionRatio", + static_cast(100 * compressed_size / original_size)); +} + +void PersistedLogsMetricsImpl::RecordDroppedLogSize(size_t size) { + UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", + static_cast(size)); +} + +void PersistedLogsMetricsImpl::RecordDroppedLogsNum(int dropped_logs_num) { + UMA_HISTOGRAM_COUNTS("UMA.UnsentLogs.Dropped", dropped_logs_num); +} + +} // namespace metrics diff --git a/components/metrics/persisted_logs_metrics_impl.h b/components/metrics/persisted_logs_metrics_impl.h new file mode 100644 index 0000000000000..544acf9ab6793 --- /dev/null +++ b/components/metrics/persisted_logs_metrics_impl.h @@ -0,0 +1,32 @@ +// 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_PERSISTED_LOGS_METRICS_IMPL_H_ +#define COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_IMPL_H_ + +#include "base/macros.h" +#include "components/metrics/persisted_logs_metrics.h" + +namespace metrics { + +// Implementation for recording metrics from PersistedLogs. +class PersistedLogsMetricsImpl : public PersistedLogsMetrics { + public: + PersistedLogsMetricsImpl() {} + ~PersistedLogsMetricsImpl() override {} + + // PersistedLogsMetrics: + void RecordLogReadStatus(PersistedLogsMetrics::LogReadStatus status) override; + void RecordCompressionRatio( + size_t compressed_size, size_t original_size) override; + void RecordDroppedLogSize(size_t size) override; + void RecordDroppedLogsNum(int dropped_logs_num) override; + + private: + DISALLOW_COPY_AND_ASSIGN(PersistedLogsMetricsImpl); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_IMPL_H_ diff --git a/components/metrics/persisted_logs_unittest.cc b/components/metrics/persisted_logs_unittest.cc new file mode 100644 index 0000000000000..1a6c931d55c8d --- /dev/null +++ b/components/metrics/persisted_logs_unittest.cc @@ -0,0 +1,290 @@ +// 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 + +#include "base/base64.h" +#include "base/macros.h" +#include "base/rand_util.h" +#include "base/sha1.h" +#include "base/values.h" +#include "components/metrics/persisted_logs_metrics_impl.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(std::unique_ptr( + new PersistedLogsMetricsImpl()), + 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) { + StageNextLog(); + 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.PersistUnsentLogs(); + const base::ListValue* list_value = prefs_.GetList(kTestPrefName); + EXPECT_EQ(0U, list_value->GetSize()); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + 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.PersistUnsentLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + EXPECT_EQ(1U, result_persisted_logs.size()); + + // Verify that the result log matches the initial log. + persisted_logs.StageNextLog(); + result_persisted_logs.StageNextLog(); + EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log()); + EXPECT_EQ(persisted_logs.staged_log_hash(), + result_persisted_logs.staged_log_hash()); + EXPECT_EQ(persisted_logs.staged_log_timestamp(), + result_persisted_logs.staged_log_timestamp()); +} + +// 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.PersistUnsentLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + 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.PersistUnsentLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + 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.PersistUnsentLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + 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.PersistUnsentLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + 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.StageNextLog(); + 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.StageNextLog(); + EXPECT_EQ(persisted_logs.staged_log(), Compress("three")); + persisted_logs.DiscardStagedLog(); + persisted_logs.StageNextLog(); + 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.StageNextLog(); + persisted_logs.StoreLog("two"); + persisted_logs.DiscardStagedLog(); + persisted_logs.PersistUnsentLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); + result_persisted_logs.LoadPersistedUnsentLogs(); + 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.StageNextLog(); + + EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log()); + EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash()); +} + +} // namespace metrics diff --git a/components/metrics/persistent_system_profile.cc b/components/metrics/persistent_system_profile.cc new file mode 100644 index 0000000000000..fa35a44d3a474 --- /dev/null +++ b/components/metrics/persistent_system_profile.cc @@ -0,0 +1,440 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/persistent_system_profile.h" + +#include + +#include "base/atomicops.h" +#include "base/bits.h" +#include "base/memory/singleton.h" +#include "base/metrics/persistent_memory_allocator.h" +#include "base/pickle.h" +#include "base/stl_util.h" +#include "components/variations/active_field_trials.h" + +namespace metrics { + +namespace { + +// To provide atomic addition of records so that there is no confusion between +// writers and readers, all of the metadata about a record is contained in a +// structure that can be stored as a single atomic 32-bit word. +union RecordHeader { + struct { + unsigned continued : 1; // Flag indicating if there is more after this. + unsigned type : 7; // The type of this record. + unsigned amount : 24; // The amount of data to follow. + } as_parts; + base::subtle::Atomic32 as_atomic; +}; + +constexpr uint32_t kTypeIdSystemProfile = 0x330A7150; // SHA1(SystemProfile) +constexpr size_t kSystemProfileAllocSize = 4 << 10; // 4 KiB +constexpr size_t kMaxRecordSize = (1 << 24) - sizeof(RecordHeader); + +static_assert(sizeof(RecordHeader) == sizeof(base::subtle::Atomic32), + "bad RecordHeader size"); + +// Calculate the size of a record based on the amount of data. This adds room +// for the record header and rounds up to the next multiple of the record-header +// size. +size_t CalculateRecordSize(size_t data_amount) { + return base::bits::Align(data_amount + sizeof(RecordHeader), + sizeof(RecordHeader)); +} + +} // namespace + +PersistentSystemProfile::RecordAllocator::RecordAllocator( + base::PersistentMemoryAllocator* memory_allocator, + size_t min_size) + : allocator_(memory_allocator), + has_complete_profile_(false), + alloc_reference_(0), + alloc_size_(0), + end_offset_(0) { + AddSegment(min_size); +} + +PersistentSystemProfile::RecordAllocator::RecordAllocator( + const base::PersistentMemoryAllocator* memory_allocator) + : allocator_( + const_cast(memory_allocator)), + alloc_reference_(0), + alloc_size_(0), + end_offset_(0) {} + +void PersistentSystemProfile::RecordAllocator::Reset() { + // Clear the first word of all blocks so they're known to be "empty". + alloc_reference_ = 0; + while (NextSegment()) { + // Get the block as a char* and cast it. It can't be fetched directly as + // an array of RecordHeader because that's not a fundamental type and only + // arrays of fundamental types are allowed. + RecordHeader* header = + reinterpret_cast(allocator_->GetAsArray( + alloc_reference_, kTypeIdSystemProfile, sizeof(RecordHeader))); + DCHECK(header); + base::subtle::NoBarrier_Store(&header->as_atomic, 0); + } + + // Reset member variables. + has_complete_profile_ = false; + alloc_reference_ = 0; + alloc_size_ = 0; + end_offset_ = 0; +} + +bool PersistentSystemProfile::RecordAllocator::Write(RecordType type, + base::StringPiece record) { + const char* data = record.data(); + size_t remaining_size = record.size(); + + // Allocate space and write records until everything has been stored. + do { + if (end_offset_ == alloc_size_) { + if (!AddSegment(remaining_size)) + return false; + } + // Write out as much of the data as possible. |data| and |remaining_size| + // are updated in place. + if (!WriteData(type, &data, &remaining_size)) + return false; + } while (remaining_size > 0); + + return true; +} + +bool PersistentSystemProfile::RecordAllocator::HasMoreData() const { + if (alloc_reference_ == 0 && !NextSegment()) + return false; + + char* block = + allocator_->GetAsArray(alloc_reference_, kTypeIdSystemProfile, + base::PersistentMemoryAllocator::kSizeAny); + if (!block) + return false; + + RecordHeader header; + header.as_atomic = base::subtle::Acquire_Load( + reinterpret_cast(block + end_offset_)); + return header.as_parts.type != kUnusedSpace; +} + +bool PersistentSystemProfile::RecordAllocator::Read(RecordType* type, + std::string* record) const { + *type = kUnusedSpace; + record->clear(); + + // Access data and read records until everything has been loaded. + while (true) { + if (end_offset_ == alloc_size_) { + if (!NextSegment()) + return false; + } + if (ReadData(type, record)) + return *type != kUnusedSpace; + } +} + +bool PersistentSystemProfile::RecordAllocator::NextSegment() const { + base::PersistentMemoryAllocator::Iterator iter(allocator_, alloc_reference_); + alloc_reference_ = iter.GetNextOfType(kTypeIdSystemProfile); + alloc_size_ = allocator_->GetAllocSize(alloc_reference_); + end_offset_ = 0; + return alloc_reference_ != 0; +} + +bool PersistentSystemProfile::RecordAllocator::AddSegment(size_t min_size) { + if (NextSegment()) { + // The first record-header should have been zeroed as part of the allocation + // or by the "reset" procedure. + DCHECK_EQ(0, base::subtle::NoBarrier_Load( + allocator_->GetAsArray( + alloc_reference_, kTypeIdSystemProfile, 1))); + return true; + } + + DCHECK_EQ(0U, alloc_reference_); + DCHECK_EQ(0U, end_offset_); + + size_t size = + std::max(CalculateRecordSize(min_size), kSystemProfileAllocSize); + + uint32_t ref = allocator_->Allocate(size, kTypeIdSystemProfile); + if (!ref) + return false; // Allocator must be full. + allocator_->MakeIterable(ref); + + alloc_reference_ = ref; + alloc_size_ = allocator_->GetAllocSize(ref); + return true; +} + +bool PersistentSystemProfile::RecordAllocator::WriteData(RecordType type, + const char** data, + size_t* data_size) { + char* block = + allocator_->GetAsArray(alloc_reference_, kTypeIdSystemProfile, + base::PersistentMemoryAllocator::kSizeAny); + if (!block) + return false; // It's bad if there is no accessible block. + + const size_t max_write_size = std::min( + kMaxRecordSize, alloc_size_ - end_offset_ - sizeof(RecordHeader)); + const size_t write_size = std::min(*data_size, max_write_size); + const size_t record_size = CalculateRecordSize(write_size); + DCHECK_LT(write_size, record_size); + + // Write the data and the record header. + RecordHeader header; + header.as_atomic = 0; + header.as_parts.type = type; + header.as_parts.amount = write_size; + header.as_parts.continued = (write_size < *data_size); + size_t offset = end_offset_; + end_offset_ += record_size; + DCHECK_GE(alloc_size_, end_offset_); + if (end_offset_ < alloc_size_) { + // An empty record header has to be next before this one gets written. + base::subtle::NoBarrier_Store( + reinterpret_cast(block + end_offset_), 0); + } + memcpy(block + offset + sizeof(header), *data, write_size); + base::subtle::Release_Store( + reinterpret_cast(block + offset), + header.as_atomic); + + // Account for what was stored and prepare for follow-on records with any + // remaining data. + *data += write_size; + *data_size -= write_size; + + return true; +} + +bool PersistentSystemProfile::RecordAllocator::ReadData( + RecordType* type, + std::string* record) const { + DCHECK_GT(alloc_size_, end_offset_); + + char* block = + allocator_->GetAsArray(alloc_reference_, kTypeIdSystemProfile, + base::PersistentMemoryAllocator::kSizeAny); + if (!block) { + *type = kUnusedSpace; + return true; // No more data. + } + + // Get and validate the record header. + RecordHeader header; + header.as_atomic = base::subtle::Acquire_Load( + reinterpret_cast(block + end_offset_)); + bool continued = !!header.as_parts.continued; + if (header.as_parts.type == kUnusedSpace) { + *type = kUnusedSpace; + return true; // End of all records. + } else if (*type == kUnusedSpace) { + *type = static_cast(header.as_parts.type); + } else if (*type != header.as_parts.type) { + NOTREACHED(); // Continuation didn't match start of record. + *type = kUnusedSpace; + record->clear(); + return false; + } + size_t read_size = header.as_parts.amount; + if (end_offset_ + sizeof(header) + read_size > alloc_size_) { + NOTREACHED(); // Invalid header amount. + *type = kUnusedSpace; + return true; // Don't try again. + } + + // Append the record data to the output string. + record->append(block + end_offset_ + sizeof(header), read_size); + end_offset_ += CalculateRecordSize(read_size); + DCHECK_GE(alloc_size_, end_offset_); + + return !continued; +} + +PersistentSystemProfile::PersistentSystemProfile() {} + +PersistentSystemProfile::~PersistentSystemProfile() {} + +void PersistentSystemProfile::RegisterPersistentAllocator( + base::PersistentMemoryAllocator* memory_allocator) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // Create and store the allocator. A |min_size| of "1" ensures that a memory + // block is reserved now. + RecordAllocator allocator(memory_allocator, 1); + allocators_.push_back(std::move(allocator)); + all_have_complete_profile_ = false; +} + +void PersistentSystemProfile::DeregisterPersistentAllocator( + base::PersistentMemoryAllocator* memory_allocator) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // This would be more efficient with a std::map but it's not expected that + // allocators will get deregistered with any frequency, if at all. + base::EraseIf(allocators_, [=](RecordAllocator& records) { + return records.allocator() == memory_allocator; + }); +} + +void PersistentSystemProfile::SetSystemProfile( + const std::string& serialized_profile, + bool complete) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (allocators_.empty() || serialized_profile.empty()) + return; + + for (auto& allocator : allocators_) { + // Don't overwrite a complete profile with an incomplete one. + if (!complete && allocator.has_complete_profile()) + continue; + // A full system profile always starts fresh. Incomplete keeps existing + // records for merging. + if (complete) + allocator.Reset(); + // Write out the serialized profile. + allocator.Write(kSystemProfileProto, serialized_profile); + // Indicate if this is a complete profile. + if (complete) + allocator.set_complete_profile(); + } + + if (complete) + all_have_complete_profile_ = true; +} + +void PersistentSystemProfile::SetSystemProfile( + const SystemProfileProto& profile, + bool complete) { + // Avoid serialization if passed profile is not complete and all allocators + // already have complete ones. + if (!complete && all_have_complete_profile_) + return; + + std::string serialized_profile; + if (!profile.SerializeToString(&serialized_profile)) + return; + SetSystemProfile(serialized_profile, complete); +} + +void PersistentSystemProfile::AddFieldTrial(base::StringPiece trial, + base::StringPiece group) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!trial.empty()); + DCHECK(!group.empty()); + + base::Pickle pickler; + pickler.WriteString(trial); + pickler.WriteString(group); + + WriteToAll(kFieldTrialInfo, + base::StringPiece(static_cast(pickler.data()), + pickler.size())); +} + +// static +bool PersistentSystemProfile::HasSystemProfile( + const base::PersistentMemoryAllocator& memory_allocator) { + const RecordAllocator records(&memory_allocator); + return records.HasMoreData(); +} + +// static +bool PersistentSystemProfile::GetSystemProfile( + const base::PersistentMemoryAllocator& memory_allocator, + SystemProfileProto* system_profile) { + const RecordAllocator records(&memory_allocator); + + RecordType type; + std::string record; + do { + if (!records.Read(&type, &record)) + return false; + } while (type != kSystemProfileProto); + + if (!system_profile) + return true; + + if (!system_profile->ParseFromString(record)) + return false; + + MergeUpdateRecords(memory_allocator, system_profile); + return true; +} + +// static +void PersistentSystemProfile::MergeUpdateRecords( + const base::PersistentMemoryAllocator& memory_allocator, + SystemProfileProto* system_profile) { + const RecordAllocator records(&memory_allocator); + + RecordType type; + std::string record; + std::set known_field_trial_ids; + + // This is done separate from the code that gets the profile because it + // compartmentalizes the code and makes it possible to reuse this section + // should it be needed to merge "update" records into a new "complete" + // system profile that somehow didn't get all the updates. + while (records.Read(&type, &record)) { + switch (type) { + case kUnusedSpace: + // These should never be returned. + NOTREACHED(); + break; + + case kSystemProfileProto: + // Profile was passed in; ignore this one. + break; + + case kFieldTrialInfo: { + // Get the set of known trial IDs so duplicates don't get added. + if (known_field_trial_ids.empty()) { + for (int i = 0; i < system_profile->field_trial_size(); ++i) { + known_field_trial_ids.insert( + system_profile->field_trial(i).name_id()); + } + } + + base::Pickle pickler(record.data(), record.size()); + base::PickleIterator iter(pickler); + base::StringPiece trial; + base::StringPiece group; + if (iter.ReadStringPiece(&trial) && iter.ReadStringPiece(&group)) { + variations::ActiveGroupId field_ids = + variations::MakeActiveGroupId(trial, group); + if (!base::ContainsKey(known_field_trial_ids, field_ids.name)) { + SystemProfileProto::FieldTrial* field_trial = + system_profile->add_field_trial(); + field_trial->set_name_id(field_ids.name); + field_trial->set_group_id(field_ids.group); + known_field_trial_ids.insert(field_ids.name); + } + } + } break; + } + } +} + +void PersistentSystemProfile::WriteToAll(RecordType type, + base::StringPiece record) { + for (auto& allocator : allocators_) + allocator.Write(type, record); +} + +GlobalPersistentSystemProfile* GlobalPersistentSystemProfile::GetInstance() { + return base::Singleton< + GlobalPersistentSystemProfile, + base::LeakySingletonTraits>::get(); +} + +} // namespace metrics diff --git a/components/metrics/persistent_system_profile.h b/components/metrics/persistent_system_profile.h new file mode 100644 index 0000000000000..6c854a927539a --- /dev/null +++ b/components/metrics/persistent_system_profile.h @@ -0,0 +1,160 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_PERSISTENT_SYSTEM_PROFILE_H_ +#define BASE_METRICS_PERSISTENT_SYSTEM_PROFILE_H_ + +#include + +#include "base/strings/string_piece.h" +#include "base/threading/thread_checker.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace base { +template +struct DefaultSingletonTraits; +class PersistentMemoryAllocator; +} // namespace base + +namespace metrics { + +// Manages a copy of the system profile inside persistent memory segments. +class PersistentSystemProfile { + public: + PersistentSystemProfile(); + ~PersistentSystemProfile(); + + // This object can store records in multiple memory allocators. + void RegisterPersistentAllocator( + base::PersistentMemoryAllocator* memory_allocator); + void DeregisterPersistentAllocator( + base::PersistentMemoryAllocator* memory_allocator); + + // Stores a complete system profile. Use the version taking the serialized + // version if available to avoid multiple serialization actions. The + // |complete| flag indicates that this profile contains all known information + // and can replace whatever exists. If the flag is false, the profile will be + // stored only if there is nothing else already present. + void SetSystemProfile(const std::string& serialized_profile, bool complete); + void SetSystemProfile(const SystemProfileProto& profile, bool complete); + + // Records the existence of a field trial. + void AddFieldTrial(base::StringPiece trial, base::StringPiece group); + + // Tests if a persistent memory allocator contains an system profile. + static bool HasSystemProfile( + const base::PersistentMemoryAllocator& memory_allocator); + + // Retrieves the system profile from a persistent memory allocator. Returns + // true if a profile was successfully retrieved. If null is passed for the + // |system_profile|, only a basic check for the existence of one will be + // done. + static bool GetSystemProfile( + const base::PersistentMemoryAllocator& memory_allocator, + SystemProfileProto* system_profile); + + private: + friend class PersistentSystemProfileTest; + + // Defines record types that can be stored inside our local Allocators. + enum RecordType : uint8_t { + kUnusedSpace = 0, // The default value for empty memory. + kSystemProfileProto, + kFieldTrialInfo, + }; + + // A class for managing record allocations inside a persistent memory segment. + class RecordAllocator { + public: + // Construct an allocator for writing. + RecordAllocator(base::PersistentMemoryAllocator* memory_allocator, + size_t min_size); + + // Construct an allocator for reading. + RecordAllocator(const base::PersistentMemoryAllocator* memory_allocator); + + // These methods manage writing records to the allocator. Do not mix these + // with "read" calls; it's one or the other. + void Reset(); + bool Write(RecordType type, base::StringPiece record); + + // Read a record from the allocator. Do not mix this with "write" calls; + // it's one or the other. + bool HasMoreData() const; + bool Read(RecordType* type, std::string* record) const; + + base::PersistentMemoryAllocator* allocator() { return allocator_; } + + bool has_complete_profile() { return has_complete_profile_; } + void set_complete_profile() { has_complete_profile_ = true; } + + private: + // Advance to the next record segment in the memory allocator. + bool NextSegment() const; + + // Advance to the next record segment, creating a new one if necessary with + // sufficent |min_size| space. + bool AddSegment(size_t min_size); + + // Writes data to the current position, updating the passed values past + // the amount written. Returns false in case of an error. + bool WriteData(RecordType type, const char** data, size_t* data_size); + + // Reads data from the current position, updating the passed string + // in-place. |type| must be initialized to kUnusedSpace and |record| must + // be an empty string before the first call but unchanged thereafter. + // Returns true when record is complete. + bool ReadData(RecordType* type, std::string* record) const; + + // This never changes but can't be "const" because vector calls operator=(). + base::PersistentMemoryAllocator* allocator_; // Storage location. + + // Indicates if a complete profile has been stored. + bool has_complete_profile_; + + // These change even though the underlying data may be "const". + mutable uint32_t alloc_reference_; // Last storage block. + mutable size_t alloc_size_; // Size of the block. + mutable size_t end_offset_; // End of data in block. + + // Copy and assign are allowed for easy use with STL containers. + }; + + // Write a record to all registered allocators. + void WriteToAll(RecordType type, base::StringPiece record); + + // Merges all "update" records into a system profile. + static void MergeUpdateRecords( + const base::PersistentMemoryAllocator& memory_allocator, + SystemProfileProto* system_profile); + + // The list of registered persistent allocators, described by RecordAllocator + // instances. + std::vector allocators_; + + // Indicates if a complete profile has been stored to all allocators. + bool all_have_complete_profile_ = false; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(PersistentSystemProfile); +}; + +// A singleton instance of the above. +class GlobalPersistentSystemProfile : public PersistentSystemProfile { + public: + static GlobalPersistentSystemProfile* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits; + + GlobalPersistentSystemProfile() {} + ~GlobalPersistentSystemProfile() {} + + DISALLOW_COPY_AND_ASSIGN(GlobalPersistentSystemProfile); +}; + +} // namespace metrics + +#endif // BASE_METRICS_PERSISTENT_SYSTEM_PROFILE_H_ diff --git a/components/metrics/persistent_system_profile_unittest.cc b/components/metrics/persistent_system_profile_unittest.cc new file mode 100644 index 0000000000000..b3a7ec6d1acd8 --- /dev/null +++ b/components/metrics/persistent_system_profile_unittest.cc @@ -0,0 +1,172 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/persistent_system_profile.h" + +#include + +#include "base/logging.h" +#include "base/metrics/persistent_memory_allocator.h" +#include "base/rand_util.h" +#include "components/variations/hashing.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +class PersistentSystemProfileTest : public testing::Test { + public: + const int32_t kAllocatorMemorySize = 1 << 20; // 1 MiB + + PersistentSystemProfileTest() {} + ~PersistentSystemProfileTest() override {} + + void SetUp() override { + memory_allocator_ = std::make_unique( + kAllocatorMemorySize, 0, ""); + records_ = std::make_unique( + memory_allocator_.get()); + persistent_profile_.RegisterPersistentAllocator(memory_allocator_.get()); + } + + void TearDown() override { + persistent_profile_.DeregisterPersistentAllocator(memory_allocator_.get()); + records_.reset(); + memory_allocator_.reset(); + } + + void WriteRecord(uint8_t type, const std::string& record) { + persistent_profile_.allocators_[0].Write( + static_cast(type), record); + } + + bool ReadRecord(uint8_t* type, std::string* record) { + PersistentSystemProfile::RecordType rec_type; + + bool success = records_->Read(&rec_type, record); + *type = rec_type; // Convert to uint8_t for testing. + return success; + } + + base::PersistentMemoryAllocator* memory_allocator() { + return memory_allocator_.get(); + } + + PersistentSystemProfile* persistent_profile() { return &persistent_profile_; } + + private: + PersistentSystemProfile persistent_profile_; + std::unique_ptr memory_allocator_; + std::unique_ptr records_; + + DISALLOW_COPY_AND_ASSIGN(PersistentSystemProfileTest); +}; + +TEST_F(PersistentSystemProfileTest, Create) { + uint32_t type; + base::PersistentMemoryAllocator::Iterator iter(memory_allocator()); + base::PersistentMemoryAllocator::Reference ref = iter.GetNext(&type); + DCHECK(ref); + DCHECK_NE(0U, type); +} + +TEST_F(PersistentSystemProfileTest, RecordSplitting) { + const size_t kRecordSize = 100 << 10; // 100 KiB + std::vector buffer; + buffer.resize(kRecordSize); + base::RandBytes(&buffer[0], kRecordSize); + + WriteRecord(42, std::string(&buffer[0], kRecordSize)); + + uint8_t type; + std::string record; + ASSERT_TRUE(ReadRecord(&type, &record)); + EXPECT_EQ(42U, type); + ASSERT_EQ(kRecordSize, record.size()); + for (size_t i = 0; i < kRecordSize; ++i) + EXPECT_EQ(buffer[i], record[i]); +} + +TEST_F(PersistentSystemProfileTest, ProfileStorage) { + SystemProfileProto proto1; + SystemProfileProto::FieldTrial* trial = proto1.add_field_trial(); + trial->set_name_id(123); + trial->set_group_id(456); + + persistent_profile()->SetSystemProfile(proto1, false); + + SystemProfileProto proto2; + ASSERT_TRUE(PersistentSystemProfile::HasSystemProfile(*memory_allocator())); + ASSERT_TRUE( + PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2)); + ASSERT_EQ(1, proto2.field_trial_size()); + EXPECT_EQ(123U, proto2.field_trial(0).name_id()); + EXPECT_EQ(456U, proto2.field_trial(0).group_id()); + + // Check that the profile can be overwritten. + + trial = proto1.add_field_trial(); + trial->set_name_id(78); + trial->set_group_id(90); + + persistent_profile()->SetSystemProfile(proto1, true); + + ASSERT_TRUE( + PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2)); + ASSERT_EQ(2, proto2.field_trial_size()); + EXPECT_EQ(123U, proto2.field_trial(0).name_id()); + EXPECT_EQ(456U, proto2.field_trial(0).group_id()); + EXPECT_EQ(78U, proto2.field_trial(1).name_id()); + EXPECT_EQ(90U, proto2.field_trial(1).group_id()); + + // Check that the profile won't be overwritten by a new non-complete profile. + + trial = proto1.add_field_trial(); + trial->set_name_id(0xC0DE); + trial->set_group_id(0xFEED); + + persistent_profile()->SetSystemProfile(proto1, false); + + ASSERT_TRUE( + PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2)); + ASSERT_EQ(2, proto2.field_trial_size()); + EXPECT_EQ(123U, proto2.field_trial(0).name_id()); + EXPECT_EQ(456U, proto2.field_trial(0).group_id()); + EXPECT_EQ(78U, proto2.field_trial(1).name_id()); + EXPECT_EQ(90U, proto2.field_trial(1).group_id()); +} + +TEST_F(PersistentSystemProfileTest, ProfileExtensions) { + persistent_profile()->AddFieldTrial("sna", "foo"); + + SystemProfileProto fetched; + ASSERT_FALSE( + PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &fetched)); + + SystemProfileProto proto; + SystemProfileProto::FieldTrial* trial = proto.add_field_trial(); + trial->set_name_id(123); + trial->set_group_id(456); + + persistent_profile()->SetSystemProfile(proto, false); + ASSERT_TRUE( + PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &fetched)); + ASSERT_EQ(2, fetched.field_trial_size()); + EXPECT_EQ(123U, fetched.field_trial(0).name_id()); + EXPECT_EQ(456U, fetched.field_trial(0).group_id()); + EXPECT_EQ(variations::HashName("sna"), fetched.field_trial(1).name_id()); + EXPECT_EQ(variations::HashName("foo"), fetched.field_trial(1).group_id()); + + persistent_profile()->AddFieldTrial("foo", "bar"); + ASSERT_TRUE( + PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &fetched)); + ASSERT_EQ(3, fetched.field_trial_size()); + EXPECT_EQ(123U, fetched.field_trial(0).name_id()); + EXPECT_EQ(456U, fetched.field_trial(0).group_id()); + EXPECT_EQ(variations::HashName("sna"), fetched.field_trial(1).name_id()); + EXPECT_EQ(variations::HashName("foo"), fetched.field_trial(1).group_id()); + EXPECT_EQ(variations::HashName("foo"), fetched.field_trial(2).name_id()); + EXPECT_EQ(variations::HashName("bar"), fetched.field_trial(2).group_id()); +} + +} // namespace metrics diff --git a/components/metrics/public/cpp/BUILD.gn b/components/metrics/public/cpp/BUILD.gn new file mode 100644 index 0000000000000..b9c2af5c2fef9 --- /dev/null +++ b/components/metrics/public/cpp/BUILD.gn @@ -0,0 +1,20 @@ +# 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +source_set("call_stack_unit_tests") { + testonly = true + sources = [ + "call_stack_profile_struct_traits_unittest.cc", + ] + + deps = [ + "//base", + "//components/metrics/public/interfaces:call_stack_mojo_test_bindings", + "//mojo/public/cpp/bindings", + "//testing/gtest", + "//third_party/metrics_proto", + ] +} diff --git a/components/metrics/public/cpp/OWNERS b/components/metrics/public/cpp/OWNERS new file mode 100644 index 0000000000000..2c44a463856dd --- /dev/null +++ b/components/metrics/public/cpp/OWNERS @@ -0,0 +1,6 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS +per-file *_struct_traits*.*=set noparent +per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS +per-file *.typemap=set noparent +per-file *.typemap=file://ipc/SECURITY_OWNERS diff --git a/components/metrics/public/cpp/call_stack_profile.typemap b/components/metrics/public/cpp/call_stack_profile.typemap new file mode 100644 index 0000000000000..e7fa49f016b2f --- /dev/null +++ b/components/metrics/public/cpp/call_stack_profile.typemap @@ -0,0 +1,14 @@ +# 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. + +mojom = + "//components/metrics/public/interfaces/call_stack_profile_collector.mojom" +public_headers = [ "//third_party/metrics_proto/sampled_profile.pb.h" ] +traits_headers = + [ "//components/metrics/public/cpp/call_stack_profile_struct_traits.h" ] +deps = [ + "//third_party/metrics_proto", +] +type_mappings = + [ "metrics.mojom.SampledProfile=metrics::SampledProfile[move_only]" ] diff --git a/components/metrics/public/cpp/call_stack_profile_struct_traits.h b/components/metrics/public/cpp/call_stack_profile_struct_traits.h new file mode 100644 index 0000000000000..bdcc6172d3581 --- /dev/null +++ b/components/metrics/public/cpp/call_stack_profile_struct_traits.h @@ -0,0 +1,48 @@ +// 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. + +// Defines StructTraits specializations for translating between mojo types and +// metrics:: types, with data validity checks. + +#ifndef COMPONENTS_METRICS_PUBLIC_CPP_CALL_STACK_PROFILE_STRUCT_TRAITS_H_ +#define COMPONENTS_METRICS_PUBLIC_CPP_CALL_STACK_PROFILE_STRUCT_TRAITS_H_ + +#include + +#include "base/strings/string_piece.h" +#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace mojo { + +template <> +struct StructTraits { + static std::string contents(const metrics::SampledProfile& profile) { + std::string output; + profile.SerializeToString(&output); + return output; + } + + static bool Read(metrics::mojom::SampledProfileDataView data, + metrics::SampledProfile* out) { + base::StringPiece contents; + if (!data.ReadContents(&contents)) + return false; + + if (!out->ParseFromArray(contents.data(), contents.size())) + return false; + + // This is purely a sanity check to minimize bad data uploaded, and not + // required for security reasons. + if (!out->unknown_fields().empty()) + return false; + + return true; + } +}; + +} // namespace mojo + +#endif // COMPONENTS_METRICS_PUBLIC_CPP_CALL_STACK_PROFILE_STRUCT_TRAITS_H_ diff --git a/components/metrics/public/cpp/call_stack_profile_struct_traits_unittest.cc b/components/metrics/public/cpp/call_stack_profile_struct_traits_unittest.cc new file mode 100644 index 0000000000000..236d6196fe4af --- /dev/null +++ b/components/metrics/public/cpp/call_stack_profile_struct_traits_unittest.cc @@ -0,0 +1,101 @@ +// 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 + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "components/metrics/public/interfaces/call_stack_profile_collector_test.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/sampled_profile.pb.h" + +namespace metrics { + +class CallStackProfileCollectorTestImpl + : public mojom::CallStackProfileCollectorTest { + public: + explicit CallStackProfileCollectorTestImpl( + mojo::InterfaceRequest request) + : binding_(this, std::move(request)) { + } + + // CallStackProfileCollectorTest: + void BounceSampledProfile(SampledProfile in, + BounceSampledProfileCallback callback) override { + std::move(callback).Run(in); + } + + private: + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileCollectorTestImpl); +}; + +class CallStackProfileStructTraitsTest : public testing::Test { + public: + CallStackProfileStructTraitsTest() : impl_(MakeRequest(&proxy_)) {} + + protected: + base::MessageLoop message_loop_; + mojom::CallStackProfileCollectorTestPtr proxy_; + CallStackProfileCollectorTestImpl impl_; + + DISALLOW_COPY_AND_ASSIGN(CallStackProfileStructTraitsTest); +}; + +// Checks serialization/deserialization of SampledProfile. +TEST_F(CallStackProfileStructTraitsTest, SampledProfile) { + // Construct a SampledProfile protocol buffer message. + SampledProfile input_proto; + + CallStackProfile* proto_profile = input_proto.mutable_call_stack_profile(); + + CallStackProfile::Sample* proto_sample = + proto_profile->add_deprecated_sample(); + proto_sample->set_count(1); + CallStackProfile::Location* location = proto_sample->add_frame(); + location->set_address(0x10ULL); + location->set_module_id_index(0); + + CallStackProfile::ModuleIdentifier* module_id = + proto_profile->add_module_id(); + module_id->set_build_id("a"); + module_id->set_name_md5_prefix(111U); + + proto_profile->set_profile_duration_ms(1000); + proto_profile->set_sampling_period_ms(2000); + + // Send the message round trip, and verify those values. + SampledProfile output_proto; + EXPECT_TRUE( + proxy_->BounceSampledProfile(std::move(input_proto), &output_proto)); + + const CallStackProfile& out_profile = output_proto.call_stack_profile(); + + ASSERT_EQ(1, out_profile.deprecated_sample_size()); + ASSERT_EQ(1, out_profile.deprecated_sample(0).frame_size()); + + ASSERT_TRUE(out_profile.deprecated_sample(0).frame(0).has_address()); + EXPECT_EQ(0x10ULL, out_profile.deprecated_sample(0).frame(0).address()); + + ASSERT_TRUE(out_profile.deprecated_sample(0).frame(0).has_module_id_index()); + EXPECT_EQ(0, out_profile.deprecated_sample(0).frame(0).module_id_index()); + + ASSERT_EQ(1, out_profile.module_id().size()); + + ASSERT_TRUE(out_profile.module_id(0).has_build_id()); + ASSERT_EQ("a", out_profile.module_id(0).build_id()); + + ASSERT_TRUE(out_profile.module_id(0).has_name_md5_prefix()); + ASSERT_EQ(111U, out_profile.module_id(0).name_md5_prefix()); + + ASSERT_TRUE(out_profile.has_profile_duration_ms()); + EXPECT_EQ(1000, out_profile.profile_duration_ms()); + + ASSERT_TRUE(out_profile.has_sampling_period_ms()); + EXPECT_EQ(2000, out_profile.sampling_period_ms()); +} + +} // namespace metrics diff --git a/components/metrics/public/cpp/call_stack_profile_unittest.typemap b/components/metrics/public/cpp/call_stack_profile_unittest.typemap new file mode 100644 index 0000000000000..5a8cd9929b156 --- /dev/null +++ b/components/metrics/public/cpp/call_stack_profile_unittest.typemap @@ -0,0 +1,18 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This file is necessary because without it compiling +# call_stack_profile_struct_traits_unittest produces error below: +# "gen\third_party/metrics_proto/sampled_profile.pb.h(9,10): fatal error: +# 'google/protobuf/stubs/common.h' file not found". + +mojom = "//components/metrics/public/interfaces/call_stack_profile_collector_test.mojom" +public_headers = [ "//third_party/metrics_proto/sampled_profile.pb.h" ] +traits_headers = + [ "//components/metrics/public/cpp/call_stack_profile_struct_traits.h" ] +deps = [ + "//third_party/metrics_proto", +] +type_mappings = + [ "metrics.mojom.SampledProfile=metrics::SampledProfile[move_only]" ] diff --git a/components/metrics/public/cpp/typemaps.gni b/components/metrics/public/cpp/typemaps.gni new file mode 100644 index 0000000000000..9e4e1db944cf2 --- /dev/null +++ b/components/metrics/public/cpp/typemaps.gni @@ -0,0 +1,8 @@ +# 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. + +typemaps = [ + "//components/metrics/public/cpp/call_stack_profile.typemap", + "//components/metrics/public/cpp/call_stack_profile_unittest.typemap", +] diff --git a/components/metrics/public/interfaces/BUILD.gn b/components/metrics/public/interfaces/BUILD.gn new file mode 100644 index 0000000000000..2a84979353f39 --- /dev/null +++ b/components/metrics/public/interfaces/BUILD.gn @@ -0,0 +1,31 @@ +# 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("call_stack_mojo_bindings") { + sources = [ + "call_stack_profile_collector.mojom", + ] + + deps = [ + "//mojo/public/mojom/base", + ] +} + +mojom("call_stack_mojo_test_bindings") { + sources = [ + "call_stack_profile_collector_test.mojom", + ] + + deps = [ + ":call_stack_mojo_bindings", + ] +} + +mojom("single_sample_metrics_mojo_bindings") { + sources = [ + "single_sample_metrics.mojom", + ] +} diff --git a/components/metrics/public/interfaces/OWNERS b/components/metrics/public/interfaces/OWNERS new file mode 100644 index 0000000000000..154435234eab2 --- /dev/null +++ b/components/metrics/public/interfaces/OWNERS @@ -0,0 +1,4 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS +per-file *_struct_traits*.*=set noparent +per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS diff --git a/components/metrics/public/interfaces/call_stack_profile_collector.mojom b/components/metrics/public/interfaces/call_stack_profile_collector.mojom new file mode 100644 index 0000000000000..51fcf816badea --- /dev/null +++ b/components/metrics/public/interfaces/call_stack_profile_collector.mojom @@ -0,0 +1,21 @@ +// 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. + +module metrics.mojom; + +import "mojo/public/mojom/base/time.mojom"; + +// |contents| is a serialized protobuf from +// src/third_party/metrics_proto/sampled_profile.proto. +// +// We pass this state via serialized protobuf because that is the ultimate +// metrics upload format. +struct SampledProfile { + string contents; +}; + +interface CallStackProfileCollector { + Collect(mojo_base.mojom.TimeTicks start_timestamp, + SampledProfile profile); +}; diff --git a/components/metrics/public/interfaces/call_stack_profile_collector_test.mojom b/components/metrics/public/interfaces/call_stack_profile_collector_test.mojom new file mode 100644 index 0000000000000..c7725c679399c --- /dev/null +++ b/components/metrics/public/interfaces/call_stack_profile_collector_test.mojom @@ -0,0 +1,12 @@ +// 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. + +module metrics.mojom; + +import "components/metrics/public/interfaces/call_stack_profile_collector.mojom"; + +interface CallStackProfileCollectorTest { + [Sync] + BounceSampledProfile(SampledProfile in) => (SampledProfile out); +}; diff --git a/components/metrics/public/interfaces/single_sample_metrics.mojom b/components/metrics/public/interfaces/single_sample_metrics.mojom new file mode 100644 index 0000000000000..788150c01888b --- /dev/null +++ b/components/metrics/public/interfaces/single_sample_metrics.mojom @@ -0,0 +1,24 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module metrics.mojom; + +// See components/metrics/single_sample_metrics_factory_impl.h for details. +interface SingleSampleMetricsProvider { + // Returns a SingleSampleMetric. + // + // A single sample metric only reports its sample once at destruction time. + // The sample may be changed prior to destruction using the SetSample() method + // as many times as desired. + // + // See base/metrics/histograms.h for parameter definitions. |request| is the + // returned histogram. + AcquireSingleSampleMetric(string histogram_name, int32 min, int32 max, + uint32 bucket_count, int32 flags, + SingleSampleMetric& request); +}; + +interface SingleSampleMetric { + SetSample(int32 sample); +}; diff --git a/components/metrics/reporting_service.cc b/components/metrics/reporting_service.cc new file mode 100644 index 0000000000000..638280402016b --- /dev/null +++ b/components/metrics/reporting_service.cc @@ -0,0 +1,214 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ReportingService handles uploading serialized logs to a server. + +#include "components/metrics/reporting_service.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "components/metrics/data_use_tracker.h" +#include "components/metrics/log_store.h" +#include "components/metrics/metrics_log_uploader.h" +#include "components/metrics/metrics_service_client.h" +#include "components/metrics/metrics_upload_scheduler.h" + +namespace metrics { + +// static +void ReportingService::RegisterPrefs(PrefRegistrySimple* registry) { + DataUseTracker::RegisterPrefs(registry); +} + +ReportingService::ReportingService(MetricsServiceClient* client, + PrefService* local_state, + size_t max_retransmit_size) + : client_(client), + max_retransmit_size_(max_retransmit_size), + reporting_active_(false), + log_upload_in_progress_(false), + data_use_tracker_(DataUseTracker::Create(local_state)), + self_ptr_factory_(this) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(client_); + DCHECK(local_state); +} + +ReportingService::~ReportingService() { + DisableReporting(); +} + +void ReportingService::Initialize() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!upload_scheduler_); + log_store()->LoadPersistedUnsentLogs(); + base::Closure send_next_log_callback = base::Bind( + &ReportingService::SendNextLog, self_ptr_factory_.GetWeakPtr()); + upload_scheduler_.reset(new MetricsUploadScheduler(send_next_log_callback)); +} + +void ReportingService::Start() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (reporting_active_) + upload_scheduler_->Start(); +} + +void ReportingService::Stop() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (upload_scheduler_) + upload_scheduler_->Stop(); +} + +void ReportingService::EnableReporting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (reporting_active_) + return; + reporting_active_ = true; + Start(); +} + +void ReportingService::DisableReporting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + reporting_active_ = false; + Stop(); +} + +bool ReportingService::reporting_active() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return reporting_active_; +} + +void ReportingService::UpdateMetricsUsagePrefs(const std::string& service_name, + int message_size, + bool is_cellular) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (data_use_tracker_) { + data_use_tracker_->UpdateMetricsUsagePrefs(service_name, message_size, + is_cellular); + } +} + +//------------------------------------------------------------------------------ +// private methods +//------------------------------------------------------------------------------ + +void ReportingService::SendNextLog() { + DVLOG(1) << "SendNextLog"; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!last_upload_finish_time_.is_null()) { + LogActualUploadInterval(base::TimeTicks::Now() - last_upload_finish_time_); + last_upload_finish_time_ = base::TimeTicks(); + } + if (!reporting_active()) { + upload_scheduler_->StopAndUploadCancelled(); + return; + } + if (!log_store()->has_unsent_logs()) { + // Should only get here if serializing the log failed somehow. + upload_scheduler_->Stop(); + // Reset backoff interval + upload_scheduler_->UploadFinished(true); + return; + } + if (!log_store()->has_staged_log()) { + reporting_info_.set_attempt_count(0); + log_store()->StageNextLog(); + } + + // Proceed to stage the log for upload if log size satisfies cellular log + // upload constrains. + bool upload_canceled = false; + bool is_cellular_logic = client_->IsUMACellularUploadLogicEnabled(); + if (is_cellular_logic && data_use_tracker_ && + !data_use_tracker_->ShouldUploadLogOnCellular( + log_store()->staged_log_hash().size())) { + upload_scheduler_->UploadOverDataUsageCap(); + upload_canceled = true; + } else { + SendStagedLog(); + } + if (is_cellular_logic) { + LogCellularConstraint(upload_canceled); + } +} + +void ReportingService::SendStagedLog() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(log_store()->has_staged_log()); + if (!log_store()->has_staged_log()) + return; + + DCHECK(!log_upload_in_progress_); + log_upload_in_progress_ = true; + + if (!log_uploader_) { + log_uploader_ = client_->CreateUploader( + GetUploadUrl(), GetInsecureUploadUrl(), upload_mime_type(), + service_type(), + base::Bind(&ReportingService::OnLogUploadComplete, + self_ptr_factory_.GetWeakPtr())); + } + + reporting_info_.set_attempt_count(reporting_info_.attempt_count() + 1); + + const std::string hash = + base::HexEncode(log_store()->staged_log_hash().data(), + log_store()->staged_log_hash().size()); + log_uploader_->UploadLog(log_store()->staged_log(), hash, reporting_info_); +} + +void ReportingService::OnLogUploadComplete(int response_code, + int error_code, + bool was_https) { + DVLOG(1) << "OnLogUploadComplete:" << response_code; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(log_upload_in_progress_); + log_upload_in_progress_ = false; + + reporting_info_.set_last_response_code(response_code); + reporting_info_.set_last_error_code(error_code); + reporting_info_.set_last_attempt_was_https(was_https); + + // Log a histogram to track response success vs. failure rates. + LogResponseOrErrorCode(response_code, error_code, was_https); + + bool upload_succeeded = response_code == 200; + + // Staged log could have been removed already (such as by Purge() in some + // implementations), otherwise we may remove it here. + if (log_store()->has_staged_log()) { + // Provide boolean for error recovery (allow us to ignore response_code). + bool discard_log = false; + const size_t log_size = log_store()->staged_log().length(); + if (upload_succeeded) { + LogSuccess(log_size); + } else if (log_size > max_retransmit_size_) { + LogLargeRejection(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_store()->DiscardStagedLog(); + // Store the updated list to disk now that the removed log is uploaded. + log_store()->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; + + if (!log_store()->has_unsent_logs()) { + DVLOG(1) << "Stopping upload_scheduler_."; + upload_scheduler_->Stop(); + } + upload_scheduler_->UploadFinished(server_is_healthy); +} + +} // namespace metrics diff --git a/components/metrics/reporting_service.h b/components/metrics/reporting_service.h new file mode 100644 index 0000000000000..d724430da6c2d --- /dev/null +++ b/components/metrics/reporting_service.h @@ -0,0 +1,153 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines a service that sends metrics logs to a server. + +#ifndef COMPONENTS_METRICS_REPORTING_SERVICE_H_ +#define COMPONENTS_METRICS_REPORTING_SERVICE_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/data_use_tracker.h" +#include "components/metrics/metrics_log_uploader.h" +#include "third_party/metrics_proto/reporting_info.pb.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class LogStore; +class MetricsUploadScheduler; +class MetricsServiceClient; + +// ReportingService is an abstract class which uploads serialized logs from a +// LogStore to a remote server. A concrete implementation of this class must +// provide the specific LogStore and parameters for the MetricsLogUploader, and +// can also implement hooks to record histograms based on certain events that +// occur while attempting to upload logs. +class ReportingService { + public: + // Creates a ReportingService with the given |client|, |local_state|, and + // |max_retransmit_size|. Does not take ownership of the parameters; instead + // it stores a weak pointer to each. Caller should ensure that the parameters + // are valid for the lifetime of this class. + ReportingService(MetricsServiceClient* client, + PrefService* local_state, + size_t max_retransmit_size); + virtual ~ReportingService(); + + // Completes setup tasks that can't be done at construction time. + // Loads persisted logs and creates the MetricsUploadScheduler. + void Initialize(); + + // Starts the metrics reporting system. + // Should be called when metrics enabled or new logs are created. + // When the service is already running, this is a safe no-op. + void Start(); + + // 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(); + + // True iff reporting is currently enabled. + bool reporting_active() const; + + // Updates data usage tracking prefs with the specified values. + void UpdateMetricsUsagePrefs(const std::string& service_name, + int message_size, + bool is_cellular); + + // Registers local state prefs used by this class. This should only be called + // once. + static void RegisterPrefs(PrefRegistrySimple* registry); + + protected: + MetricsServiceClient* client() const { return client_; }; + + private: + // Retrieves the log store backing this service. + virtual LogStore* log_store() = 0; + + // Getters for MetricsLogUploader parameters. + virtual std::string GetUploadUrl() const = 0; + virtual std::string GetInsecureUploadUrl() const = 0; + virtual base::StringPiece upload_mime_type() const = 0; + virtual MetricsLogUploader::MetricServiceType service_type() const = 0; + + // Methods for recording data to histograms. + virtual void LogActualUploadInterval(base::TimeDelta interval) {} + virtual void LogCellularConstraint(bool upload_canceled) {} + virtual void LogResponseOrErrorCode(int response_code, + int error_code, + bool was_https) {} + virtual void LogSuccess(size_t log_size) {} + virtual void LogLargeRejection(size_t log_size) {} + + // If recording is enabled, begins uploading the next completed log from + // the log manager, staging it if necessary. + void SendNextLog(); + + // 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, int error_code, bool was_https); + + // Used to interact with the embedder. Weak pointer; must outlive |this| + // instance. + MetricsServiceClient* const client_; + + // Largest log size to attempt to retransmit. + size_t max_retransmit_size_; + + // Indicate whether recording and reporting are currently happening. + // These should not be set directly, but by calling SetRecording and + // SetReporting. + bool reporting_active_; + + // Instance of the helper class for uploading logs. + std::unique_ptr log_uploader_; + + // Whether there is a current log upload in progress. + bool log_upload_in_progress_; + + // The scheduler for determining when uploads should happen. + std::unique_ptr upload_scheduler_; + + // Pointer used for obtaining data use pref updater callback on above layers. + std::unique_ptr data_use_tracker_; + + // 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_; + + // Info on current reporting state to send along with reports. + ReportingInfo reporting_info_; + + SEQUENCE_CHECKER(sequence_checker_); + + // Weak pointers factory used to post task on different threads. All weak + // pointers managed by this factory have the same lifetime as + // ReportingService. + base::WeakPtrFactory self_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ReportingService); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_REPORTING_SERVICE_H_ diff --git a/components/metrics/reporting_service_unittest.cc b/components/metrics/reporting_service_unittest.cc new file mode 100644 index 0000000000000..fb838cb27dfb7 --- /dev/null +++ b/components/metrics/reporting_service_unittest.cc @@ -0,0 +1,142 @@ +// 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/reporting_service.h" + +#include + +#include +#include +#include + +#include "base/bind.h" +#include "base/macros.h" +#include "base/sha1.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/metrics/log_store.h" +#include "components/metrics/test_metrics_service_client.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 kTestUploadUrl[] = "test_url"; +const char kTestMimeType[] = "test_mime_type"; + +class TestLogStore : public LogStore { + public: + TestLogStore() {} + ~TestLogStore() {} + + void AddLog(const std::string& log) { logs_.push_back(log); } + + // LogStore: + bool has_unsent_logs() const override { return !logs_.empty(); } + bool has_staged_log() const override { return !staged_log_hash_.empty(); } + const std::string& staged_log() const override { return logs_.front(); } + const std::string& staged_log_hash() const override { + return staged_log_hash_; + } + void StageNextLog() override { + if (has_unsent_logs()) + staged_log_hash_ = base::SHA1HashString(logs_.front()); + } + void DiscardStagedLog() override { + if (!has_staged_log()) + return; + logs_.pop_front(); + staged_log_hash_.clear(); + } + void PersistUnsentLogs() const override {} + void LoadPersistedUnsentLogs() override {} + + private: + std::string staged_log_hash_; + std::deque logs_; +}; + +class TestReportingService : public ReportingService { + public: + TestReportingService(MetricsServiceClient* client, PrefService* local_state) + : ReportingService(client, local_state, 100) { + Initialize(); + } + ~TestReportingService() override {} + + void AddLog(const std::string& log) { log_store_.AddLog(log); } + + private: + // ReportingService: + LogStore* log_store() override { return &log_store_; } + std::string GetUploadUrl() const override { return kTestUploadUrl; } + std::string GetInsecureUploadUrl() const override { return kTestUploadUrl; } + base::StringPiece upload_mime_type() const override { return kTestMimeType; } + MetricsLogUploader::MetricServiceType service_type() const override { + return MetricsLogUploader::MetricServiceType::UMA; + } + + TestLogStore log_store_; + + DISALLOW_COPY_AND_ASSIGN(TestReportingService); +}; + +class ReportingServiceTest : public testing::Test { + public: + ReportingServiceTest() + : task_runner_(new base::TestSimpleTaskRunner), + task_runner_handle_(task_runner_) { + ReportingService::RegisterPrefs(testing_local_state_.registry()); + } + + ~ReportingServiceTest() override {} + + PrefService* GetLocalState() { return &testing_local_state_; } + + protected: + scoped_refptr task_runner_; + base::ThreadTaskRunnerHandle task_runner_handle_; + TestMetricsServiceClient client_; + + private: + TestingPrefServiceSimple testing_local_state_; + + DISALLOW_COPY_AND_ASSIGN(ReportingServiceTest); +}; + +} // namespace + +TEST_F(ReportingServiceTest, BasicTest) { + TestReportingService service(&client_, GetLocalState()); + service.AddLog("log1"); + service.AddLog("log2"); + + service.EnableReporting(); + task_runner_->RunPendingTasks(); + client_.uploader()->is_uploading(); + EXPECT_TRUE(client_.uploader()->is_uploading()); + EXPECT_EQ(1, client_.uploader()->reporting_info().attempt_count()); + EXPECT_FALSE(client_.uploader()->reporting_info().has_last_response_code()); + + client_.uploader()->CompleteUpload(404); + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client_.uploader()->is_uploading()); + EXPECT_EQ(2, client_.uploader()->reporting_info().attempt_count()); + EXPECT_EQ(404, client_.uploader()->reporting_info().last_response_code()); + + client_.uploader()->CompleteUpload(200); + task_runner_->RunPendingTasks(); + EXPECT_TRUE(client_.uploader()->is_uploading()); + EXPECT_EQ(1, client_.uploader()->reporting_info().attempt_count()); + EXPECT_EQ(200, client_.uploader()->reporting_info().last_response_code()); + + client_.uploader()->CompleteUpload(200); + EXPECT_EQ(0U, task_runner_->NumPendingTasks()); + EXPECT_FALSE(client_.uploader()->is_uploading()); +} + +} // namespace metrics diff --git a/components/metrics/serialization/metric_sample.cc b/components/metrics/serialization/metric_sample.cc new file mode 100644 index 0000000000000..24f3757af0751 --- /dev/null +++ b/components/metrics/serialization/metric_sample.cc @@ -0,0 +1,196 @@ +// 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 +#include + +#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'); + } + if (type_ == SPARSE_HISTOGRAM) { + return base::StringPrintf("sparsehistogram%c%s %d%c", + '\0', + name().c_str(), + sample_, + '\0'); + } + if (type_ == LINEAR_HISTOGRAM) { + return base::StringPrintf("linearhistogram%c%s %d %d%c", + '\0', + name().c_str(), + sample_, + max_, + '\0'); + } + if (type_ == HISTOGRAM) { + return base::StringPrintf("histogram%c%s %d %d %d %d%c", + '\0', + name().c_str(), + sample_, + min_, + max_, + bucket_count_, + '\0'); + } + // 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 +std::unique_ptr MetricSample::CrashSample( + const std::string& crash_name) { + return std::unique_ptr( + new MetricSample(CRASH, crash_name, 0, 0, 0, 0)); +} + +// static +std::unique_ptr MetricSample::HistogramSample( + const std::string& histogram_name, + int sample, + int min, + int max, + int bucket_count) { + return std::unique_ptr(new MetricSample( + HISTOGRAM, histogram_name, sample, min, max, bucket_count)); +} + +// static +std::unique_ptr MetricSample::ParseHistogram( + const std::string& serialized_histogram) { + std::vector parts = base::SplitStringPiece( + serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (parts.size() != 5) + return std::unique_ptr(); + 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 std::unique_ptr(); + } + + return HistogramSample(parts[0].as_string(), sample, min, max, bucket_count); +} + +// static +std::unique_ptr MetricSample::SparseHistogramSample( + const std::string& histogram_name, + int sample) { + return std::unique_ptr( + new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0)); +} + +// static +std::unique_ptr MetricSample::ParseSparseHistogram( + const std::string& serialized_histogram) { + std::vector parts = base::SplitStringPiece( + serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (parts.size() != 2) + return std::unique_ptr(); + int sample; + if (parts[0].empty() || !base::StringToInt(parts[1], &sample)) + return std::unique_ptr(); + + return SparseHistogramSample(parts[0].as_string(), sample); +} + +// static +std::unique_ptr MetricSample::LinearHistogramSample( + const std::string& histogram_name, + int sample, + int max) { + return std::unique_ptr( + new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0)); +} + +// static +std::unique_ptr MetricSample::ParseLinearHistogram( + const std::string& serialized_histogram) { + std::vector parts = base::SplitStringPiece( + serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + int sample, max; + if (parts.size() != 3) + return std::unique_ptr(); + if (parts[0].empty() || !base::StringToInt(parts[1], &sample) || + !base::StringToInt(parts[2], &max)) { + return std::unique_ptr(); + } + + return LinearHistogramSample(parts[0].as_string(), sample, max); +} + +// static +std::unique_ptr MetricSample::UserActionSample( + const std::string& action_name) { + return std::unique_ptr( + 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/components/metrics/serialization/metric_sample.h b/components/metrics/serialization/metric_sample.h new file mode 100644 index 0000000000000..5a64c418a2b52 --- /dev/null +++ b/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 +#include + +#include "base/macros.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 std::unique_ptr CrashSample( + const std::string& crash_name); + + // Builds a histogram sample. + static std::unique_ptr HistogramSample( + const std::string& histogram_name, + int sample, + int min, + int max, + int bucket_count); + // Deserializes a histogram sample. + static std::unique_ptr ParseHistogram( + const std::string& serialized); + + // Builds a sparse histogram sample. + static std::unique_ptr SparseHistogramSample( + const std::string& histogram_name, + int sample); + // Deserializes a sparse histogram sample. + static std::unique_ptr ParseSparseHistogram( + const std::string& serialized); + + // Builds a linear histogram sample. + static std::unique_ptr + LinearHistogramSample(const std::string& histogram_name, int sample, int max); + // Deserializes a linear histogram sample. + static std::unique_ptr ParseLinearHistogram( + const std::string& serialized); + + // Builds a user action sample. + static std::unique_ptr 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/components/metrics/serialization/serialization_utils.cc b/components/metrics/serialization/serialization_utils.cc new file mode 100644 index 0000000000000..7214b5d788568 --- /dev/null +++ b/components/metrics/serialization/serialization_utils.cc @@ -0,0 +1,219 @@ +// 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 +#include +#include + +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.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 + +std::unique_ptr SerializationUtils::ParseSample( + const std::string& sample) { + if (sample.empty()) + return std::unique_ptr(); + + std::vector 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 std::unique_ptr(); + } + const std::string& name = parts[0]; + const std::string& value = parts[1]; + + if (base::LowerCaseEqualsASCII(name, "crash")) + return MetricSample::CrashSample(value); + if (base::LowerCaseEqualsASCII(name, "histogram")) + return MetricSample::ParseHistogram(value); + if (base::LowerCaseEqualsASCII(name, "linearhistogram")) + return MetricSample::ParseLinearHistogram(value); + if (base::LowerCaseEqualsASCII(name, "sparsehistogram")) + return MetricSample::ParseSparseHistogram(value); + if (base::LowerCaseEqualsASCII(name, "useraction")) + return MetricSample::UserActionSample(value); + DLOG(ERROR) << "invalid event type: " << name << ", value: " << value; + return std::unique_ptr(); +} + +void SerializationUtils::ReadAndTruncateMetricsFromFile( + const std::string& filename, + std::vector>* 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; + + std::unique_ptr 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 opening 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(&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/components/metrics/serialization/serialization_utils.h b/components/metrics/serialization/serialization_utils.h new file mode 100644 index 0000000000000..c741cb2f29880 --- /dev/null +++ b/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 +#include +#include + +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. +std::unique_ptr ParseSample(const std::string& sample); + +// Reads all samples from a file and truncate the file when done. +void ReadAndTruncateMetricsFromFile( + const std::string& filename, + std::vector>* 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/components/metrics/serialization/serialization_utils_unittest.cc b/components/metrics/serialization/serialization_utils_unittest.cc new file mode 100644 index 0000000000000..5685a1f28ab30 --- /dev/null +++ b/components/metrics/serialization/serialization_utils_unittest.cc @@ -0,0 +1,172 @@ +// 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 +#include + +#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.GetPath(); + 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()); + std::unique_ptr 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) { + std::unique_ptr sample1 = + MetricSample::SparseHistogramSample("no space", 10); + std::unique_ptr 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(nullptr, MetricSample::ParseSparseHistogram(input).get()); +} + +TEST_F(SerializationUtilsTest, MessageSeparatedByZero) { + std::unique_ptr 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'); + + std::unique_ptr 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(&message_size), + sizeof(message_size)); + test_file.WriteAtCurrentPos(message.c_str(), message.length()); + test_file.Close(); + + std::unique_ptr crash = MetricSample::CrashSample("test"); + SerializationUtils::WriteMetricToFile(*crash.get(), filename); + + std::vector> samples; + SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples); + ASSERT_EQ(size_t(1), samples.size()); + ASSERT_TRUE(samples[0].get() != nullptr); + EXPECT_TRUE(crash->IsEqual(*samples[0])); +} + +TEST_F(SerializationUtilsTest, WriteReadTest) { + std::unique_ptr hist = + MetricSample::HistogramSample("myhist", 1, 2, 3, 4); + std::unique_ptr crash = MetricSample::CrashSample("mycrash"); + std::unique_ptr lhist = + MetricSample::LinearHistogramSample("linear", 1, 10); + std::unique_ptr shist = + MetricSample::SparseHistogramSample("mysparse", 30); + std::unique_ptr 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); + std::vector> vect; + SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect); + ASSERT_EQ(vect.size(), size_t(5)); + for (auto& sample : vect) { + ASSERT_NE(nullptr, sample.get()); + } + 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/components/metrics/single_sample_metrics.cc b/components/metrics/single_sample_metrics.cc new file mode 100644 index 0000000000000..201ef730bd2f0 --- /dev/null +++ b/components/metrics/single_sample_metrics.cc @@ -0,0 +1,85 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/single_sample_metrics.h" + +#include +#include + +#include "base/metrics/single_sample_metrics.h" +#include "base/threading/thread_checker.h" +#include "components/metrics/single_sample_metrics_factory_impl.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace metrics { + +namespace { + +class MojoSingleSampleMetric : public mojom::SingleSampleMetric { + public: + MojoSingleSampleMetric(const std::string& histogram_name, + base::HistogramBase::Sample min, + base::HistogramBase::Sample max, + uint32_t bucket_count, + int32_t flags) + : metric_(histogram_name, min, max, bucket_count, flags) {} + ~MojoSingleSampleMetric() override {} + + private: + // mojom::SingleSampleMetric: + void SetSample(base::HistogramBase::Sample sample) override { + metric_.SetSample(sample); + } + + base::DefaultSingleSampleMetric metric_; + + DISALLOW_COPY_AND_ASSIGN(MojoSingleSampleMetric); +}; + +class MojoSingleSampleMetricsProvider + : public mojom::SingleSampleMetricsProvider { + public: + MojoSingleSampleMetricsProvider() {} + ~MojoSingleSampleMetricsProvider() override { + DCHECK(thread_checker_.CalledOnValidThread()); + } + + private: + // mojom::SingleSampleMetricsProvider: + void AcquireSingleSampleMetric( + const std::string& histogram_name, + base::HistogramBase::Sample min, + base::HistogramBase::Sample max, + uint32_t bucket_count, + int32_t flags, + mojom::SingleSampleMetricRequest request) override { + DCHECK(thread_checker_.CalledOnValidThread()); + mojo::MakeStrongBinding(std::make_unique( + histogram_name, min, max, bucket_count, flags), + std::move(request)); + } + + // Providers must be created, used on, and destroyed on the same thread. + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(MojoSingleSampleMetricsProvider); +}; + +} // namespace + +// static +void InitializeSingleSampleMetricsFactory(CreateProviderCB create_provider_cb) { + base::SingleSampleMetricsFactory::SetFactory( + std::make_unique( + std::move(create_provider_cb))); +} + +// static +void CreateSingleSampleMetricsProvider( + mojom::SingleSampleMetricsProviderRequest request) { + mojo::MakeStrongBinding(std::make_unique(), + std::move(request)); +} + +} // namespace metrics diff --git a/components/metrics/single_sample_metrics.h b/components/metrics/single_sample_metrics.h new file mode 100644 index 0000000000000..989f6a29b9997 --- /dev/null +++ b/components/metrics/single_sample_metrics.h @@ -0,0 +1,41 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_SINGLE_SAMPLE_METRICS_H_ +#define COMPONENTS_METRICS_SINGLE_SAMPLE_METRICS_H_ + +#include "base/callback.h" +#include "components/metrics/public/interfaces/single_sample_metrics.mojom.h" + +namespace metrics { + +using CreateProviderCB = + base::RepeatingCallback; + +// Initializes and sets the base::SingleSampleMetricsFactory for the current +// process. |create_provider_cb| is used to create provider instances per each +// thread that the factory is used on; this is necessary since the underlying +// providers must only be used on the same thread as construction. +// +// We use a callback here to avoid taking additional DEPS on content and a +// service_manager::Connector() for simplicity and to avoid the need for +// using the service test harness in metrics unittests. +// +// Typically this is called in the process where termination may occur without +// warning; e.g. perhaps a renderer process. +extern void InitializeSingleSampleMetricsFactory( + CreateProviderCB create_provider_cb); + +// Creates a mojom::SingleSampleMetricsProvider capable of vending single sample +// metrics attached to a mojo pipe. +// +// Typically this is given to a service_manager::BinderRegistry in the process +// that has a deterministic shutdown path and which serves as a stable endpoint +// for the factory created by the above initialize method in another process. +extern void CreateSingleSampleMetricsProvider( + mojom::SingleSampleMetricsProviderRequest request); + +} // namespace metrics + +#endif // COMPONENTS_METRICS_SINGLE_SAMPLE_METRICS_H_ diff --git a/components/metrics/single_sample_metrics_factory_impl.cc b/components/metrics/single_sample_metrics_factory_impl.cc new file mode 100644 index 0000000000000..4ab48f8a9f2c9 --- /dev/null +++ b/components/metrics/single_sample_metrics_factory_impl.cc @@ -0,0 +1,92 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/single_sample_metrics_factory_impl.h" + +#include + +#include "base/threading/thread_checker.h" + +namespace metrics { + +namespace { + +class SingleSampleMetricImpl : public base::SingleSampleMetric { + public: + SingleSampleMetricImpl(mojom::SingleSampleMetricPtr metric) + : metric_(std::move(metric)) {} + + ~SingleSampleMetricImpl() override { + DCHECK(thread_checker_.CalledOnValidThread()); + } + + void SetSample(base::HistogramBase::Sample sample) override { + DCHECK(thread_checker_.CalledOnValidThread()); + metric_->SetSample(sample); + } + + private: + base::ThreadChecker thread_checker_; + mojom::SingleSampleMetricPtr metric_; + + DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricImpl); +}; + +} // namespace + +SingleSampleMetricsFactoryImpl::SingleSampleMetricsFactoryImpl( + CreateProviderCB create_provider_cb) + : create_provider_cb_(std::move(create_provider_cb)) {} + +SingleSampleMetricsFactoryImpl::~SingleSampleMetricsFactoryImpl() {} + +std::unique_ptr +SingleSampleMetricsFactoryImpl::CreateCustomCountsMetric( + const std::string& histogram_name, + base::HistogramBase::Sample min, + base::HistogramBase::Sample max, + uint32_t bucket_count) { + return CreateMetric(histogram_name, min, max, bucket_count, + base::HistogramBase::kUmaTargetedHistogramFlag); +} + +void SingleSampleMetricsFactoryImpl::DestroyProviderForTesting() { + if (auto* provider = provider_tls_.Get()) + delete provider; + provider_tls_.Set(nullptr); +} + +std::unique_ptr +SingleSampleMetricsFactoryImpl::CreateMetric(const std::string& histogram_name, + base::HistogramBase::Sample min, + base::HistogramBase::Sample max, + uint32_t bucket_count, + int32_t flags) { + mojom::SingleSampleMetricPtr metric; + GetProvider()->AcquireSingleSampleMetric(histogram_name, min, max, + bucket_count, flags, + mojo::MakeRequest(&metric)); + return std::make_unique(std::move(metric)); +} + +mojom::SingleSampleMetricsProvider* +SingleSampleMetricsFactoryImpl::GetProvider() { + // Check the current TLS slot to see if we have created a provider already for + // this thread. + if (auto* provider = provider_tls_.Get()) + return provider->get(); + + // If not, create a new one which will persist until process shutdown and put + // it in the TLS slot for the current thread. + mojom::SingleSampleMetricsProviderPtr* provider = + new mojom::SingleSampleMetricsProviderPtr(); + provider_tls_.Set(provider); + + // Start the provider connection and return it; it won't be fully connected + // until later, but mojo will buffer all calls prior to completion. + create_provider_cb_.Run(mojo::MakeRequest(provider)); + return provider->get(); +} + +} // namespace metrics diff --git a/components/metrics/single_sample_metrics_factory_impl.h b/components/metrics/single_sample_metrics_factory_impl.h new file mode 100644 index 0000000000000..297324f97b7a3 --- /dev/null +++ b/components/metrics/single_sample_metrics_factory_impl.h @@ -0,0 +1,71 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_SINGLE_VALUE_HISTOGRAM_FACTORY_IMPL_H_ +#define COMPONENTS_METRICS_SINGLE_VALUE_HISTOGRAM_FACTORY_IMPL_H_ + +#include + +#include "base/metrics/single_sample_metrics.h" +#include "base/threading/thread_local.h" +#include "components/metrics/public/interfaces/single_sample_metrics.mojom.h" +#include "components/metrics/single_sample_metrics.h" + +namespace metrics { + +// SingleSampleMetricsFactory implementation for creating SingleSampleMetric +// instances that communicate over mojo to instances in another process. +// +// Persistance outside of the current process allows these metrics to record a +// sample even in the event of sudden process termination. As an example, this +// is useful for garbage collected objects which may never get a chance to run +// their destructors in the event of a fast shutdown event (process kill). +class SingleSampleMetricsFactoryImpl : public base::SingleSampleMetricsFactory { + public: + // Constructs a factory capable of vending single sample metrics from any + // thread. |create_provider_cb| will be called from arbitrary threads to + // create providers as necessary; the callback must handle thread safety. + // + // We use a callback here to avoid taking additional DEPS on content and a + // service_manager::Connector() for simplicitly and to avoid the need for + // using the service test harness just for instantiating this class. + explicit SingleSampleMetricsFactoryImpl(CreateProviderCB create_provider_cb); + ~SingleSampleMetricsFactoryImpl() override; + + // base::SingleSampleMetricsFactory: + std::unique_ptr CreateCustomCountsMetric( + const std::string& histogram_name, + base::HistogramBase::Sample min, + base::HistogramBase::Sample max, + uint32_t bucket_count) override; + + // Providers live forever in production, but tests should be kind and clean up + // after themselves to avoid tests trampling on one another. Destroys the + // provider in the TLS slot for the calling thread. + void DestroyProviderForTesting(); + + private: + // Creates a single sample metric. + std::unique_ptr CreateMetric( + const std::string& histogram_name, + base::HistogramBase::Sample min, + base::HistogramBase::Sample max, + uint32_t bucket_count, + int32_t flags); + + // Gets the SingleSampleMetricsProvider for the current thread. If none + // exists, then a new instance is created and set in the TLS slot. + mojom::SingleSampleMetricsProvider* GetProvider(); + + CreateProviderCB create_provider_cb_; + + // Per thread storage slot for the mojo provider. + base::ThreadLocalPointer provider_tls_; + + DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricsFactoryImpl); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_SINGLE_VALUE_HISTOGRAM_FACTORY_IMPL_H_ diff --git a/components/metrics/single_sample_metrics_factory_impl_unittest.cc b/components/metrics/single_sample_metrics_factory_impl_unittest.cc new file mode 100644 index 0000000000000..73620853ac5fd --- /dev/null +++ b/components/metrics/single_sample_metrics_factory_impl_unittest.cc @@ -0,0 +1,184 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/single_sample_metrics_factory_impl.h" + +#include "base/message_loop/message_loop.h" +#include "base/metrics/dummy_histogram.h" +#include "base/run_loop.h" +#include "base/test/gtest_util.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/threading/thread.h" +#include "components/metrics/single_sample_metrics.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +const base::HistogramBase::Sample kMin = 1; +const base::HistogramBase::Sample kMax = 10; +const uint32_t kBucketCount = 10; +const char kMetricName[] = "Single.Sample.Metric"; + +class SingleSampleMetricsFactoryImplTest : public testing::Test { + public: + SingleSampleMetricsFactoryImplTest() : thread_("TestThread") { + InitializeSingleSampleMetricsFactory( + base::BindRepeating(&SingleSampleMetricsFactoryImplTest::CreateProvider, + base::Unretained(this))); + factory_ = static_cast( + base::SingleSampleMetricsFactory::Get()); + } + + ~SingleSampleMetricsFactoryImplTest() override { + factory_->DestroyProviderForTesting(); + if (thread_.IsRunning()) + ShutdownThread(); + base::SingleSampleMetricsFactory::DeleteFactoryForTesting(); + } + + protected: + void StartThread() { ASSERT_TRUE(thread_.Start()); } + + void ShutdownThread() { + thread_.task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + &SingleSampleMetricsFactoryImpl::DestroyProviderForTesting, + base::Unretained(factory_))); + thread_.Stop(); + } + + void CreateProvider(mojom::SingleSampleMetricsProviderRequest request) { + CreateSingleSampleMetricsProvider(std::move(request)); + provider_count_++; + } + + std::unique_ptr CreateMetricOnThread() { + std::unique_ptr metric; + base::RunLoop run_loop; + thread_.task_runner()->PostTaskAndReply( + FROM_HERE, + base::BindOnce( + &SingleSampleMetricsFactoryImplTest::CreateAndStoreMetric, + base::Unretained(this), &metric), + run_loop.QuitClosure()); + run_loop.Run(); + return metric; + } + + void CreateAndStoreMetric(std::unique_ptr* metric) { + *metric = factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, + kBucketCount); + } + + base::MessageLoop message_looqp_; + SingleSampleMetricsFactoryImpl* factory_; + base::Thread thread_; + size_t provider_count_ = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricsFactoryImplTest); +}; + +} // namespace + +TEST_F(SingleSampleMetricsFactoryImplTest, SingleProvider) { + std::unique_ptr metric1 = + factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + + std::unique_ptr metric2 = + factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + + // Verify that only a single provider is created for multiple metrics. + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1u, provider_count_); +} + +TEST_F(SingleSampleMetricsFactoryImplTest, DoesNothing) { + base::HistogramTester tester; + + std::unique_ptr metric = + factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + metric.reset(); + + // Verify that no sample is recorded if SetSample() is never called. + base::RunLoop().RunUntilIdle(); + tester.ExpectTotalCount(kMetricName, 0); +} + +TEST_F(SingleSampleMetricsFactoryImplTest, DefaultSingleSampleMetricWithValue) { + base::HistogramTester tester; + std::unique_ptr metric = + factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + + const base::HistogramBase::Sample kLastSample = 9; + metric->SetSample(1); + metric->SetSample(3); + metric->SetSample(5); + metric->SetSample(kLastSample); + metric.reset(); + + // Verify only the last sample sent to SetSample() is recorded. + base::RunLoop().RunUntilIdle(); + tester.ExpectUniqueSample(kMetricName, kLastSample, 1); + + // Verify construction implicitly by requesting a histogram with the same + // parameters; this test relies on the fact that histogram objects are unique + // per name. Different parameters will result in a Dummy histogram returned. + EXPECT_EQ(base::DummyHistogram::GetInstance(), + base::Histogram::FactoryGet(kMetricName, 1, 3, 3, + base::HistogramBase::kNoFlags)); + EXPECT_NE(base::DummyHistogram::GetInstance(), + base::Histogram::FactoryGet( + kMetricName, kMin, kMax, kBucketCount, + base::HistogramBase::kUmaTargetedHistogramFlag)); +} + +TEST_F(SingleSampleMetricsFactoryImplTest, MultithreadedMetrics) { + base::HistogramTester tester; + std::unique_ptr metric = + factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount); + EXPECT_EQ(1u, provider_count_); + + StartThread(); + + std::unique_ptr threaded_metric = + CreateMetricOnThread(); + ASSERT_TRUE(threaded_metric); + + // A second provider should be created to handle requests on our new thread. + EXPECT_EQ(2u, provider_count_); + + // Calls from the wrong thread should DCHECK. + EXPECT_DCHECK_DEATH(threaded_metric->SetSample(5)); + EXPECT_DCHECK_DEATH(threaded_metric.reset()); + + // Test that samples are set on each thread correctly. + const base::HistogramBase::Sample kSample = 7; + + { + metric->SetSample(kSample); + + base::RunLoop run_loop; + thread_.task_runner()->PostTaskAndReply( + FROM_HERE, + base::BindOnce(&base::SingleSampleMetric::SetSample, + base::Unretained(threaded_metric.get()), kSample), + run_loop.QuitClosure()); + run_loop.Run(); + } + + // Release metrics and shutdown thread to ensure destruction completes. + thread_.task_runner()->DeleteSoon(FROM_HERE, threaded_metric.release()); + ShutdownThread(); + + metric.reset(); + base::RunLoop().RunUntilIdle(); + + tester.ExpectUniqueSample(kMetricName, kSample, 2); +} + +} // namespace metrics diff --git a/components/metrics/stability_metrics_helper.cc b/components/metrics/stability_metrics_helper.cc new file mode 100644 index 0000000000000..517fc71fc86ca --- /dev/null +++ b/components/metrics/stability_metrics_helper.cc @@ -0,0 +1,322 @@ +// 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 + +#include + +#include "base/logging.h" +#include "base/metrics/histogram_functions.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "build/build_config.h" +#include "build/buildflag.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/variations/hashing.h" +#include "extensions/buildflags/buildflags.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +#if defined(OS_WIN) +#include // 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(exit_code) == STATUS_GUARD_PAGE_VIOLATION) + return 0x1FCF7EC3; // Randomly picked number. +#endif + + return std::abs(exit_code); +} + +void RecordChildKills(RendererType histogram_type) { + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills", + histogram_type, RENDERER_TYPE_COUNT); +} + +// Macro for logging the age of a crashed process. +// +// Notes: +// - IMPORTANT: When changing the constants below, please change the names of +// the histograms logged via UMA_HISTOGRAM_CRASHED_PROCESS_AGE. +// - 99th percentile of Memory.Experimental.Renderer.Uptime hovers around 17h. +// - |kCrashedProcessAgeMin| is as low as possible, so that we may with +// high-confidence categorize crashes that occur during early startup (e.g. +// crashes that end up with STATUS_DLL_INIT_FAILED or STATUS_DLL_NOT_FOUND). +// - Note that even with just 50 buckets, we still get narrow and accurate +// buckets at the lower end: 0ms, 1ms, 2ms, 3ms, 4-5ms, 6-8ms, 9-12ms, ... +constexpr auto kCrashedProcessAgeMin = base::TimeDelta::FromMilliseconds(1); +constexpr auto kCrashedProcessAgeMax = base::TimeDelta::FromHours(48); +constexpr uint32_t kCrashedProcessAgeCount = 50; +#define UMA_HISTOGRAM_CRASHED_PROCESS_AGE(histogram_name, uptime) \ + UMA_HISTOGRAM_CUSTOM_TIMES(histogram_name, uptime, kCrashedProcessAgeMin, \ + kCrashedProcessAgeMax, kCrashedProcessAgeCount) + +} // 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::kStabilityRendererLaunchCount); + if (count) { + stability_proto->set_renderer_launch_count(count); + local_state_->SetInteger(prefs::kStabilityRendererLaunchCount, 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); + } + + count = + local_state_->GetInteger(prefs::kStabilityExtensionRendererLaunchCount); + if (count) { + stability_proto->set_extension_renderer_launch_count(count); + local_state_->SetInteger(prefs::kStabilityExtensionRendererLaunchCount, 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::kStabilityExtensionRendererLaunchCount, 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); + local_state_->SetInteger(prefs::kStabilityRendererLaunchCount, 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::kStabilityExtensionRendererLaunchCount, + 0); + registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererFailedLaunchCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererHangCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityRendererLaunchCount, 0); + + registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0); +} + +void StabilityMetricsHelper::IncreaseRendererCrashCount() { + IncrementPrefValue(prefs::kStabilityRendererCrashCount); +} + +void StabilityMetricsHelper::BrowserUtilityProcessLaunched( + const std::string& metrics_name) { + uint32_t hash = variations::HashName(metrics_name); + base::UmaHistogramSparse("ChildProcess.Launched.UtilityProcessHash", hash); +} + +void StabilityMetricsHelper::BrowserUtilityProcessCrashed( + const std::string& metrics_name, + int exit_code) { + // TODO(wfh): there doesn't appear to be a good way to log these exit_codes + // without adding something into the stability proto, so for now only log the + // crash and if the numbers are high enough, logging exit codes can be added + // later. + uint32_t hash = variations::HashName(metrics_name); + base::UmaHistogramSparse("ChildProcess.Crashed.UtilityProcessHash", hash); +} + +void StabilityMetricsHelper::BrowserChildProcessCrashed() { + IncrementPrefValue(prefs::kStabilityChildProcessCrashCount); +} + +void StabilityMetricsHelper::LogLoadStarted(bool is_incognito) { + base::RecordAction(base::UserMetricsAction("PageLoad")); + if (is_incognito) + base::RecordAction(base::UserMetricsAction("PageLoadInIncognito")); + 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, + base::Optional uptime) { + RendererType histogram_type = + was_extension_process ? RENDERER_TYPE_EXTENSION : RENDERER_TYPE_RENDERER; + + switch (status) { + case base::TERMINATION_STATUS_NORMAL_TERMINATION: + break; + case base::TERMINATION_STATUS_PROCESS_CRASHED: + case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: + case base::TERMINATION_STATUS_OOM: + if (was_extension_process) { +#if !BUILDFLAG(ENABLE_EXTENSIONS) + NOTREACHED(); +#endif + IncrementPrefValue(prefs::kStabilityExtensionRendererCrashCount); + + base::UmaHistogramSparse("CrashExitCodes.Extension", + MapCrashExitCodeForHistogram(exit_code)); + if (uptime.has_value()) { + UMA_HISTOGRAM_CRASHED_PROCESS_AGE( + "Stability.CrashedProcessAge.Extension", uptime.value()); + } + } else { + IncrementPrefValue(prefs::kStabilityRendererCrashCount); + + base::UmaHistogramSparse("CrashExitCodes.Renderer", + MapCrashExitCodeForHistogram(exit_code)); + if (uptime.has_value()) { + UMA_HISTOGRAM_CRASHED_PROCESS_AGE( + "Stability.CrashedProcessAge.Renderer", uptime.value()); + } + } + + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildCrashes", + histogram_type, RENDERER_TYPE_COUNT); + break; + case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: + RecordChildKills(histogram_type); + break; +#if defined(OS_ANDROID) + case base::TERMINATION_STATUS_OOM_PROTECTED: + // TODO(wfh): Check if this should be a Kill or a Crash on Android. + break; +#endif +#if defined(OS_CHROMEOS) + case 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); + break; +#endif + case base::TERMINATION_STATUS_STILL_RUNNING: + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.DisconnectedAlive", + histogram_type, RENDERER_TYPE_COUNT); + break; + case base::TERMINATION_STATUS_LAUNCH_FAILED: + UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildLaunchFailures", + histogram_type, RENDERER_TYPE_COUNT); + base::UmaHistogramSparse( + "BrowserRenderProcessHost.ChildLaunchFailureCodes", exit_code); + if (was_extension_process) + IncrementPrefValue(prefs::kStabilityExtensionRendererFailedLaunchCount); + else + IncrementPrefValue(prefs::kStabilityRendererFailedLaunchCount); + break; + case base::TERMINATION_STATUS_MAX_ENUM: + NOTREACHED(); + break; + } +} + +void StabilityMetricsHelper::LogRendererLaunched(bool was_extension_process) { + if (was_extension_process) + IncrementPrefValue(prefs::kStabilityExtensionRendererLaunchCount); + else + IncrementPrefValue(prefs::kStabilityRendererLaunchCount); +} + +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/components/metrics/stability_metrics_helper.h b/components/metrics/stability_metrics_helper.h new file mode 100644 index 0000000000000..5a22882af4bcf --- /dev/null +++ b/components/metrics/stability_metrics_helper.h @@ -0,0 +1,78 @@ +// 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/optional.h" +#include "base/process/kill.h" +#include "base/time/time.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 utility process launch with name |metrics_name|. + void BrowserUtilityProcessLaunched(const std::string& metrics_name); + + // Records a utility process crash with name |metrics_name|. + void BrowserUtilityProcessCrashed(const std::string& metrics_name, + int exit_code); + + // Records a browser child process crash. + void BrowserChildProcessCrashed(); + + // Logs the initiation of a page load. + void LogLoadStarted(bool is_incognito); + + // Records a renderer process crash. + void LogRendererCrash(bool was_extension_process, + base::TerminationStatus status, + int exit_code, + base::Optional uptime); + + // Records that a new renderer process was successfully launched. + void LogRendererLaunched(bool was_extension_process); + + // Records a renderer process hang. + void LogRendererHang(); + + // Registers local state prefs used by this class. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // Increments the RendererCrash pref. + void IncreaseRendererCrashCount(); + + private: + // Increments an Integer pref value specified by |path|. + void IncrementPrefValue(const char* path); + + // Increments 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/components/metrics/stability_metrics_helper_unittest.cc b/components/metrics/stability_metrics_helper_unittest.cc new file mode 100644 index 0000000000000..66fc041db0974 --- /dev/null +++ b/components/metrics/stability_metrics_helper_unittest.cc @@ -0,0 +1,159 @@ +// 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 + +#include "base/macros.h" +#include "base/test/metrics/histogram_tester.h" +#include "build/build_config.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/prefs/testing_pref_service.h" +#include "extensions/buildflags/buildflags.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +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 +}; + +class StabilityMetricsHelperTest : public testing::Test { + protected: + StabilityMetricsHelperTest() : prefs_(new TestingPrefServiceSimple) { + StabilityMetricsHelper::RegisterPrefs(prefs()->registry()); + } + + TestingPrefServiceSimple* prefs() { return prefs_.get(); } + + private: + std::unique_ptr 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()); + base::HistogramTester histogram_tester; + const base::TimeDelta kUptime = base::TimeDelta::FromSeconds(123); + + // Crash and abnormal termination should increment renderer crash count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_CRASHED, 1, + kUptime); + + helper.LogRendererCrash(false, base::TERMINATION_STATUS_ABNORMAL_TERMINATION, + 1, kUptime); + + // OOM should increment renderer crash count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_OOM, 1, kUptime); + + // Kill does not increment renderer crash count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_WAS_KILLED, 1, + kUptime); + + // Failed launch increments failed launch count. + helper.LogRendererCrash(false, base::TERMINATION_STATUS_LAUNCH_FAILED, 1, + kUptime); + + metrics::SystemProfileProto system_profile; + + // Call ProvideStabilityMetrics to check that it will force pending tasks to + // be executed immediately. + helper.ProvideStabilityMetrics(&system_profile); + + EXPECT_EQ(3, 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()); + + histogram_tester.ExpectUniqueSample("CrashExitCodes.Renderer", 1, 3); + histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildCrashes", + RENDERER_TYPE_RENDERER, 3); + + // One launch failure each. + histogram_tester.ExpectBucketCount( + "BrowserRenderProcessHost.ChildLaunchFailures", RENDERER_TYPE_RENDERER, + 1); + + // TERMINATION_STATUS_PROCESS_WAS_KILLED for a renderer. + histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildKills", + RENDERER_TYPE_RENDERER, 1); + histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildKills", + RENDERER_TYPE_EXTENSION, 0); + histogram_tester.ExpectBucketCount( + "BrowserRenderProcessHost.ChildLaunchFailureCodes", 1, 1); + histogram_tester.ExpectUniqueSample("Stability.CrashedProcessAge.Renderer", + kUptime.InMilliseconds(), 3); +} + +// Note: ENABLE_EXTENSIONS is set to false in Android +#if BUILDFLAG(ENABLE_EXTENSIONS) +TEST_F(StabilityMetricsHelperTest, LogRendererCrashEnableExtensions) { + StabilityMetricsHelper helper(prefs()); + base::HistogramTester histogram_tester; + const base::TimeDelta kUptime = base::TimeDelta::FromSeconds(123); + + // Crash and abnormal termination should increment extension crash count. + helper.LogRendererCrash(true, base::TERMINATION_STATUS_PROCESS_CRASHED, 1, + kUptime); + + // OOM should increment extension renderer crash count. + helper.LogRendererCrash(true, base::TERMINATION_STATUS_OOM, 1, kUptime); + + // Failed launch increments extension failed launch count. + helper.LogRendererCrash(true, base::TERMINATION_STATUS_LAUNCH_FAILED, 1, + kUptime); + + metrics::SystemProfileProto system_profile; + helper.ProvideStabilityMetrics(&system_profile); + + EXPECT_EQ(0, system_profile.stability().renderer_crash_count()); + EXPECT_EQ(2, system_profile.stability().extension_renderer_crash_count()); + EXPECT_EQ( + 1, system_profile.stability().extension_renderer_failed_launch_count()); + + histogram_tester.ExpectBucketCount( + "BrowserRenderProcessHost.ChildLaunchFailureCodes", 1, 1); + histogram_tester.ExpectUniqueSample("CrashExitCodes.Extension", 1, 2); + histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildCrashes", + RENDERER_TYPE_EXTENSION, 2); + histogram_tester.ExpectBucketCount( + "BrowserRenderProcessHost.ChildLaunchFailures", RENDERER_TYPE_EXTENSION, + 1); + histogram_tester.ExpectUniqueSample("Stability.CrashedProcessAge.Extension", + kUptime.InMilliseconds(), 2); +} +#endif + +} // namespace metrics diff --git a/components/metrics/stability_metrics_provider.cc b/components/metrics/stability_metrics_provider.cc new file mode 100644 index 0000000000000..7d411d3e89618 --- /dev/null +++ b/components/metrics/stability_metrics_provider.cc @@ -0,0 +1,262 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/stability_metrics_provider.h" + +#include "base/metrics/histogram_macros.h" +#include "build/build_config.h" +#include "components/metrics/metrics_pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#endif +#if defined(OS_WIN) +#include "components/metrics/system_session_analyzer_win.h" +#endif + +namespace metrics { + +namespace { + +#if defined(OS_ANDROID) +bool HasGmsCoreVersionChanged(PrefService* local_state) { + std::string previous_version = + local_state->GetString(prefs::kStabilityGmsCoreVersion); + std::string current_version = + base::android::BuildInfo::GetInstance()->gms_version_code(); + + // If the last version is empty, treat it as consistent. + if (previous_version.empty()) + return false; + + return previous_version != current_version; +} + +void UpdateGmsCoreVersionPref(PrefService* local_state) { + std::string current_version = + base::android::BuildInfo::GetInstance()->gms_version_code(); + local_state->SetString(prefs::kStabilityGmsCoreVersion, current_version); +} +#endif + +} // namespace + +StabilityMetricsProvider::StabilityMetricsProvider(PrefService* local_state) + : local_state_(local_state) {} + +StabilityMetricsProvider::~StabilityMetricsProvider() = default; + +// static +void StabilityMetricsProvider::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityIncompleteSessionEndCount, 0); + registry->RegisterBooleanPref(prefs::kStabilitySessionEndCompleted, true); + registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationFail, 0); + registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationSuccess, + 0); + registry->RegisterIntegerPref(prefs::kStabilityDebuggerPresent, 0); + registry->RegisterIntegerPref(prefs::kStabilityDebuggerNotPresent, 0); + registry->RegisterIntegerPref(prefs::kStabilityDeferredCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityDiscardCount, 0); + registry->RegisterIntegerPref(prefs::kStabilityVersionMismatchCount, 0); +#if defined(OS_ANDROID) + registry->RegisterStringPref(prefs::kStabilityGmsCoreVersion, ""); + registry->RegisterIntegerPref(prefs::kStabilityCrashCountWithoutGmsCoreUpdate, + 0); +#endif +#if defined(OS_WIN) + registry->RegisterIntegerPref(prefs::kStabilitySystemCrashCount, 0); +#endif +} + +void StabilityMetricsProvider::Init() { +#if defined(OS_ANDROID) + // This method has to be called after HasGmsCoreVersionChanged() to avoid + // overwriting thie result. + UpdateGmsCoreVersionPref(local_state_); +#endif +} + +void StabilityMetricsProvider::ClearSavedStabilityMetrics() { + local_state_->SetInteger(prefs::kStabilityCrashCount, 0); + local_state_->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0); + local_state_->SetInteger(prefs::kStabilityBreakpadRegistrationSuccess, 0); + local_state_->SetInteger(prefs::kStabilityBreakpadRegistrationFail, 0); + local_state_->SetInteger(prefs::kStabilityDebuggerPresent, 0); + local_state_->SetInteger(prefs::kStabilityDebuggerNotPresent, 0); + local_state_->SetInteger(prefs::kStabilityLaunchCount, 0); + local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true); + local_state_->SetInteger(prefs::kStabilityDeferredCount, 0); + // Note: kStabilityDiscardCount is not cleared as its intent is to measure + // the number of times data is discarded, even across versions. + local_state_->SetInteger(prefs::kStabilityVersionMismatchCount, 0); +#if defined(OS_WIN) + local_state_->SetInteger(prefs::kStabilitySystemCrashCount, 0); +#endif +} + +void StabilityMetricsProvider::ProvideStabilityMetrics( + SystemProfileProto* system_profile) { + SystemProfileProto::Stability* stability = + system_profile->mutable_stability(); + + int pref_value = 0; + + if (GetPrefValue(prefs::kStabilityLaunchCount, &pref_value)) + stability->set_launch_count(pref_value); + + if (GetPrefValue(prefs::kStabilityCrashCount, &pref_value)) + stability->set_crash_count(pref_value); + +#if defined(OS_ANDROID) + if (GetPrefValue(prefs::kStabilityCrashCountWithoutGmsCoreUpdate, + &pref_value)) { + stability->set_crash_count_without_gms_core_update(pref_value); + } +#endif + + if (GetPrefValue(prefs::kStabilityIncompleteSessionEndCount, &pref_value)) + stability->set_incomplete_shutdown_count(pref_value); + + if (GetPrefValue(prefs::kStabilityBreakpadRegistrationSuccess, &pref_value)) + stability->set_breakpad_registration_success_count(pref_value); + + if (GetPrefValue(prefs::kStabilityBreakpadRegistrationFail, &pref_value)) + stability->set_breakpad_registration_failure_count(pref_value); + + if (GetPrefValue(prefs::kStabilityDebuggerPresent, &pref_value)) + stability->set_debugger_present_count(pref_value); + + if (GetPrefValue(prefs::kStabilityDebuggerNotPresent, &pref_value)) + stability->set_debugger_not_present_count(pref_value); + + // Note: only logging the following histograms for non-zero values. + if (GetPrefValue(prefs::kStabilityDeferredCount, &pref_value)) { + UMA_STABILITY_HISTOGRAM_COUNTS_100( + "Stability.Internals.InitialStabilityLogDeferredCount", pref_value); + } + + // Note: only logging the following histograms for non-zero values. + if (GetPrefValue(prefs::kStabilityDiscardCount, &pref_value)) { + UMA_STABILITY_HISTOGRAM_COUNTS_100("Stability.Internals.DataDiscardCount", + pref_value); + } + + // Note: only logging the following histograms for non-zero values. + if (GetPrefValue(prefs::kStabilityVersionMismatchCount, &pref_value)) { + UMA_STABILITY_HISTOGRAM_COUNTS_100( + "Stability.Internals.VersionMismatchCount", pref_value); + } + +#if defined(OS_WIN) + if (GetPrefValue(prefs::kStabilitySystemCrashCount, &pref_value)) { + UMA_STABILITY_HISTOGRAM_COUNTS_100("Stability.Internals.SystemCrashCount", + pref_value); + } +#endif +} + +void StabilityMetricsProvider::RecordBreakpadRegistration(bool success) { + if (!success) + IncrementPrefValue(prefs::kStabilityBreakpadRegistrationFail); + else + IncrementPrefValue(prefs::kStabilityBreakpadRegistrationSuccess); +} + +void StabilityMetricsProvider::RecordBreakpadHasDebugger(bool has_debugger) { + if (!has_debugger) + IncrementPrefValue(prefs::kStabilityDebuggerNotPresent); + else + IncrementPrefValue(prefs::kStabilityDebuggerPresent); +} + +void StabilityMetricsProvider::CheckLastSessionEndCompleted() { + if (!local_state_->GetBoolean(prefs::kStabilitySessionEndCompleted)) { + IncrementPrefValue(prefs::kStabilityIncompleteSessionEndCount); + // This is marked false when we get a WM_ENDSESSION. + MarkSessionEndCompleted(true); + } +} + +void StabilityMetricsProvider::MarkSessionEndCompleted(bool end_completed) { + local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, end_completed); +} + +void StabilityMetricsProvider::LogCrash(base::Time last_live_timestamp) { + IncrementPrefValue(prefs::kStabilityCrashCount); + +#if defined(OS_ANDROID) + // On Android, if there is an update for GMS core when Chrome is running, + // Chrome will be killed and restart. This is expected and we should only + // report crash if the GMS core version has not been changed. + if (!HasGmsCoreVersionChanged(local_state_)) + IncrementPrefValue(prefs::kStabilityCrashCountWithoutGmsCoreUpdate); +#endif + +#if defined(OS_WIN) + MaybeLogSystemCrash(last_live_timestamp); +#endif +} + +void StabilityMetricsProvider::LogStabilityLogDeferred() { + IncrementPrefValue(prefs::kStabilityDeferredCount); +} + +void StabilityMetricsProvider::LogStabilityDataDiscarded() { + IncrementPrefValue(prefs::kStabilityDiscardCount); +} + +void StabilityMetricsProvider::LogLaunch() { + IncrementPrefValue(prefs::kStabilityLaunchCount); +} + +void StabilityMetricsProvider::LogStabilityVersionMismatch() { + IncrementPrefValue(prefs::kStabilityVersionMismatchCount); +} + +#if defined(OS_WIN) +bool StabilityMetricsProvider::IsUncleanSystemSession( + base::Time last_live_timestamp) { + DCHECK_NE(base::Time(), last_live_timestamp); + // There's a non-null last live timestamp, see if this occurred in + // a Windows system session that ended uncleanly. The expectation is that + // |last_live_timestamp| will have occurred in the immediately previous system + // session, but if the system has been restarted many times since Chrome last + // ran, that's not necessarily true. Log traversal can be expensive, so we + // limit the analyzer to reaching back three previous system sessions to bound + // the cost of the traversal. + SystemSessionAnalyzer analyzer(3); + + SystemSessionAnalyzer::Status status = + analyzer.IsSessionUnclean(last_live_timestamp); + + return status == SystemSessionAnalyzer::UNCLEAN; +} + +void StabilityMetricsProvider::MaybeLogSystemCrash( + base::Time last_live_timestamp) { + if (last_live_timestamp != base::Time() && + IsUncleanSystemSession(last_live_timestamp)) { + IncrementPrefValue(prefs::kStabilitySystemCrashCount); + } +} +#endif + +void StabilityMetricsProvider::IncrementPrefValue(const char* path) { + int value = local_state_->GetInteger(path); + local_state_->SetInteger(path, value + 1); +} + +int StabilityMetricsProvider::GetPrefValue(const char* path, int* value) { + *value = local_state_->GetInteger(path); + if (*value != 0) + local_state_->SetInteger(path, 0); + return *value; +} + +} // namespace metrics diff --git a/components/metrics/stability_metrics_provider.h b/components/metrics/stability_metrics_provider.h new file mode 100644 index 0000000000000..3aeff6317fb7c --- /dev/null +++ b/components/metrics/stability_metrics_provider.h @@ -0,0 +1,67 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_METRICS_STABILITY_METRICS_PROVIDER_H_ +#define COMPONENTS_METRICS_STABILITY_METRICS_PROVIDER_H_ + +#include "base/time/time.h" +#include "build/build_config.h" +#include "components/metrics/metrics_provider.h" + +class PrefService; +class PrefRegistrySimple; + +namespace metrics { + +class SystemProfileProto; + +// Stores and loads system information to prefs for stability logs. +class StabilityMetricsProvider : public MetricsProvider { + public: + StabilityMetricsProvider(PrefService* local_state); + ~StabilityMetricsProvider() override; + + static void RegisterPrefs(PrefRegistrySimple* registry); + + void RecordBreakpadRegistration(bool success); + void RecordBreakpadHasDebugger(bool has_debugger); + + void CheckLastSessionEndCompleted(); + void MarkSessionEndCompleted(bool end_completed); + + void LogCrash(base::Time last_live_timestamp); + void LogStabilityLogDeferred(); + void LogStabilityDataDiscarded(); + void LogLaunch(); + void LogStabilityVersionMismatch(); + + private: +#if defined(OS_WIN) + // This function is virtual for testing. The |last_live_timestamp| is a + // time point where the previous browser was known to be alive, and is used + // to determine whether the system session embedding that timestamp terminated + // uncleanly. + virtual bool IsUncleanSystemSession(base::Time last_live_timestamp); + void MaybeLogSystemCrash(base::Time last_live_timestamp); +#endif + // Increments an Integer pref value specified by |path|. + void IncrementPrefValue(const char* path); + + // Gets pref value specified by |path| and resets it to 0 after retrieving. + int GetPrefValue(const char* path, int* value); + + // MetricsProvider: + void Init() override; + void ClearSavedStabilityMetrics() override; + void ProvideStabilityMetrics( + SystemProfileProto* system_profile_proto) override; + + PrefService* local_state_; + + DISALLOW_COPY_AND_ASSIGN(StabilityMetricsProvider); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_STABILITY_METRICS_PROVIDER_H_ diff --git a/components/metrics/stability_metrics_provider_unittest.cc b/components/metrics/stability_metrics_provider_unittest.cc new file mode 100644 index 0000000000000..e0ba0ce9ba775 --- /dev/null +++ b/components/metrics/stability_metrics_provider_unittest.cc @@ -0,0 +1,132 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/stability_metrics_provider.h" + +#include "base/test/metrics/histogram_tester.h" +#include "build/build_config.h" +#include "components/prefs/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/metrics_proto/system_profile.pb.h" + +namespace metrics { + +class StabilityMetricsProviderTest : public testing::Test { + public: + StabilityMetricsProviderTest() { + StabilityMetricsProvider::RegisterPrefs(prefs_.registry()); + } + + ~StabilityMetricsProviderTest() override {} + + protected: + TestingPrefServiceSimple prefs_; + + private: + DISALLOW_COPY_AND_ASSIGN(StabilityMetricsProviderTest); +}; + +TEST_F(StabilityMetricsProviderTest, ProvideStabilityMetrics) { + StabilityMetricsProvider stability_provider(&prefs_); + MetricsProvider* provider = &stability_provider; + SystemProfileProto system_profile; + provider->ProvideStabilityMetrics(&system_profile); + + const SystemProfileProto_Stability& stability = system_profile.stability(); + // Initial log metrics: only expected if non-zero. + EXPECT_FALSE(stability.has_launch_count()); + EXPECT_FALSE(stability.has_crash_count()); + 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()); +} + +TEST_F(StabilityMetricsProviderTest, RecordStabilityMetrics) { + { + StabilityMetricsProvider recorder(&prefs_); + recorder.LogLaunch(); + recorder.LogCrash(base::Time()); + recorder.MarkSessionEndCompleted(false); + recorder.CheckLastSessionEndCompleted(); + recorder.RecordBreakpadRegistration(true); + recorder.RecordBreakpadRegistration(false); + recorder.RecordBreakpadHasDebugger(true); + recorder.RecordBreakpadHasDebugger(false); + } + + { + StabilityMetricsProvider stability_provider(&prefs_); + MetricsProvider* provider = &stability_provider; + SystemProfileProto system_profile; + provider->ProvideStabilityMetrics(&system_profile); + + const SystemProfileProto_Stability& stability = system_profile.stability(); + // Initial log metrics: only expected if non-zero. + EXPECT_EQ(1, stability.launch_count()); + EXPECT_EQ(1, stability.crash_count()); + EXPECT_EQ(1, stability.incomplete_shutdown_count()); + EXPECT_EQ(1, stability.breakpad_registration_success_count()); + EXPECT_EQ(1, stability.breakpad_registration_failure_count()); + EXPECT_EQ(1, stability.debugger_present_count()); + EXPECT_EQ(1, stability.debugger_not_present_count()); + } +} + +#if defined(OS_WIN) +namespace { + +class TestingStabilityMetricsProvider : public StabilityMetricsProvider { + public: + TestingStabilityMetricsProvider(PrefService* local_state, + base::Time unclean_session_time) + : StabilityMetricsProvider(local_state), + unclean_session_time_(unclean_session_time) {} + + bool IsUncleanSystemSession(base::Time last_live_timestamp) override { + return last_live_timestamp == unclean_session_time_; + } + + private: + const base::Time unclean_session_time_; +}; + +} // namespace + +TEST_F(StabilityMetricsProviderTest, RecordSystemCrashMetrics) { + { + base::Time unclean_time = base::Time::Now(); + TestingStabilityMetricsProvider recorder(&prefs_, unclean_time); + + // Any crash with a last_live_timestamp equal to unclean_time will + // be logged as a system crash as per the implementation of + // TestingStabilityMetricsProvider, so this will log a system crash. + recorder.LogCrash(unclean_time); + + // Record a crash with no system crash. + recorder.LogCrash(unclean_time - base::TimeDelta::FromMinutes(1)); + } + + { + StabilityMetricsProvider stability_provider(&prefs_); + MetricsProvider* provider = &stability_provider; + SystemProfileProto system_profile; + + base::HistogramTester histogram_tester; + + provider->ProvideStabilityMetrics(&system_profile); + + const SystemProfileProto_Stability& stability = system_profile.stability(); + // Two crashes, one system crash. + EXPECT_EQ(2, stability.crash_count()); + + histogram_tester.ExpectTotalCount("Stability.Internals.SystemCrashCount", + 1); + } +} + +#endif + +} // namespace metrics diff --git a/components/metrics/system_memory_stats_recorder.h b/components/metrics/system_memory_stats_recorder.h new file mode 100644 index 0000000000000..bdd30f00f4252 --- /dev/null +++ b/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/components/metrics/system_memory_stats_recorder_linux.cc b/components/metrics/system_memory_stats_recorder_linux.cc new file mode 100644 index 0000000000000..c69dbaa0460c2 --- /dev/null +++ b/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/components/metrics/system_memory_stats_recorder_win.cc b/components/metrics/system_memory_stats_recorder_win.cc new file mode 100644 index 0000000000000..7d011821a4dbd --- /dev/null +++ b/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 + +#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, 1, 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() << "Received unexpected notification"; + break; + } +} + +} // namespace metrics diff --git a/components/metrics/system_session_analyzer_win.cc b/components/metrics/system_session_analyzer_win.cc new file mode 100644 index 0000000000000..e64501f8b55f2 --- /dev/null +++ b/components/metrics/system_session_analyzer_win.cc @@ -0,0 +1,263 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/metrics/system_session_analyzer_win.h" + +#include "base/macros.h" +#include "base/time/time.h" + +namespace metrics { + +namespace { + +// The name of the log channel to query. +const wchar_t kChannelName[] = L"System"; + +// Event ids of system startup / shutdown events. These were obtained from +// inspection of the System log in Event Viewer on Windows 10: +// - id 6005: "The Event log service was started." +// - id 6006: "The Event log service was stopped." +// - id 6008: "The previous system shutdown at