summaryrefslogtreecommitdiff
path: root/chromium/components/metrics
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/metrics')
-rw-r--r--chromium/components/metrics/BUILD.gn367
-rw-r--r--chromium/components/metrics/DEPS14
-rw-r--r--chromium/components/metrics/OWNERS6
-rw-r--r--chromium/components/metrics/README23
-rw-r--r--chromium/components/metrics/call_stack_profile_metrics_provider.cc396
-rw-r--r--chromium/components/metrics/call_stack_profile_metrics_provider.h83
-rw-r--r--chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc619
-rw-r--r--chromium/components/metrics/clean_exit_beacon.cc80
-rw-r--r--chromium/components/metrics/clean_exit_beacon.h45
-rw-r--r--chromium/components/metrics/client_info.cc13
-rw-r--r--chromium/components/metrics/client_info.h36
-rw-r--r--chromium/components/metrics/cloned_install_detector.cc101
-rw-r--r--chromium/components/metrics/cloned_install_detector.h60
-rw-r--r--chromium/components/metrics/cloned_install_detector_unittest.cc53
-rw-r--r--chromium/components/metrics/daily_event.cc106
-rw-r--r--chromium/components/metrics/daily_event.h92
-rw-r--r--chromium/components/metrics/daily_event_unittest.cc94
-rw-r--r--chromium/components/metrics/data_use_tracker.cc205
-rw-r--r--chromium/components/metrics/data_use_tracker.h99
-rw-r--r--chromium/components/metrics/data_use_tracker_unittest.cc206
-rw-r--r--chromium/components/metrics/drive_metrics_provider.cc97
-rw-r--r--chromium/components/metrics/drive_metrics_provider.h100
-rw-r--r--chromium/components/metrics/drive_metrics_provider_android.cc16
-rw-r--r--chromium/components/metrics/drive_metrics_provider_ios.mm16
-rw-r--r--chromium/components/metrics/drive_metrics_provider_linux.cc65
-rw-r--r--chromium/components/metrics/drive_metrics_provider_mac.mm76
-rw-r--r--chromium/components/metrics/drive_metrics_provider_unittest.cc20
-rw-r--r--chromium/components/metrics/drive_metrics_provider_win.cc52
-rw-r--r--chromium/components/metrics/file_metrics_provider.cc291
-rw-r--r--chromium/components/metrics/file_metrics_provider.h157
-rw-r--r--chromium/components/metrics/file_metrics_provider_unittest.cc192
-rw-r--r--chromium/components/metrics/gpu/DEPS4
-rw-r--r--chromium/components/metrics/gpu/gpu_metrics_provider.cc36
-rw-r--r--chromium/components/metrics/gpu/gpu_metrics_provider.h29
-rw-r--r--chromium/components/metrics/histogram_encoder.cc57
-rw-r--r--chromium/components/metrics/histogram_encoder.h29
-rw-r--r--chromium/components/metrics/histogram_encoder_unittest.cc71
-rw-r--r--chromium/components/metrics/leak_detector/OWNERS2
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_manager.cc72
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_manager.h102
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc260
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_table.cc77
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_table.h87
-rw-r--r--chromium/components/metrics/leak_detector/call_stack_table_unittest.cc364
-rw-r--r--chromium/components/metrics/leak_detector/custom_allocator.cc61
-rw-r--r--chromium/components/metrics/leak_detector/custom_allocator.h50
-rw-r--r--chromium/components/metrics/leak_detector/leak_analyzer.cc139
-rw-r--r--chromium/components/metrics/leak_detector/leak_analyzer.h87
-rw-r--r--chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc366
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector.cc340
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector.h174
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_impl.cc241
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_impl.h185
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc629
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_unittest.cc119
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_value_type.cc47
-rw-r--r--chromium/components/metrics/leak_detector/leak_detector_value_type.h52
-rw-r--r--chromium/components/metrics/leak_detector/ranked_set.cc52
-rw-r--r--chromium/components/metrics/leak_detector/ranked_set.h96
-rw-r--r--chromium/components/metrics/leak_detector/ranked_set_unittest.cc324
-rw-r--r--chromium/components/metrics/leak_detector/stl_allocator.h61
-rw-r--r--chromium/components/metrics/machine_id_provider.h46
-rw-r--r--chromium/components/metrics/machine_id_provider_stub.cc24
-rw-r--r--chromium/components/metrics/machine_id_provider_win.cc118
-rw-r--r--chromium/components/metrics/machine_id_provider_win_unittest.cc28
-rw-r--r--chromium/components/metrics/metrics_log.cc423
-rw-r--r--chromium/components/metrics/metrics_log.h209
-rw-r--r--chromium/components/metrics/metrics_log_manager.cc130
-rw-r--r--chromium/components/metrics/metrics_log_manager.h118
-rw-r--r--chromium/components/metrics/metrics_log_manager_unittest.cc306
-rw-r--r--chromium/components/metrics/metrics_log_unittest.cc465
-rw-r--r--chromium/components/metrics/metrics_log_uploader.cc21
-rw-r--r--chromium/components/metrics/metrics_log_uploader.h44
-rw-r--r--chromium/components/metrics/metrics_pref_names.cc181
-rw-r--r--chromium/components/metrics/metrics_pref_names.h64
-rw-r--r--chromium/components/metrics/metrics_provider.cc54
-rw-r--r--chromium/components/metrics/metrics_provider.h86
-rw-r--r--chromium/components/metrics/metrics_reporting_scheduler.cc181
-rw-r--r--chromium/components/metrics/metrics_reporting_scheduler.h99
-rw-r--r--chromium/components/metrics/metrics_reporting_scheduler_unittest.cc71
-rw-r--r--chromium/components/metrics/metrics_service.cc1193
-rw-r--r--chromium/components/metrics/metrics_service.h501
-rw-r--r--chromium/components/metrics/metrics_service_accessor.cc70
-rw-r--r--chromium/components/metrics/metrics_service_accessor.h77
-rw-r--r--chromium/components/metrics/metrics_service_client.cc26
-rw-r--r--chromium/components/metrics/metrics_service_client.h127
-rw-r--r--chromium/components/metrics/metrics_service_unittest.cc413
-rw-r--r--chromium/components/metrics/metrics_state_manager.cc314
-rw-r--r--chromium/components/metrics/metrics_state_manager.h173
-rw-r--r--chromium/components/metrics/metrics_state_manager_unittest.cc386
-rw-r--r--chromium/components/metrics/metrics_switches.cc15
-rw-r--r--chromium/components/metrics/metrics_switches.h18
-rw-r--r--chromium/components/metrics/net/DEPS6
-rw-r--r--chromium/components/metrics/net/net_metrics_log_uploader.cc77
-rw-r--r--chromium/components/metrics/net/net_metrics_log_uploader.h55
-rw-r--r--chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc59
-rw-r--r--chromium/components/metrics/net/network_metrics_provider.cc265
-rw-r--r--chromium/components/metrics/net/network_metrics_provider.h88
-rw-r--r--chromium/components/metrics/net/version_utils.cc41
-rw-r--r--chromium/components/metrics/net/version_utils.h29
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider.cc25
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider.h54
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc123
-rw-r--r--chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h48
-rw-r--r--chromium/components/metrics/persisted_logs.cc171
-rw-r--r--chromium/components/metrics/persisted_logs.h143
-rw-r--r--chromium/components/metrics/persisted_logs_unittest.cc289
-rw-r--r--chromium/components/metrics/profiler/content/DEPS6
-rw-r--r--chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc97
-rw-r--r--chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h52
-rw-r--r--chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc35
-rw-r--r--chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h40
-rw-r--r--chromium/components/metrics/profiler/profiler_metrics_provider.cc135
-rw-r--r--chromium/components/metrics/profiler/profiler_metrics_provider.h65
-rw-r--r--chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc277
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer.cc378
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer.h167
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_delegate.h31
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_observer.cc21
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_observer.h88
-rw-r--r--chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc154
-rw-r--r--chromium/components/metrics/proto/BUILD.gn24
-rw-r--r--chromium/components/metrics/proto/call_stack_profile.proto63
-rw-r--r--chromium/components/metrics/proto/cast_logs.proto182
-rw-r--r--chromium/components/metrics/proto/chrome_user_metrics_extension.proto76
-rw-r--r--chromium/components/metrics/proto/histogram_event.proto49
-rw-r--r--chromium/components/metrics/proto/memory_leak_report.proto81
-rw-r--r--chromium/components/metrics/proto/omnibox_event.proto286
-rw-r--r--chromium/components/metrics/proto/omnibox_input_type.proto39
-rw-r--r--chromium/components/metrics/proto/perf_data.proto412
-rw-r--r--chromium/components/metrics/proto/perf_stat.proto52
-rw-r--r--chromium/components/metrics/proto/profiler_event.proto127
-rw-r--r--chromium/components/metrics/proto/sampled_profile.proto83
-rw-r--r--chromium/components/metrics/proto/system_profile.proto775
-rw-r--r--chromium/components/metrics/proto/user_action_event.proto23
-rw-r--r--chromium/components/metrics/serialization/metric_sample.cc197
-rw-r--r--chromium/components/metrics/serialization/metric_sample.h118
-rw-r--r--chromium/components/metrics/serialization/serialization_utils.cc224
-rw-r--r--chromium/components/metrics/serialization/serialization_utils.h48
-rw-r--r--chromium/components/metrics/serialization/serialization_utils_unittest.cc171
-rw-r--r--chromium/components/metrics/stability_metrics_helper.cc221
-rw-r--r--chromium/components/metrics/stability_metrics_helper.h63
-rw-r--r--chromium/components/metrics/stability_metrics_helper_unittest.cc96
-rw-r--r--chromium/components/metrics/system_memory_stats_recorder.h30
-rw-r--r--chromium/components/metrics/system_memory_stats_recorder_linux.cc98
-rw-r--r--chromium/components/metrics/system_memory_stats_recorder_win.cc47
-rw-r--r--chromium/components/metrics/test_metrics_provider.cc35
-rw-r--r--chromium/components/metrics/test_metrics_provider.h57
-rw-r--r--chromium/components/metrics/test_metrics_service_client.cc93
-rw-r--r--chromium/components/metrics/test_metrics_service_client.h69
-rw-r--r--chromium/components/metrics/ui/DEPS3
-rw-r--r--chromium/components/metrics/ui/screen_info_metrics_provider.cc92
-rw-r--r--chromium/components/metrics/ui/screen_info_metrics_provider.h42
-rw-r--r--chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc66
-rw-r--r--chromium/components/metrics/url_constants.cc13
-rw-r--r--chromium/components/metrics/url_constants.h18
156 files changed, 21066 insertions, 0 deletions
diff --git a/chromium/components/metrics/BUILD.gn b/chromium/components/metrics/BUILD.gn
new file mode 100644
index 00000000000..224070db0d4
--- /dev/null
+++ b/chromium/components/metrics/BUILD.gn
@@ -0,0 +1,367 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+ # Overrides os name in uma metrics log to "Blimp".
+ metrics_use_blimp = false
+}
+
+# GYP version: components/metrics.gypi:metrics
+source_set("metrics") {
+ sources = [
+ "call_stack_profile_metrics_provider.cc",
+ "call_stack_profile_metrics_provider.h",
+ "clean_exit_beacon.cc",
+ "clean_exit_beacon.h",
+ "client_info.cc",
+ "client_info.h",
+ "cloned_install_detector.cc",
+ "cloned_install_detector.h",
+ "daily_event.cc",
+ "daily_event.h",
+ "data_use_tracker.cc",
+ "data_use_tracker.h",
+ "drive_metrics_provider.cc",
+ "drive_metrics_provider.h",
+ "drive_metrics_provider_android.cc",
+ "drive_metrics_provider_ios.mm",
+ "drive_metrics_provider_linux.cc",
+ "drive_metrics_provider_mac.mm",
+ "drive_metrics_provider_win.cc",
+ "file_metrics_provider.cc",
+ "file_metrics_provider.h",
+ "histogram_encoder.cc",
+ "histogram_encoder.h",
+ "machine_id_provider.h",
+ "machine_id_provider_stub.cc",
+ "machine_id_provider_win.cc",
+ "metrics_log.cc",
+ "metrics_log.h",
+ "metrics_log_manager.cc",
+ "metrics_log_manager.h",
+ "metrics_log_uploader.cc",
+ "metrics_log_uploader.h",
+ "metrics_pref_names.cc",
+ "metrics_pref_names.h",
+ "metrics_provider.cc",
+ "metrics_provider.h",
+ "metrics_reporting_scheduler.cc",
+ "metrics_reporting_scheduler.h",
+ "metrics_service.cc",
+ "metrics_service.h",
+ "metrics_service_accessor.cc",
+ "metrics_service_accessor.h",
+ "metrics_service_client.cc",
+ "metrics_service_client.h",
+ "metrics_state_manager.cc",
+ "metrics_state_manager.h",
+ "metrics_switches.cc",
+ "metrics_switches.h",
+ "persisted_logs.cc",
+ "persisted_logs.h",
+ "stability_metrics_helper.cc",
+ "stability_metrics_helper.h",
+ "system_memory_stats_recorder.h",
+ "system_memory_stats_recorder_linux.cc",
+ "system_memory_stats_recorder_win.cc",
+ "url_constants.cc",
+ "url_constants.h",
+ ]
+
+ public_deps = [
+ "//components/metrics/proto",
+ ]
+ deps = [
+ "//base",
+ "//base:base_static",
+ "//components/prefs",
+ "//components/variations",
+ "//third_party/zlib:compression_utils",
+ ]
+
+ if (is_chromeos) {
+ deps += [ ":serialization" ]
+ }
+
+ if (is_mac) {
+ libs = [
+ # The below are all needed for drive_metrics_provider_mac.mm.
+ "CoreFoundation.framework",
+ "DiskArbitration.framework",
+ "Foundation.framework",
+ "IOKit.framework",
+ ]
+ }
+
+ if (is_win) {
+ sources -= [ "machine_id_provider_stub.cc" ]
+ }
+}
+
+if (!is_ios) {
+ # GYP version: components/metrics.gypi:metrics_gpu
+ source_set("gpu") {
+ sources = [
+ "gpu/gpu_metrics_provider.cc",
+ "gpu/gpu_metrics_provider.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ "//content/public/browser",
+ "//gpu/config",
+ ]
+ }
+}
+
+if (is_chromeos) {
+ # GYP version: components/metrics.gypi:metrics_leak_detector
+ source_set("leak_detector") {
+ sources = [
+ "leak_detector/call_stack_manager.cc",
+ "leak_detector/call_stack_manager.h",
+ "leak_detector/call_stack_table.cc",
+ "leak_detector/call_stack_table.h",
+ "leak_detector/custom_allocator.cc",
+ "leak_detector/custom_allocator.h",
+ "leak_detector/leak_analyzer.cc",
+ "leak_detector/leak_analyzer.h",
+ "leak_detector/leak_detector.cc",
+ "leak_detector/leak_detector.h",
+ "leak_detector/leak_detector_impl.cc",
+ "leak_detector/leak_detector_impl.h",
+ "leak_detector/leak_detector_value_type.cc",
+ "leak_detector/leak_detector_value_type.h",
+ "leak_detector/ranked_set.cc",
+ "leak_detector/ranked_set.h",
+ ]
+
+ deps = [
+ "//base",
+ "//content/public/browser",
+ ]
+ }
+}
+
+# GYP version: components/metrics.gypi:metrics_net
+static_library("net") {
+ sources = [
+ "net/net_metrics_log_uploader.cc",
+ "net/net_metrics_log_uploader.h",
+ "net/network_metrics_provider.cc",
+ "net/network_metrics_provider.h",
+ "net/version_utils.cc",
+ "net/version_utils.h",
+ "net/wifi_access_point_info_provider.cc",
+ "net/wifi_access_point_info_provider.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ allow_circular_includes_from = [ ":metrics" ]
+
+ deps = [
+ "//base",
+ "//components/data_use_measurement/core",
+ "//components/version_info",
+ "//net",
+ "//url",
+ ]
+
+ if (is_chromeos) {
+ sources += [
+ "net/wifi_access_point_info_provider_chromeos.cc",
+ "net/wifi_access_point_info_provider_chromeos.h",
+ ]
+ deps += [ "//chromeos" ]
+ }
+}
+
+# GYP version: components/metrics.gypi:metrics_profiler
+source_set("profiler") {
+ sources = [
+ "profiler/profiler_metrics_provider.cc",
+ "profiler/profiler_metrics_provider.h",
+ "profiler/tracking_synchronizer.cc",
+ "profiler/tracking_synchronizer.h",
+ "profiler/tracking_synchronizer_delegate.h",
+ "profiler/tracking_synchronizer_observer.cc",
+ "profiler/tracking_synchronizer_observer.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ "//components/variations",
+ ]
+}
+
+# GYP version: components/metrics.gypi:metrics_ui
+source_set("ui") {
+ sources = [
+ "ui/screen_info_metrics_provider.cc",
+ "ui/screen_info_metrics_provider.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+}
+
+if (!is_ios) {
+ # GYP version: components/metrics.gypi:metrics_profiler_content
+ source_set("profiler_content") {
+ sources = [
+ "profiler/content/content_tracking_synchronizer_delegate.cc",
+ "profiler/content/content_tracking_synchronizer_delegate.h",
+ ]
+
+ public_deps = [
+ ":profiler",
+ ]
+ deps = [
+ "//base",
+ "//components/nacl/common:process_type",
+ "//content/public/browser",
+ "//content/public/common",
+ ]
+ }
+} else {
+ # GYP version: components/metrics.gypi:metrics_profiler_ios
+ source_set("profiler_ios") {
+ sources = [
+ "profiler/ios/ios_tracking_synchronizer_delegate.cc",
+ "profiler/ios/ios_tracking_synchronizer_delegate.h",
+ ]
+
+ public_deps = [
+ ":profiler",
+ ]
+ deps = [
+ "//base",
+ ]
+ }
+}
+
+# GYP version: components/metrics.gypi:metrics_test_support
+source_set("test_support") {
+ sources = [
+ "test_metrics_provider.cc",
+ "test_metrics_provider.h",
+ "test_metrics_service_client.cc",
+ "test_metrics_service_client.h",
+ ]
+
+ public_deps = [
+ ":metrics",
+ ]
+ deps = [
+ "//base",
+ ]
+}
+
+if (is_linux) {
+ # GYP version: components/metrics.gypi:metrics_serialization
+ source_set("serialization") {
+ sources = [
+ "serialization/metric_sample.cc",
+ "serialization/metric_sample.h",
+ "serialization/serialization_utils.cc",
+ "serialization/serialization_utils.h",
+ ]
+ deps = [
+ "//base",
+ ]
+ }
+}
+
+if (is_chromeos) {
+ source_set("leak_detector_unit_tests") {
+ testonly = true
+ sources = [
+ "leak_detector/call_stack_manager_unittest.cc",
+ "leak_detector/call_stack_table_unittest.cc",
+ "leak_detector/leak_analyzer_unittest.cc",
+ "leak_detector/leak_detector_impl_unittest.cc",
+ "leak_detector/leak_detector_unittest.cc",
+ "leak_detector/ranked_set_unittest.cc",
+ ]
+
+ deps = [
+ ":leak_detector",
+ "//base",
+ "//content/test:test_support",
+ "//testing/gtest",
+ ]
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "call_stack_profile_metrics_provider_unittest.cc",
+ "cloned_install_detector_unittest.cc",
+ "daily_event_unittest.cc",
+ "data_use_tracker_unittest.cc",
+ "drive_metrics_provider_unittest.cc",
+ "histogram_encoder_unittest.cc",
+ "machine_id_provider_win_unittest.cc",
+ "metrics_log_manager_unittest.cc",
+ "metrics_log_unittest.cc",
+ "metrics_reporting_scheduler_unittest.cc",
+ "metrics_service_unittest.cc",
+ "metrics_state_manager_unittest.cc",
+ "net/net_metrics_log_uploader_unittest.cc",
+ "persisted_logs_unittest.cc",
+ "profiler/profiler_metrics_provider_unittest.cc",
+ "profiler/tracking_synchronizer_unittest.cc",
+ "stability_metrics_helper_unittest.cc",
+ "ui/screen_info_metrics_provider_unittest.cc",
+ ]
+
+ deps = [
+ ":metrics",
+ ":net",
+ ":profiler",
+ ":test_support",
+ ":ui",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//components/variations",
+ "//net:test_support",
+ "//testing/gtest",
+ "//third_party/zlib:compression_utils",
+ "//ui/gfx/geometry",
+ ]
+
+ if (is_linux) {
+ sources += [ "serialization/serialization_utils_unittest.cc" ]
+ deps += [ ":serialization" ]
+ }
+
+ if (is_chromeos) {
+ deps += [ ":leak_detector_unit_tests" ]
+ }
+
+ # These are only used by the blimp team, which has entirely migrated to gn,
+ # so this logic is not replicated in the gyp file.
+ if (metrics_use_blimp) {
+ defines = [
+ "OVERRIDE_OS_NAME_TO_BLIMP",
+ "ENABLE_REPORTING_BLIMP",
+ ]
+ }
+}
+# TODO(GYP): metrics_chromeos
diff --git a/chromium/components/metrics/DEPS b/chromium/components/metrics/DEPS
new file mode 100644
index 00000000000..8960a59060b
--- /dev/null
+++ b/chromium/components/metrics/DEPS
@@ -0,0 +1,14 @@
+# This component is shared with the Chrome OS build, so it's important to limit
+# dependencies to a minimal set.
+include_rules = [
+ "-components",
+ "+components/compression",
+ "+components/metrics",
+ "+components/prefs",
+ "+components/variations",
+ "+components/version_info",
+ "+content/public/browser",
+ "+content/public/test",
+ "+third_party/zlib/google",
+ "-net",
+]
diff --git a/chromium/components/metrics/OWNERS b/chromium/components/metrics/OWNERS
new file mode 100644
index 00000000000..e17cf1464a7
--- /dev/null
+++ b/chromium/components/metrics/OWNERS
@@ -0,0 +1,6 @@
+asvitkine@chromium.org
+holte@chromium.org
+isherman@chromium.org
+jar@chromium.org
+mpearson@chromium.org
+rkaplow@chromium.org
diff --git a/chromium/components/metrics/README b/chromium/components/metrics/README
new file mode 100644
index 00000000000..5a2abbb2f69
--- /dev/null
+++ b/chromium/components/metrics/README
@@ -0,0 +1,23 @@
+This component contains the base classes for the metrics service and only
+depends on //base. It is used by ChromeOS as the base for a standalone service
+that will upload the metrics when ChromeOS is not installed (headless install).
+
+This is the first step towards the componentization of metrics that will happen
+later this spring.
+
+A proposed structure for the metrics component is:
+//components/metrics/base,
+ Depends on base only. Contains the protobuf definitions.
+//components/metrics/core
+ Depends on everything iOS depends on
+//components/metrics/content
+ Depends on content
+
+Ideally, the component would abstract the network stack and have a clean
+separation between the metrics upload logic (protbuf generation, retry, etc...),
+the chrome part (gathering histogram from all the threads, populating the
+log with hardware characteristics, plugin state, etc.).
+
+It is a plus if the code currently in the component (i.e., the code that can
+depend only on //base) stays in a single directory as it would be easier
+for ChromeOS to pull it :).
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.cc b/chromium/components/metrics/call_stack_profile_metrics_provider.cc
new file mode 100644
index 00000000000..7ad942d3fca
--- /dev/null
+++ b/chromium/components/metrics/call_stack_profile_metrics_provider.cc
@@ -0,0 +1,396 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cstring>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+using base::StackSamplingProfiler;
+
+namespace metrics {
+
+namespace {
+
+// ProfilesState --------------------------------------------------------------
+
+// A set of profiles and the CallStackProfileMetricsProvider state associated
+// with them.
+struct ProfilesState {
+ ProfilesState(const CallStackProfileMetricsProvider::Params& params,
+ const base::StackSamplingProfiler::CallStackProfiles& profiles,
+ base::TimeTicks start_timestamp);
+
+ // The metrics-related parameters provided to
+ // CallStackProfileMetricsProvider::GetProfilerCallback().
+ CallStackProfileMetricsProvider::Params params;
+
+ // The call stack profiles collected by the profiler.
+ base::StackSamplingProfiler::CallStackProfiles profiles;
+
+ // The time at which the CallStackProfileMetricsProvider became aware of the
+ // request for profiling. In particular, this is when callback was requested
+ // via CallStackProfileMetricsProvider::GetProfilerCallback(). Used to
+ // determine if collection was disabled during the collection of the profile.
+ base::TimeTicks start_timestamp;
+};
+
+ProfilesState::ProfilesState(
+ const CallStackProfileMetricsProvider::Params& params,
+ const base::StackSamplingProfiler::CallStackProfiles& profiles,
+ base::TimeTicks start_timestamp)
+ : params(params),
+ profiles(profiles),
+ start_timestamp(start_timestamp) {
+}
+
+// PendingProfiles ------------------------------------------------------------
+
+// Singleton class responsible for retaining profiles received via the callback
+// created by CallStackProfileMetricsProvider::GetProfilerCallback(). These are
+// then sent to UMA on the invocation of
+// CallStackProfileMetricsProvider::ProvideGeneralMetrics(). We need to store
+// the profiles outside of a CallStackProfileMetricsProvider instance since
+// callers may start profiling before the CallStackProfileMetricsProvider is
+// created.
+//
+// Member functions on this class may be called on any thread.
+class PendingProfiles {
+ public:
+ static PendingProfiles* GetInstance();
+
+ void Clear();
+ void Swap(std::vector<ProfilesState>* profiles);
+
+ // Enables the collection of profiles by CollectProfilesIfCollectionEnabled if
+ // |enabled| is true. Otherwise, clears current profiles and ignores profiles
+ // provided to future invocations of CollectProfilesIfCollectionEnabled.
+ void SetCollectionEnabled(bool enabled);
+
+ // True if profiles are being collected.
+ bool IsCollectionEnabled() const;
+
+ // Adds |profile| to the list of profiles if collection is enabled.
+ void CollectProfilesIfCollectionEnabled(const ProfilesState& profiles);
+
+ // Allows testing against the initial state multiple times.
+ void ResetToDefaultStateForTesting();
+
+ private:
+ friend struct base::DefaultSingletonTraits<PendingProfiles>;
+
+ PendingProfiles();
+ ~PendingProfiles();
+
+ mutable base::Lock lock_;
+
+ // If true, profiles provided to CollectProfilesIfCollectionEnabled should be
+ // collected. Otherwise they will be ignored.
+ bool collection_enabled_;
+
+ // The last time collection was disabled. Used to determine if collection was
+ // disabled at any point since a profile was started.
+ base::TimeTicks last_collection_disable_time_;
+
+ // The set of completed profiles that should be reported.
+ std::vector<ProfilesState> profiles_;
+
+ DISALLOW_COPY_AND_ASSIGN(PendingProfiles);
+};
+
+// static
+PendingProfiles* PendingProfiles::GetInstance() {
+ // Leaky for performance rather than correctness reasons.
+ return base::Singleton<PendingProfiles,
+ base::LeakySingletonTraits<PendingProfiles>>::get();
+}
+
+void PendingProfiles::Clear() {
+ base::AutoLock scoped_lock(lock_);
+ profiles_.clear();
+}
+
+void PendingProfiles::Swap(std::vector<ProfilesState>* profiles) {
+ base::AutoLock scoped_lock(lock_);
+ profiles_.swap(*profiles);
+}
+
+void PendingProfiles::SetCollectionEnabled(bool enabled) {
+ base::AutoLock scoped_lock(lock_);
+
+ collection_enabled_ = enabled;
+
+ if (!collection_enabled_) {
+ profiles_.clear();
+ last_collection_disable_time_ = base::TimeTicks::Now();
+ }
+}
+
+bool PendingProfiles::IsCollectionEnabled() const {
+ base::AutoLock scoped_lock(lock_);
+ return collection_enabled_;
+}
+
+void PendingProfiles::CollectProfilesIfCollectionEnabled(
+ const ProfilesState& profiles) {
+ base::AutoLock scoped_lock(lock_);
+
+ // Only collect if collection is not disabled and hasn't been disabled
+ // since the start of collection for this profile.
+ if (!collection_enabled_ ||
+ (!last_collection_disable_time_.is_null() &&
+ last_collection_disable_time_ >= profiles.start_timestamp)) {
+ return;
+ }
+
+ profiles_.push_back(profiles);
+}
+
+void PendingProfiles::ResetToDefaultStateForTesting() {
+ base::AutoLock scoped_lock(lock_);
+
+ collection_enabled_ = true;
+ last_collection_disable_time_ = base::TimeTicks();
+ profiles_.clear();
+}
+
+// |collection_enabled_| is initialized to true to collect any profiles that are
+// generated prior to creation of the CallStackProfileMetricsProvider. The
+// ultimate disposition of these pre-creation collected profiles will be
+// determined by the initial recording state provided to
+// CallStackProfileMetricsProvider.
+PendingProfiles::PendingProfiles() : collection_enabled_(true) {}
+
+PendingProfiles::~PendingProfiles() {}
+
+// Functions to process completed profiles ------------------------------------
+
+// Invoked on the profiler's thread. Provides the profiles to PendingProfiles to
+// append, if the collecting state allows.
+void ReceiveCompletedProfiles(
+ const CallStackProfileMetricsProvider::Params& params,
+ base::TimeTicks start_timestamp,
+ const StackSamplingProfiler::CallStackProfiles& profiles) {
+ PendingProfiles::GetInstance()->CollectProfilesIfCollectionEnabled(
+ ProfilesState(params, profiles, start_timestamp));
+}
+
+// Invoked on an arbitrary thread. Ignores the provided profiles.
+void IgnoreCompletedProfiles(
+ const StackSamplingProfiler::CallStackProfiles& profiles) {
+}
+
+// Functions to encode protobufs ----------------------------------------------
+
+// The protobuf expects the MD5 checksum prefix of the module name.
+uint64_t HashModuleFilename(const base::FilePath& filename) {
+ const base::FilePath::StringType basename = filename.BaseName().value();
+ // Copy the bytes in basename into a string buffer.
+ size_t basename_length_in_bytes =
+ basename.size() * sizeof(base::FilePath::CharType);
+ std::string name_bytes(basename_length_in_bytes, '\0');
+ memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
+ return base::HashMetricName(name_bytes);
+}
+
+// Transcode |sample| into |proto_sample|, using base addresses in |modules| to
+// compute module instruction pointer offsets.
+void CopySampleToProto(
+ const StackSamplingProfiler::Sample& sample,
+ const std::vector<StackSamplingProfiler::Module>& modules,
+ CallStackProfile::Sample* proto_sample) {
+ for (const StackSamplingProfiler::Frame& frame : sample) {
+ CallStackProfile::Entry* entry = proto_sample->add_entry();
+ // A frame may not have a valid module. If so, we can't compute the
+ // instruction pointer offset, and we don't want to send bare pointers, so
+ // leave call_stack_entry empty.
+ if (frame.module_index == StackSamplingProfiler::Frame::kUnknownModuleIndex)
+ continue;
+ int64_t module_offset =
+ reinterpret_cast<const char*>(frame.instruction_pointer) -
+ reinterpret_cast<const char*>(modules[frame.module_index].base_address);
+ DCHECK_GE(module_offset, 0);
+ entry->set_address(static_cast<uint64_t>(module_offset));
+ entry->set_module_id_index(frame.module_index);
+ }
+}
+
+// Transcode |profile| into |proto_profile|.
+void CopyProfileToProto(
+ const StackSamplingProfiler::CallStackProfile& profile,
+ bool preserve_sample_ordering,
+ CallStackProfile* proto_profile) {
+ if (profile.samples.empty())
+ return;
+
+ if (preserve_sample_ordering) {
+ // Collapse only consecutive repeated samples together.
+ CallStackProfile::Sample* current_sample_proto = nullptr;
+ for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) {
+ if (!current_sample_proto || *it != *(it - 1)) {
+ current_sample_proto = proto_profile->add_sample();
+ CopySampleToProto(*it, profile.modules, current_sample_proto);
+ current_sample_proto->set_count(1);
+ } else {
+ current_sample_proto->set_count(current_sample_proto->count() + 1);
+ }
+ }
+ } else {
+ // Collapse all repeated samples together.
+ std::map<StackSamplingProfiler::Sample, int> sample_index;
+ for (auto it = profile.samples.begin(); it != profile.samples.end(); ++it) {
+ auto location = sample_index.find(*it);
+ if (location == sample_index.end()) {
+ CallStackProfile::Sample* sample_proto = proto_profile->add_sample();
+ CopySampleToProto(*it, profile.modules, sample_proto);
+ sample_proto->set_count(1);
+ sample_index.insert(
+ std::make_pair(
+ *it, static_cast<int>(proto_profile->sample().size()) - 1));
+ } else {
+ CallStackProfile::Sample* sample_proto =
+ proto_profile->mutable_sample()->Mutable(location->second);
+ sample_proto->set_count(sample_proto->count() + 1);
+ }
+ }
+ }
+
+ for (const StackSamplingProfiler::Module& module : profile.modules) {
+ CallStackProfile::ModuleIdentifier* module_id =
+ proto_profile->add_module_id();
+ module_id->set_build_id(module.id);
+ module_id->set_name_md5_prefix(HashModuleFilename(module.filename));
+ }
+
+ proto_profile->set_profile_duration_ms(
+ profile.profile_duration.InMilliseconds());
+ proto_profile->set_sampling_period_ms(
+ profile.sampling_period.InMilliseconds());
+}
+
+// Translates CallStackProfileMetricsProvider's trigger to the corresponding
+// SampledProfile TriggerEvent.
+SampledProfile::TriggerEvent ToSampledProfileTriggerEvent(
+ CallStackProfileMetricsProvider::Trigger trigger) {
+ switch (trigger) {
+ case CallStackProfileMetricsProvider::UNKNOWN:
+ return SampledProfile::UNKNOWN_TRIGGER_EVENT;
+ break;
+ case CallStackProfileMetricsProvider::PROCESS_STARTUP:
+ return SampledProfile::PROCESS_STARTUP;
+ break;
+ case CallStackProfileMetricsProvider::JANKY_TASK:
+ return SampledProfile::JANKY_TASK;
+ break;
+ case CallStackProfileMetricsProvider::THREAD_HUNG:
+ return SampledProfile::THREAD_HUNG;
+ break;
+ }
+ NOTREACHED();
+ return SampledProfile::UNKNOWN_TRIGGER_EVENT;
+}
+
+} // namespace
+
+// CallStackProfileMetricsProvider::Params ------------------------------------
+
+CallStackProfileMetricsProvider::Params::Params(
+ CallStackProfileMetricsProvider::Trigger trigger)
+ : Params(trigger, false) {
+}
+
+CallStackProfileMetricsProvider::Params::Params(
+ CallStackProfileMetricsProvider::Trigger trigger,
+ bool preserve_sample_ordering)
+ : trigger(trigger),
+ preserve_sample_ordering(preserve_sample_ordering) {
+}
+
+// CallStackProfileMetricsProvider --------------------------------------------
+
+const char CallStackProfileMetricsProvider::kFieldTrialName[] =
+ "StackProfiling";
+const char CallStackProfileMetricsProvider::kReportProfilesGroupName[] =
+ "Report profiles";
+
+CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() {
+}
+
+CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {
+}
+
+// This function can be invoked on an abitrary thread.
+base::StackSamplingProfiler::CompletedCallback
+CallStackProfileMetricsProvider::GetProfilerCallback(const Params& params) {
+ // Ignore the profiles if the collection is disabled. If the collection state
+ // changes while collecting, this will be detected by the callback and
+ // profiles will be ignored at that point.
+ if (!PendingProfiles::GetInstance()->IsCollectionEnabled())
+ return base::Bind(&IgnoreCompletedProfiles);
+
+ return base::Bind(&ReceiveCompletedProfiles, params, base::TimeTicks::Now());
+}
+
+void CallStackProfileMetricsProvider::OnRecordingEnabled() {
+ PendingProfiles::GetInstance()->SetCollectionEnabled(true);
+}
+
+void CallStackProfileMetricsProvider::OnRecordingDisabled() {
+ PendingProfiles::GetInstance()->SetCollectionEnabled(false);
+}
+
+void CallStackProfileMetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto) {
+ std::vector<ProfilesState> pending_profiles;
+ PendingProfiles::GetInstance()->Swap(&pending_profiles);
+
+ DCHECK(IsReportingEnabledByFieldTrial() || pending_profiles.empty());
+
+ for (const ProfilesState& profiles_state : pending_profiles) {
+ for (const StackSamplingProfiler::CallStackProfile& profile :
+ profiles_state.profiles) {
+ SampledProfile* sampled_profile = uma_proto->add_sampled_profile();
+ sampled_profile->set_trigger_event(ToSampledProfileTriggerEvent(
+ profiles_state.params.trigger));
+ CopyProfileToProto(profile,
+ profiles_state.params.preserve_sample_ordering,
+ sampled_profile->mutable_call_stack_profile());
+ }
+ }
+}
+
+// static
+void CallStackProfileMetricsProvider::ResetStaticStateForTesting() {
+ PendingProfiles::GetInstance()->ResetToDefaultStateForTesting();
+}
+
+// static
+bool CallStackProfileMetricsProvider::IsReportingEnabledByFieldTrial() {
+ const std::string group_name = base::FieldTrialList::FindFullName(
+ CallStackProfileMetricsProvider::kFieldTrialName);
+ return group_name ==
+ CallStackProfileMetricsProvider::kReportProfilesGroupName;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider.h b/chromium/components/metrics/call_stack_profile_metrics_provider.h
new file mode 100644
index 00000000000..746b82928ed
--- /dev/null
+++ b/chromium/components/metrics/call_stack_profile_metrics_provider.h
@@ -0,0 +1,83 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+
+// Performs metrics logging for the stack sampling profiler.
+class CallStackProfileMetricsProvider : public MetricsProvider {
+ public:
+ // The event that triggered the profile collection.
+ // This enum should be kept in sync with content/common/profiled_stack_state.h
+ enum Trigger {
+ UNKNOWN,
+ PROCESS_STARTUP,
+ JANKY_TASK,
+ THREAD_HUNG,
+ TRIGGER_LAST = THREAD_HUNG
+ };
+
+ // Parameters to pass back to the metrics provider.
+ struct Params {
+ explicit Params(Trigger trigger);
+ Params(Trigger trigger, bool preserve_sample_ordering);
+
+ // The triggering event.
+ Trigger trigger;
+
+ // True if sample ordering is important and should be preserved when the
+ // associated profiles are compressed. This should only be set to true if
+ // the intended use of the requires that the sequence of call stacks within
+ // a particular profile be preserved. The default value of false provides
+ // better compression of the encoded profile and is sufficient for the
+ // typical use case of recording profiles for stack frequency analysis in
+ // aggregate.
+ bool preserve_sample_ordering;
+ };
+
+ CallStackProfileMetricsProvider();
+ ~CallStackProfileMetricsProvider() override;
+
+ // Get a callback for use with StackSamplingProfiler that provides completed
+ // profiles to this object. The callback should be immediately passed to the
+ // StackSamplingProfiler, and should not be reused between
+ // StackSamplingProfilers. This function may be called on any thread.
+ static base::StackSamplingProfiler::CompletedCallback GetProfilerCallback(
+ const Params& params);
+
+ // MetricsProvider:
+ void OnRecordingEnabled() override;
+ void OnRecordingDisabled() override;
+ void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
+
+ protected:
+ // Finch field trial and group for reporting profiles. Provided here for test
+ // use.
+ static const char kFieldTrialName[];
+ static const char kReportProfilesGroupName[];
+
+ // Reset the static state to the defaults after startup.
+ static void ResetStaticStateForTesting();
+
+ private:
+ // Returns true if reporting of profiles is enabled according to the
+ // controlling Finch field trial.
+ static bool IsReportingEnabledByFieldTrial();
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..e361137e168
--- /dev/null
+++ b/chromium/components/metrics/call_stack_profile_metrics_provider_unittest.cc
@@ -0,0 +1,619 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "components/variations/entropy_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StackSamplingProfiler;
+using Frame = StackSamplingProfiler::Frame;
+using Module = StackSamplingProfiler::Module;
+using Profile = StackSamplingProfiler::CallStackProfile;
+using Profiles = StackSamplingProfiler::CallStackProfiles;
+using Sample = StackSamplingProfiler::Sample;
+
+namespace metrics {
+
+using Params = CallStackProfileMetricsProvider::Params;
+
+// This test fixture enables the field trial that
+// CallStackProfileMetricsProvider depends on to report profiles.
+class CallStackProfileMetricsProviderTest : public testing::Test {
+ public:
+ CallStackProfileMetricsProviderTest()
+ : field_trial_list_(nullptr) {
+ base::FieldTrialList::CreateFieldTrial(
+ TestState::kFieldTrialName,
+ TestState::kReportProfilesGroupName);
+ TestState::ResetStaticStateForTesting();
+ }
+
+ ~CallStackProfileMetricsProviderTest() override {}
+
+ // Utility function to append profiles to the metrics provider.
+ void AppendProfiles(const Params& params, const Profiles& profiles) {
+ CallStackProfileMetricsProvider::GetProfilerCallback(params).Run(profiles);
+ }
+
+ private:
+ // Exposes field trial/group names from the CallStackProfileMetricsProvider.
+ class TestState : public CallStackProfileMetricsProvider {
+ public:
+ using CallStackProfileMetricsProvider::kFieldTrialName;
+ using CallStackProfileMetricsProvider::kReportProfilesGroupName;
+ using CallStackProfileMetricsProvider::ResetStaticStateForTesting;
+ };
+
+ base::FieldTrialList field_trial_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProviderTest);
+};
+
+// Checks that all properties from multiple profiles are filled as expected.
+TEST_F(CallStackProfileMetricsProviderTest, MultipleProfiles) {
+ const uintptr_t module1_base_address = 0x1000;
+ const uintptr_t module2_base_address = 0x2000;
+ const uintptr_t module3_base_address = 0x3000;
+
+ const Module profile_modules[][2] = {
+ {
+ Module(
+ module1_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ ),
+ Module(
+ module2_base_address,
+ "EFGH",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\third_party.dll")
+#else
+ base::FilePath("/some/path/to/third_party.so")
+#endif
+ ),
+ },
+ {
+ Module(
+ module3_base_address,
+ "MNOP",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\third_party2.dll")
+#else
+ base::FilePath("/some/path/to/third_party2.so")
+#endif
+ ),
+ Module( // Repeated from the first profile.
+ module1_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ )
+ }
+ };
+
+ // Values for Windows generated with:
+ // perl -MDigest::MD5=md5 -MEncode=encode
+ // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 encode "UTF-16LE", $_}'
+ // chrome.exe third_party.dll third_party2.dll
+ //
+ // Values for Linux generated with:
+ // perl -MDigest::MD5=md5
+ // -e 'for(@ARGV){printf "%x\n", unpack "Q>", md5 $_}'
+ // chrome third_party.so third_party2.so
+ const uint64_t profile_expected_name_md5_prefixes[][2] = {
+ {
+#if defined(OS_WIN)
+ 0x46c3e4166659ac02ULL, 0x7e2b8bfddeae1abaULL
+#else
+ 0x554838a8451ac36cUL, 0x843661148659c9f8UL
+#endif
+ },
+ {
+#if defined(OS_WIN)
+ 0x87b66f4573a4d5caULL, 0x46c3e4166659ac02ULL
+#else
+ 0xb4647e539fa6ec9eUL, 0x554838a8451ac36cUL
+#endif
+ }};
+
+ // Represents two stack samples for each of two profiles, where each stack
+ // contains three frames. Each frame contains an instruction pointer and a
+ // module index corresponding to the module for the profile in
+ // profile_modules.
+ //
+ // So, the first stack sample below has its top frame in module 0 at an offset
+ // of 0x10 from the module's base address, the next-to-top frame in module 1
+ // at an offset of 0x20 from the module's base address, and the bottom frame
+ // in module 0 at an offset of 0x30 from the module's base address
+ const Frame profile_sample_frames[][2][3] = {
+ {
+ {
+ Frame(module1_base_address + 0x10, 0),
+ Frame(module2_base_address + 0x20, 1),
+ Frame(module1_base_address + 0x30, 0)
+ },
+ {
+ Frame(module2_base_address + 0x10, 1),
+ Frame(module1_base_address + 0x20, 0),
+ Frame(module2_base_address + 0x30, 1)
+ }
+ },
+ {
+ {
+ Frame(module3_base_address + 0x10, 0),
+ Frame(module1_base_address + 0x20, 1),
+ Frame(module3_base_address + 0x30, 0)
+ },
+ {
+ Frame(module1_base_address + 0x10, 1),
+ Frame(module3_base_address + 0x20, 0),
+ Frame(module1_base_address + 0x30, 1)
+ }
+ }
+ };
+
+ base::TimeDelta profile_durations[2] = {
+ base::TimeDelta::FromMilliseconds(100),
+ base::TimeDelta::FromMilliseconds(200)
+ };
+
+ base::TimeDelta profile_sampling_periods[2] = {
+ base::TimeDelta::FromMilliseconds(10),
+ base::TimeDelta::FromMilliseconds(20)
+ };
+
+ std::vector<Profile> profiles;
+ for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) {
+ Profile profile;
+ profile.modules.insert(
+ profile.modules.end(), &profile_modules[i][0],
+ &profile_modules[i][0] + arraysize(profile_modules[i]));
+
+ for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) {
+ profile.samples.push_back(Sample());
+ Sample& sample = profile.samples.back();
+ sample.insert(sample.end(), &profile_sample_frames[i][j][0],
+ &profile_sample_frames[i][j][0] +
+ arraysize(profile_sample_frames[i][j]));
+ }
+
+ profile.profile_duration = profile_durations[i];
+ profile.sampling_period = profile_sampling_periods[i];
+
+ profiles.push_back(profile);
+ }
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ profiles);
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames)),
+ uma_proto.sampled_profile().size());
+ for (size_t i = 0; i < arraysize(profile_sample_frames); ++i) {
+ SCOPED_TRACE("profile " + base::SizeTToString(i));
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(i);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i])),
+ call_stack_profile.sample().size());
+ for (size_t j = 0; j < arraysize(profile_sample_frames[i]); ++j) {
+ SCOPED_TRACE("sample " + base::SizeTToString(j));
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(j);
+ ASSERT_EQ(static_cast<int>(arraysize(profile_sample_frames[i][j])),
+ proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(1u, proto_sample.count());
+ for (size_t k = 0; k < arraysize(profile_sample_frames[i][j]); ++k) {
+ SCOPED_TRACE("frame " + base::SizeTToString(k));
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(k);
+ ASSERT_TRUE(entry.has_address());
+ const char* instruction_pointer = reinterpret_cast<const char*>(
+ profile_sample_frames[i][j][k].instruction_pointer);
+ const char* module_base_address = reinterpret_cast<const char*>(
+ profile_modules[i][profile_sample_frames[i][j][k].module_index]
+ .base_address);
+ EXPECT_EQ(
+ static_cast<uint64_t>(instruction_pointer - module_base_address),
+ entry.address());
+ ASSERT_TRUE(entry.has_module_id_index());
+ EXPECT_EQ(profile_sample_frames[i][j][k].module_index,
+ static_cast<size_t>(entry.module_id_index()));
+ }
+ }
+
+ ASSERT_EQ(static_cast<int>(arraysize(profile_modules[i])),
+ call_stack_profile.module_id().size());
+ for (size_t j = 0; j < arraysize(profile_modules[i]); ++j) {
+ SCOPED_TRACE("module " + base::SizeTToString(j));
+ const CallStackProfile::ModuleIdentifier& module_identifier =
+ call_stack_profile.module_id().Get(j);
+ ASSERT_TRUE(module_identifier.has_build_id());
+ EXPECT_EQ(profile_modules[i][j].id, module_identifier.build_id());
+ ASSERT_TRUE(module_identifier.has_name_md5_prefix());
+ EXPECT_EQ(profile_expected_name_md5_prefixes[i][j],
+ module_identifier.name_md5_prefix());
+ }
+
+ ASSERT_TRUE(call_stack_profile.has_profile_duration_ms());
+ EXPECT_EQ(profile_durations[i].InMilliseconds(),
+ call_stack_profile.profile_duration_ms());
+ ASSERT_TRUE(call_stack_profile.has_sampling_period_ms());
+ EXPECT_EQ(profile_sampling_periods[i].InMilliseconds(),
+ call_stack_profile.sampling_period_ms());
+ ASSERT_TRUE(sampled_profile.has_trigger_event());
+ EXPECT_EQ(SampledProfile::PROCESS_STARTUP, sampled_profile.trigger_event());
+ }
+}
+
+// Checks that all duplicate samples are collapsed with
+// preserve_sample_ordering = false.
+TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksUnordered) {
+ const uintptr_t module_base_address = 0x1000;
+
+ const Module modules[] = {
+ Module(
+ module_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ )
+ };
+
+ // Duplicate samples in slots 0, 2, and 3.
+ const Frame sample_frames[][1] = {
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x20, 0), },
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x10, 0) }
+ };
+
+ Profile profile;
+ profile.modules.insert(profile.modules.end(), &modules[0],
+ &modules[0] + arraysize(modules));
+
+ for (size_t i = 0; i < arraysize(sample_frames); ++i) {
+ profile.samples.push_back(Sample());
+ Sample& sample = profile.samples.back();
+ sample.insert(sample.end(), &sample_frames[i][0],
+ &sample_frames[i][0] + arraysize(sample_frames[i]));
+ }
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(2, call_stack_profile.sample().size());
+ for (int i = 0; i < 2; ++i) {
+ SCOPED_TRACE("sample " + base::IntToString(i));
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(i);
+ ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])),
+ proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(i == 0 ? 3u : 1u, proto_sample.count());
+ for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) {
+ SCOPED_TRACE("frame " + base::SizeTToString(j));
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(j);
+ ASSERT_TRUE(entry.has_address());
+ const char* instruction_pointer = reinterpret_cast<const char*>(
+ sample_frames[i][j].instruction_pointer);
+ const char* module_base_address = reinterpret_cast<const char*>(
+ modules[sample_frames[i][j].module_index].base_address);
+ EXPECT_EQ(
+ static_cast<uint64_t>(instruction_pointer - module_base_address),
+ entry.address());
+ ASSERT_TRUE(entry.has_module_id_index());
+ EXPECT_EQ(sample_frames[i][j].module_index,
+ static_cast<size_t>(entry.module_id_index()));
+ }
+ }
+}
+
+// Checks that only contiguous duplicate samples are collapsed with
+// preserve_sample_ordering = true.
+TEST_F(CallStackProfileMetricsProviderTest, RepeatedStacksOrdered) {
+ const uintptr_t module_base_address = 0x1000;
+
+ const Module modules[] = {
+ Module(
+ module_base_address,
+ "ABCD",
+#if defined(OS_WIN)
+ base::FilePath(L"c:\\some\\path\\to\\chrome.exe")
+#else
+ base::FilePath("/some/path/to/chrome")
+#endif
+ )
+ };
+
+ // Duplicate samples in slots 0, 2, and 3.
+ const Frame sample_frames[][1] = {
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x20, 0), },
+ { Frame(module_base_address + 0x10, 0), },
+ { Frame(module_base_address + 0x10, 0) }
+ };
+
+ Profile profile;
+ profile.modules.insert(profile.modules.end(), &modules[0],
+ &modules[0] + arraysize(modules));
+
+ for (size_t i = 0; i < arraysize(sample_frames); ++i) {
+ profile.samples.push_back(Sample());
+ Sample& sample = profile.samples.back();
+ sample.insert(sample.end(), &sample_frames[i][0],
+ &sample_frames[i][0] + arraysize(sample_frames[i]));
+ }
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, true),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(3, call_stack_profile.sample().size());
+ for (int i = 0; i < 3; ++i) {
+ SCOPED_TRACE("sample " + base::IntToString(i));
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(i);
+ ASSERT_EQ(static_cast<int>(arraysize(sample_frames[i])),
+ proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(i == 2 ? 2u : 1u, proto_sample.count());
+ for (size_t j = 0; j < arraysize(sample_frames[i]); ++j) {
+ SCOPED_TRACE("frame " + base::SizeTToString(j));
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(j);
+ ASSERT_TRUE(entry.has_address());
+ const char* instruction_pointer = reinterpret_cast<const char*>(
+ sample_frames[i][j].instruction_pointer);
+ const char* module_base_address = reinterpret_cast<const char*>(
+ modules[sample_frames[i][j].module_index].base_address);
+ EXPECT_EQ(
+ static_cast<uint64_t>(instruction_pointer - module_base_address),
+ entry.address());
+ ASSERT_TRUE(entry.has_module_id_index());
+ EXPECT_EQ(sample_frames[i][j].module_index,
+ static_cast<size_t>(entry.module_id_index()));
+ }
+ }
+}
+
+// Checks that unknown modules produce an empty Entry.
+TEST_F(CallStackProfileMetricsProviderTest, UnknownModule) {
+ const Frame frame(0x1000, Frame::kUnknownModuleIndex);
+
+ Profile profile;
+
+ profile.samples.push_back(Sample(1, frame));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+
+ ASSERT_EQ(1, call_stack_profile.sample().size());
+ const CallStackProfile::Sample& proto_sample =
+ call_stack_profile.sample().Get(0);
+ ASSERT_EQ(1, proto_sample.entry().size());
+ ASSERT_TRUE(proto_sample.has_count());
+ EXPECT_EQ(1u, proto_sample.count());
+ const CallStackProfile::Entry& entry = proto_sample.entry().Get(0);
+ EXPECT_FALSE(entry.has_address());
+ EXPECT_FALSE(entry.has_module_id_index());
+}
+
+// Checks that pending profiles are only passed back to ProvideGeneralMetrics
+// once.
+TEST_F(CallStackProfileMetricsProviderTest, ProfilesProvidedOnlyOnce) {
+ CallStackProfileMetricsProvider provider;
+ for (int i = 0; i < 2; ++i) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ // Use the sampling period to distinguish the two profiles.
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(i);
+
+ provider.OnRecordingEnabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ ASSERT_EQ(1, uma_proto.sampled_profile().size());
+ const SampledProfile& sampled_profile = uma_proto.sampled_profile().Get(0);
+ ASSERT_TRUE(sampled_profile.has_call_stack_profile());
+ const CallStackProfile& call_stack_profile =
+ sampled_profile.call_stack_profile();
+ ASSERT_TRUE(call_stack_profile.has_sampling_period_ms());
+ EXPECT_EQ(i, call_stack_profile.sampling_period_ms());
+ }
+}
+
+// Checks that pending profiles are provided to ProvideGeneralMetrics
+// when collected before CallStackProfileMetricsProvider is instantiated.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesProvidedWhenCollectedBeforeInstantiation) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(1, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics
+// while recording is disabled.
+TEST_F(CallStackProfileMetricsProviderTest, ProfilesNotProvidedWhileDisabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingDisabled();
+ AppendProfiles(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false),
+ std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics
+// if recording is disabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesNotProvidedAfterChangeToDisabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ base::StackSamplingProfiler::CompletedCallback callback =
+ CallStackProfileMetricsProvider::GetProfilerCallback(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
+
+ provider.OnRecordingDisabled();
+ callback.Run(std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics if
+// recording is enabled, but then disabled and reenabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesNotProvidedAfterChangeToDisabledThenEnabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingEnabled();
+ base::StackSamplingProfiler::CompletedCallback callback =
+ CallStackProfileMetricsProvider::GetProfilerCallback(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
+
+ provider.OnRecordingDisabled();
+ provider.OnRecordingEnabled();
+ callback.Run(std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that pending profiles are not provided to ProvideGeneralMetrics
+// if recording is disabled, but then enabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+ ProfilesNotProvidedAfterChangeFromDisabled) {
+ Profile profile;
+ profile.samples.push_back(
+ Sample(1, Frame(0x1000, Frame::kUnknownModuleIndex)));
+
+ profile.profile_duration = base::TimeDelta::FromMilliseconds(100);
+ profile.sampling_period = base::TimeDelta::FromMilliseconds(10);
+
+ CallStackProfileMetricsProvider provider;
+ provider.OnRecordingDisabled();
+ base::StackSamplingProfiler::CompletedCallback callback =
+ CallStackProfileMetricsProvider::GetProfilerCallback(
+ Params(CallStackProfileMetricsProvider::PROCESS_STARTUP, false));
+
+ provider.OnRecordingEnabled();
+ callback.Run(std::vector<Profile>(1, profile));
+ ChromeUserMetricsExtension uma_proto;
+ provider.ProvideGeneralMetrics(&uma_proto);
+
+ EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/clean_exit_beacon.cc b/chromium/components/metrics/clean_exit_beacon.cc
new file mode 100644
index 00000000000..f90f2d00626
--- /dev/null
+++ b/chromium/components/metrics/clean_exit_beacon.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/clean_exit_beacon.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_service.h"
+
+#if defined(OS_WIN)
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#endif
+
+namespace metrics {
+
+CleanExitBeacon::CleanExitBeacon(const base::string16& backup_registry_key,
+ PrefService* local_state)
+ : local_state_(local_state),
+ initial_value_(local_state->GetBoolean(prefs::kStabilityExitedCleanly)),
+ backup_registry_key_(backup_registry_key) {
+ DCHECK_NE(PrefService::INITIALIZATION_STATUS_WAITING,
+ local_state_->GetInitializationStatus());
+
+#if defined(OS_WIN)
+ // An enumeration of all possible permutations of the the beacon state in the
+ // registry and in Local State.
+ enum {
+ DIRTY_DIRTY,
+ DIRTY_CLEAN,
+ CLEAN_DIRTY,
+ CLEAN_CLEAN,
+ MISSING_DIRTY,
+ MISSING_CLEAN,
+ NUM_CONSISTENCY_ENUMS
+ } consistency = DIRTY_DIRTY;
+
+ base::win::RegKey regkey;
+ DWORD value = 0u;
+ if (regkey.Open(HKEY_CURRENT_USER,
+ backup_registry_key_.c_str(),
+ KEY_ALL_ACCESS) == ERROR_SUCCESS &&
+ regkey.ReadValueDW(
+ base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(), &value) ==
+ ERROR_SUCCESS) {
+ if (value)
+ consistency = initial_value_ ? CLEAN_CLEAN : CLEAN_DIRTY;
+ else
+ consistency = initial_value_ ? DIRTY_CLEAN : DIRTY_DIRTY;
+ } else {
+ consistency = initial_value_ ? MISSING_CLEAN : MISSING_DIRTY;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION(
+ "UMA.CleanExitBeaconConsistency", consistency, NUM_CONSISTENCY_ENUMS);
+#endif
+}
+
+CleanExitBeacon::~CleanExitBeacon() {
+}
+
+void CleanExitBeacon::WriteBeaconValue(bool value) {
+ local_state_->SetBoolean(prefs::kStabilityExitedCleanly, value);
+
+#if defined(OS_WIN)
+ base::win::RegKey regkey;
+ if (regkey.Create(HKEY_CURRENT_USER,
+ backup_registry_key_.c_str(),
+ KEY_ALL_ACCESS) == ERROR_SUCCESS) {
+ regkey.WriteValue(
+ base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(),
+ value ? 1u : 0u);
+ }
+#endif
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/clean_exit_beacon.h b/chromium/components/metrics/clean_exit_beacon.h
new file mode 100644
index 00000000000..6b896fa1784
--- /dev/null
+++ b/chromium/components/metrics/clean_exit_beacon.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
+#define COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+
+class PrefService;
+
+namespace metrics {
+
+// Reads and updates a beacon used to detect whether the previous browser
+// process exited cleanly.
+class CleanExitBeacon {
+ public:
+ // Instantiates a CleanExitBeacon whose value is stored in |local_state|.
+ // |local_state| must be fully initialized.
+ // On Windows, |backup_registry_key| is used to store a backup of the beacon.
+ // It is ignored on other platforms.
+ CleanExitBeacon(
+ const base::string16& backup_registry_key,
+ PrefService* local_state);
+
+ ~CleanExitBeacon();
+
+ // Returns the original value of the beacon.
+ bool exited_cleanly() const { return initial_value_; }
+
+ // Writes the provided beacon value.
+ void WriteBeaconValue(bool exited_cleanly);
+
+ private:
+ PrefService* const local_state_;
+ const bool initial_value_;
+ const base::string16 backup_registry_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(CleanExitBeacon);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
diff --git a/chromium/components/metrics/client_info.cc b/chromium/components/metrics/client_info.cc
new file mode 100644
index 00000000000..57ad3947c1b
--- /dev/null
+++ b/chromium/components/metrics/client_info.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/client_info.h"
+
+namespace metrics {
+
+ClientInfo::ClientInfo() : installation_date(0), reporting_enabled_date(0) {}
+
+ClientInfo::~ClientInfo() {}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/client_info.h b/chromium/components/metrics/client_info.h
new file mode 100644
index 00000000000..7dcf5d8de39
--- /dev/null
+++ b/chromium/components/metrics/client_info.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLIENT_INFO_H_
+#define COMPONENTS_METRICS_CLIENT_INFO_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// A data object used to pass data from outside the metrics component into the
+// metrics component.
+struct ClientInfo {
+ public:
+ ClientInfo();
+ ~ClientInfo();
+
+ // The metrics ID of this client: represented as a GUID string.
+ std::string client_id;
+
+ // The installation date: represented as an epoch time in seconds.
+ int64_t installation_date;
+
+ // The date on which metrics reporting was enabled: represented as an epoch
+ // time in seconds.
+ int64_t reporting_enabled_date;
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CLIENT_INFO_H_
diff --git a/chromium/components/metrics/cloned_install_detector.cc b/chromium/components/metrics/cloned_install_detector.cc
new file mode 100644
index 00000000000..7e9be8c3f10
--- /dev/null
+++ b/chromium/components/metrics/cloned_install_detector.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/cloned_install_detector.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+uint32_t HashRawId(const std::string& value) {
+ uint64_t hash = base::HashMetricName(value);
+
+ // Only use 24 bits from the 64-bit hash.
+ return hash & ((1 << 24) - 1);
+}
+
+// State of the generated machine id in relation to the previously stored value.
+// Note: UMA histogram enum - don't re-order or remove entries
+enum MachineIdState {
+ ID_GENERATION_FAILED,
+ ID_NO_STORED_VALUE,
+ ID_CHANGED,
+ ID_UNCHANGED,
+ ID_ENUM_SIZE
+};
+
+// Logs the state of generating a machine id and comparing it to a stored value.
+void LogMachineIdState(MachineIdState state) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.MachineIdState", state, ID_ENUM_SIZE);
+}
+
+} // namespace
+
+ClonedInstallDetector::ClonedInstallDetector(MachineIdProvider* raw_id_provider)
+ : raw_id_provider_(raw_id_provider), weak_ptr_factory_(this) {
+}
+
+ClonedInstallDetector::~ClonedInstallDetector() {
+}
+
+void ClonedInstallDetector::CheckForClonedInstall(
+ PrefService* local_state,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ base::PostTaskAndReplyWithResult(
+ task_runner.get(),
+ FROM_HERE,
+ base::Bind(&MachineIdProvider::GetMachineId, raw_id_provider_),
+ base::Bind(&ClonedInstallDetector::SaveMachineId,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_state));
+}
+
+void ClonedInstallDetector::SaveMachineId(PrefService* local_state,
+ const std::string& raw_id) {
+ if (raw_id.empty()) {
+ LogMachineIdState(ID_GENERATION_FAILED);
+ local_state->ClearPref(prefs::kMetricsMachineId);
+ return;
+ }
+
+ int hashed_id = HashRawId(raw_id);
+
+ MachineIdState id_state = ID_NO_STORED_VALUE;
+ if (local_state->HasPrefPath(prefs::kMetricsMachineId)) {
+ if (local_state->GetInteger(prefs::kMetricsMachineId) != hashed_id) {
+ id_state = ID_CHANGED;
+ // TODO(jwd): Use a callback to set the reset pref. That way
+ // ClonedInstallDetector doesn't need to know about this pref.
+ local_state->SetBoolean(prefs::kMetricsResetIds, true);
+ } else {
+ id_state = ID_UNCHANGED;
+ }
+ }
+
+ LogMachineIdState(id_state);
+
+ local_state->SetInteger(prefs::kMetricsMachineId, hashed_id);
+}
+
+// static
+void ClonedInstallDetector::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(prefs::kMetricsMachineId, 0);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/cloned_install_detector.h b/chromium/components/metrics/cloned_install_detector.h
new file mode 100644
index 00000000000..0609122cdaf
--- /dev/null
+++ b/chromium/components/metrics/cloned_install_detector.h
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+#define COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace metrics {
+
+class MachineIdProvider;
+
+// A class for detecting if an install is cloned. It does this by detecting
+// when the hardware running Chrome changes.
+class ClonedInstallDetector {
+ public:
+ explicit ClonedInstallDetector(MachineIdProvider* raw_id_provider);
+ virtual ~ClonedInstallDetector();
+
+ // Posts a task to |task_runner| to generate a machine ID and store it to a
+ // local state pref. If the newly generated ID is different than the
+ // previously stored one, then the install is considered cloned. The ID is a
+ // 24-bit value based off of machine characteristics. This value should never
+ // be sent over the network.
+ // TODO(jwd): Implement change detection.
+ void CheckForClonedInstall(
+ PrefService* local_state,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, SaveId);
+ FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, DetectClone);
+
+ // Converts raw_id into a 24-bit hash and stores the hash in |local_state|.
+ // |raw_id| is not a const ref because it's passed from a cross-thread post
+ // task.
+ void SaveMachineId(PrefService* local_state, const std::string& raw_id);
+
+ scoped_refptr<MachineIdProvider> raw_id_provider_;
+ base::WeakPtrFactory<ClonedInstallDetector> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClonedInstallDetector);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
diff --git a/chromium/components/metrics/cloned_install_detector_unittest.cc b/chromium/components/metrics/cloned_install_detector_unittest.cc
new file mode 100644
index 00000000000..91b24e8bf57
--- /dev/null
+++ b/chromium/components/metrics/cloned_install_detector_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/cloned_install_detector.h"
+
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const std::string kTestRawId = "test";
+// Hashed machine id for |kTestRawId|.
+const int kTestHashedId = 2216819;
+
+} // namespace
+
+// TODO(jwd): Change these test to test the full flow and histogram outputs. It
+// should also remove the need to make the test a friend of
+// ClonedInstallDetector.
+TEST(ClonedInstallDetectorTest, SaveId) {
+ TestingPrefServiceSimple prefs;
+ ClonedInstallDetector::RegisterPrefs(prefs.registry());
+
+ scoped_ptr<ClonedInstallDetector> detector(
+ new ClonedInstallDetector(MachineIdProvider::CreateInstance()));
+
+ detector->SaveMachineId(&prefs, kTestRawId);
+
+ EXPECT_EQ(kTestHashedId, prefs.GetInteger(prefs::kMetricsMachineId));
+}
+
+TEST(ClonedInstallDetectorTest, DetectClone) {
+ TestingPrefServiceSimple prefs;
+ MetricsStateManager::RegisterPrefs(prefs.registry());
+
+ // Save a machine id that will cause a clone to be detected.
+ prefs.SetInteger(prefs::kMetricsMachineId, kTestHashedId + 1);
+
+ scoped_ptr<ClonedInstallDetector> detector(
+ new ClonedInstallDetector(MachineIdProvider::CreateInstance()));
+
+ detector->SaveMachineId(&prefs, kTestRawId);
+
+ EXPECT_TRUE(prefs.GetBoolean(prefs::kMetricsResetIds));
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/daily_event.cc b/chromium/components/metrics/daily_event.cc
new file mode 100644
index 00000000000..2ab54909251
--- /dev/null
+++ b/chromium/components/metrics/daily_event.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/daily_event.h"
+
+#include <utility>
+
+#include "base/metrics/histogram.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+enum IntervalType {
+ FIRST_RUN,
+ DAY_ELAPSED,
+ CLOCK_CHANGED,
+ NUM_INTERVAL_TYPES
+};
+
+void RecordIntervalTypeHistogram(const std::string& histogram_name,
+ IntervalType type) {
+ base::Histogram::FactoryGet(
+ histogram_name,
+ 1,
+ NUM_INTERVAL_TYPES,
+ NUM_INTERVAL_TYPES + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag)->Add(type);
+}
+
+} // namespace
+
+DailyEvent::Observer::Observer() {
+}
+
+DailyEvent::Observer::~Observer() {
+}
+
+DailyEvent::DailyEvent(PrefService* pref_service,
+ const char* pref_name,
+ const std::string& histogram_name)
+ : pref_service_(pref_service),
+ pref_name_(pref_name),
+ histogram_name_(histogram_name) {
+}
+
+DailyEvent::~DailyEvent() {
+}
+
+// static
+void DailyEvent::RegisterPref(PrefRegistrySimple* registry,
+ const char* pref_name) {
+ registry->RegisterInt64Pref(pref_name, base::Time().ToInternalValue());
+}
+
+void DailyEvent::AddObserver(scoped_ptr<DailyEvent::Observer> observer) {
+ DVLOG(2) << "DailyEvent observer added.";
+ DCHECK(last_fired_.is_null());
+ observers_.push_back(std::move(observer));
+}
+
+void DailyEvent::CheckInterval() {
+ base::Time now = base::Time::Now();
+ if (last_fired_.is_null()) {
+ // The first time we call CheckInterval, we read the time stored in prefs.
+ last_fired_ = base::Time::FromInternalValue(
+ pref_service_->GetInt64(pref_name_));
+ DVLOG(1) << "DailyEvent time loaded: " << last_fired_;
+ if (last_fired_.is_null()) {
+ DVLOG(1) << "DailyEvent first run.";
+ RecordIntervalTypeHistogram(histogram_name_, FIRST_RUN);
+ OnInterval(now);
+ return;
+ }
+ }
+ int days_elapsed = (now - last_fired_).InDays();
+ if (days_elapsed >= 1) {
+ DVLOG(1) << "DailyEvent day elapsed.";
+ RecordIntervalTypeHistogram(histogram_name_, DAY_ELAPSED);
+ OnInterval(now);
+ } else if (days_elapsed <= -1) {
+ // The "last fired" time is more than a day in the future, so the clock
+ // must have been changed.
+ DVLOG(1) << "DailyEvent clock change detected.";
+ RecordIntervalTypeHistogram(histogram_name_, CLOCK_CHANGED);
+ OnInterval(now);
+ }
+}
+
+void DailyEvent::OnInterval(base::Time now) {
+ DCHECK(!now.is_null());
+ last_fired_ = now;
+ pref_service_->SetInt64(pref_name_, last_fired_.ToInternalValue());
+
+ // Notify all observers
+ for (ScopedVector<DailyEvent::Observer>::iterator it = observers_.begin();
+ it != observers_.end();
+ ++it) {
+ (*it)->OnDailyEvent();
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/daily_event.h b/chromium/components/metrics/daily_event.h
new file mode 100644
index 00000000000..24b31dc58ea
--- /dev/null
+++ b/chromium/components/metrics/daily_event.h
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DAILY_EVENT_H_
+#define COMPONENTS_METRICS_DAILY_EVENT_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/time/time.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+// DailyEvent is used for throttling an event to about once per day, even if
+// the program is restarted more frequently. It is based on local machine
+// time, so it could be fired more often if the clock is changed.
+//
+// The service using the DailyEvent should first provide all of the Observers
+// for the interval, and then arrange for CheckInterval() to be called
+// periodically to test if the event should be fired.
+class DailyEvent {
+ public:
+ // Observer receives notifications from a DailyEvent.
+ // Observers must be added before the DailyEvent begins checking time,
+ // and will be owned by the DailyEvent.
+ class Observer {
+ public:
+ Observer();
+ virtual ~Observer();
+
+ // Called when the daily event is fired.
+ virtual void OnDailyEvent() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ // Constructs DailyEvent monitor which stores the time it last fired in the
+ // preference |pref_name|. |pref_name| should be registered by calling
+ // RegisterPref before using this object.
+ // Caller is responsible for ensuring that |pref_service| and |pref_name|
+ // outlive the DailyEvent.
+ // |histogram_name| is the name of the UMA metric which record when this
+ // interval fires, and should be registered in histograms.xml
+ DailyEvent(PrefService* pref_service,
+ const char* pref_name,
+ const std::string& histogram_name);
+ ~DailyEvent();
+
+ // Adds a observer to be notified when a day elapses. All observers should
+ // be registered before the the DailyEvent starts checking time.
+ void AddObserver(scoped_ptr<Observer> observer);
+
+ // Checks if a day has elapsed. If it has, OnDailyEvent will be called on
+ // all observers.
+ void CheckInterval();
+
+ // Registers the preference used by this interval.
+ static void RegisterPref(PrefRegistrySimple* registry, const char* pref_name);
+
+ private:
+ // Handles an interval elapsing.
+ void OnInterval(base::Time now);
+
+ // A weak pointer to the PrefService object to read and write preferences
+ // from. Calling code should ensure this object continues to exist for the
+ // lifetime of the DailyEvent object.
+ PrefService* pref_service_;
+
+ // The name of the preference to store the last fired time in.
+ // Calling code should ensure this outlives the DailyEvent.
+ const char* pref_name_;
+
+ // The name of the histogram to record intervals.
+ std::string histogram_name_;
+
+ // A list of observers.
+ ScopedVector<Observer> observers_;
+
+ // The time that the daily event was last fired.
+ base::Time last_fired_;
+
+ DISALLOW_COPY_AND_ASSIGN(DailyEvent);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_DAILY_EVENT_H_
diff --git a/chromium/components/metrics/daily_event_unittest.cc b/chromium/components/metrics/daily_event_unittest.cc
new file mode 100644
index 00000000000..4adbaaf92be
--- /dev/null
+++ b/chromium/components/metrics/daily_event_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/daily_event.h"
+
+#include "base/macros.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestPrefName[] = "TestPref";
+const char kTestMetricName[] = "TestMetric";
+
+class TestDailyObserver : public DailyEvent::Observer {
+ public:
+ TestDailyObserver() : fired_(false) {}
+
+ bool fired() const { return fired_; }
+
+ void OnDailyEvent() override { fired_ = true; }
+
+ void Reset() {
+ fired_ = false;
+ }
+
+ private:
+ // True if this event has been observed.
+ bool fired_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDailyObserver);
+};
+
+class DailyEventTest : public testing::Test {
+ public:
+ DailyEventTest() : event_(&prefs_, kTestPrefName, kTestMetricName) {
+ DailyEvent::RegisterPref(prefs_.registry(), kTestPrefName);
+ observer_ = new TestDailyObserver();
+ event_.AddObserver(make_scoped_ptr(observer_));
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+ TestDailyObserver* observer_;
+ DailyEvent event_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DailyEventTest);
+};
+
+} // namespace
+
+// The event should fire if the preference is not available.
+TEST_F(DailyEventTest, TestNewFires) {
+ event_.CheckInterval();
+ EXPECT_TRUE(observer_->fired());
+}
+
+// The event should fire if the preference is more than a day old.
+TEST_F(DailyEventTest, TestOldFires) {
+ base::Time last_time = base::Time::Now() - base::TimeDelta::FromHours(25);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_TRUE(observer_->fired());
+}
+
+// The event should fire if the preference is more than a day in the future.
+TEST_F(DailyEventTest, TestFutureFires) {
+ base::Time last_time = base::Time::Now() + base::TimeDelta::FromHours(25);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_TRUE(observer_->fired());
+}
+
+// The event should not fire if the preference is more recent than a day.
+TEST_F(DailyEventTest, TestRecentNotFired) {
+ base::Time last_time = base::Time::Now() - base::TimeDelta::FromMinutes(2);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_FALSE(observer_->fired());
+}
+
+// The event should not fire if the preference is less than a day in the future.
+TEST_F(DailyEventTest, TestSoonNotFired) {
+ base::Time last_time = base::Time::Now() + base::TimeDelta::FromMinutes(2);
+ prefs_.SetInt64(kTestPrefName, last_time.ToInternalValue());
+ event_.CheckInterval();
+ EXPECT_FALSE(observer_->fired());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/data_use_tracker.cc b/chromium/components/metrics/data_use_tracker.cc
new file mode 100644
index 00000000000..a74bdf117eb
--- /dev/null
+++ b/chromium/components/metrics/data_use_tracker.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/data_use_tracker.h"
+
+#include <string>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace metrics {
+
+namespace {
+
+// This function is for forwarding metrics usage pref changes to the appropriate
+// callback on the appropriate thread.
+// TODO(gayane): Reduce the frequency of posting tasks from IO to UI thread.
+void UpdateMetricsUsagePrefs(
+ const UpdateUsagePrefCallbackType& update_on_ui_callback,
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+ const std::string& service_name,
+ int message_size,
+ bool is_cellular) {
+ ui_task_runner->PostTask(
+ FROM_HERE, base::Bind(update_on_ui_callback, service_name, message_size,
+ is_cellular));
+}
+
+} // namespace
+
+DataUseTracker::DataUseTracker(PrefService* local_state)
+ : local_state_(local_state), weak_ptr_factory_(this) {}
+
+DataUseTracker::~DataUseTracker() {}
+
+// static
+scoped_ptr<DataUseTracker> DataUseTracker::Create(PrefService* local_state) {
+ scoped_ptr<DataUseTracker> data_use_tracker;
+ if (variations::GetVariationParamValue("UMA_EnableCellularLogUpload",
+ "Uma_Quota") != "" &&
+ variations::GetVariationParamValue("UMA_EnableCellularLogUpload",
+ "Uma_Ratio") != "") {
+ data_use_tracker.reset(new DataUseTracker(local_state));
+ }
+ return data_use_tracker;
+}
+
+// static
+void DataUseTracker::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse);
+ registry->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse);
+}
+
+UpdateUsagePrefCallbackType DataUseTracker::GetDataUseForwardingCallback(
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner) {
+ DCHECK(ui_task_runner->RunsTasksOnCurrentThread());
+
+ return base::Bind(
+ &UpdateMetricsUsagePrefs,
+ base::Bind(&DataUseTracker::UpdateMetricsUsagePrefsOnUIThread,
+ weak_ptr_factory_.GetWeakPtr()),
+ ui_task_runner);
+}
+
+bool DataUseTracker::ShouldUploadLogOnCellular(int log_bytes) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RemoveExpiredEntries();
+
+ int uma_weekly_quota_bytes;
+ if (!GetUmaWeeklyQuota(&uma_weekly_quota_bytes))
+ return true;
+
+ int uma_total_data_use = ComputeTotalDataUse(prefs::kUmaCellDataUse);
+ int new_uma_total_data_use = log_bytes + uma_total_data_use;
+ // If the new log doesn't increase the total UMA traffic to be above the
+ // allowed quota then the log should be uploaded.
+ if (new_uma_total_data_use <= uma_weekly_quota_bytes)
+ return true;
+
+ double uma_ratio;
+ if (!GetUmaRatio(&uma_ratio))
+ return true;
+
+ int user_total_data_use = ComputeTotalDataUse(prefs::kUserCellDataUse);
+ // If after adding the new log the uma ratio is still under the allowed ratio
+ // then the log should be uploaded and vice versa.
+ return new_uma_total_data_use /
+ static_cast<double>(log_bytes + user_total_data_use) <=
+ uma_ratio;
+}
+
+void DataUseTracker::UpdateMetricsUsagePrefsOnUIThread(
+ const std::string& service_name,
+ int message_size,
+ bool is_celllular) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!is_celllular)
+ return;
+
+ UpdateUsagePref(prefs::kUserCellDataUse, message_size);
+ if (service_name == "UMA")
+ UpdateUsagePref(prefs::kUmaCellDataUse, message_size);
+}
+
+void DataUseTracker::UpdateUsagePref(const std::string& pref_name,
+ int message_size) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DictionaryPrefUpdate pref_updater(local_state_, pref_name);
+ int todays_traffic = 0;
+ std::string todays_key = GetCurrentMeasurementDateAsString();
+
+ const base::DictionaryValue* user_pref_dict =
+ local_state_->GetDictionary(pref_name);
+ user_pref_dict->GetInteger(todays_key, &todays_traffic);
+ pref_updater->SetInteger(todays_key, todays_traffic + message_size);
+}
+
+void DataUseTracker::RemoveExpiredEntries() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ RemoveExpiredEntriesForPref(prefs::kUmaCellDataUse);
+ RemoveExpiredEntriesForPref(prefs::kUserCellDataUse);
+}
+
+void DataUseTracker::RemoveExpiredEntriesForPref(const std::string& pref_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const base::DictionaryValue* user_pref_dict =
+ local_state_->GetDictionary(pref_name);
+ const base::Time current_date = GetCurrentMeasurementDate();
+ const base::Time week_ago = current_date - base::TimeDelta::FromDays(7);
+
+ base::DictionaryValue user_pref_new_dict;
+ for (base::DictionaryValue::Iterator it(*user_pref_dict); !it.IsAtEnd();
+ it.Advance()) {
+ base::Time key_date;
+ base::Time::FromUTCString(it.key().c_str(), &key_date);
+ if (key_date > week_ago)
+ user_pref_new_dict.Set(it.key(), it.value().CreateDeepCopy());
+ }
+ local_state_->Set(pref_name, user_pref_new_dict);
+}
+
+// Note: We compute total data use regardless of what is the current date. In
+// scenario when user travels back in time zone and current date becomes earlier
+// than latest registered date in perf, we still count that in total use as user
+// actually used that data.
+int DataUseTracker::ComputeTotalDataUse(const std::string& pref_name) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ int total_data_use = 0;
+ const base::DictionaryValue* pref_dict =
+ local_state_->GetDictionary(pref_name);
+ for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd();
+ it.Advance()) {
+ int value = 0;
+ it.value().GetAsInteger(&value);
+ total_data_use += value;
+ }
+ return total_data_use;
+}
+
+bool DataUseTracker::GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::string param_value_str = variations::GetVariationParamValue(
+ "UMA_EnableCellularLogUpload", "Uma_Quota");
+ if (param_value_str.empty())
+ return false;
+
+ base::StringToInt(param_value_str, uma_weekly_quota_bytes);
+ return true;
+}
+
+bool DataUseTracker::GetUmaRatio(double* ratio) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::string param_value_str = variations::GetVariationParamValue(
+ "UMA_EnableCellularLogUpload", "Uma_Ratio");
+ if (param_value_str.empty())
+ return false;
+ base::StringToDouble(param_value_str, ratio);
+ return true;
+}
+
+base::Time DataUseTracker::GetCurrentMeasurementDate() const {
+ return base::Time::Now().LocalMidnight();
+}
+
+std::string DataUseTracker::GetCurrentMeasurementDateAsString() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::Time::Exploded today_exploded;
+ GetCurrentMeasurementDate().LocalExplode(&today_exploded);
+ return base::StringPrintf("%04d-%02d-%02d", today_exploded.year,
+ today_exploded.month, today_exploded.day_of_month);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/data_use_tracker.h b/chromium/components/metrics/data_use_tracker.h
new file mode 100644
index 00000000000..5682a0c0720
--- /dev/null
+++ b/chromium/components/metrics/data_use_tracker.h
@@ -0,0 +1,99 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DATA_USE_TRACKER_H_
+#define COMPONENTS_METRICS_DATA_USE_TRACKER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+typedef base::Callback<void(const std::string&, int, bool)>
+ UpdateUsagePrefCallbackType;
+
+// Records the data use of user traffic and UMA traffic in user prefs. Taking
+// into account those prefs it can verify whether certain UMA log upload is
+// allowed.
+class DataUseTracker {
+ public:
+ explicit DataUseTracker(PrefService* local_state);
+ ~DataUseTracker();
+
+ // Returns an instance of |DataUseTracker| with provided |local_state| if
+ // users data use should be tracked and null pointer otherwise.
+ static scoped_ptr<DataUseTracker> Create(PrefService* local_state);
+
+ // Registers data use prefs using provided |registry|.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Returns a callback to data use pref updating function. Should be called on
+ // UI thread.
+ UpdateUsagePrefCallbackType GetDataUseForwardingCallback(
+ scoped_refptr<base::SequencedTaskRunner> ui_task_runner);
+
+ // Returns whether a log with provided |log_bytes| can be uploaded according
+ // to data use ratio and UMA quota provided by variations.
+ bool ShouldUploadLogOnCellular(int log_bytes);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckUpdateUsagePref);
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckRemoveExpiredEntries);
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckComputeTotalDataUse);
+ FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckCanUploadUMALog);
+
+ // Updates data usage prefs on UI thread according to what Prefservice
+ // expects.
+ void UpdateMetricsUsagePrefsOnUIThread(const std::string& service_name,
+ int message_size,
+ bool is_cellular);
+
+ // Updates provided |pref_name| for a current date with the given message
+ // size.
+ void UpdateUsagePref(const std::string& pref_name, int message_size);
+
+ // Removes entries from the all data use prefs.
+ void RemoveExpiredEntries();
+
+ // Removes entries from the given |pref_name| if they are more than 7 days
+ // old.
+ void RemoveExpiredEntriesForPref(const std::string& pref_name);
+
+ // Computes data usage according to all the entries in the given dictionary
+ // pref.
+ int ComputeTotalDataUse(const std::string& pref_name);
+
+ // Returns the weekly allowed quota for UMA data use.
+ virtual bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const;
+
+ // Returns the allowed ratio for UMA data use over overall data use.
+ virtual bool GetUmaRatio(double* ratio) const;
+
+ // Returns the current date for measurement.
+ virtual base::Time GetCurrentMeasurementDate() const;
+
+ // Returns the current date as a string with a proper formatting.
+ virtual std::string GetCurrentMeasurementDateAsString() const;
+
+ PrefService* local_state_;
+
+ base::ThreadChecker thread_checker_;
+
+ base::WeakPtrFactory<DataUseTracker> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DataUseTracker);
+};
+
+} // namespace metrics
+#endif // COMPONENTS_METRICS_DATA_USE_TRACKER_H_
diff --git a/chromium/components/metrics/data_use_tracker_unittest.cc b/chromium/components/metrics/data_use_tracker_unittest.cc
new file mode 100644
index 00000000000..ed32a4a2024
--- /dev/null
+++ b/chromium/components/metrics/data_use_tracker_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/data_use_tracker.h"
+
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTodayStr[] = "2016-03-16";
+const char kYesterdayStr[] = "2016-03-15";
+const char kExpiredDateStr1[] = "2016-03-09";
+const char kExpiredDateStr2[] = "2016-03-01";
+
+class TestDataUsePrefService : public TestingPrefServiceSimple {
+ public:
+ TestDataUsePrefService() {
+ registry()->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse);
+ registry()->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse);
+ }
+
+ void ClearDataUsePrefs() {
+ ClearPref(metrics::prefs::kUserCellDataUse);
+ ClearPref(metrics::prefs::kUmaCellDataUse);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestDataUsePrefService);
+};
+
+class FakeDataUseTracker : public DataUseTracker {
+ public:
+ FakeDataUseTracker(PrefService* local_state) : DataUseTracker(local_state) {}
+
+ bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const override {
+ *uma_weekly_quota_bytes = 200;
+ return true;
+ }
+
+ bool GetUmaRatio(double* ratio) const override {
+ *ratio = 0.05;
+ return true;
+ }
+
+ base::Time GetCurrentMeasurementDate() const override {
+ base::Time today_for_test;
+ base::Time::FromUTCString(kTodayStr, &today_for_test);
+ return today_for_test;
+ }
+
+ std::string GetCurrentMeasurementDateAsString() const override {
+ return kTodayStr;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeDataUseTracker);
+};
+
+// Sets up data usage prefs with mock values so that UMA traffic is above the
+// allowed ratio.
+void SetPrefTestValuesOverRatio(PrefService* local_state) {
+ base::DictionaryValue user_pref_dict;
+ user_pref_dict.SetInteger(kTodayStr, 2 * 100);
+ user_pref_dict.SetInteger(kYesterdayStr, 2 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr1, 2 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr2, 2 * 100);
+ local_state->Set(prefs::kUserCellDataUse, user_pref_dict);
+
+ base::DictionaryValue uma_pref_dict;
+ uma_pref_dict.SetInteger(kTodayStr, 50);
+ uma_pref_dict.SetInteger(kYesterdayStr, 50);
+ uma_pref_dict.SetInteger(kExpiredDateStr1, 50);
+ uma_pref_dict.SetInteger(kExpiredDateStr2, 50);
+ local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict);
+}
+
+// Sets up data usage prefs with mock values which can be valid.
+void SetPrefTestValuesValidRatio(PrefService* local_state) {
+ base::DictionaryValue user_pref_dict;
+ user_pref_dict.SetInteger(kTodayStr, 100 * 100);
+ user_pref_dict.SetInteger(kYesterdayStr, 100 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr1, 100 * 100);
+ user_pref_dict.SetInteger(kExpiredDateStr2, 100 * 100);
+ local_state->Set(prefs::kUserCellDataUse, user_pref_dict);
+
+ // Should be 4% of user traffic
+ base::DictionaryValue uma_pref_dict;
+ uma_pref_dict.SetInteger(kTodayStr, 4 * 100);
+ uma_pref_dict.SetInteger(kYesterdayStr, 4 * 100);
+ uma_pref_dict.SetInteger(kExpiredDateStr1, 4 * 100);
+ uma_pref_dict.SetInteger(kExpiredDateStr2, 4 * 100);
+ local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict);
+}
+
+} // namespace
+
+TEST(DataUseTrackerTest, CheckUpdateUsagePref) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+
+ int user_pref_value = 0;
+ int uma_pref_value = 0;
+
+ data_use_tracker.UpdateMetricsUsagePrefsOnUIThread("", 2 * 100, true);
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kTodayStr, &user_pref_value);
+ EXPECT_EQ(2 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kTodayStr, &uma_pref_value);
+ EXPECT_EQ(0, uma_pref_value);
+
+ data_use_tracker.UpdateMetricsUsagePrefsOnUIThread("UMA", 100, true);
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kTodayStr, &user_pref_value);
+ EXPECT_EQ(3 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kTodayStr, &uma_pref_value);
+ EXPECT_EQ(100, uma_pref_value);
+}
+
+TEST(DataUseTrackerTest, CheckRemoveExpiredEntries) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesOverRatio(&local_state);
+ data_use_tracker.RemoveExpiredEntries();
+
+ int user_pref_value = 0;
+ int uma_pref_value = 0;
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kExpiredDateStr1, &user_pref_value);
+ EXPECT_EQ(0, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kExpiredDateStr1, &uma_pref_value);
+ EXPECT_EQ(0, uma_pref_value);
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kExpiredDateStr2, &user_pref_value);
+ EXPECT_EQ(0, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kExpiredDateStr2, &uma_pref_value);
+ EXPECT_EQ(0, uma_pref_value);
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kTodayStr, &user_pref_value);
+ EXPECT_EQ(2 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kTodayStr, &uma_pref_value);
+ EXPECT_EQ(50, uma_pref_value);
+
+ local_state.GetDictionary(prefs::kUserCellDataUse)
+ ->GetInteger(kYesterdayStr, &user_pref_value);
+ EXPECT_EQ(2 * 100, user_pref_value);
+ local_state.GetDictionary(prefs::kUmaCellDataUse)
+ ->GetInteger(kYesterdayStr, &uma_pref_value);
+ EXPECT_EQ(50, uma_pref_value);
+}
+
+TEST(DataUseTrackerTest, CheckComputeTotalDataUse) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesOverRatio(&local_state);
+
+ int user_data_use =
+ data_use_tracker.ComputeTotalDataUse(prefs::kUserCellDataUse);
+ EXPECT_EQ(8 * 100, user_data_use);
+ int uma_data_use =
+ data_use_tracker.ComputeTotalDataUse(prefs::kUmaCellDataUse);
+ EXPECT_EQ(4 * 50, uma_data_use);
+}
+
+TEST(DataUseTrackerTest, CheckShouldUploadLogOnCellular) {
+ TestDataUsePrefService local_state;
+ FakeDataUseTracker data_use_tracker(&local_state);
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesOverRatio(&local_state);
+
+ bool can_upload = data_use_tracker.ShouldUploadLogOnCellular(50);
+ EXPECT_TRUE(can_upload);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(100);
+ EXPECT_TRUE(can_upload);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(150);
+ EXPECT_FALSE(can_upload);
+
+ local_state.ClearDataUsePrefs();
+ SetPrefTestValuesValidRatio(&local_state);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(100);
+ EXPECT_TRUE(can_upload);
+ // this is about 0.49%
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(200);
+ EXPECT_TRUE(can_upload);
+ can_upload = data_use_tracker.ShouldUploadLogOnCellular(300);
+ EXPECT_FALSE(can_upload);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider.cc b/chromium/components/metrics/drive_metrics_provider.cc
new file mode 100644
index 00000000000..22ad0891919
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider.cc
@@ -0,0 +1,97 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/task_runner_util.h"
+#include "base/time/time.h"
+
+namespace metrics {
+
+DriveMetricsProvider::DriveMetricsProvider(
+ scoped_refptr<base::SequencedTaskRunner> file_thread,
+ int local_state_path_key)
+ : file_thread_(file_thread),
+ local_state_path_key_(local_state_path_key),
+ weak_ptr_factory_(this) {}
+
+DriveMetricsProvider::~DriveMetricsProvider() {}
+
+void DriveMetricsProvider::ProvideSystemProfileMetrics(
+ metrics::SystemProfileProto* system_profile_proto) {
+ auto* hardware = system_profile_proto->mutable_hardware();
+ FillDriveMetrics(metrics_.app_drive, hardware->mutable_app_drive());
+ FillDriveMetrics(metrics_.user_data_drive,
+ hardware->mutable_user_data_drive());
+}
+
+void DriveMetricsProvider::GetDriveMetrics(const base::Closure& done_callback) {
+ base::PostTaskAndReplyWithResult(
+ file_thread_.get(), FROM_HERE,
+ base::Bind(&DriveMetricsProvider::GetDriveMetricsOnFileThread,
+ local_state_path_key_),
+ base::Bind(&DriveMetricsProvider::GotDriveMetrics,
+ weak_ptr_factory_.GetWeakPtr(), done_callback));
+}
+
+DriveMetricsProvider::SeekPenaltyResponse::SeekPenaltyResponse()
+ : success(false) {}
+
+// static
+DriveMetricsProvider::DriveMetrics
+DriveMetricsProvider::GetDriveMetricsOnFileThread(int local_state_path_key) {
+ DriveMetricsProvider::DriveMetrics metrics;
+ QuerySeekPenalty(base::FILE_EXE, &metrics.app_drive);
+ QuerySeekPenalty(local_state_path_key, &metrics.user_data_drive);
+ return metrics;
+}
+
+// static
+void DriveMetricsProvider::QuerySeekPenalty(
+ int path_service_key,
+ DriveMetricsProvider::SeekPenaltyResponse* response) {
+ DCHECK(response);
+
+ base::FilePath path;
+ if (!PathService::Get(path_service_key, &path))
+ return;
+
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ response->success = HasSeekPenalty(path, &response->has_seek_penalty);
+
+ UMA_HISTOGRAM_TIMES("Hardware.Drive.HasSeekPenalty_Time",
+ base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty_Success",
+ response->success);
+ if (response->success) {
+ UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty",
+ response->has_seek_penalty);
+ }
+}
+
+void DriveMetricsProvider::GotDriveMetrics(
+ const base::Closure& done_callback,
+ const DriveMetricsProvider::DriveMetrics& metrics) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ metrics_ = metrics;
+ done_callback.Run();
+}
+
+void DriveMetricsProvider::FillDriveMetrics(
+ const DriveMetricsProvider::SeekPenaltyResponse& response,
+ metrics::SystemProfileProto::Hardware::Drive* drive) {
+ if (response.success)
+ drive->set_has_seek_penalty(response.has_seek_penalty);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider.h b/chromium/components/metrics/drive_metrics_provider.h
new file mode 100644
index 00000000000..cab799d40ea
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider.h
@@ -0,0 +1,100 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace metrics {
+
+// Provides metrics about the local drives on a user's computer. Currently only
+// checks to see if they incur a seek-time penalty (e.g. if they're SSDs).
+//
+// Defers gathering metrics until after "rush hour" (startup) so as to not bog
+// down the file thread.
+class DriveMetricsProvider : public metrics::MetricsProvider {
+ public:
+ DriveMetricsProvider(scoped_refptr<base::SequencedTaskRunner> file_thread,
+ int local_state_path_key);
+ ~DriveMetricsProvider() override;
+
+ // metrics::MetricsDataProvider:
+ void ProvideSystemProfileMetrics(
+ metrics::SystemProfileProto* system_profile_proto) override;
+
+ // Called to start gathering metrics.
+ void GetDriveMetrics(const base::Closure& done_callback);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(DriveMetricsProviderTest, HasSeekPenalty);
+
+ // A response to querying a drive as to whether it incurs a seek penalty.
+ // |has_seek_penalty| is set if |success| is true.
+ struct SeekPenaltyResponse {
+ SeekPenaltyResponse();
+ bool success;
+ bool has_seek_penalty;
+ };
+
+ struct DriveMetrics {
+ SeekPenaltyResponse app_drive;
+ SeekPenaltyResponse user_data_drive;
+ };
+
+ // Determine whether the device that services |path| has a seek penalty.
+ // Returns false if it couldn't be determined (e.g., |path| doesn't exist).
+ static bool HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty);
+
+ // Gather metrics about various drives on |file_thread_|.
+ static DriveMetrics GetDriveMetricsOnFileThread(int local_state_path_key);
+
+ // Tries to determine whether there is a penalty for seeking on the drive that
+ // hosts |path_service_key| (for example: the drive that holds "Local State").
+ static void QuerySeekPenalty(int path_service_key,
+ SeekPenaltyResponse* response);
+
+ // Called when metrics are done being gathered from the FILE thread.
+ // |done_callback| is the callback that should be called once all metrics are
+ // gathered.
+ void GotDriveMetrics(const base::Closure& done_callback,
+ const DriveMetrics& metrics);
+
+ // Fills |drive| with information from successful |response|s.
+ void FillDriveMetrics(const SeekPenaltyResponse& response,
+ metrics::SystemProfileProto::Hardware::Drive* drive);
+
+ // The thread on which file operations are performed (supplied by the
+ // embedder).
+ scoped_refptr<base::SequencedTaskRunner> file_thread_;
+
+ // The key to give to base::PathService to obtain the path to local state
+ // (supplied by the embedder).
+ int local_state_path_key_;
+
+ // Information gathered about various important drives.
+ DriveMetrics metrics_;
+
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<DriveMetricsProvider> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/drive_metrics_provider_android.cc b/chromium/components/metrics/drive_metrics_provider_android.cc
new file mode 100644
index 00000000000..a653dd6f3ef
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_android.cc
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ *has_seek_penalty = false;
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_ios.mm b/chromium/components/metrics/drive_metrics_provider_ios.mm
new file mode 100644
index 00000000000..a653dd6f3ef
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_ios.mm
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ *has_seek_penalty = false;
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_linux.cc b/chromium/components/metrics/drive_metrics_provider_linux.cc
new file mode 100644
index 00000000000..35c505a3492
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_linux.cc
@@ -0,0 +1,65 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <linux/kdev_t.h> // For MAJOR()/MINOR().
+#include <sys/stat.h>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+
+#if defined(OS_CHROMEOS)
+#include "base/sys_info.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+// See http://www.kernel.org/doc/Documentation/devices.txt for more info.
+const int kFirstScsiMajorNumber = 8;
+const int kPartitionsPerScsiDevice = 16;
+const char kRotationalFormat[] = "/sys/block/sd%c/queue/rotational";
+
+} // namespace
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+#if defined(OS_CHROMEOS)
+ std::string board = base::SysInfo::GetLsbReleaseBoard();
+ if (board != "unknown" && board != "parrot") {
+ // All ChromeOS devices have SSDs. Except some parrots.
+ *has_seek_penalty = false;
+ return true;
+ }
+#endif
+
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid())
+ return false;
+
+ struct stat path_stat;
+ int error = fstat(file.GetPlatformFile(), &path_stat);
+ if (error < 0 || MAJOR(path_stat.st_dev) != kFirstScsiMajorNumber) {
+ // TODO(dbeam): support more SCSI major numbers (e.g. /dev/sdq+) and LVM?
+ return false;
+ }
+
+ char sdX = 'a' + MINOR(path_stat.st_dev) / kPartitionsPerScsiDevice;
+ std::string rotational_path = base::StringPrintf(kRotationalFormat, sdX);
+ std::string rotates;
+ if (!base::ReadFileToString(base::FilePath(rotational_path), &rotates))
+ return false;
+
+ *has_seek_penalty = rotates.substr(0, 1) == "1";
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_mac.mm b/chromium/components/metrics/drive_metrics_provider_mac.mm
new file mode 100644
index 00000000000..a6a37614d0c
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_mac.mm
@@ -0,0 +1,76 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <DiskArbitration/DiskArbitration.h>
+#include <Foundation/Foundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "base/files/file_path.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_ioobject.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ struct stat path_stat;
+ if (stat(path.value().c_str(), &path_stat) < 0)
+ return false;
+
+ const char* dev_name = devname(path_stat.st_dev, S_IFBLK);
+ if (!dev_name)
+ return false;
+
+ std::string bsd_name("/dev/");
+ bsd_name.append(dev_name);
+
+ base::ScopedCFTypeRef<DASessionRef> session(
+ DASessionCreate(kCFAllocatorDefault));
+ if (!session)
+ return false;
+
+ base::ScopedCFTypeRef<DADiskRef> disk(
+ DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsd_name.c_str()));
+ if (!disk)
+ return false;
+
+ base::mac::ScopedIOObject<io_object_t> io_media(DADiskCopyIOMedia(disk));
+ base::ScopedCFTypeRef<CFDictionaryRef> characteristics(
+ static_cast<CFDictionaryRef>(IORegistryEntrySearchCFProperty(
+ io_media, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey),
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively | kIORegistryIterateParents)));
+ if (!characteristics)
+ return false;
+
+ CFStringRef type_ref = base::mac::GetValueFromDictionary<CFStringRef>(
+ characteristics, CFSTR(kIOPropertyMediumTypeKey));
+ if (!type_ref)
+ return false;
+
+ NSString* type = base::mac::CFToNSCast(type_ref);
+ if ([type isEqualToString:@kIOPropertyMediumTypeRotationalKey]) {
+ *has_seek_penalty = true;
+ return true;
+ } else if ([type isEqualToString:@kIOPropertyMediumTypeSolidStateKey]) {
+ *has_seek_penalty = false;
+ return true;
+ }
+
+ // TODO(dbeam): should I look for these Rotational/Solid State keys in
+ // |characteristics|? What if I find device characteristic but there's no
+ // type? Assume rotational?
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_unittest.cc b/chromium/components/metrics/drive_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..142faf72be1
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(DriveMetricsProviderTest, HasSeekPenalty) {
+ base::FilePath tmp_path;
+ ASSERT_TRUE(base::GetTempDir(&tmp_path));
+ bool unused;
+ DriveMetricsProvider::HasSeekPenalty(tmp_path, &unused);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/drive_metrics_provider_win.cc b/chromium/components/metrics/drive_metrics_provider_win.cc
new file mode 100644
index 00000000000..360b80e690a
--- /dev/null
+++ b/chromium/components/metrics/drive_metrics_provider_win.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <windows.h>
+#include <winioctl.h>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/strings/stringprintf.h"
+#include "base/win/windows_version.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+ bool* has_seek_penalty) {
+ if (base::win::GetVersion() < base::win::VERSION_WIN7) {
+ // TODO(dbeam): re-enable XP and Vista detection in a utility process.
+ return false;
+ }
+
+ std::vector<base::FilePath::StringType> components;
+ path.GetComponents(&components);
+
+ base::File volume(base::FilePath(L"\\\\.\\" + components[0]),
+ base::File::FLAG_OPEN);
+ if (!volume.IsValid())
+ return false;
+
+ STORAGE_PROPERTY_QUERY query = {};
+ query.QueryType = PropertyStandardQuery;
+ query.PropertyId = StorageDeviceSeekPenaltyProperty;
+
+ DEVICE_SEEK_PENALTY_DESCRIPTOR result;
+ DWORD bytes_returned;
+
+ BOOL success = DeviceIoControl(
+ volume.GetPlatformFile(), IOCTL_STORAGE_QUERY_PROPERTY, &query,
+ sizeof(query), &result, sizeof(result), &bytes_returned, NULL);
+
+ if (success == FALSE || bytes_returned < sizeof(result))
+ return false;
+
+ *has_seek_penalty = result.IncursSeekPenalty != FALSE;
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/file_metrics_provider.cc b/chromium/components/metrics/file_metrics_provider.cc
new file mode 100644
index 00000000000..78fb90a1211
--- /dev/null
+++ b/chromium/components/metrics/file_metrics_provider.cc
@@ -0,0 +1,291 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/file_metrics_provider.h"
+
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+
+namespace metrics {
+
+// This structure stores all the information about the files being monitored
+// and their current reporting state.
+struct FileMetricsProvider::FileInfo {
+ FileInfo() {}
+ ~FileInfo() {}
+
+ // Where on disk the file is located.
+ base::FilePath path;
+
+ // How to access this file (atomic/active).
+ FileType type;
+
+ // Name used inside prefs to persistent metadata.
+ std::string prefs_key;
+
+ // The last-seen time of this file to detect change.
+ base::Time last_seen;
+
+ // Once a file has been recognized as needing to be read, it is |mapped|
+ // into memory. If that file is "atomic" then the data from that file
+ // will be copied to |data| and the mapped file released. If the file is
+ // "active", it remains mapped and nothing is copied to local memory.
+ std::vector<uint8_t> data;
+ scoped_ptr<base::MemoryMappedFile> mapped;
+ scoped_ptr<base::PersistentHistogramAllocator> allocator;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileInfo);
+};
+
+FileMetricsProvider::FileMetricsProvider(
+ const scoped_refptr<base::TaskRunner>& task_runner,
+ PrefService* local_state)
+ : task_runner_(task_runner),
+ pref_service_(local_state),
+ weak_factory_(this) {
+}
+
+FileMetricsProvider::~FileMetricsProvider() {}
+
+void FileMetricsProvider::RegisterFile(const base::FilePath& path,
+ FileType type,
+ const base::StringPiece prefs_key) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ scoped_ptr<FileInfo> file(new FileInfo());
+ file->path = path;
+ file->type = type;
+ file->prefs_key = prefs_key.as_string();
+
+ // |prefs_key| may be empty if the caller does not wish to persist the
+ // state across instances of the program.
+ if (pref_service_ && !prefs_key.empty()) {
+ file->last_seen = base::Time::FromInternalValue(
+ pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix +
+ file->prefs_key));
+ }
+
+ files_to_check_.push_back(std::move(file));
+}
+
+// static
+void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs,
+ const base::StringPiece prefs_key) {
+ prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix +
+ prefs_key.as_string(), 0);
+}
+
+// static
+void FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner(
+ FileMetricsProvider::FileInfoList* files) {
+ // This method has all state information passed in |files| and is intended
+ // to run on a worker thread rather than the UI thread.
+ for (scoped_ptr<FileInfo>& file : *files) {
+ AccessResult result = CheckAndMapNewMetrics(file.get());
+ // Some results are not reported in order to keep the dashboard clean.
+ if (result != ACCESS_RESULT_DOESNT_EXIST &&
+ result != ACCESS_RESULT_NOT_MODIFIED) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "UMA.FileMetricsProvider.AccessResult", result, ACCESS_RESULT_MAX);
+ }
+ }
+}
+
+// This method has all state information passed in |file| and is intended
+// to run on a worker thread rather than the UI thread.
+// static
+FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapNewMetrics(
+ FileMetricsProvider::FileInfo* file) {
+ DCHECK(!file->mapped);
+ DCHECK(file->data.empty());
+
+ base::File::Info info;
+ if (!base::GetFileInfo(file->path, &info))
+ return ACCESS_RESULT_DOESNT_EXIST;
+
+ if (info.is_directory || info.size == 0)
+ return ACCESS_RESULT_INVALID_FILE;
+
+ if (file->last_seen >= info.last_modified)
+ return ACCESS_RESULT_NOT_MODIFIED;
+
+ // A new file of metrics has been found. Map it into memory.
+ // TODO(bcwhite): Make this open read/write when supported for "active".
+ file->mapped.reset(new base::MemoryMappedFile());
+ if (!file->mapped->Initialize(file->path)) {
+ file->mapped.reset();
+ return ACCESS_RESULT_SYSTEM_MAP_FAILURE;
+ }
+
+ // Ensure any problems below don't occur repeatedly.
+ file->last_seen = info.last_modified;
+
+ // Test the validity of the file contents.
+ if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*file->mapped))
+ return ACCESS_RESULT_INVALID_CONTENTS;
+
+ // For an "atomic" file, immediately copy the data into local memory and
+ // release the file so that it is not held open.
+ if (file->type == FILE_HISTOGRAMS_ATOMIC) {
+ file->data.assign(file->mapped->data(),
+ file->mapped->data() + file->mapped->length());
+ file->mapped.reset();
+ }
+
+ return ACCESS_RESULT_SUCCESS;
+}
+
+void FileMetricsProvider::ScheduleFilesCheck() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (files_to_check_.empty())
+ return;
+
+ // Create an independent list of files for checking. This will be Owned()
+ // by the reply call given to the task-runner, to be deleted when that call
+ // has returned. It is also passed Unretained() to the task itself, safe
+ // because that must complete before the reply runs.
+ FileInfoList* check_list = new FileInfoList();
+ std::swap(files_to_check_, *check_list);
+ task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&FileMetricsProvider::CheckAndMapNewMetricFilesOnTaskRunner,
+ base::Unretained(check_list)),
+ base::Bind(&FileMetricsProvider::RecordFilesChecked,
+ weak_factory_.GetWeakPtr(), base::Owned(check_list)));
+}
+
+void FileMetricsProvider::RecordHistogramSnapshotsFromFile(
+ base::HistogramSnapshotManager* snapshot_manager,
+ FileInfo* file) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::PersistentHistogramAllocator::Iterator histogram_iter(
+ file->allocator.get());
+
+ int histogram_count = 0;
+ while (true) {
+ scoped_ptr<base::HistogramBase> histogram = histogram_iter.GetNext();
+ if (!histogram)
+ break;
+ if (file->type == FILE_HISTOGRAMS_ATOMIC)
+ snapshot_manager->PrepareAbsoluteTakingOwnership(std::move(histogram));
+ else
+ snapshot_manager->PrepareDeltaTakingOwnership(std::move(histogram));
+ ++histogram_count;
+ }
+
+ DVLOG(1) << "Reported " << histogram_count << " histograms from "
+ << file->path.value();
+}
+
+void FileMetricsProvider::CreateAllocatorForFile(FileInfo* file) {
+ DCHECK(!file->allocator);
+
+ // File data was validated earlier. Files are not considered "untrusted"
+ // as some processes might be (e.g. Renderer) so there's no need to check
+ // again to try to thwart some malicious actor that may have modified the
+ // data between then and now.
+ if (file->mapped) {
+ DCHECK(file->data.empty());
+ // TODO(bcwhite): Make this do read/write when supported for "active".
+ file->allocator.reset(new base::PersistentHistogramAllocator(
+ make_scoped_ptr(new base::FilePersistentMemoryAllocator(
+ std::move(file->mapped), 0, ""))));
+ } else {
+ DCHECK(!file->mapped);
+ file->allocator.reset(new base::PersistentHistogramAllocator(
+ make_scoped_ptr(new base::PersistentMemoryAllocator(
+ &file->data[0], file->data.size(), 0, 0, "", true))));
+ }
+}
+
+void FileMetricsProvider::RecordFilesChecked(FileInfoList* checked) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Move each processed file to either the "to-read" list (for processing) or
+ // the "to-check" list (for future checking).
+ for (auto iter = checked->begin(); iter != checked->end();) {
+ auto temp = iter++;
+ const FileInfo* file = temp->get();
+ if (file->mapped || !file->data.empty())
+ files_to_read_.splice(files_to_read_.end(), *checked, temp);
+ else
+ files_to_check_.splice(files_to_check_.end(), *checked, temp);
+ }
+}
+
+void FileMetricsProvider::RecordFileAsSeen(FileInfo* file) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (pref_service_ && !file->prefs_key.empty()) {
+ pref_service_->SetInt64(metrics::prefs::kMetricsLastSeenPrefix +
+ file->prefs_key,
+ file->last_seen.ToInternalValue());
+ }
+}
+
+void FileMetricsProvider::OnDidCreateMetricsLog() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Move finished metric files back to list of monitored files.
+ for (auto iter = files_to_read_.begin(); iter != files_to_read_.end();) {
+ auto temp = iter++;
+ const FileInfo* file = temp->get();
+ if (!file->allocator && !file->mapped && file->data.empty())
+ files_to_check_.splice(files_to_check_.end(), files_to_read_, temp);
+ }
+
+ // Schedule a check to see if there are new metrics to load. If so, they
+ // will be reported during the next collection run after this one. The
+ // check is run off of the worker-pool so as to not cause delays on the
+ // main UI thread (which is currently where metric collection is done).
+ ScheduleFilesCheck();
+}
+
+void FileMetricsProvider::RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ for (scoped_ptr<FileInfo>& file : files_to_read_) {
+ // If the file is mapped or loaded then it needs to have an allocator
+ // attached to it in order to read histograms out of it.
+ if (file->mapped || !file->data.empty())
+ CreateAllocatorForFile(file.get());
+
+ // A file should not be under "files to read" unless it has an allocator
+ // or is memory-mapped (at which point it will have received an allocator
+ // above). However, if this method gets called twice before the scheduled-
+ // files-check has a chance to clean up, this may trigger. This also
+ // catches the case where creating an allocator from the file has failed.
+ if (!file->allocator)
+ continue;
+
+ // Dump all histograms contained within the file to the snapshot-manager.
+ RecordHistogramSnapshotsFromFile(snapshot_manager, file.get());
+
+ // Atomic files are read once and then ignored unless they change.
+ if (file->type == FILE_HISTOGRAMS_ATOMIC) {
+ DCHECK(!file->mapped);
+ file->allocator.reset();
+ file->data.clear();
+ RecordFileAsSeen(file.get());
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/file_metrics_provider.h b/chromium/components/metrics/file_metrics_provider.h
new file mode 100644
index 00000000000..88c025f0ec1
--- /dev/null
+++ b/chromium/components/metrics/file_metrics_provider.h
@@ -0,0 +1,157 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
+
+#include <list>
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_provider.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class MemoryMappedFile;
+class PersistentMemoryAllocator;
+class TaskRunner;
+}
+
+namespace metrics {
+
+// FileMetricsProvider gathers and logs histograms written to files on disk.
+// Any number of files can be registered and will be polled once per upload
+// cycle (at startup and periodically thereafter -- about every 30 minutes
+// for desktop) for data to send.
+class FileMetricsProvider : public metrics::MetricsProvider {
+ public:
+ enum FileType {
+ // "Atomic" files are a collection of histograms that are written
+ // completely in a single atomic operation (typically a write followed
+ // by an atomic rename) and the file is never updated again except to
+ // be replaced by a completely new set of histograms. This is the only
+ // option that can be used if the file is not writeable by *this*
+ // process.
+ FILE_HISTOGRAMS_ATOMIC,
+
+ // "Active" files may be open by one or more other processes and updated
+ // at any time with new samples or new histograms. Such files may also be
+ // inactive for any period of time only to be opened again and have new
+ // data written to them. The file should probably never be deleted because
+ // there would be no guarantee that the data has been reported.
+ // TODO(bcwhite): Enable when read/write mem-mapped files are supported.
+ //FILE_HISTOGRAMS_ACTIVE,
+ };
+
+ FileMetricsProvider(const scoped_refptr<base::TaskRunner>& task_runner,
+ PrefService* local_state);
+ ~FileMetricsProvider() override;
+
+ // Indicates a file to be monitored and how the file is used. Because some
+ // metadata may persist across process restarts, preferences entries are
+ // used based on the |prefs_key| name. Call RegisterPrefs() with the same
+ // name to create the necessary keys in advance. Set |prefs_key| empty
+ // if no persistence is required.
+ void RegisterFile(const base::FilePath& path,
+ FileType type,
+ const base::StringPiece prefs_key);
+
+ // Registers all necessary preferences for maintaining persistent state
+ // about a monitored file across process restarts. The |prefs_key| is
+ // typically the filename.
+ static void RegisterPrefs(PrefRegistrySimple* prefs,
+ const base::StringPiece prefs_key);
+
+ private:
+ friend class FileMetricsProviderTest;
+ FRIEND_TEST_ALL_PREFIXES(FileMetricsProviderTest, AccessMetrics);
+
+ // The different results that can occur accessing a file.
+ enum AccessResult {
+ // File was successfully mapped.
+ ACCESS_RESULT_SUCCESS,
+
+ // File does not exist.
+ ACCESS_RESULT_DOESNT_EXIST,
+
+ // File exists but not modified since last read.
+ ACCESS_RESULT_NOT_MODIFIED,
+
+ // File is not valid: is a directory or zero-size.
+ ACCESS_RESULT_INVALID_FILE,
+
+ // System could not map file into memory.
+ ACCESS_RESULT_SYSTEM_MAP_FAILURE,
+
+ // File had invalid contents.
+ ACCESS_RESULT_INVALID_CONTENTS,
+
+ ACCESS_RESULT_MAX
+ };
+
+ // Information about files being monitored; defined and used exclusively
+ // inside the .cc file.
+ struct FileInfo;
+ using FileInfoList = std::list<scoped_ptr<FileInfo>>;
+
+ // Checks a list of files (on a task-runner allowed to do I/O) to see if
+ // any should be processed during the next histogram collection.
+ static void CheckAndMapNewMetricFilesOnTaskRunner(FileInfoList* files);
+
+ // Checks an individual file as part of CheckAndMapNewMetricFilesOnTaskRunner.
+ static AccessResult CheckAndMapNewMetrics(FileInfo* file);
+
+ // Creates a task to check all monitored files for updates.
+ void ScheduleFilesCheck();
+
+ // Creates a PersistentMemoryAllocator for a file that has been marked to
+ // have its metrics collected.
+ void CreateAllocatorForFile(FileInfo* file);
+
+ // Records all histograms from a given file via a snapshot-manager.
+ void RecordHistogramSnapshotsFromFile(
+ base::HistogramSnapshotManager* snapshot_manager,
+ FileInfo* file);
+
+ // Takes a list of files checked by an external task and determines what
+ // to do with each.
+ void RecordFilesChecked(FileInfoList* checked);
+
+ // Updates the persistent state information to show a file as being read.
+ void RecordFileAsSeen(FileInfo* file);
+
+ // metrics::MetricsDataProvider:
+ void OnDidCreateMetricsLog() override;
+ void RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager) override;
+
+ // A task-runner capable of performing I/O.
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // A list of files not currently active that need to be checked for changes.
+ FileInfoList files_to_check_;
+
+ // A list of files that have data to be read and reported.
+ FileInfoList files_to_read_;
+
+ // The preferences-service used to store persistent state about files.
+ PrefService* pref_service_;
+
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<FileMetricsProvider> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/file_metrics_provider_unittest.cc b/chromium/components/metrics/file_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..0949c2d3690
--- /dev/null
+++ b/chromium/components/metrics/file_metrics_provider_unittest.cc
@@ -0,0 +1,192 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/file_metrics_provider.h"
+
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kMetricsName[] = "TestMetrics";
+const char kMetricsFilename[] = "file.metrics";
+} // namespace
+
+namespace metrics {
+
+class HistogramFlattenerDeltaRecorder : public base::HistogramFlattener {
+ public:
+ HistogramFlattenerDeltaRecorder() {}
+
+ void RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) override {
+ recorded_delta_histogram_names_.push_back(histogram.histogram_name());
+ }
+
+ void InconsistencyDetected(base::HistogramBase::Inconsistency problem)
+ override {
+ ASSERT_TRUE(false);
+ }
+
+ void UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override {
+ ASSERT_TRUE(false);
+ }
+
+ void InconsistencyDetectedInLoggedCount(int amount) override {
+ ASSERT_TRUE(false);
+ }
+
+ std::vector<std::string> GetRecordedDeltaHistogramNames() {
+ return recorded_delta_histogram_names_;
+ }
+
+ private:
+ std::vector<std::string> recorded_delta_histogram_names_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramFlattenerDeltaRecorder);
+};
+
+class FileMetricsProviderTest : public testing::Test {
+ protected:
+ FileMetricsProviderTest()
+ : task_runner_(new base::TestSimpleTaskRunner()),
+ thread_task_runner_handle_(task_runner_),
+ prefs_(new TestingPrefServiceSimple) {
+ EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+ FileMetricsProvider::RegisterPrefs(prefs_->registry(), kMetricsName);
+ }
+
+ TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+ base::FilePath temp_dir() { return temp_dir_.path(); }
+ base::FilePath metrics_file() {
+ return temp_dir_.path().AppendASCII(kMetricsFilename);
+ }
+
+ FileMetricsProvider* provider() {
+ if (!provider_)
+ provider_.reset(new FileMetricsProvider(task_runner_, prefs()));
+ return provider_.get();
+ }
+
+ void RunTasks() {
+ task_runner_->RunUntilIdle();
+ }
+
+ private:
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+ base::ThreadTaskRunnerHandle thread_task_runner_handle_;
+
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<TestingPrefServiceSimple> prefs_;
+ scoped_ptr<FileMetricsProvider> provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMetricsProviderTest);
+};
+
+
+TEST_F(FileMetricsProviderTest, AccessMetrics) {
+ ASSERT_FALSE(PathExists(metrics_file()));
+
+ {
+ // Get this first so it isn't created inside the persistent allocator.
+ base::GlobalHistogramAllocator::GetCreateHistogramResultHistogram();
+
+ base::GlobalHistogramAllocator::CreateWithLocalMemory(
+ 64 << 10, 0, kMetricsName);
+ base::HistogramBase* foo =
+ base::Histogram::FactoryGet("foo", 1, 100, 10, 0);
+ base::HistogramBase* bar =
+ base::Histogram::FactoryGet("bar", 1, 100, 10, 0);
+ foo->Add(42);
+ bar->Add(84);
+
+ scoped_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+ base::GlobalHistogramAllocator::ReleaseForTesting();
+ base::PersistentMemoryAllocator* allocator =
+ histogram_allocator->memory_allocator();
+ base::File writer(metrics_file(),
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(writer.IsValid());
+ ASSERT_EQ(static_cast<int>(allocator->used()),
+ writer.Write(0, (const char*)allocator->data(),
+ allocator->used()));
+ }
+
+ // Register the file and allow the "checker" task to run.
+ ASSERT_TRUE(PathExists(metrics_file()));
+ provider()->RegisterFile(metrics_file(),
+ FileMetricsProvider::FILE_HISTOGRAMS_ATOMIC,
+ kMetricsName);
+
+ // Record embedded snapshots via snapshot-manager.
+ provider()->OnDidCreateMetricsLog();
+ RunTasks();
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+
+ // Make sure a second call to the snapshot-recorder doesn't break anything.
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+
+ // Second full run on the same file should produce nothing.
+ provider()->OnDidCreateMetricsLog();
+ RunTasks();
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+
+ // Update the time-stamp of the file to indicate that it is "new" and
+ // must be recorded.
+ {
+ base::File touch(metrics_file(),
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ ASSERT_TRUE(touch.IsValid());
+ base::Time next = base::Time::Now() + base::TimeDelta::FromSeconds(1);
+ touch.SetTimes(next, next);
+ }
+
+ // This run should again have "new" histograms.
+ provider()->OnDidCreateMetricsLog();
+ RunTasks();
+ {
+ HistogramFlattenerDeltaRecorder flattener;
+ base::HistogramSnapshotManager snapshot_manager(&flattener);
+ snapshot_manager.StartDeltas();
+ provider()->RecordHistogramSnapshots(&snapshot_manager);
+ snapshot_manager.FinishDeltas();
+ EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/gpu/DEPS b/chromium/components/metrics/gpu/DEPS
new file mode 100644
index 00000000000..c2ff8a0af67
--- /dev/null
+++ b/chromium/components/metrics/gpu/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+content/public/browser",
+ "+gpu/config",
+]
diff --git a/chromium/components/metrics/gpu/gpu_metrics_provider.cc b/chromium/components/metrics/gpu/gpu_metrics_provider.cc
new file mode 100644
index 00000000000..19cf9b49c8e
--- /dev/null
+++ b/chromium/components/metrics/gpu/gpu_metrics_provider.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/gpu/gpu_metrics_provider.h"
+
+#include "components/metrics/proto/system_profile.pb.h"
+#include "content/public/browser/gpu_data_manager.h"
+#include "gpu/config/gpu_info.h"
+
+namespace metrics {
+
+GPUMetricsProvider::GPUMetricsProvider() {
+}
+
+GPUMetricsProvider::~GPUMetricsProvider() {
+}
+
+void GPUMetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) {
+ SystemProfileProto::Hardware* hardware =
+ system_profile_proto->mutable_hardware();
+
+ const gpu::GPUInfo& gpu_info =
+ content::GpuDataManager::GetInstance()->GetGPUInfo();
+ SystemProfileProto::Hardware::Graphics* gpu =
+ hardware->mutable_gpu();
+ gpu->set_vendor_id(gpu_info.gpu.vendor_id);
+ gpu->set_device_id(gpu_info.gpu.device_id);
+ gpu->set_driver_version(gpu_info.driver_version);
+ gpu->set_driver_date(gpu_info.driver_date);
+ gpu->set_gl_vendor(gpu_info.gl_vendor);
+ gpu->set_gl_renderer(gpu_info.gl_renderer);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/gpu/gpu_metrics_provider.h b/chromium/components/metrics/gpu/gpu_metrics_provider.h
new file mode 100644
index 00000000000..581c7651ce6
--- /dev/null
+++ b/chromium/components/metrics/gpu/gpu_metrics_provider.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// GPUMetricsProvider provides GPU-related metrics.
+class GPUMetricsProvider : public MetricsProvider {
+ public:
+ GPUMetricsProvider();
+ ~GPUMetricsProvider() override;
+
+ // MetricsProvider:
+ void ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(GPUMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/histogram_encoder.cc b/chromium/components/metrics/histogram_encoder.cc
new file mode 100644
index 00000000000..595fb539867
--- /dev/null
+++ b/chromium/components/metrics/histogram_encoder.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/histogram_encoder.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+
+using base::SampleCountIterator;
+
+namespace metrics {
+
+void EncodeHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot,
+ ChromeUserMetricsExtension* uma_proto) {
+ DCHECK_NE(0, snapshot.TotalCount());
+ DCHECK(uma_proto);
+
+ // We will ignore the MAX_INT/infinite value in the last element of range[].
+
+ HistogramEventProto* histogram_proto = uma_proto->add_histogram_event();
+ histogram_proto->set_name_hash(base::HashMetricName(histogram_name));
+ if (snapshot.sum() != 0)
+ histogram_proto->set_sum(snapshot.sum());
+
+ for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done();
+ it->Next()) {
+ base::Histogram::Sample min;
+ base::Histogram::Sample max;
+ base::Histogram::Count count;
+ it->Get(&min, &max, &count);
+ HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+ bucket->set_min(min);
+ bucket->set_max(max);
+ // Note: The default for count is 1 in the proto, so omit it in that case.
+ if (count != 1)
+ bucket->set_count(count);
+ }
+
+ // Omit fields to save space (see rules in histogram_event.proto comments).
+ for (int i = 0; i < histogram_proto->bucket_size(); ++i) {
+ HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i);
+ if (i + 1 < histogram_proto->bucket_size() &&
+ bucket->max() == histogram_proto->bucket(i + 1).min()) {
+ bucket->clear_max();
+ } else if (bucket->max() == bucket->min() + 1) {
+ bucket->clear_min();
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/histogram_encoder.h b/chromium/components/metrics/histogram_encoder.h
new file mode 100644
index 00000000000..8ef528de2b7
--- /dev/null
+++ b/chromium/components/metrics/histogram_encoder.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines an utility function that records any changes in a given
+// histogram for transmission.
+
+#ifndef COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
+#define COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
+
+#include <string>
+
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class HistogramSamples;
+}
+
+namespace metrics {
+
+// Record any changes (histogram deltas of counts from |snapshot|) into
+// |uma_proto| for the given histogram (|histogram_name|).
+void EncodeHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot,
+ ChromeUserMetricsExtension* uma_proto);
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
diff --git a/chromium/components/metrics/histogram_encoder_unittest.cc b/chromium/components/metrics/histogram_encoder_unittest.cc
new file mode 100644
index 00000000000..dfe7f847d5d
--- /dev/null
+++ b/chromium/components/metrics/histogram_encoder_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/histogram_encoder.h"
+
+#include <string>
+
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(HistogramEncoder, HistogramBucketFields) {
+ // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+ base::BucketRanges ranges(8);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 7);
+ ranges.set_range(3, 8);
+ ranges.set_range(4, 9);
+ ranges.set_range(5, 10);
+ ranges.set_range(6, 11);
+ ranges.set_range(7, 12);
+
+ base::SampleVector samples(1, &ranges);
+ samples.Accumulate(3, 1); // Bucket 1-5.
+ samples.Accumulate(6, 1); // Bucket 5-7.
+ samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped)
+ samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped)
+ samples.Accumulate(11, 1); // Bucket 11-12.
+
+ ChromeUserMetricsExtension uma_proto;
+ EncodeHistogramDelta("Test", samples, &uma_proto);
+
+ const HistogramEventProto& histogram_proto =
+ uma_proto.histogram_event(uma_proto.histogram_event_size() - 1);
+
+ // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+ // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+ ASSERT_EQ(5, histogram_proto.bucket_size());
+
+ // 1-5 becomes 1-/ (max is same as next min).
+ EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+ EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+ // 5-7 stays 5-7 (no optimization possible).
+ EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+ EXPECT_EQ(5, histogram_proto.bucket(1).min());
+ EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+ // 8-9 becomes /-9 (min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+ EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+ // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+ EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+ EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+ // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+ EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/OWNERS b/chromium/components/metrics/leak_detector/OWNERS
new file mode 100644
index 00000000000..d8bfc45aa54
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/OWNERS
@@ -0,0 +1,2 @@
+sque@chromium.org
+wfh@chromium.org
diff --git a/chromium/components/metrics/leak_detector/call_stack_manager.cc b/chromium/components/metrics/leak_detector/call_stack_manager.cc
new file mode 100644
index 00000000000..dfebd3c7251
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_manager.cc
@@ -0,0 +1,72 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_manager.h"
+
+#include <algorithm> // For std::copy.
+#include <new>
+
+#include "base/hash.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+CallStackManager::CallStackManager() {}
+
+CallStackManager::~CallStackManager() {
+ // Free all call stack objects and clear |call_stacks_|. Make sure to save the
+ // CallStack object pointer and remove it from the container before freeing
+ // the CallStack memory.
+ while (!call_stacks_.empty()) {
+ CallStack* call_stack = *call_stacks_.begin();
+ call_stacks_.erase(call_stacks_.begin());
+
+ CustomAllocator::Free(call_stack->stack,
+ call_stack->depth * sizeof(*call_stack->stack));
+ call_stack->stack = nullptr;
+ call_stack->depth = 0;
+
+ CustomAllocator::Free(call_stack, sizeof(CallStack));
+ }
+}
+
+const CallStack* CallStackManager::GetCallStack(size_t depth,
+ const void* const stack[]) {
+ // Temporarily create a call stack object for lookup in |call_stacks_|.
+ CallStack temp;
+ temp.depth = depth;
+ temp.stack = const_cast<const void**>(stack);
+ // This is the only place where the call stack's hash is computed. This value
+ // can be reused in the created object to avoid further hash computation.
+ temp.hash =
+ base::Hash(reinterpret_cast<const char*>(stack), sizeof(*stack) * depth);
+
+ auto iter = call_stacks_.find(&temp);
+ if (iter != call_stacks_.end())
+ return *iter;
+
+ // Since |call_stacks_| stores CallStack pointers rather than actual objects,
+ // create new call objects manually here.
+ CallStack* call_stack =
+ new (CustomAllocator::Allocate(sizeof(CallStack))) CallStack;
+ call_stack->depth = depth;
+ call_stack->hash = temp.hash; // Don't run the hash function again.
+ call_stack->stack = reinterpret_cast<const void**>(
+ CustomAllocator::Allocate(sizeof(*stack) * depth));
+ std::copy(stack, stack + depth, call_stack->stack);
+
+ call_stacks_.insert(call_stack);
+ return call_stack;
+}
+
+bool CallStackManager::CallStackPointerEqual::operator()(
+ const CallStack* c1,
+ const CallStack* c2) const {
+ return c1->depth == c2->depth && c1->hash == c2->hash &&
+ std::equal(c1->stack, c1->stack + c1->depth, c2->stack);
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/call_stack_manager.h b/chromium/components/metrics/leak_detector/call_stack_manager.h
new file mode 100644
index 00000000000..a411bcab8d9
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_manager.h
@@ -0,0 +1,102 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+// Summary of structures:
+//
+// struct CallStack:
+// Represents a unique call stack, defined by its raw call stack (array of
+// pointers), and hash value. All CallStack objects are owned by class
+// CallStackManager. Other classes may hold pointers to them but should not
+// attempt to create or modify any CallStack objects.
+//
+// class CallStackManager:
+// Creates CallStack objects to represent unique call stacks. Owns all
+// CallStack objects that it creates, storing them internally.
+//
+// Returns the call stacks as const pointers because no caller should take
+// ownership of them and modify or delete them. The lifetime of all CallStack
+// objects is limited to that of the CallStackManager object that created
+// them. When the CallStackManager is destroyed, the CallStack objects will be
+// invalidated. Hence the caller should make sure that it does not use
+// CallStack objects beyond the lifetime of the CallStackManager that created
+// them.
+
+namespace metrics {
+namespace leak_detector {
+
+// Struct to represent a call stack.
+struct CallStack {
+ // Call stack as an array of pointers, |stack|. The array length is stored in
+ // |depth|. There is no null terminator.
+ const void** stack;
+ size_t depth;
+
+ // Hash of call stack. It is cached here so that it doesn not have to be
+ // recomputed each time.
+ size_t hash;
+};
+
+// Maintains and owns all unique call stack objects.
+// Not thread-safe.
+class CallStackManager {
+ public:
+ CallStackManager();
+ ~CallStackManager();
+
+ // Returns a CallStack object for a given raw call stack. The first time a
+ // particular raw call stack is passed into this function, it will create a
+ // new CallStack object to hold the raw call stack data, and then return it.
+ // The CallStack object is stored internally.
+ //
+ // On subsequent calls with the same raw call stack, this function will return
+ // the previously created CallStack object.
+ const CallStack* GetCallStack(size_t depth, const void* const stack[]);
+
+ size_t size() const { return call_stacks_.size(); }
+
+ private:
+ // Allocator class for unique call stacks. Uses CustomAllocator to avoid
+ // recursive malloc hook invocation when analyzing allocs and frees.
+ using CallStackPointerAllocator = STLAllocator<CallStack*, CustomAllocator>;
+
+ // Hash operator for call stack object given as a pointer.
+ // Does not actually compute the hash. Instead, returns the already computed
+ // hash value stored in a CallStack object.
+ struct CallStackPointerStoredHash {
+ size_t operator()(const CallStack* call_stack) const {
+ return call_stack->hash;
+ }
+ };
+
+ // Equality comparator for call stack objects given as pointers. Compares
+ // their stack trace contents.
+ struct CallStackPointerEqual {
+ bool operator()(const CallStack* c1, const CallStack* c2) const;
+ };
+
+ // Holds all call stack objects. Each object is allocated elsewhere and stored
+ // as a pointer because the container may rearrange itself internally.
+ base::hash_set<CallStack*,
+ CallStackPointerStoredHash,
+ CallStackPointerEqual,
+ CallStackPointerAllocator> call_stacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackManager);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_MANAGER_H_
diff --git a/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc b/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc
new file mode 100644
index 00000000000..f71bbfff47d
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_manager_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_manager.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Some test call stacks. The addresses are 64-bit but they should automatically
+// be truncated to 32 bits on a 32-bit machine.
+const void* kRawStack0[] = {
+ reinterpret_cast<const void*>(0x8899aabbccddeeff),
+ reinterpret_cast<const void*>(0x0000112233445566),
+ reinterpret_cast<const void*>(0x5566778899aabbcc),
+ reinterpret_cast<const void*>(0x9988776655443322),
+};
+// This is similar to kRawStack0, differing only in one address by 1. It should
+// still produce a distinct CallStack object and hash.
+const void* kRawStack1[] = {
+ kRawStack0[0], kRawStack0[1],
+ reinterpret_cast<const void*>(reinterpret_cast<uintptr_t>(kRawStack0[2]) +
+ 1),
+ kRawStack0[3],
+};
+const void* kRawStack2[] = {
+ reinterpret_cast<const void*>(0x900df00dcab58888),
+ reinterpret_cast<const void*>(0x00001337cafedeed),
+ reinterpret_cast<const void*>(0x0000deafbabe1234),
+};
+const void* kRawStack3[] = {
+ reinterpret_cast<const void*>(0x0000000012345678),
+ reinterpret_cast<const void*>(0x00000000abcdef01),
+ reinterpret_cast<const void*>(0x00000000fdecab98),
+ reinterpret_cast<const void*>(0x0000deadbeef0001),
+ reinterpret_cast<const void*>(0x0000900ddeed0002),
+ reinterpret_cast<const void*>(0x0000f00dcafe0003),
+ reinterpret_cast<const void*>(0x0000f00d900d0004),
+ reinterpret_cast<const void*>(0xdeedcafebabe0005),
+};
+
+// Creates a copy of a call stack as a scoped_ptr to a raw stack. The depth is
+// the same as the original stack, but it is not stored in the result.
+scoped_ptr<const void* []> CopyStack(const CallStack* stack) {
+ scoped_ptr<const void* []> stack_copy(new const void*[stack->depth]);
+ std::copy(stack->stack, stack->stack + stack->depth, stack_copy.get());
+ return stack_copy;
+}
+
+} // namespace
+
+class CallStackManagerTest : public ::testing::Test {
+ public:
+ CallStackManagerTest() {}
+
+ void SetUp() override { CustomAllocator::Initialize(); }
+ void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CallStackManagerTest);
+};
+
+TEST_F(CallStackManagerTest, NewStacks) {
+ CallStackManager manager;
+ EXPECT_EQ(0U, manager.size());
+
+ // Request some new stacks and make sure their creation is reflected in the
+ // size of |manager|.
+ const CallStack* stack0 =
+ manager.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ EXPECT_EQ(arraysize(kRawStack0), stack0->depth);
+ EXPECT_EQ(1U, manager.size());
+
+ const CallStack* stack1 =
+ manager.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ EXPECT_EQ(arraysize(kRawStack1), stack1->depth);
+ EXPECT_EQ(2U, manager.size());
+
+ const CallStack* stack2 =
+ manager.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ EXPECT_EQ(arraysize(kRawStack2), stack2->depth);
+ EXPECT_EQ(3U, manager.size());
+
+ const CallStack* stack3 =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+ EXPECT_EQ(arraysize(kRawStack3), stack3->depth);
+ EXPECT_EQ(4U, manager.size());
+
+ // Call stack objects should be unique.
+ EXPECT_NE(stack0, stack1);
+ EXPECT_NE(stack0, stack2);
+ EXPECT_NE(stack0, stack3);
+ EXPECT_NE(stack1, stack2);
+ EXPECT_NE(stack1, stack3);
+ EXPECT_NE(stack2, stack3);
+}
+
+TEST_F(CallStackManagerTest, Hashes) {
+ CallStackManager manager;
+
+ const CallStack* stack0 =
+ manager.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ const CallStack* stack1 =
+ manager.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ const CallStack* stack2 =
+ manager.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack3 =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ // Hash values should be unique. This test is not designed to make sure the
+ // hash function is generating unique hashes, but that CallStackManager is
+ // properly storing the hashes in CallStack structs.
+ EXPECT_NE(stack0->hash, stack1->hash);
+ EXPECT_NE(stack0->hash, stack2->hash);
+ EXPECT_NE(stack0->hash, stack3->hash);
+ EXPECT_NE(stack1->hash, stack2->hash);
+ EXPECT_NE(stack1->hash, stack3->hash);
+ EXPECT_NE(stack2->hash, stack3->hash);
+}
+
+TEST_F(CallStackManagerTest, MultipleManagersHashes) {
+ CallStackManager manager1;
+ const CallStack* stack10 =
+ manager1.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ const CallStack* stack11 =
+ manager1.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ const CallStack* stack12 =
+ manager1.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack13 =
+ manager1.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ CallStackManager manager2;
+ const CallStack* stack20 =
+ manager2.GetCallStack(arraysize(kRawStack0), kRawStack0);
+ const CallStack* stack21 =
+ manager2.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ const CallStack* stack22 =
+ manager2.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack23 =
+ manager2.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ // Different CallStackManagers should still generate the same hashes.
+ EXPECT_EQ(stack10->hash, stack20->hash);
+ EXPECT_EQ(stack11->hash, stack21->hash);
+ EXPECT_EQ(stack12->hash, stack22->hash);
+ EXPECT_EQ(stack13->hash, stack23->hash);
+}
+
+TEST_F(CallStackManagerTest, HashWithReducedDepth) {
+ CallStackManager manager;
+ const CallStack* stack =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+
+ // Hash function should only operate on the first |CallStack::depth| elements
+ // of CallStack::stack. To test this, reduce the depth value of one of the
+ // stacks and make sure the hash changes.
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 1, stack->stack)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 2, stack->stack)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 3, stack->stack)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 4, stack->stack)->hash);
+
+ // Also try subsets of the stack that don't start from the beginning.
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 1, stack->stack + 1)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 2, stack->stack + 2)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 3, stack->stack + 3)->hash);
+ EXPECT_NE(stack->hash,
+ manager.GetCallStack(stack->depth - 4, stack->stack + 4)->hash);
+}
+
+TEST_F(CallStackManagerTest, DuplicateStacks) {
+ CallStackManager manager;
+ EXPECT_EQ(0U, manager.size());
+
+ // Calling manager.GetCallStack() multiple times with the same raw stack
+ // arguments will not result in creation of new call stack objects after the
+ // first call. Instead, the previously created object will be returned, and
+ // the size of |manager| will remain unchanged.
+ //
+ // Thus a call to GetCallStack() will always return the same result, given the
+ // same inputs.
+
+ // Add stack0.
+ const CallStack* stack0 =
+ manager.GetCallStack(arraysize(kRawStack0), kRawStack0);
+
+ scoped_ptr<const void* []> rawstack0_duplicate0 = CopyStack(stack0);
+ const CallStack* stack0_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate0.get());
+ EXPECT_EQ(1U, manager.size());
+ EXPECT_EQ(stack0, stack0_duplicate0);
+
+ // Add stack1.
+ const CallStack* stack1 =
+ manager.GetCallStack(arraysize(kRawStack1), kRawStack1);
+ EXPECT_EQ(2U, manager.size());
+
+ scoped_ptr<const void* []> rawstack0_duplicate1 = CopyStack(stack0);
+ const CallStack* stack0_duplicate1 =
+ manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate1.get());
+ EXPECT_EQ(2U, manager.size());
+ EXPECT_EQ(stack0, stack0_duplicate1);
+
+ scoped_ptr<const void* []> rawstack1_duplicate0 = CopyStack(stack1);
+ const CallStack* stack1_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack1), rawstack1_duplicate0.get());
+ EXPECT_EQ(2U, manager.size());
+ EXPECT_EQ(stack1, stack1_duplicate0);
+
+ // Add stack2 and stack3.
+ const CallStack* stack2 =
+ manager.GetCallStack(arraysize(kRawStack2), kRawStack2);
+ const CallStack* stack3 =
+ manager.GetCallStack(arraysize(kRawStack3), kRawStack3);
+ EXPECT_EQ(4U, manager.size());
+
+ scoped_ptr<const void* []> rawstack1_duplicate1 = CopyStack(stack1);
+ const CallStack* stack1_duplicate1 =
+ manager.GetCallStack(arraysize(kRawStack1), rawstack1_duplicate1.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack1, stack1_duplicate1);
+
+ scoped_ptr<const void* []> rawstack0_duplicate2 = CopyStack(stack0);
+ const CallStack* stack0_duplicate2 =
+ manager.GetCallStack(arraysize(kRawStack0), rawstack0_duplicate2.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack0, stack0_duplicate2);
+
+ scoped_ptr<const void* []> rawstack3_duplicate0 = CopyStack(stack3);
+ const CallStack* stack3_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack3), rawstack3_duplicate0.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack3, stack3_duplicate0);
+
+ scoped_ptr<const void* []> rawstack2_duplicate0 = CopyStack(stack2);
+ const CallStack* stack2_duplicate0 =
+ manager.GetCallStack(arraysize(kRawStack2), rawstack2_duplicate0.get());
+ EXPECT_EQ(4U, manager.size());
+ EXPECT_EQ(stack2, stack2_duplicate0);
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/call_stack_table.cc b/chromium/components/metrics/leak_detector/call_stack_table.cc
new file mode 100644
index 00000000000..8f9540bc0d4
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_table.cc
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_table.h"
+
+#include <algorithm>
+
+#include "components/metrics/leak_detector/call_stack_manager.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+using ValueType = LeakDetectorValueType;
+
+// During leak analysis, we only want to examine the top
+// |kMaxCountOfSuspciousStacks| entries.
+const int kMaxCountOfSuspciousStacks = 16;
+
+const int kInitialHashTableSize = 1999;
+
+} // namespace
+
+size_t CallStackTable::StoredHash::operator()(
+ const CallStack* call_stack) const {
+ // The call stack object should already have a hash computed when it was
+ // created.
+ //
+ // This is NOT the actual hash computation function for a new call stack.
+ return call_stack->hash;
+}
+
+CallStackTable::CallStackTable(int call_stack_suspicion_threshold)
+ : num_allocs_(0),
+ num_frees_(0),
+ entry_map_(kInitialHashTableSize),
+ leak_analyzer_(kMaxCountOfSuspciousStacks,
+ call_stack_suspicion_threshold) {}
+
+CallStackTable::~CallStackTable() {}
+
+void CallStackTable::Add(const CallStack* call_stack) {
+ ++entry_map_[call_stack];
+ ++num_allocs_;
+}
+
+void CallStackTable::Remove(const CallStack* call_stack) {
+ auto iter = entry_map_.find(call_stack);
+ if (iter == entry_map_.end())
+ return;
+ uint32_t& count_for_call_stack = iter->second;
+ --count_for_call_stack;
+ ++num_frees_;
+
+ // Delete zero-alloc entries to free up space.
+ if (count_for_call_stack == 0)
+ entry_map_.erase(iter);
+}
+
+void CallStackTable::TestForLeaks() {
+ // Add all entries to the ranked list.
+ RankedSet ranked_entries(kMaxCountOfSuspciousStacks);
+ GetTopCallStacks(&ranked_entries);
+ leak_analyzer_.AddSample(std::move(ranked_entries));
+}
+
+void CallStackTable::GetTopCallStacks(RankedSet* top_entries) const {
+ for (const auto& call_stack_and_count : entry_map_) {
+ top_entries->AddCallStack(call_stack_and_count.first,
+ call_stack_and_count.second);
+ }
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/call_stack_table.h b/chromium/components/metrics/leak_detector/call_stack_table.h
new file mode 100644
index 00000000000..77a4b2a3ed7
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_table.h
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <functional> // For std::equal_to.
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_analyzer.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+struct CallStack;
+
+// Contains a hash table where the key is the call stack and the value is the
+// number of allocations from that call stack.
+// Not thread-safe.
+class CallStackTable {
+ public:
+ struct StoredHash {
+ size_t operator()(const CallStack* call_stack) const;
+ };
+
+ explicit CallStackTable(int call_stack_suspicion_threshold);
+ ~CallStackTable();
+
+ // Add/Remove an allocation for the given call stack.
+ // Note that this class does NOT own the CallStack objects. Instead, it
+ // identifies different CallStacks by their hashes.
+ void Add(const CallStack* call_stack);
+ void Remove(const CallStack* call_stack);
+
+ // Check for leak patterns in the allocation data.
+ void TestForLeaks();
+
+ // Get the top N entries in the CallStackTable, ranked by net number of
+ // allocations. N is given by |top_entries->max_size()|, so |*top_entries|
+ // must already be initialized with that number.
+ void GetTopCallStacks(RankedSet* top_entries) const;
+
+ const LeakAnalyzer& leak_analyzer() const { return leak_analyzer_; }
+
+ size_t size() const { return entry_map_.size(); }
+ bool empty() const { return entry_map_.empty(); }
+
+ uint32_t num_allocs() const { return num_allocs_; }
+ uint32_t num_frees() const { return num_frees_; }
+
+ private:
+ // Total number of allocs and frees in this table.
+ uint32_t num_allocs_;
+ uint32_t num_frees_;
+
+ // Hash table containing entries. Uses CustomAllocator to avoid recursive
+ // malloc hook invocation when analyzing allocs and frees.
+ using TableEntryAllocator =
+ STLAllocator<std::pair<const CallStack* const, uint32_t>,
+ CustomAllocator>;
+
+ // Stores a mapping of each call stack to the number of recorded allocations
+ // made from that call site.
+ base::hash_map<const CallStack*,
+ uint32_t,
+ StoredHash,
+ std::equal_to<const CallStack*>,
+ TableEntryAllocator> entry_map_;
+
+ // For detecting leak patterns in incoming allocations.
+ LeakAnalyzer leak_analyzer_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackTable);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CALL_STACK_TABLE_H_
diff --git a/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc b/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc
new file mode 100644
index 00000000000..6600ef714ac
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/call_stack_table_unittest.cc
@@ -0,0 +1,364 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/call_stack_table.h"
+
+#include <set>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/leak_detector/call_stack_manager.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Default threshold used for leak analysis.
+const int kDefaultLeakThreshold = 5;
+
+// Some test call stacks.
+const void* kRawStack0[] = {
+ reinterpret_cast<const void*>(0xaabbccdd),
+ reinterpret_cast<const void*>(0x11223344),
+ reinterpret_cast<const void*>(0x55667788),
+ reinterpret_cast<const void*>(0x99887766),
+};
+const void* kRawStack1[] = {
+ reinterpret_cast<const void*>(0xdeadbeef),
+ reinterpret_cast<const void*>(0x900df00d),
+ reinterpret_cast<const void*>(0xcafedeed),
+ reinterpret_cast<const void*>(0xdeafbabe),
+};
+const void* kRawStack2[] = {
+ reinterpret_cast<const void*>(0x12345678),
+ reinterpret_cast<const void*>(0xabcdef01),
+ reinterpret_cast<const void*>(0xfdecab98),
+};
+const void* kRawStack3[] = {
+ reinterpret_cast<const void*>(0xdead0001),
+ reinterpret_cast<const void*>(0xbeef0002),
+ reinterpret_cast<const void*>(0x900d0003),
+ reinterpret_cast<const void*>(0xf00d0004),
+ reinterpret_cast<const void*>(0xcafe0005),
+ reinterpret_cast<const void*>(0xdeed0006),
+ reinterpret_cast<const void*>(0xdeaf0007),
+ reinterpret_cast<const void*>(0xbabe0008),
+};
+
+} // namespace
+
+class CallStackTableTest : public ::testing::Test {
+ public:
+ CallStackTableTest()
+ : stack0_(nullptr),
+ stack1_(nullptr),
+ stack2_(nullptr),
+ stack3_(nullptr) {}
+
+ void SetUp() override {
+ CustomAllocator::Initialize();
+
+ manager_.reset(new CallStackManager);
+
+ // The unit tests expect a certain order to the call stack pointers. It is
+ // an important detail when checking the output of LeakAnalyzer's suspected
+ // leaks, which are ordered by the leak value (call stack pointer). Use a
+ // set to sort the pointers as they are created.
+ std::set<const CallStack*> stacks;
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack0), kRawStack0));
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack1), kRawStack1));
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack2), kRawStack2));
+ stacks.insert(manager_->GetCallStack(arraysize(kRawStack3), kRawStack3));
+ ASSERT_EQ(4U, stacks.size());
+
+ std::set<const CallStack*>::const_iterator iter = stacks.begin();
+ stack0_ = *iter++;
+ stack1_ = *iter++;
+ stack2_ = *iter++;
+ stack3_ = *iter++;
+ }
+
+ void TearDown() override {
+ // All call stacks generated by |manager_| will be invalidated when it is
+ // destroyed.
+ stack0_ = nullptr;
+ stack1_ = nullptr;
+ stack2_ = nullptr;
+ stack3_ = nullptr;
+
+ // Destroy the call stack manager before shutting down the allocator.
+ manager_.reset();
+
+ EXPECT_TRUE(CustomAllocator::Shutdown());
+ }
+
+ protected:
+ // Unit tests should directly reference these pointers to CallStack objects.
+ const CallStack* stack0_;
+ const CallStack* stack1_;
+ const CallStack* stack2_;
+ const CallStack* stack3_;
+
+ private:
+ scoped_ptr<CallStackManager> manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallStackTableTest);
+};
+
+TEST_F(CallStackTableTest, PointerOrder) {
+ EXPECT_LT(stack0_, stack1_);
+ EXPECT_LT(stack1_, stack2_);
+ EXPECT_LT(stack2_, stack3_);
+}
+
+TEST_F(CallStackTableTest, EmptyTable) {
+ CallStackTable table(kDefaultLeakThreshold);
+ EXPECT_TRUE(table.empty());
+
+ EXPECT_EQ(0U, table.num_allocs());
+ EXPECT_EQ(0U, table.num_frees());
+
+ // The table should be able to gracefully handle an attempt to remove a call
+ // stack entry when none exists.
+ table.Remove(stack0_);
+ table.Remove(stack1_);
+ table.Remove(stack2_);
+ table.Remove(stack3_);
+
+ EXPECT_EQ(0U, table.num_allocs());
+ EXPECT_EQ(0U, table.num_frees());
+}
+
+TEST_F(CallStackTableTest, InsertionAndRemoval) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ table.Add(stack0_);
+ EXPECT_EQ(1U, table.size());
+ EXPECT_EQ(1U, table.num_allocs());
+ table.Add(stack1_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(2U, table.num_allocs());
+ table.Add(stack2_);
+ EXPECT_EQ(3U, table.size());
+ EXPECT_EQ(3U, table.num_allocs());
+ table.Add(stack3_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(4U, table.num_allocs());
+
+ // Add some call stacks that have already been added. There should be no
+ // change in the number of entries, as they are aggregated by call stack.
+ table.Add(stack2_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(5U, table.num_allocs());
+ table.Add(stack3_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(6U, table.num_allocs());
+
+ // Start removing entries.
+ EXPECT_EQ(0U, table.num_frees());
+
+ table.Remove(stack0_);
+ EXPECT_EQ(3U, table.size());
+ EXPECT_EQ(1U, table.num_frees());
+ table.Remove(stack1_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(2U, table.num_frees());
+
+ // Removing call stacks with multiple counts will not reduce the overall
+ // number of table entries, until the count reaches 0.
+ table.Remove(stack2_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(3U, table.num_frees());
+ table.Remove(stack3_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(4U, table.num_frees());
+
+ table.Remove(stack2_);
+ EXPECT_EQ(1U, table.size());
+ EXPECT_EQ(5U, table.num_frees());
+ table.Remove(stack3_);
+ EXPECT_EQ(0U, table.size());
+ EXPECT_EQ(6U, table.num_frees());
+
+ // Now the table should be empty, but attempt to remove some more and make
+ // sure nothing breaks.
+ table.Remove(stack0_);
+ table.Remove(stack1_);
+ table.Remove(stack2_);
+ table.Remove(stack3_);
+
+ EXPECT_TRUE(table.empty());
+ EXPECT_EQ(6U, table.num_allocs());
+ EXPECT_EQ(6U, table.num_frees());
+}
+
+TEST_F(CallStackTableTest, MassiveInsertionAndRemoval) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack3_);
+ EXPECT_EQ(1U, table.size());
+ EXPECT_EQ(100U, table.num_allocs());
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack2_);
+ EXPECT_EQ(2U, table.size());
+ EXPECT_EQ(200U, table.num_allocs());
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack1_);
+ EXPECT_EQ(3U, table.size());
+ EXPECT_EQ(300U, table.num_allocs());
+
+ for (int i = 0; i < 100; ++i)
+ table.Add(stack0_);
+ EXPECT_EQ(4U, table.size());
+ EXPECT_EQ(400U, table.num_allocs());
+
+ // Remove them in a different order, by removing one of each stack during one
+ // iteration. The size should not decrease until the last iteration.
+ EXPECT_EQ(0U, table.num_frees());
+
+ for (int i = 0; i < 100; ++i) {
+ table.Remove(stack0_);
+ EXPECT_EQ(4U * i + 1, table.num_frees());
+
+ table.Remove(stack1_);
+ EXPECT_EQ(4U * i + 2, table.num_frees());
+
+ table.Remove(stack2_);
+ EXPECT_EQ(4U * i + 3, table.num_frees());
+
+ table.Remove(stack3_);
+ EXPECT_EQ(4U * i + 4, table.num_frees());
+ }
+ EXPECT_EQ(400U, table.num_frees());
+ EXPECT_TRUE(table.empty());
+
+ // Try to remove some more from an empty table and make sure nothing breaks.
+ table.Remove(stack0_);
+ table.Remove(stack1_);
+ table.Remove(stack2_);
+ table.Remove(stack3_);
+
+ EXPECT_TRUE(table.empty());
+ EXPECT_EQ(400U, table.num_allocs());
+ EXPECT_EQ(400U, table.num_frees());
+}
+
+TEST_F(CallStackTableTest, DetectLeak) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ // Add some base number of entries.
+ for (int i = 0; i < 60; ++i)
+ table.Add(stack0_);
+ for (int i = 0; i < 50; ++i)
+ table.Add(stack1_);
+ for (int i = 0; i < 64; ++i)
+ table.Add(stack2_);
+ for (int i = 0; i < 72; ++i)
+ table.Add(stack3_);
+
+ table.TestForLeaks();
+ EXPECT_TRUE(table.leak_analyzer().suspected_leaks().empty());
+
+ // Use the following scheme:
+ // - stack0_: increase by 4 each time -- leak suspect
+ // - stack1_: increase by 3 each time -- leak suspect
+ // - stack2_: increase by 1 each time -- not a suspect
+ // - stack3_: alternate between increasing and decreasing - not a suspect
+ bool increase_kstack3 = true;
+ for (int i = 0; i < kDefaultLeakThreshold; ++i) {
+ EXPECT_TRUE(table.leak_analyzer().suspected_leaks().empty());
+
+ for (int j = 0; j < 4; ++j)
+ table.Add(stack0_);
+
+ for (int j = 0; j < 3; ++j)
+ table.Add(stack1_);
+
+ table.Add(stack2_);
+
+ // Alternate between adding and removing.
+ if (increase_kstack3)
+ table.Add(stack3_);
+ else
+ table.Remove(stack3_);
+ increase_kstack3 = !increase_kstack3;
+
+ table.TestForLeaks();
+ }
+
+ // Check that the correct leak values have been detected.
+ const auto& leaks = table.leak_analyzer().suspected_leaks();
+ ASSERT_EQ(2U, leaks.size());
+ // Suspected leaks are reported in increasing leak value -- in this case, the
+ // CallStack object's address.
+ EXPECT_EQ(stack0_, leaks[0].call_stack());
+ EXPECT_EQ(stack1_, leaks[1].call_stack());
+}
+
+TEST_F(CallStackTableTest, GetTopCallStacks) {
+ CallStackTable table(kDefaultLeakThreshold);
+
+ // Add a bunch of entries.
+ for (int i = 0; i < 60; ++i)
+ table.Add(stack0_);
+ for (int i = 0; i < 50; ++i)
+ table.Add(stack1_);
+ for (int i = 0; i < 64; ++i)
+ table.Add(stack2_);
+ for (int i = 0; i < 72; ++i)
+ table.Add(stack3_);
+
+ // Get the call sites ordered from least to greatest number of entries.
+ RankedSet top_four(4);
+ table.GetTopCallStacks(&top_four);
+ ASSERT_EQ(4U, top_four.size());
+ auto iter = top_four.begin();
+ EXPECT_EQ(72, iter->count);
+ EXPECT_EQ(stack3_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(64, iter->count);
+ EXPECT_EQ(stack2_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(60, iter->count);
+ EXPECT_EQ(stack0_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(50, iter->count);
+ EXPECT_EQ(stack1_, iter->value.call_stack());
+
+ // Get the top three call sites ordered from least to greatest number of
+ // entries.
+ RankedSet top_three(3);
+ table.GetTopCallStacks(&top_three);
+ ASSERT_EQ(3U, top_three.size());
+ iter = top_three.begin();
+ EXPECT_EQ(72, iter->count);
+ EXPECT_EQ(stack3_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(64, iter->count);
+ EXPECT_EQ(stack2_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(60, iter->count);
+ EXPECT_EQ(stack0_, iter->value.call_stack());
+
+ // Get the top two call sites ordered from least to greatest number of
+ // entries.
+ RankedSet top_two(2);
+ table.GetTopCallStacks(&top_two);
+ ASSERT_EQ(2U, top_two.size());
+ iter = top_two.begin();
+ EXPECT_EQ(72, iter->count);
+ EXPECT_EQ(stack3_, iter->value.call_stack());
+ ++iter;
+ EXPECT_EQ(64, iter->count);
+ EXPECT_EQ(stack2_, iter->value.call_stack());
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/custom_allocator.cc b/chromium/components/metrics/leak_detector/custom_allocator.cc
new file mode 100644
index 00000000000..1f80a9712f8
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/custom_allocator.cc
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/custom_allocator.h"
+
+#include <stddef.h>
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Wrappers around new and delete.
+void* DefaultAlloc(size_t size) {
+ return new char[size];
+}
+void DefaultFree(void* ptr, size_t /* size */) {
+ delete[] reinterpret_cast<char*>(ptr);
+}
+
+CustomAllocator::AllocFunc g_alloc_func = nullptr;
+CustomAllocator::FreeFunc g_free_func = nullptr;
+
+} // namespace
+
+// static
+void CustomAllocator::Initialize() {
+ Initialize(&DefaultAlloc, &DefaultFree);
+}
+
+// static
+void CustomAllocator::Initialize(AllocFunc alloc_func, FreeFunc free_func) {
+ g_alloc_func = alloc_func;
+ g_free_func = free_func;
+}
+
+// static
+bool CustomAllocator::Shutdown() {
+ g_alloc_func = nullptr;
+ g_free_func = nullptr;
+ return true;
+}
+
+// static
+bool CustomAllocator::IsInitialized() {
+ return g_alloc_func && g_free_func;
+}
+
+// static
+void* CustomAllocator::Allocate(size_t size) {
+ return g_alloc_func(size);
+}
+
+// static
+void CustomAllocator::Free(void* ptr, size_t size) {
+ g_free_func(ptr, size);
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/custom_allocator.h b/chromium/components/metrics/leak_detector/custom_allocator.h
new file mode 100644
index 00000000000..fdbfc779b9f
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/custom_allocator.h
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_
+
+#include <stddef.h>
+
+#include <type_traits>
+
+namespace metrics {
+namespace leak_detector {
+
+// Custom allocator class to be passed to STLAllocator as a template argument.
+//
+// By default, CustomAllocator uses the default allocator (new/delete), but the
+// caller of Initialize ()can provide a pair of alternative alloc/ free
+// functions to use as an external allocator.
+//
+// This is a stateless class, but there is static data within the module that
+// needs to be created and deleted.
+//
+// Not thread-safe.
+class CustomAllocator {
+ public:
+ using AllocFunc = std::add_pointer<void*(size_t)>::type;
+ using FreeFunc = std::add_pointer<void(void*, size_t)>::type;
+
+ // Initialize CustomAllocator to use the default allocator.
+ static void Initialize();
+
+ // Initialize CustomAllocator to use the given alloc/free functions.
+ static void Initialize(AllocFunc alloc_func, FreeFunc free_func);
+
+ // Performs any cleanup required, e.g. unset custom functions. Returns true
+ // on success or false if something failed.
+ static bool Shutdown();
+
+ static bool IsInitialized();
+
+ // These functions must match the specifications in STLAllocator.
+ static void* Allocate(size_t size);
+ static void Free(void* ptr, size_t size);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_CUSTOM_ALLOCATOR_H_
diff --git a/chromium/components/metrics/leak_detector/leak_analyzer.cc b/chromium/components/metrics/leak_detector/leak_analyzer.cc
new file mode 100644
index 00000000000..54568ac212e
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_analyzer.cc
@@ -0,0 +1,139 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_analyzer.h"
+
+#include <set>
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+using RankedEntry = RankedSet::Entry;
+
+// Increase suspicion scores by this much each time an entry is suspected as
+// being a leak.
+const int kSuspicionScoreIncrease = 1;
+
+} // namespace
+
+LeakAnalyzer::LeakAnalyzer(uint32_t ranking_size,
+ uint32_t num_suspicions_threshold)
+ : ranking_size_(ranking_size),
+ score_threshold_(num_suspicions_threshold),
+ ranked_entries_(ranking_size),
+ prev_ranked_entries_(ranking_size) {
+ suspected_leaks_.reserve(ranking_size);
+}
+
+LeakAnalyzer::~LeakAnalyzer() {}
+
+void LeakAnalyzer::AddSample(RankedSet ranked_set) {
+ // Save the ranked entries from the previous call.
+ prev_ranked_entries_ = std::move(ranked_entries_);
+
+ // Save the current entries.
+ ranked_entries_ = std::move(ranked_set);
+
+ RankedSet ranked_deltas(ranking_size_);
+ for (const RankedEntry& entry : ranked_entries_) {
+ // Determine what count was recorded for this value last time.
+ uint32_t prev_count = 0;
+ if (GetPreviousCountForValue(entry.value, &prev_count))
+ ranked_deltas.Add(entry.value, entry.count - prev_count);
+ }
+
+ AnalyzeDeltas(ranked_deltas);
+}
+
+void LeakAnalyzer::AnalyzeDeltas(const RankedSet& ranked_deltas) {
+ bool found_drop = false;
+ RankedSet::const_iterator drop_position = ranked_deltas.end();
+
+ if (ranked_deltas.size() > 1) {
+ RankedSet::const_iterator entry_iter = ranked_deltas.begin();
+ RankedSet::const_iterator next_entry_iter = ranked_deltas.begin();
+ ++next_entry_iter;
+
+ // If the first entry is 0, that means all deltas are 0 or negative. Do
+ // not treat this as a suspicion of leaks; just quit.
+ if (entry_iter->count > 0) {
+ while (next_entry_iter != ranked_deltas.end()) {
+ const RankedEntry& entry = *entry_iter;
+ const RankedEntry& next_entry = *next_entry_iter;
+
+ // Find the first major drop in values (i.e. by 50% or more).
+ if (entry.count > next_entry.count * 2) {
+ found_drop = true;
+ drop_position = next_entry_iter;
+ break;
+ }
+ ++entry_iter;
+ ++next_entry_iter;
+ }
+ }
+ }
+
+ // All leak values before the drop are suspected during this analysis.
+ std::set<ValueType, std::less<ValueType>, Allocator<ValueType>>
+ current_suspects;
+ if (found_drop) {
+ for (RankedSet::const_iterator ranked_set_iter = ranked_deltas.begin();
+ ranked_set_iter != drop_position; ++ranked_set_iter) {
+ current_suspects.insert(ranked_set_iter->value);
+ }
+ }
+
+ // Reset the score to 0 for all previously suspected leak values that did
+ // not get suspected this time.
+ auto iter = suspected_histogram_.begin();
+ while (iter != suspected_histogram_.end()) {
+ const ValueType& value = iter->first;
+ // Erase entries whose suspicion score reaches 0.
+ auto erase_iter = iter++;
+ if (current_suspects.find(value) == current_suspects.end())
+ suspected_histogram_.erase(erase_iter);
+ }
+
+ // For currently suspected values, increase the leak score.
+ for (const ValueType& value : current_suspects) {
+ auto histogram_iter = suspected_histogram_.find(value);
+ if (histogram_iter != suspected_histogram_.end()) {
+ histogram_iter->second += kSuspicionScoreIncrease;
+ } else if (suspected_histogram_.size() < ranking_size_) {
+ // Create a new entry if it didn't already exist.
+ suspected_histogram_[value] = kSuspicionScoreIncrease;
+ }
+ }
+
+ // Now check the leak suspicion scores. Make sure to erase the suspected
+ // leaks from the previous call.
+ suspected_leaks_.clear();
+ for (const auto& entry : suspected_histogram_) {
+ if (suspected_leaks_.size() > ranking_size_)
+ break;
+
+ // Only report suspected values that have accumulated a suspicion score.
+ // This is achieved by maintaining suspicion for several cycles, with few
+ // skips.
+ if (entry.second >= score_threshold_)
+ suspected_leaks_.emplace_back(entry.first);
+ }
+}
+
+bool LeakAnalyzer::GetPreviousCountForValue(const ValueType& value,
+ uint32_t* count) const {
+ // Determine what count was recorded for this value last time.
+ for (const RankedEntry& entry : prev_ranked_entries_) {
+ if (entry.value == value) {
+ *count = entry.count;
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_analyzer.h b/chromium/components/metrics/leak_detector/leak_analyzer.h
new file mode 100644
index 00000000000..55ca297cf6a
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_analyzer.h
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+// This class looks for possible leak patterns in allocation data over time.
+// Not thread-safe.
+class LeakAnalyzer {
+ public:
+ using ValueType = LeakDetectorValueType;
+
+ // This class uses CustomAllocator to avoid recursive malloc hook invocation
+ // when analyzing allocs and frees.
+ template <typename Type>
+ using Allocator = STLAllocator<Type, CustomAllocator>;
+
+ LeakAnalyzer(uint32_t ranking_size, uint32_t num_suspicions_threshold);
+ ~LeakAnalyzer();
+
+ // Take in a RankedSet of allocations, sorted by count. Removes the contents
+ // of |ranked_entries| to be stored internally, which is why it is not passed
+ // in as a const reference.
+ void AddSample(RankedSet ranked_entries);
+
+ // Used to report suspected leaks. Reported leaks are sorted by ValueType.
+ const std::vector<ValueType, Allocator<ValueType>>& suspected_leaks() const {
+ return suspected_leaks_;
+ }
+
+ private:
+ // Analyze a list of allocation count deltas from the previous iteration. If
+ // anything looks like a possible leak, update the suspicion scores.
+ void AnalyzeDeltas(const RankedSet& ranked_deltas);
+
+ // Returns the count for the given value from the previous analysis in
+ // |count|. Returns true if the given value was present in the previous
+ // analysis, or false if not.
+ bool GetPreviousCountForValue(const ValueType& value, uint32_t* count) const;
+
+ // Look for the top |ranking_size_| entries when analyzing leaks.
+ const uint32_t ranking_size_;
+
+ // Report suspected leaks when the suspicion score reaches this value.
+ const uint32_t score_threshold_;
+
+ // A mapping of allocation values to suspicion score. All allocations in this
+ // container are suspected leaks. The score can increase or decrease over
+ // time. Once the score reaches |score_threshold_|, the entry is reported as
+ // a suspected leak in |suspected_leaks_|.
+ std::map<ValueType,
+ uint32_t,
+ std::less<ValueType>,
+ Allocator<std::pair<const ValueType, uint32_t>>>
+ suspected_histogram_;
+
+ // Array of allocated values that passed the suspicion threshold and are being
+ // reported.
+ std::vector<ValueType, Allocator<ValueType>> suspected_leaks_;
+
+ // The most recent allocation entries, since the last call to AddSample().
+ RankedSet ranked_entries_;
+ // The previous allocation entries, from before the last call to AddSample().
+ RankedSet prev_ranked_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakAnalyzer);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_ANALYZER_H_
diff --git a/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc b/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc
new file mode 100644
index 00000000000..71555b4c21d
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_analyzer_unittest.cc
@@ -0,0 +1,366 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_analyzer.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Default ranking size and threshold used for leak analysis.
+const int kDefaultRankedSetSize = 10;
+const int kDefaultLeakThreshold = 5;
+
+// Makes it easier to instantiate LeakDetectorValueTypes. Instantiates with an
+// integer value that indicates an allocation size. Storing the size allows us
+// to track the storage of the LeakDetectorValueType object within LeakAnalyzer.
+//
+// There is no need to test this with call stacks in addition to sizes because
+// call stacks will be contained in a LeakDetectorValueType object as well.
+LeakDetectorValueType Size(uint32_t value) {
+ return LeakDetectorValueType(value);
+}
+
+} // namespace
+
+class LeakAnalyzerTest : public ::testing::Test {
+ public:
+ LeakAnalyzerTest() {}
+
+ void SetUp() override { CustomAllocator::Initialize(); }
+ void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeakAnalyzerTest);
+};
+
+TEST_F(LeakAnalyzerTest, Empty) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+}
+
+TEST_F(LeakAnalyzerTest, SingleSize) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 10);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, VariousSizesWithoutIncrease) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, VariousSizesWithEqualIncrease) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10 + i * 10);
+ set.Add(Size(56), 90 + i * 10);
+ set.Add(Size(64), 40 + i * 10);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, NotEnoughRunsToTriggerLeakReport) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ // Run this one iteration short of the number of cycles needed to trigger a
+ // leak report. Because LeakAnalyzer requires |kDefaultLeakThreshold|
+ // suspicions based on deltas between AddSample() calls, the below loop needs
+ // to run |kDefaultLeakThreshold + 1| times to trigger a leak report.
+ for (int i = 0; i <= kDefaultLeakThreshold - 1; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10); // This one has a potential leak.
+ set.Add(Size(32), 10 + i * 2);
+ set.Add(Size(56), 90 + i);
+ set.Add(Size(64), 40 + i / 2);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakSingleSize) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ // Run this past the number of iterations required to trigger a leak report.
+ for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(24), 30 + i * 10); // This one has a potential leak.
+ set.Add(Size(64), 40);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kDefaultLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(1U, leaks.size());
+ EXPECT_EQ(24U, leaks[0].size());
+ }
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakSingleSizeOthersAlsoIncreasing) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10); // This one has a potential leak.
+ set.Add(Size(32), 10 + i * 2);
+ set.Add(Size(56), 90 + i);
+ set.Add(Size(64), 40 + i / 2);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kDefaultLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(1U, leaks.size());
+ EXPECT_EQ(24U, leaks[0].size());
+ }
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakMultipleSizes) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 5);
+ set.Add(Size(32), 10 + i * 40);
+ set.Add(Size(56), 90 + i * 30);
+ set.Add(Size(64), 40 + i * 20);
+ set.Add(Size(80), 20 + i * 3);
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kDefaultLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(3U, leaks.size());
+ // These should be in order of increasing allocation size.
+ EXPECT_EQ(32U, leaks[0].size());
+ EXPECT_EQ(56U, leaks[1].size());
+ EXPECT_EQ(64U, leaks[2].size());
+ }
+ }
+}
+
+TEST_F(LeakAnalyzerTest, LeakMultipleSizesValueOrder) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i <= kDefaultLeakThreshold; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ // These are similar to LeakMultipleSizes, but the relative order of
+ // allocation increases is different from the relative order of sizes.
+ set.Add(Size(24), 30 + i * 5);
+ set.Add(Size(32), 10 + i * 20);
+ set.Add(Size(56), 90 + i * 40);
+ set.Add(Size(64), 40 + i * 30);
+ set.Add(Size(80), 20 + i * 3);
+ analyzer.AddSample(std::move(set));
+ }
+
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(3U, leaks.size());
+ // These should be in order of increasing allocation size, NOT in order of
+ // allocation count or deltas.
+ EXPECT_EQ(32U, leaks[0].size());
+ EXPECT_EQ(56U, leaks[1].size());
+ EXPECT_EQ(64U, leaks[2].size());
+}
+
+TEST_F(LeakAnalyzerTest, EqualIncreasesNoLeak) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10 + i * 10);
+ set.Add(Size(56), 90 + i * 10);
+ set.Add(Size(64), 40 + i * 10);
+ set.Add(Size(80), 20 + i * 10);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, NotBigEnoughDeltaGap) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i < kDefaultLeakThreshold + 20; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ // These all have different increments but there is no clear group of
+ // increases that are larger than the rest.
+ set.Add(Size(24), 30 + i * 80);
+ set.Add(Size(32), 10 + i * 45);
+ set.Add(Size(56), 90 + i * 25);
+ set.Add(Size(64), 40 + i * 15);
+ set.Add(Size(80), 20 + i * 10);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+}
+
+TEST_F(LeakAnalyzerTest, RepeatedRisesUntilLeakFound) {
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kDefaultLeakThreshold);
+
+ // Remember, there is an extra iteration beyond |kDefaultLeakThreshold| needed
+ // to actually trigger the leak detection.
+ for (int i = 0; i <= kDefaultLeakThreshold - 2; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(80), 20);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+
+ // Drop back down to 30.
+ for (int i = 0; i <= kDefaultLeakThreshold - 1; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(80), 20);
+ analyzer.AddSample(std::move(set));
+
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ }
+
+ // Drop back down to 30.
+ for (int i = 0; i <= kDefaultLeakThreshold; ++i) {
+ // Initially there should not be any leak detected.
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+
+ RankedSet set(kDefaultRankedSetSize);
+ set.Add(Size(24), 30 + i * 10);
+ set.Add(Size(32), 10);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(80), 20);
+ analyzer.AddSample(std::move(set));
+ }
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(1U, leaks.size());
+ EXPECT_EQ(24U, leaks[0].size());
+}
+
+TEST_F(LeakAnalyzerTest, LeakWithMultipleGroupsOfDeltas) {
+ const int kRankedSetSize = 20;
+ LeakAnalyzer analyzer(kRankedSetSize, kDefaultLeakThreshold);
+
+ for (int i = 0; i <= kDefaultLeakThreshold; ++i) {
+ RankedSet set(kRankedSetSize);
+ set.Add(Size(24), 30 + i * 10); // A group of smaller deltas.
+ set.Add(Size(32), 10 + i * 3);
+ set.Add(Size(80), 20 + i * 5);
+ set.Add(Size(40), 30 + i * 7);
+ set.Add(Size(56), 90);
+ set.Add(Size(64), 40);
+ set.Add(Size(128), 100);
+ set.Add(Size(44), 100 + i * 10); // A group of medium deltas.
+ set.Add(Size(16), 60 + i * 50);
+ set.Add(Size(4), 20 + i * 40);
+ set.Add(Size(8), 100 + i * 60);
+ set.Add(Size(48), 100);
+ set.Add(Size(72), 60 + i * 240); // A group of largest deltas.
+ set.Add(Size(28), 100);
+ set.Add(Size(100), 100 + i * 200);
+ set.Add(Size(104), 60 + i * 128);
+ analyzer.AddSample(std::move(set));
+ }
+ // Only the group of largest deltas should be caught.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(3U, leaks.size());
+ // These should be in order of increasing allocation size.
+ EXPECT_EQ(72U, leaks[0].size());
+ EXPECT_EQ(100U, leaks[1].size());
+ EXPECT_EQ(104U, leaks[2].size());
+}
+
+TEST_F(LeakAnalyzerTest, LeakMultipleSizesWithLargeThreshold) {
+ const int kLeakThreshold = 50;
+ LeakAnalyzer analyzer(kDefaultRankedSetSize, kLeakThreshold);
+
+ for (int i = 0; i <= kLeakThreshold + 10; ++i) {
+ RankedSet set(kDefaultRankedSetSize);
+ // * - Cluster of larger deltas
+ set.Add(Size(24), 30 + i * 5);
+ set.Add(Size(32), 10 + i * 40); // *
+ set.Add(Size(56), 90 + i * 30); // *
+ set.Add(Size(40), 30 + i * 7);
+ set.Add(Size(64), 40 + i * 25); // *
+ set.Add(Size(80), 20 + i * 3);
+ set.Add(Size(128), 100);
+ set.Add(Size(44), 100 + i * 10);
+ set.Add(Size(16), 60 + i * 50); // *
+ analyzer.AddSample(std::move(set));
+
+ // No leaks should have been detected initially...
+ if (i < kLeakThreshold) {
+ EXPECT_TRUE(analyzer.suspected_leaks().empty());
+ } else {
+ // ... but there should be reported leaks once the threshold is reached.
+ const auto& leaks = analyzer.suspected_leaks();
+ ASSERT_EQ(4U, leaks.size());
+ // These should be in order of increasing allocation size.
+ EXPECT_EQ(16U, leaks[0].size());
+ EXPECT_EQ(32U, leaks[1].size());
+ EXPECT_EQ(56U, leaks[2].size());
+ EXPECT_EQ(64U, leaks[3].size());
+ }
+ }
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector.cc b/chromium/components/metrics/leak_detector/leak_detector.cc
new file mode 100644
index 00000000000..a7151a86382
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector.cc
@@ -0,0 +1,340 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector.h"
+
+#include <stdint.h>
+
+#include "base/allocator/allocator_extension.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/threading/thread_local.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_impl.h"
+#include "content/public/browser/browser_thread.h"
+
+#if defined(OS_CHROMEOS)
+#include <link.h> // for dl_iterate_phdr
+#else
+#error "Getting binary mapping info is not supported on this platform."
+#endif // defined(OS_CHROMEOS)
+
+namespace metrics {
+
+using LeakReport = LeakDetector::LeakReport;
+using InternalLeakReport = leak_detector::LeakDetectorImpl::LeakReport;
+template <typename T>
+using InternalVector = leak_detector::LeakDetectorImpl::InternalVector<T>;
+
+namespace {
+
+// Add the thread-local alloc size count to the shared alloc size count
+// (LeakDetector::total_alloc_size_) whenever the local counter reaches
+// |LeakDetector::analysis_interval_bytes_| divided by this value. Choose a
+// high enough value that there is plenty of granularity, but low enough that a
+// thread is not frequently updating the shared counter.
+const int kTotalAllocSizeUpdateIntervalDivisor = 1024;
+
+#if defined(OS_CHROMEOS)
+// For storing the address range of the Chrome binary in memory.
+struct MappingInfo {
+ uintptr_t addr;
+ size_t size;
+};
+#endif // defined(OS_CHROMEOS)
+
+// Local data to be used in the alloc/free hook functions to keep track of
+// things across hook function calls.
+struct HookData {
+ // The total number of bytes nominally allocated from the allocator on the
+ // current thread.
+ size_t alloc_size;
+
+ // Flag indicating that one of the alloc hooks have already been entered. Used
+ // to handle recursive hook calls. Anything allocated when this flag is set
+ // should also be freed when this flag is set.
+ bool entered_hook;
+};
+
+#if defined(OS_CHROMEOS)
+// Callback for dl_iterate_phdr() to find the Chrome binary mapping.
+int IterateLoadedObjects(struct dl_phdr_info* shared_object,
+ size_t /* size */,
+ void* data) {
+ for (int i = 0; i < shared_object->dlpi_phnum; i++) {
+ // Find the ELF segment header that contains the actual code of the Chrome
+ // binary.
+ const ElfW(Phdr)& segment_header = shared_object->dlpi_phdr[i];
+ if (segment_header.p_type == SHT_PROGBITS && segment_header.p_offset == 0 &&
+ data) {
+ MappingInfo* mapping = reinterpret_cast<MappingInfo*>(data);
+
+ // Make sure the fields in the ELF header and MappingInfo have the
+ // same size.
+ static_assert(sizeof(mapping->addr) == sizeof(shared_object->dlpi_addr),
+ "Integer size mismatch between MappingInfo::addr and "
+ "dl_phdr_info::dlpi_addr.");
+ static_assert(sizeof(mapping->size) == sizeof(segment_header.p_offset),
+ "Integer size mismatch between MappingInfo::size and "
+ "ElfW(Phdr)::p_memsz.");
+
+ mapping->addr = shared_object->dlpi_addr + segment_header.p_offset;
+ mapping->size = segment_header.p_memsz;
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif // defined(OS_CHROMEOS)
+
+// Convert a pointer to a hash value. Returns only the upper eight bits.
+inline uint64_t PointerToHash(const void* ptr) {
+ // The input data is the pointer address, not the location in memory pointed
+ // to by the pointer.
+ // The multiplier is taken from Farmhash code:
+ // https://github.com/google/farmhash/blob/master/src/farmhash.cc
+ const uint64_t kMultiplier = 0x9ddfea08eb382d69ULL;
+ return reinterpret_cast<uint64_t>(ptr) * kMultiplier;
+}
+
+// Converts a vector of leak reports generated by LeakDetectorImpl
+// (InternalLeakReport) to a vector of leak reports suitable for sending to
+// LeakDetector's observers (LeakReport).
+void GetReportsForObservers(
+ const InternalVector<InternalLeakReport>& leak_reports,
+ std::vector<LeakReport>* reports_for_observers) {
+ reports_for_observers->clear();
+ reports_for_observers->reserve(leak_reports.size());
+ for (const InternalLeakReport& report : leak_reports) {
+ reports_for_observers->push_back(LeakReport());
+ LeakReport* new_report = &reports_for_observers->back();
+
+ new_report->alloc_size_bytes = report.alloc_size_bytes();
+ if (!report.call_stack().empty()) {
+ new_report->call_stack.resize(report.call_stack().size());
+ memcpy(new_report->call_stack.data(), report.call_stack().data(),
+ report.call_stack().size() * sizeof(report.call_stack()[0]));
+ }
+ }
+}
+
+// The only instance of LeakDetector that should be used.
+base::LazyInstance<LeakDetector>::Leaky g_instance = LAZY_INSTANCE_INITIALIZER;
+
+// Thread-specific data to be used by hook functions.
+base::LazyInstance<base::ThreadLocalPointer<void>>::Leaky g_hook_data_tls =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Returns the contents of |g_hook_data_tls| as a HookData structure.
+inline HookData LoadHookDataFromTLS() {
+ uintptr_t ptr_value =
+ reinterpret_cast<uintptr_t>(g_hook_data_tls.Get().Get());
+
+ // The lower bit of |ptr_value| indicates whether a hook has already been
+ // entered. The remaining bits store the alloc size.
+ HookData result;
+ result.entered_hook = ptr_value & 0x01;
+ result.alloc_size = ptr_value >> 1;
+ return result;
+}
+
+// Stores a HookData structure in |g_hook_data_tls|. HookData is a trivial
+// struct so it is faster to pass by value.
+inline void StoreHookDataToTLS(HookData hook_data) {
+ // NOTE: |alloc_size| loses its upper bit when it gets stored in the TLS here.
+ // The effective max value of |alloc_size| is thus half its nominal max value.
+ uintptr_t ptr_value =
+ (hook_data.entered_hook ? 1 : 0) | (hook_data.alloc_size << 1);
+ g_hook_data_tls.Get().Set(reinterpret_cast<void*>(ptr_value));
+}
+
+} // namespace
+
+LeakDetector::LeakReport::LeakReport() {}
+
+LeakDetector::LeakReport::LeakReport(const LeakReport& other) = default;
+
+LeakDetector::LeakReport::~LeakReport() {}
+
+// static
+LeakDetector* LeakDetector::GetInstance() {
+ return g_instance.Pointer();
+}
+
+void LeakDetector::Init(float sampling_rate,
+ size_t max_call_stack_unwind_depth,
+ uint64_t analysis_interval_bytes,
+ uint32_t size_suspicion_threshold,
+ uint32_t call_stack_suspicion_threshold) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(sampling_rate > 0) << "Sampling rate cannot be zero or negative.";
+
+ sampling_factor_ = base::saturated_cast<uint64_t>(sampling_rate * UINT64_MAX);
+
+ analysis_interval_bytes_ = analysis_interval_bytes;
+ max_call_stack_unwind_depth_ = max_call_stack_unwind_depth;
+
+ MappingInfo mapping = {0};
+#if defined(OS_CHROMEOS)
+ // Locate the Chrome binary mapping info.
+ dl_iterate_phdr(IterateLoadedObjects, &mapping);
+#endif // defined(OS_CHROMEOS)
+
+ // CustomAllocator can use the default allocator, as long as the hook
+ // functions can handle recursive calls.
+ leak_detector::CustomAllocator::Initialize();
+
+ // The initialization should be done only once. Check for this by examining
+ // whether |impl_| has already been initialized.
+ CHECK(!impl_.get()) << "Cannot initialize LeakDetector more than once!";
+ impl_.reset(new leak_detector::LeakDetectorImpl(
+ mapping.addr, mapping.size, size_suspicion_threshold,
+ call_stack_suspicion_threshold));
+
+ // Register allocator hook functions. This must be done last since the
+ // preceding code will need to call the allocator.
+ base::allocator::SetHooks(&AllocHook, &FreeHook);
+}
+
+void LeakDetector::AddObserver(Observer* observer) {
+ base::AutoLock lock(observers_lock_);
+ observers_.AddObserver(observer);
+}
+
+void LeakDetector::RemoveObserver(Observer* observer) {
+ base::AutoLock lock(observers_lock_);
+ observers_.RemoveObserver(observer);
+}
+
+LeakDetector::LeakDetector()
+ : total_alloc_size_(0),
+ last_analysis_alloc_size_(0),
+ analysis_interval_bytes_(0),
+ max_call_stack_unwind_depth_(0),
+ sampling_factor_(0) {}
+
+LeakDetector::~LeakDetector() {}
+
+// static
+void LeakDetector::AllocHook(const void* ptr, size_t size) {
+ HookData hook_data = LoadHookDataFromTLS();
+ if (hook_data.entered_hook)
+ return;
+
+ hook_data.alloc_size += size;
+
+ LeakDetector* detector = GetInstance();
+ if (!detector->ShouldSample(ptr)) {
+ StoreHookDataToTLS(hook_data);
+ return;
+ }
+
+ hook_data.entered_hook = true;
+ StoreHookDataToTLS(hook_data);
+
+ // Get stack trace if necessary.
+ std::vector<void*> stack;
+ int depth = 0;
+ if (detector->impl_->ShouldGetStackTraceForSize(size)) {
+ stack.resize(detector->max_call_stack_unwind_depth_);
+ depth = base::allocator::GetCallStack(stack.data(), stack.size());
+ }
+
+ {
+ base::AutoLock lock(detector->recording_lock_);
+ detector->impl_->RecordAlloc(ptr, size, depth, stack.data());
+
+ const auto& analysis_interval_bytes = detector->analysis_interval_bytes_;
+ auto& total_alloc_size = detector->total_alloc_size_;
+ // Update the shared counter, |detector->total_alloc_size_|, once the local
+ // counter reaches a threshold that is a fraction of the analysis interval.
+ // The fraction should be small enough (and hence the value of
+ // kTotalAllocSizeUpdateIntervalDivisor should be large enough) that the
+ // shared counter is updated with sufficient granularity. This way, even if
+ // a few threads were slow to reach the threshold, the leak analysis would
+ // not be delayed by too much.
+ if (hook_data.alloc_size >=
+ analysis_interval_bytes / kTotalAllocSizeUpdateIntervalDivisor) {
+ total_alloc_size += hook_data.alloc_size;
+ hook_data.alloc_size = 0;
+ }
+
+ // Check for leaks after |analysis_interval_bytes_| bytes have been
+ // allocated since the last time that was done.
+ if (total_alloc_size >
+ detector->last_analysis_alloc_size_ + analysis_interval_bytes) {
+ // Try to maintain regular intervals of size |analysis_interval_bytes_|.
+ detector->last_analysis_alloc_size_ =
+ total_alloc_size - total_alloc_size % analysis_interval_bytes;
+
+ InternalVector<InternalLeakReport> leak_reports;
+ detector->impl_->TestForLeaks(&leak_reports);
+
+ // Pass leak reports to observers.
+ std::vector<LeakReport> leak_reports_for_observers;
+ GetReportsForObservers(leak_reports, &leak_reports_for_observers);
+ detector->NotifyObservers(leak_reports_for_observers);
+ }
+ }
+
+ {
+ // The internal memory of |stack| should be freed before setting
+ // |entered_hook| to false at the end of this function. Free it here by
+ // moving the internal memory to a temporary variable that will go out of
+ // scope.
+ std::vector<void*> dummy_stack;
+ dummy_stack.swap(stack);
+ }
+
+ hook_data.entered_hook = false;
+ StoreHookDataToTLS(hook_data);
+}
+
+// static
+void LeakDetector::FreeHook(const void* ptr) {
+ LeakDetector* detector = GetInstance();
+ if (!detector->ShouldSample(ptr))
+ return;
+
+ HookData hook_data = LoadHookDataFromTLS();
+ if (hook_data.entered_hook)
+ return;
+
+ hook_data.entered_hook = true;
+ StoreHookDataToTLS(hook_data);
+
+ {
+ base::AutoLock lock(detector->recording_lock_);
+ detector->impl_->RecordFree(ptr);
+ }
+
+ hook_data.entered_hook = false;
+ StoreHookDataToTLS(hook_data);
+}
+
+inline bool LeakDetector::ShouldSample(const void* ptr) const {
+ return PointerToHash(ptr) < sampling_factor_;
+}
+
+void LeakDetector::NotifyObservers(const std::vector<LeakReport>& reports) {
+ if (reports.empty())
+ return;
+
+ if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&LeakDetector::NotifyObservers, base::Unretained(this),
+ reports));
+ return;
+ }
+
+ for (const LeakReport& report : reports) {
+ base::AutoLock lock(observers_lock_);
+ FOR_EACH_OBSERVER(Observer, observers_, OnLeakFound(report));
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector.h b/chromium/components/metrics/leak_detector/leak_detector.h
new file mode 100644
index 00000000000..8dcba5e9de2
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector.h
@@ -0,0 +1,174 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+template <typename T>
+struct DefaultLazyInstanceTraits;
+}
+
+namespace metrics {
+
+namespace leak_detector {
+class LeakDetectorImpl;
+}
+
+// LeakDetector is an interface layer that connects the allocator
+// (base::allocator), the leak detector logic (LeakDetectorImpl), and any
+// external classes interested in receiving leak reports (extend the Observer
+// class).
+//
+// Only one instance of this class can exist. Access this instance using
+// GetInstance(). Do not create an instance of this class directly.
+//
+// These member functions are thread-safe:
+// - AllocHook
+// - FreeHook
+// - AddObserver
+// - RemoveObserver
+//
+// All other functions must always be called from the same thread. This is
+// enforced with a DCHECK.
+class LeakDetector {
+ public:
+ // Contains a report of a detected memory leak.
+ struct LeakReport {
+ LeakReport();
+ LeakReport(const LeakReport& other);
+ ~LeakReport();
+
+ size_t alloc_size_bytes;
+
+ // Unlike the CallStack struct, which consists of addresses, this call stack
+ // will contain offsets in the executable binary.
+ std::vector<uintptr_t> call_stack;
+ };
+
+ // Interface for receiving leak reports.
+ class Observer {
+ public:
+ virtual ~Observer() {}
+
+ // Called by leak detector to report a leak.
+ virtual void OnLeakFound(const LeakReport& report) = 0;
+ };
+
+ // Returns the sole instance, or creates it if it hasn't already been created.
+ static LeakDetector* GetInstance();
+
+ // Initializer arguments:
+ // sampling_rate:
+ // Pseudorandomly sample a fraction of the incoming allocations and frees,
+ // based on hash values. Setting to 0 means no allocs/frees are sampled.
+ // Setting to 1.0 or more means all allocs/frees are sampled. Anything in
+ // between will result in an approximately that fraction of allocs/frees
+ // being sampled.
+ // max_call_stack_unwind_depth:
+ // The max number of call stack frames to unwind.
+ // analysis_interval_bytes:
+ // Perform a leak analysis each time this many bytes have been allocated
+ // since the previous analysis.
+ // size_suspicion_threshold, call_stack_suspicion_threshold:
+ // A possible leak should be suspected this many times to take action on i
+ // For size analysis, the action is to start profiling by call stack.
+ // For call stack analysis, the action is to generate a leak report.
+ void Init(float sampling_rate,
+ size_t max_call_stack_unwind_depth,
+ uint64_t analysis_interval_bytes,
+ uint32_t size_suspicion_threshold,
+ uint32_t call_stack_suspicion_threshold);
+
+ // Add |observer| to the list of stored Observers, i.e. |observers_|, to which
+ // the leak detector will report leaks.
+ void AddObserver(Observer* observer);
+
+ // Remove |observer| from |observers_|.
+ void RemoveObserver(Observer* observer);
+
+ private:
+ friend base::DefaultLazyInstanceTraits<LeakDetector>;
+ FRIEND_TEST_ALL_PREFIXES(LeakDetectorTest, NotifyObservers);
+
+ // Keep these private, as this class is meant to be initialized only through
+ // the lazy instance, and never destroyed.
+ LeakDetector();
+ ~LeakDetector();
+
+ // Allocator hook function that processes each alloc. Performs sampling and
+ // unwinds call stack if necessary. Passes the allocated memory |ptr| and
+ // allocation size |size| along with call stack info to RecordAlloc().
+ static void AllocHook(const void* ptr, size_t size);
+
+ // Allocator hook function that processes each free. Performs sampling and
+ // passes the allocation address |ptr| to |impl_|.
+ static void FreeHook(const void* ptr);
+
+ // Give an pointer |ptr|, computes a hash of the pointer value and compares it
+ // against |sampling_factor_| to determine if it should be sampled. This
+ // allows the same pointer to be sampled during both alloc and free.
+ bool ShouldSample(const void* ptr) const;
+
+ // Notifies all Observers in |observers_| with the given vector of leak
+ // reports.
+ void NotifyObservers(const std::vector<LeakReport>& reports);
+
+ // List of observers to notify when there's a leak report.
+ // TODO(sque): Consider using ObserverListThreadSafe instead.
+ base::ObserverList<Observer> observers_;
+
+ // For atomic access to |observers_|.
+ base::Lock observers_lock_;
+
+ // Handles leak detection logic. Must be called under lock as LeakDetectorImpl
+ // uses shared resources.
+ scoped_ptr<leak_detector::LeakDetectorImpl> impl_;
+
+ // For thread safety.
+ base::ThreadChecker thread_checker_;
+
+ // Total number of bytes allocated, computed before sampling.
+ size_t total_alloc_size_;
+
+ // The value of |total_alloc_size_| the last time there was a leak analysis,
+ // rounded down to the nearest multiple of |analysis_interval_bytes_|.
+ size_t last_analysis_alloc_size_;
+
+ // For atomic access to |impl_|, |total_alloc_size_| and
+ // |last_analysis_alloc_size_|.
+ base::Lock recording_lock_;
+
+ // Perform a leak analysis each time this many bytes have been allocated since
+ // the previous analysis.
+ size_t analysis_interval_bytes_;
+
+ // When unwinding call stacks, unwind no more than this number of frames.
+ size_t max_call_stack_unwind_depth_;
+
+ // Sampling factor used by ShouldSample(). It's full range of values
+ // corresponds to the allowable range of |sampling_rate| passed in during
+ // initialization: [0.0f, 1.0f] -> [0, UINT64_MAX].
+ uint64_t sampling_factor_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakDetector);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_H_
diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl.cc b/chromium/components/metrics/leak_detector/leak_detector_impl.cc
new file mode 100644
index 00000000000..e174cc20cc8
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_impl.cc
@@ -0,0 +1,241 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "leak_detector_impl.h"
+
+#include <inttypes.h>
+#include <stddef.h>
+
+#include <algorithm>
+#include <new>
+
+#include "base/hash.h"
+#include "base/process/process_handle.h"
+#include "components/metrics/leak_detector/call_stack_table.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/ranked_set.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Look for leaks in the the top N entries in each tier, where N is this value.
+const int kRankedSetSize = 16;
+
+// Initial hash table size for |LeakDetectorImpl::address_map_|.
+const int kAddressMapNumBuckets = 100003;
+
+// Number of entries in the alloc size table. As sizes are aligned to 32-bits
+// the max supported allocation size is (kNumSizeEntries * 4 - 1). Any larger
+// sizes are ignored. This value is chosen high enough that such large sizes
+// are rare if not nonexistent.
+const int kNumSizeEntries = 2048;
+
+// Record only the first |kNumSizeEntriesInHistory| size classes in
+// |LeakDetectorImpl::size_breakdown_history_|.
+const int kNumSizeEntriesInHistory = 32;
+
+// |LeakDetectorImpl::size_breakdown_history_| can have up to this many entries.
+// Any older entries must be discarded to make way for new ones.
+const int kMaxNumHistoryEntries = 32;
+
+using ValueType = LeakDetectorValueType;
+
+// Functions to convert an allocation size to/from the array index used for
+// |LeakDetectorImpl::size_entries_|.
+size_t SizeToIndex(const size_t size) {
+ int result = static_cast<int>(size / sizeof(uint32_t));
+ if (result < kNumSizeEntries)
+ return result;
+ return 0;
+}
+
+size_t IndexToSize(size_t index) {
+ return sizeof(uint32_t) * index;
+}
+
+} // namespace
+
+LeakDetectorImpl::LeakReport::LeakReport() : alloc_size_bytes_(0) {}
+
+LeakDetectorImpl::LeakReport::LeakReport(const LeakReport& other) = default;
+
+LeakDetectorImpl::LeakReport::~LeakReport() {}
+
+bool LeakDetectorImpl::LeakReport::operator<(const LeakReport& other) const {
+ if (alloc_size_bytes_ != other.alloc_size_bytes_)
+ return alloc_size_bytes_ < other.alloc_size_bytes_;
+ for (size_t i = 0; i < call_stack_.size() && i < other.call_stack_.size();
+ ++i) {
+ if (call_stack_[i] != other.call_stack_[i])
+ return call_stack_[i] < other.call_stack_[i];
+ }
+ return call_stack_.size() < other.call_stack_.size();
+}
+
+LeakDetectorImpl::LeakDetectorImpl(uintptr_t mapping_addr,
+ size_t mapping_size,
+ int size_suspicion_threshold,
+ int call_stack_suspicion_threshold)
+ : num_allocs_(0),
+ num_frees_(0),
+ alloc_size_(0),
+ free_size_(0),
+ num_allocs_with_call_stack_(0),
+ num_stack_tables_(0),
+ address_map_(kAddressMapNumBuckets),
+ size_leak_analyzer_(kRankedSetSize, size_suspicion_threshold),
+ size_entries_(kNumSizeEntries),
+ mapping_addr_(mapping_addr),
+ mapping_size_(mapping_size),
+ call_stack_suspicion_threshold_(call_stack_suspicion_threshold) {}
+
+LeakDetectorImpl::~LeakDetectorImpl() {
+ // Free any call stack tables.
+ for (AllocSizeEntry& entry : size_entries_) {
+ CallStackTable* table = entry.stack_table;
+ if (!table)
+ continue;
+ table->~CallStackTable();
+ CustomAllocator::Free(table, sizeof(CallStackTable));
+ }
+ size_entries_.clear();
+}
+
+bool LeakDetectorImpl::ShouldGetStackTraceForSize(size_t size) const {
+ return size_entries_[SizeToIndex(size)].stack_table != nullptr;
+}
+
+void LeakDetectorImpl::RecordAlloc(const void* ptr,
+ size_t size,
+ int stack_depth,
+ const void* const stack[]) {
+ AllocInfo alloc_info;
+ alloc_info.size = size;
+
+ alloc_size_ += alloc_info.size;
+ ++num_allocs_;
+
+ AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)];
+ ++entry->num_allocs;
+
+ if (entry->stack_table && stack_depth > 0) {
+ alloc_info.call_stack =
+ call_stack_manager_.GetCallStack(stack_depth, stack);
+ entry->stack_table->Add(alloc_info.call_stack);
+
+ ++num_allocs_with_call_stack_;
+ }
+
+ uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
+ address_map_.insert(std::pair<uintptr_t, AllocInfo>(addr, alloc_info));
+}
+
+void LeakDetectorImpl::RecordFree(const void* ptr) {
+ // Look up address.
+ uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
+ auto iter = address_map_.find(addr);
+ // TODO(sque): Catch and report double frees.
+ if (iter == address_map_.end())
+ return;
+
+ const AllocInfo& alloc_info = iter->second;
+
+ AllocSizeEntry* entry = &size_entries_[SizeToIndex(alloc_info.size)];
+ ++entry->num_frees;
+
+ const CallStack* call_stack = alloc_info.call_stack;
+ if (call_stack) {
+ if (entry->stack_table)
+ entry->stack_table->Remove(call_stack);
+ }
+ ++num_frees_;
+ free_size_ += alloc_info.size;
+
+ address_map_.erase(iter);
+}
+
+void LeakDetectorImpl::TestForLeaks(InternalVector<LeakReport>* reports) {
+ // Add net alloc counts for each size to a ranked list.
+ RankedSet size_ranked_set(kRankedSetSize);
+ for (size_t i = 0; i < size_entries_.size(); ++i) {
+ const AllocSizeEntry& entry = size_entries_[i];
+ ValueType size_value(IndexToSize(i));
+ size_ranked_set.Add(size_value, entry.GetNetAllocs());
+ }
+ size_leak_analyzer_.AddSample(std::move(size_ranked_set));
+
+ // Record a snapshot of the current size table.
+ InternalVector<uint32_t> current_size_table_record;
+ current_size_table_record.reserve(kNumSizeEntriesInHistory);
+ for (const AllocSizeEntry& entry : size_entries_) {
+ if (current_size_table_record.size() == kNumSizeEntriesInHistory)
+ break;
+ current_size_table_record.push_back(entry.GetNetAllocs());
+ }
+ size_breakdown_history_.emplace_back(std::move(current_size_table_record));
+ if (size_breakdown_history_.size() > kMaxNumHistoryEntries)
+ size_breakdown_history_.pop_front();
+
+ // Get suspected leaks by size.
+ for (const ValueType& size_value : size_leak_analyzer_.suspected_leaks()) {
+ uint32_t size = size_value.size();
+ AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)];
+ if (entry->stack_table)
+ continue;
+ entry->stack_table = new (CustomAllocator::Allocate(sizeof(CallStackTable)))
+ CallStackTable(call_stack_suspicion_threshold_);
+ ++num_stack_tables_;
+ }
+
+ // Check for leaks in each CallStackTable. It makes sense to this before
+ // checking the size allocations, because that could potentially create new
+ // CallStackTable. However, the overhead to check a new CallStackTable is
+ // small since this function is run very rarely. So handle the leak checks of
+ // Tier 2 here.
+ reports->clear();
+ for (size_t i = 0; i < size_entries_.size(); ++i) {
+ const AllocSizeEntry& entry = size_entries_[i];
+ CallStackTable* stack_table = entry.stack_table;
+ if (!stack_table || stack_table->empty())
+ continue;
+
+ size_t size = IndexToSize(i);
+
+ // Get suspected leaks by call stack.
+ stack_table->TestForLeaks();
+ const LeakAnalyzer& leak_analyzer = stack_table->leak_analyzer();
+ for (const ValueType& call_stack_value : leak_analyzer.suspected_leaks()) {
+ const CallStack* call_stack = call_stack_value.call_stack();
+
+ // Return reports by storing in |*reports|.
+ reports->resize(reports->size() + 1);
+ LeakReport* report = &reports->back();
+ report->alloc_size_bytes_ = size;
+ report->call_stack_.resize(call_stack->depth);
+ for (size_t j = 0; j < call_stack->depth; ++j) {
+ report->call_stack_[j] = GetOffset(call_stack->stack[j]);
+ }
+ // Copy over the historical size data.
+ report->size_breakdown_history_.reserve(size_breakdown_history_.size());
+ report->size_breakdown_history_.assign(size_breakdown_history_.begin(),
+ size_breakdown_history_.end());
+ }
+ }
+}
+
+size_t LeakDetectorImpl::AddressHash::operator()(uintptr_t addr) const {
+ return base::Hash(reinterpret_cast<const char*>(&addr), sizeof(addr));
+}
+
+uintptr_t LeakDetectorImpl::GetOffset(const void* ptr) const {
+ uintptr_t ptr_value = reinterpret_cast<uintptr_t>(ptr);
+ if (ptr_value >= mapping_addr_ && ptr_value < mapping_addr_ + mapping_size_)
+ return ptr_value - mapping_addr_;
+ return ptr_value;
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl.h b/chromium/components/metrics/leak_detector/leak_detector_impl.h
new file mode 100644
index 00000000000..fafd7b814f4
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_impl.h
@@ -0,0 +1,185 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <list>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/macros.h"
+#include "components/metrics/leak_detector/call_stack_manager.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_analyzer.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+class CallStackTable;
+
+// Class that contains the actual leak detection mechanism.
+// Not thread-safe.
+class LeakDetectorImpl {
+ public:
+ // STL types that are safe to use within the memory leak detector. They use
+ // CustomAllocator to avoid recursive malloc hook invocation when analyzing
+ // allocs and frees.
+ template <typename T>
+ using InternalList = std::list<T, STLAllocator<T, CustomAllocator>>;
+ template <typename T>
+ using InternalVector = std::vector<T, STLAllocator<T, CustomAllocator>>;
+
+ // Leak report generated by LeakDetectorImpl.
+ class LeakReport {
+ public:
+ LeakReport();
+ LeakReport(const LeakReport& other);
+ ~LeakReport();
+
+ size_t alloc_size_bytes() const { return alloc_size_bytes_; }
+
+ const InternalVector<uintptr_t>& call_stack() const { return call_stack_; }
+
+ const InternalVector<InternalVector<uint32_t>>& size_breakdown_history()
+ const {
+ return size_breakdown_history_;
+ }
+
+ // Used to compare the contents of two leak reports.
+ bool operator<(const LeakReport& other) const;
+
+ private:
+ // LeakDetectorImpl needs access to class members when creating a new leak
+ // report.
+ friend class LeakDetectorImpl;
+
+ // Number of bytes allocated by the leak site during each allocation.
+ size_t alloc_size_bytes_;
+
+ // Unlike the CallStack struct, which consists of addresses, this call stack
+ // will contain offsets in the executable binary.
+ InternalVector<uintptr_t> call_stack_;
+
+ // A snapshot of LeakDetectorImpl::size_breakdown_history_ when this report
+ // was generated. See comment description of that variable.
+ InternalVector<InternalVector<uint32_t>> size_breakdown_history_;
+ };
+
+ LeakDetectorImpl(uintptr_t mapping_addr,
+ size_t mapping_size,
+ int size_suspicion_threshold,
+ int call_stack_suspicion_threshold);
+ ~LeakDetectorImpl();
+
+ // Indicates whether the given allocation size has an associated call stack
+ // table, and thus requires a stack unwind.
+ bool ShouldGetStackTraceForSize(size_t size) const;
+
+ // Record allocs and frees.
+ void RecordAlloc(const void* ptr,
+ size_t size,
+ int stack_depth,
+ const void* const call_stack[]);
+ void RecordFree(const void* ptr);
+
+ // Run check for possible leaks based on the current profiling data.
+ void TestForLeaks(InternalVector<LeakReport>* reports);
+
+ private:
+ // A record of allocations for a particular size.
+ struct AllocSizeEntry {
+ // Number of allocations and frees for this size.
+ uint32_t num_allocs;
+ uint32_t num_frees;
+
+ // A stack table, if this size is being profiled for stack as well.
+ CallStackTable* stack_table;
+
+ // Returns net number of allocs.
+ uint32_t GetNetAllocs() const { return num_allocs - num_frees; }
+ };
+
+ // Info for a single allocation.
+ struct AllocInfo {
+ AllocInfo() : call_stack(nullptr) {}
+
+ // Number of bytes in this allocation.
+ size_t size;
+
+ // Points to a unique call stack.
+ const CallStack* call_stack;
+ };
+
+ // Allocator class for allocation entry map. Maps allocated addresses to
+ // AllocInfo objects.
+ using AllocationEntryAllocator =
+ STLAllocator<std::pair<const uintptr_t, AllocInfo>, CustomAllocator>;
+
+ // Hash class for addresses.
+ struct AddressHash {
+ size_t operator()(uintptr_t addr) const;
+ };
+
+ // Returns the offset of |ptr| within the current binary. If it is not in the
+ // current binary, just return |ptr| as an integer.
+ uintptr_t GetOffset(const void* ptr) const;
+
+ // Owns all unique call stack objects, which are allocated on the heap. Any
+ // other class or function that references a call stack must get it from here,
+ // but may not take ownership of the call stack object.
+ CallStackManager call_stack_manager_;
+
+ // Allocation stats.
+ uint64_t num_allocs_;
+ uint64_t num_frees_;
+ uint64_t alloc_size_;
+ uint64_t free_size_;
+
+ uint32_t num_allocs_with_call_stack_;
+ uint32_t num_stack_tables_;
+
+ // Stores all individual recorded allocations.
+ base::hash_map<uintptr_t,
+ AllocInfo,
+ AddressHash,
+ std::equal_to<uintptr_t>,
+ AllocationEntryAllocator> address_map_;
+
+ // Used to analyze potential leak patterns in the allocation sizes.
+ LeakAnalyzer size_leak_analyzer_;
+
+ // Allocation stats for each size.
+ InternalVector<AllocSizeEntry> size_entries_;
+
+ // Tracks the net number of allocations per size over time. Each list item is
+ // a vector containing the allocation counts for each size. The vector element
+ // with index i corresponds to sizes |i * 4| to |i * 4 + 3|. The oldest size
+ // breakdowns is at the head of the list, and new size breakdowns should be
+ // added to the tail of the list.
+ InternalList<InternalVector<uint32_t>> size_breakdown_history_;
+
+ // Address mapping info of the current binary.
+ uintptr_t mapping_addr_;
+ size_t mapping_size_;
+
+ // Number of consecutive times an allocation size must trigger suspicion to be
+ // considered a leak suspect.
+ int size_suspicion_threshold_;
+
+ // Number of consecutive times a call stack must trigger suspicion to be
+ // considered a leak suspect.
+ int call_stack_suspicion_threshold_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakDetectorImpl);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_IMPL_H_
diff --git a/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc b/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc
new file mode 100644
index 00000000000..7a7e30a4413
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_impl_unittest.cc
@@ -0,0 +1,629 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector_impl.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <complex>
+#include <new>
+#include <set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+using InternalLeakReport = LeakDetectorImpl::LeakReport;
+template <typename T>
+using InternalVector = LeakDetectorImpl::InternalVector<T>;
+
+namespace {
+
+// Makes working with complex numbers easier.
+using Complex = std::complex<double>;
+
+// The mapping location in memory for a fictional executable.
+const uintptr_t kMappingAddr = 0x800000;
+const size_t kMappingSize = 0x200000;
+
+// Some call stacks within the fictional executable.
+// * - outside the mapping range, e.g. JIT code.
+const uintptr_t kRawStack0[] = {
+ 0x800100, 0x900000, 0x880080, 0x810000,
+};
+const uintptr_t kRawStack1[] = {
+ 0x940000, 0x980000,
+ 0xdeadbeef, // *
+ 0x9a0000,
+};
+const uintptr_t kRawStack2[] = {
+ 0x8f0d00, 0x803abc, 0x9100a0,
+};
+const uintptr_t kRawStack3[] = {
+ 0x90fcde,
+ 0x900df00d, // *
+ 0x801000, 0x880088,
+ 0xdeadcafe, // *
+ 0x9f0000, 0x8700a0, 0x96037c,
+};
+const uintptr_t kRawStack4[] = {
+ 0x8c0000, 0x85d00d, 0x921337,
+ 0x780000, // *
+};
+const uintptr_t kRawStack5[] = {
+ 0x990000, 0x888888, 0x830ac0, 0x8e0000,
+ 0xc00000, // *
+};
+
+// This struct makes it easier to pass call stack info to
+// LeakDetectorImplTest::Alloc().
+struct TestCallStack {
+ const uintptr_t* stack; // A reference to the original stack data.
+ size_t depth;
+};
+
+const TestCallStack kStack0 = {kRawStack0, arraysize(kRawStack0)};
+const TestCallStack kStack1 = {kRawStack1, arraysize(kRawStack1)};
+const TestCallStack kStack2 = {kRawStack2, arraysize(kRawStack2)};
+const TestCallStack kStack3 = {kRawStack3, arraysize(kRawStack3)};
+const TestCallStack kStack4 = {kRawStack4, arraysize(kRawStack4)};
+const TestCallStack kStack5 = {kRawStack5, arraysize(kRawStack5)};
+
+// The interval between consecutive analyses (LeakDetectorImpl::TestForLeaks),
+// in number of bytes allocated. e.g. if |kAllocedSizeAnalysisInterval| = 1024
+// then call TestForLeaks() every 1024 bytes of allocation that occur.
+const size_t kAllocedSizeAnalysisInterval = 8192;
+
+// Suspicion thresholds used by LeakDetectorImpl for size and call stacks.
+const uint32_t kSizeSuspicionThreshold = 4;
+const uint32_t kCallStackSuspicionThreshold = 4;
+
+// Returns the offset within [kMappingAddr, kMappingAddr + kMappingSize) if
+// |addr| falls in that range. Otherwise, returns |addr|.
+uintptr_t GetOffsetInMapping(uintptr_t addr) {
+ if (addr >= kMappingAddr && addr < kMappingAddr + kMappingSize)
+ return addr - kMappingAddr;
+ return addr;
+}
+
+// Copied from leak_detector_impl.cc. Converts a size to a size class index.
+// Any size in the range [index * 4, index * 4 + 3] falls into that size class.
+uint32_t SizeToIndex(size_t size) {
+ return size / sizeof(uint32_t);
+}
+
+} // namespace
+
+// This test suite will test the ability of LeakDetectorImpl to catch leaks in
+// a program. Individual tests can run leaky code locally.
+//
+// The leaky code must call Alloc() and Free() for heap memory management. It
+// should not call See comments on those
+// functions for more details.
+class LeakDetectorImplTest : public ::testing::Test {
+ public:
+ LeakDetectorImplTest()
+ : total_num_allocs_(0),
+ total_num_frees_(0),
+ total_alloced_size_(0),
+ next_analysis_total_alloced_size_(kAllocedSizeAnalysisInterval) {}
+
+ void SetUp() override {
+ CustomAllocator::Initialize();
+
+ detector_.reset(new LeakDetectorImpl(kMappingAddr, kMappingSize,
+ kSizeSuspicionThreshold,
+ kCallStackSuspicionThreshold));
+ }
+
+ void TearDown() override {
+ // Free any memory that was leaked by test cases. Do not use Free() because
+ // that will try to modify |alloced_ptrs_|.
+ for (void* ptr : alloced_ptrs_)
+ delete[] reinterpret_cast<char*>(ptr);
+ alloced_ptrs_.clear();
+
+ // Must destroy all objects that use CustomAllocator before shutting down.
+ detector_.reset();
+ stored_reports_.clear();
+
+ EXPECT_TRUE(CustomAllocator::Shutdown());
+ }
+
+ protected:
+ // Alloc and free functions that allocate and free heap memory and
+ // automatically pass alloc/free info to |detector_|. They emulate the
+ // alloc/free hook functions that would call into LeakDetectorImpl in
+ // real-life usage. They also keep track of individual allocations locally, so
+ // any leaked memory could be cleaned up.
+ //
+ // |stack| is just a nominal call stack object to identify the call site. It
+ // doesn't have to contain the stack trace of the actual call stack.
+ void* Alloc(size_t size, const TestCallStack& stack) {
+ void* ptr = new char[size];
+ detector_->RecordAlloc(ptr, size, stack.depth,
+ reinterpret_cast<const void* const*>(stack.stack));
+
+ EXPECT_TRUE(alloced_ptrs_.find(ptr) == alloced_ptrs_.end());
+ alloced_ptrs_.insert(ptr);
+
+ ++total_num_allocs_;
+ total_alloced_size_ += size;
+ if (total_alloced_size_ >= next_analysis_total_alloced_size_) {
+ InternalVector<InternalLeakReport> reports;
+ detector_->TestForLeaks(&reports);
+ for (const InternalLeakReport& report : reports) {
+ auto iter = stored_reports_.find(report);
+ if (iter == stored_reports_.end()) {
+ stored_reports_.insert(report);
+ } else {
+ // InternalLeakReports are uniquely identified by |alloc_size_bytes_|
+ // and |call_stack_|. See InternalLeakReport::operator<().
+ // If a report with the same size and call stack already exists,
+ // overwrite it with the new report, which has a newer history.
+ stored_reports_.erase(iter);
+ stored_reports_.insert(report);
+ }
+ }
+
+ // Determine when the next leak analysis should occur.
+ while (total_alloced_size_ >= next_analysis_total_alloced_size_)
+ next_analysis_total_alloced_size_ += kAllocedSizeAnalysisInterval;
+ }
+ return ptr;
+ }
+
+ // See comment for Alloc().
+ void Free(void* ptr) {
+ auto find_ptr_iter = alloced_ptrs_.find(ptr);
+ EXPECT_FALSE(find_ptr_iter == alloced_ptrs_.end());
+ if (find_ptr_iter == alloced_ptrs_.end())
+ return;
+ alloced_ptrs_.erase(find_ptr_iter);
+ ++total_num_frees_;
+
+ detector_->RecordFree(ptr);
+
+ delete[] reinterpret_cast<char*>(ptr);
+ }
+
+ // TEST CASE: Simple program that leaks memory regularly. Pass in
+ // enable_leaks=true to trigger some memory leaks.
+ void SimpleLeakyFunction(bool enable_leaks);
+
+ // TEST CASE: Julia set fractal computation. Pass in enable_leaks=true to
+ // trigger some memory leaks.
+ void JuliaSet(bool enable_leaks);
+
+ // Instance of the class being tested.
+ scoped_ptr<LeakDetectorImpl> detector_;
+
+ // Number of pointers allocated and freed so far.
+ size_t total_num_allocs_;
+ size_t total_num_frees_;
+
+ // Keeps count of total size allocated by Alloc().
+ size_t total_alloced_size_;
+
+ // The cumulative allocation size at which to trigger the TestForLeaks() call.
+ size_t next_analysis_total_alloced_size_;
+
+ // Stores all pointers to memory allocated by by Alloc() so we can manually
+ // free the leaked pointers at the end. This also serves as redundant
+ // bookkeepping: it stores all pointers that have been allocated but not yet
+ // freed.
+ std::set<void*> alloced_ptrs_;
+
+ // Store leak reports here. Use a set so duplicate reports are not stored.
+ std::set<InternalLeakReport> stored_reports_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LeakDetectorImplTest);
+};
+
+void LeakDetectorImplTest::SimpleLeakyFunction(bool enable_leaks) {
+ std::vector<uint32_t*> ptrs(7);
+
+ const int kNumOuterIterations = 20;
+ for (int j = 0; j < kNumOuterIterations; ++j) {
+ // The inner loop allocates 256 bytes. Run it 32 times so that 8192 bytes
+ // (|kAllocedSizeAnalysisInterval|) are allocated for each iteration of the
+ // outer loop.
+ const int kNumInnerIterations = 32;
+ static_assert(kNumInnerIterations * 256 == kAllocedSizeAnalysisInterval,
+ "Inner loop iterations do not allocate the correct number of "
+ "bytes.");
+ for (int i = 0; i < kNumInnerIterations; ++i) {
+ size_t alloc_size_at_beginning = total_alloced_size_;
+
+ ptrs[0] = new(Alloc(16, kStack0)) uint32_t;
+ ptrs[1] = new(Alloc(32, kStack1)) uint32_t;
+ ptrs[2] = new(Alloc(48, kStack2)) uint32_t;
+ // Allocate two 32-byte blocks and record them as from the same call site.
+ ptrs[3] = new(Alloc(32, kStack3)) uint32_t;
+ ptrs[4] = new(Alloc(32, kStack3)) uint32_t;
+ // Allocate two 48-byte blocks and record them as from the same call site.
+ ptrs[5] = new(Alloc(48, kStack4)) uint32_t;
+ ptrs[6] = new(Alloc(48, kStack4)) uint32_t;
+
+ // Now free these pointers.
+ Free(ptrs[0]);
+ if (!enable_leaks) // Leak with size=32, call_stack=kStack1.
+ Free(ptrs[1]);
+ if (!enable_leaks) // Leak with size=48, call_stack=kStack2.
+ Free(ptrs[2]);
+ Free(ptrs[3]);
+ Free(ptrs[4]);
+ Free(ptrs[5]);
+ Free(ptrs[6]);
+
+ // Make sure that the above code actually allocates 256 bytes.
+ EXPECT_EQ(alloc_size_at_beginning + 256, total_alloced_size_);
+ }
+ }
+}
+
+void LeakDetectorImplTest::JuliaSet(bool enable_leaks) {
+ // The center region of the complex plane that is the basis for our Julia set
+ // computations is a circle of radius kRadius.
+ constexpr double kRadius = 2;
+
+ // To track points in the complex plane, we will use a rectangular grid in the
+ // range defined by [-kRadius, kRadius] along both axes.
+ constexpr double kRangeMin = -kRadius;
+ constexpr double kRangeMax = kRadius;
+
+ // Divide each axis into intervals, each of which is associated with a point
+ // on that axis at its center.
+ constexpr double kIntervalInverse = 64;
+ constexpr double kInterval = 1.0 / kIntervalInverse;
+ constexpr int kNumPoints = (kRangeMax - kRangeMin) / kInterval + 1;
+
+ // Contains some useful functions for converting between points on the complex
+ // plane and in a gridlike data structure.
+ struct ComplexPlane {
+ static int GetXGridIndex(const Complex& value) {
+ return (value.real() + kInterval / 2 - kRangeMin) / kInterval;
+ }
+ static int GetYGridIndex(const Complex& value) {
+ return (value.imag() + kInterval / 2 - kRangeMin) / kInterval;
+ }
+ static int GetArrayIndex(const Complex& value) {
+ return GetXGridIndex(value) + GetYGridIndex(value) * kNumPoints;
+ }
+ static Complex GetComplexForGridPoint(size_t x, size_t y) {
+ return Complex(kRangeMin + x * kInterval, kRangeMin + y * kInterval);
+ }
+ };
+
+ // Make sure the choice of interval doesn't result in any loss of precision.
+ ASSERT_EQ(1.0, kInterval * kIntervalInverse);
+
+ // Create a grid for part of the complex plane, with each axis within the
+ // range [kRangeMin, kRangeMax].
+ constexpr size_t width = kNumPoints;
+ constexpr size_t height = kNumPoints;
+ std::vector<Complex*> grid(width * height);
+
+ // Initialize an object for each point within the inner circle |z| < kRadius.
+ for (size_t i = 0; i < width; ++i) {
+ for (size_t j = 0; j < height; ++j) {
+ Complex point = ComplexPlane::GetComplexForGridPoint(i, j);
+ // Do not store any values outside the inner circle.
+ if (abs(point) <= kRadius) {
+ grid[i + j * width] =
+ new (Alloc(sizeof(Complex), kStack0)) Complex(point);
+ }
+ }
+ }
+ EXPECT_LE(alloced_ptrs_.size(), width * height);
+
+ // Create a new grid for the result of the transformation.
+ std::vector<Complex*> next_grid(width * height, nullptr);
+
+ // Number of times to run the Julia set iteration. This is not the same as the
+ // number of analyses performed by LeakDetectorImpl, which is determined by
+ // the total number of bytes allocated divided by
+ // |kAllocedSizeAnalysisInterval|.
+ const int kNumIterations = 20;
+ for (int n = 0; n < kNumIterations; ++n) {
+ for (int i = 0; i < kNumPoints; ++i) {
+ for (int j = 0; j < kNumPoints; ++j) {
+ if (!grid[i + j * width])
+ continue;
+
+ // NOTE: The below code is NOT an efficient way to compute a Julia set.
+ // This is only to test the leak detector with some nontrivial code.
+
+ // A simple polynomial function for generating Julia sets is:
+ // f(z) = z^n + c
+
+ // But in this algorithm, we need the inverse:
+ // fInv(z) = (z - c)^(1/n)
+
+ // Here, let's use n=5 and c=0.544.
+ const Complex c(0.544, 0);
+ const Complex& z = *grid[i + j * width];
+
+ // This is the principal root.
+ Complex root = pow(z - c, 0.2);
+
+ // Discard the result if it is too far out from the center of the plane.
+ if (abs(root) > kRadius)
+ continue;
+
+ // The below code only allocates Complex objects of the same size. The
+ // leak detector expects various sizes, so increase the allocation size
+ // by a different amount at each call site.
+
+ // Nth root produces N results.
+ // Place all root results on |next_grid|.
+
+ // First, place the principal root.
+ if (!next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 24, kStack1)) Complex(root);
+ }
+
+ double magnitude = abs(root);
+ double angle = arg(root);
+ // To generate other roots, rotate the principal root by increments of
+ // 1/N of a full circle.
+ const double kAngleIncrement = M_PI * 2 / 5;
+
+ // Second root.
+ root = std::polar(magnitude, angle + kAngleIncrement);
+ if (!next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 40, kStack2)) Complex(root);
+ }
+
+ // In some of the sections below, setting |enable_leaks| to true will
+ // trigger a memory leak by overwriting the old Complex pointer value
+ // without freeing it. Due to the nature of complex roots being confined
+ // to equal sections of the complex plane, each new pointer will
+ // displace an old pointer that was allocated from the same line of
+ // code.
+
+ // Third root.
+ root = std::polar(magnitude, angle + kAngleIncrement * 2);
+ // *** LEAK ***
+ if (enable_leaks || !next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 40, kStack3)) Complex(root);
+ }
+
+ // Fourth root.
+ root = std::polar(magnitude, angle + kAngleIncrement * 3);
+ // *** LEAK ***
+ if (enable_leaks || !next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 52, kStack4)) Complex(root);
+ }
+
+ // Fifth root.
+ root = std::polar(magnitude, angle + kAngleIncrement * 4);
+ if (!next_grid[ComplexPlane::GetArrayIndex(root)]) {
+ next_grid[ComplexPlane::GetArrayIndex(root)] =
+ new (Alloc(sizeof(Complex) + 52, kStack5)) Complex(root);
+ }
+ }
+ }
+
+ // Clear the previously allocated points.
+ for (Complex*& point : grid) {
+ if (point) {
+ Free(point);
+ point = nullptr;
+ }
+ }
+
+ // Now swap the two grids for the next iteration.
+ grid.swap(next_grid);
+ }
+
+ // Clear the previously allocated points.
+ for (Complex*& point : grid) {
+ if (point) {
+ Free(point);
+ point = nullptr;
+ }
+ }
+}
+
+TEST_F(LeakDetectorImplTest, CheckTestFramework) {
+ EXPECT_EQ(0U, total_num_allocs_);
+ EXPECT_EQ(0U, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+
+ // Allocate some memory.
+ void* ptr0 = Alloc(12, kStack0);
+ void* ptr1 = Alloc(16, kStack0);
+ void* ptr2 = Alloc(24, kStack0);
+ EXPECT_EQ(3U, total_num_allocs_);
+ EXPECT_EQ(0U, total_num_frees_);
+ EXPECT_EQ(3U, alloced_ptrs_.size());
+
+ // Free one of the pointers.
+ Free(ptr1);
+ EXPECT_EQ(3U, total_num_allocs_);
+ EXPECT_EQ(1U, total_num_frees_);
+ EXPECT_EQ(2U, alloced_ptrs_.size());
+
+ // Allocate some more memory.
+ void* ptr3 = Alloc(72, kStack1);
+ void* ptr4 = Alloc(104, kStack1);
+ void* ptr5 = Alloc(96, kStack1);
+ void* ptr6 = Alloc(24, kStack1);
+ EXPECT_EQ(7U, total_num_allocs_);
+ EXPECT_EQ(1U, total_num_frees_);
+ EXPECT_EQ(6U, alloced_ptrs_.size());
+
+ // Free more pointers.
+ Free(ptr2);
+ Free(ptr4);
+ Free(ptr6);
+ EXPECT_EQ(7U, total_num_allocs_);
+ EXPECT_EQ(4U, total_num_frees_);
+ EXPECT_EQ(3U, alloced_ptrs_.size());
+
+ // Free remaining memory.
+ Free(ptr0);
+ Free(ptr3);
+ Free(ptr5);
+ EXPECT_EQ(7U, total_num_allocs_);
+ EXPECT_EQ(7U, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+}
+
+TEST_F(LeakDetectorImplTest, SimpleLeakyFunctionNoLeak) {
+ SimpleLeakyFunction(false /* enable_leaks */);
+
+ // SimpleLeakyFunction() should have run cleanly without leaking.
+ EXPECT_EQ(total_num_allocs_, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+ ASSERT_EQ(0U, stored_reports_.size());
+}
+
+TEST_F(LeakDetectorImplTest, SimpleLeakyFunctionWithLeak) {
+ SimpleLeakyFunction(true /* enable_leaks */);
+
+ // SimpleLeakyFunction() should generated some leak reports.
+ EXPECT_GT(total_num_allocs_, total_num_frees_);
+ EXPECT_GT(alloced_ptrs_.size(), 0U);
+ ASSERT_EQ(2U, stored_reports_.size());
+
+ // The reports should be stored in order of size.
+
+ // |report1| comes from the call site marked with kStack1, with size=32.
+ const InternalLeakReport& report1 = *stored_reports_.begin();
+ EXPECT_EQ(32U, report1.alloc_size_bytes());
+ ASSERT_EQ(kStack1.depth, report1.call_stack().size());
+ for (size_t i = 0; i < kStack1.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack1.stack[i]),
+ report1.call_stack()[i]) << i;
+ }
+
+ // |report2| comes from the call site marked with kStack2, with size=48.
+ const InternalLeakReport& report2 = *(++stored_reports_.begin());
+ EXPECT_EQ(48U, report2.alloc_size_bytes());
+ ASSERT_EQ(kStack2.depth, report2.call_stack().size());
+ for (size_t i = 0; i < kStack2.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack2.stack[i]),
+ report2.call_stack()[i]) << i;
+ }
+
+ // Check historical data recorded in the reports.
+ // - Each inner loop iteration allocates a net of 1x 32 bytes and 1x 48 bytes.
+ // - Each outer loop iteration allocates a net of 32x 32 bytes and 32x 48
+ // bytes.
+ // - However, the leak analysis happens after the allocs but before the frees
+ // that come right after. So it should count the two extra allocs made at
+ // call sites |kStack3| and |kStack4|. The formula is |(i + 1) * 32 + 2|,
+ // where |i| is the iteration index.
+ // There should have been one leak analysis per outer loop iteration, for a
+ // total of 20 history records (|kNumOuterIterations|) per report.
+
+ const auto& report1_size_history = report1.size_breakdown_history();
+ EXPECT_EQ(20U, report1_size_history.size());
+
+ size_t index = 0;
+ for (const InternalVector<uint32_t>& entry : report1_size_history) {
+ ASSERT_GT(entry.size(), SizeToIndex(48));
+
+ // Check the two leaky sizes, 32 and 48.
+ EXPECT_EQ((index + 1) * 32 + 2, entry[SizeToIndex(32)]);
+ EXPECT_EQ((index + 1) * 32 + 2, entry[SizeToIndex(48)]);
+
+ // Not related to the leaks, but there should be a dangling 16-byte
+ // allocation during each leak analysis, because it hasn't yet been freed.
+ EXPECT_EQ(1U, entry[SizeToIndex(16)]);
+ ++index;
+ }
+
+ // |report2| should have the same size history as |report1|.
+ const auto& report2_size_history = report2.size_breakdown_history();
+ EXPECT_TRUE(std::equal(report1_size_history.begin(),
+ report1_size_history.end(),
+ report2_size_history.begin()));
+}
+
+TEST_F(LeakDetectorImplTest, JuliaSetNoLeak) {
+ JuliaSet(false /* enable_leaks */);
+
+ // JuliaSet() should have run cleanly without leaking.
+ EXPECT_EQ(total_num_allocs_, total_num_frees_);
+ EXPECT_EQ(0U, alloced_ptrs_.size());
+ ASSERT_EQ(0U, stored_reports_.size());
+}
+
+TEST_F(LeakDetectorImplTest, JuliaSetWithLeak) {
+ JuliaSet(true /* enable_leaks */);
+
+ // JuliaSet() should have leaked some memory from two call sites.
+ EXPECT_GT(total_num_allocs_, total_num_frees_);
+ EXPECT_GT(alloced_ptrs_.size(), 0U);
+
+ // There should be one unique leak report generated for each leaky call site.
+ ASSERT_EQ(2U, stored_reports_.size());
+
+ // The reports should be stored in order of size.
+
+ // |report1| comes from the call site in JuliaSet() corresponding to
+ // |kStack3|.
+ const InternalLeakReport& report1 = *stored_reports_.begin();
+ EXPECT_EQ(sizeof(Complex) + 40, report1.alloc_size_bytes());
+ ASSERT_EQ(kStack3.depth, report1.call_stack().size());
+ for (size_t i = 0; i < kStack3.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack3.stack[i]),
+ report1.call_stack()[i]) << i;
+ }
+
+ // |report2| comes from the call site in JuliaSet() corresponding to
+ // |kStack4|.
+ const InternalLeakReport& report2 = *(++stored_reports_.begin());
+ EXPECT_EQ(sizeof(Complex) + 52, report2.alloc_size_bytes());
+ ASSERT_EQ(kStack4.depth, report2.call_stack().size());
+ for (size_t i = 0; i < kStack4.depth; ++i) {
+ EXPECT_EQ(GetOffsetInMapping(kStack4.stack[i]),
+ report2.call_stack()[i]) << i;
+ }
+
+ // Check |report1|'s historical data.
+ const auto& report1_size_history = report1.size_breakdown_history();
+ // Computing the exact number of leak analyses is not trivial, but we know it
+ // must be at least |kSizeSuspicionThreshold + kCallStackSuspicionThreshold|
+ // in order to have generated a report.
+ EXPECT_GT(report1_size_history.size(),
+ kSizeSuspicionThreshold + kCallStackSuspicionThreshold);
+
+ // Make sure that the final allocation counts for the leaky sizes are larger
+ // than that of the non-leaky size by at least an order of magnitude.
+ const InternalVector<uint32_t>& final_entry = *report1_size_history.rbegin();
+ uint32_t size_0_index = SizeToIndex(sizeof(Complex) + 24);
+ uint32_t size_1_index = SizeToIndex(sizeof(Complex) + 40);
+ uint32_t size_2_index = SizeToIndex(sizeof(Complex) + 52);
+ ASSERT_LT(size_0_index, final_entry.size());
+ ASSERT_LT(size_1_index, final_entry.size());
+ ASSERT_LT(size_2_index, final_entry.size());
+
+ EXPECT_GT(final_entry[size_1_index], final_entry[size_0_index] * 10);
+ EXPECT_GT(final_entry[size_2_index], final_entry[size_0_index] * 10);
+
+ // |report2| should have the same size history as |report1|.
+ const auto& report2_size_history = report2.size_breakdown_history();
+ EXPECT_TRUE(std::equal(report1_size_history.begin(),
+ report1_size_history.end(),
+ report2_size_history.begin()));
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_unittest.cc b/chromium/components/metrics/leak_detector/leak_detector_unittest.cc
new file mode 100644
index 00000000000..6f33c012bb6
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector.h"
+
+#include <set>
+
+#include "base/allocator/allocator_extension.h"
+#include "base/macros.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Default values for LeakDetector params. See header file for the meaning of
+// each parameter.
+const float kDefaultSamplingRate = 1.0f;
+const size_t kDefaultMaxCallStackUnwindDepth = 4;
+const uint64_t kDefaultAnalysisIntervalBytes = 32 * 1024 * 1024; // 32 MiB.
+const uint32_t kDefaultSizeSuspicionThreshold = 4;
+const uint32_t kDefaultCallStackSuspicionThreshold = 4;
+
+using LeakReport = LeakDetector::LeakReport;
+
+// Observer class that receives leak reports and stores them in |reports_|.
+// Only one copy of each unique report will be stored.
+class TestObserver : public LeakDetector::Observer {
+ public:
+ // Contains a comparator function used to compare LeakReports for uniqueness.
+ struct Comparator {
+ bool operator()(const LeakReport& a, const LeakReport& b) const {
+ if (a.alloc_size_bytes != b.alloc_size_bytes)
+ return a.alloc_size_bytes < b.alloc_size_bytes;
+
+ return a.call_stack < b.call_stack;
+ }
+ };
+
+ TestObserver() {}
+
+ void OnLeakFound(const LeakReport& report) override {
+ reports_.insert(report);
+ }
+
+ const std::set<LeakReport, Comparator>& reports() const { return reports_; }
+
+ private:
+ // Container for all leak reports received through OnLeakFound(). Stores only
+ // one copy of each unique report.
+ std::set<LeakReport, Comparator> reports_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+} // namespace
+
+class LeakDetectorTest : public ::testing::Test {
+ public:
+ LeakDetectorTest() : detector_(LeakDetector::GetInstance()) {
+ detector_->Init(kDefaultSamplingRate, kDefaultMaxCallStackUnwindDepth,
+ kDefaultAnalysisIntervalBytes,
+ kDefaultSizeSuspicionThreshold,
+ kDefaultCallStackSuspicionThreshold);
+ }
+
+ protected:
+ // Points to the instance of LeakDetector returned by GetInstance().
+ LeakDetector* detector_;
+
+ private:
+ // For supporting content::BrowserThread operations.
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakDetectorTest);
+};
+
+TEST_F(LeakDetectorTest, NotifyObservers) {
+ // Generate two sets of leak reports.
+ std::vector<LeakReport> reports1(3);
+ reports1[0].alloc_size_bytes = 8;
+ reports1[0].call_stack = {1, 2, 3, 4};
+ reports1[1].alloc_size_bytes = 16;
+ reports1[1].call_stack = {5, 6, 7, 8};
+ reports1[2].alloc_size_bytes = 24;
+ reports1[2].call_stack = {9, 10, 11, 12};
+
+ std::vector<LeakReport> reports2(3);
+ reports2[0].alloc_size_bytes = 32;
+ reports2[0].call_stack = {1, 2, 4, 8};
+ reports2[1].alloc_size_bytes = 40;
+ reports2[1].call_stack = {16, 32, 64, 128};
+ reports2[2].alloc_size_bytes = 48;
+ reports2[2].call_stack = {256, 512, 1024, 2048};
+
+ // Register three observers;
+ TestObserver obs1, obs2, obs3;
+ detector_->AddObserver(&obs1);
+ detector_->AddObserver(&obs2);
+ detector_->AddObserver(&obs3);
+
+ // Pass both sets of reports to the leak detector.
+ detector_->NotifyObservers(reports1);
+ detector_->NotifyObservers(reports2);
+
+ // Check that all three observers got both sets of reports, passed in
+ // separately.
+ for (const TestObserver* obs : {&obs1, &obs2, &obs3}) {
+ EXPECT_EQ(6U, obs->reports().size());
+ for (const auto& report : {reports1[0], reports1[1], reports1[2],
+ reports2[0], reports2[1], reports2[2]}) {
+ EXPECT_TRUE(obs->reports().find(report) != obs->reports().end());
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_value_type.cc b/chromium/components/metrics/leak_detector/leak_detector_value_type.cc
new file mode 100644
index 00000000000..4642f5150f8
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_value_type.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+
+#include <stdio.h>
+
+namespace metrics {
+namespace leak_detector {
+
+bool LeakDetectorValueType::operator==(
+ const LeakDetectorValueType& other) const {
+ if (type_ != other.type_)
+ return false;
+
+ switch (type_) {
+ case TYPE_SIZE:
+ return size_ == other.size_;
+ case TYPE_CALL_STACK:
+ return call_stack_ == other.call_stack_;
+ case TYPE_NONE:
+ // "NONE" types are considered to be all identical.
+ return true;
+ }
+ return false;
+}
+
+bool LeakDetectorValueType::operator<(
+ const LeakDetectorValueType& other) const {
+ if (type_ != other.type_)
+ return type_ < other.type_;
+
+ switch (type_) {
+ case TYPE_SIZE:
+ return size_ < other.size_;
+ case TYPE_CALL_STACK:
+ return call_stack_ < other.call_stack_;
+ case TYPE_NONE:
+ // "NONE" types are considered to be all identical.
+ return false;
+ }
+ return false;
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/leak_detector_value_type.h b/chromium/components/metrics/leak_detector/leak_detector_value_type.h
new file mode 100644
index 00000000000..40f61081e9a
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/leak_detector_value_type.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace metrics {
+namespace leak_detector {
+
+// Used for tracking unique call stacks.
+// Not thread-safe.
+struct CallStack;
+
+class LeakDetectorValueType {
+ public:
+ // Supported types.
+ enum Type {
+ TYPE_NONE,
+ TYPE_SIZE,
+ TYPE_CALL_STACK,
+ };
+
+ LeakDetectorValueType() : type_(TYPE_NONE), size_(0), call_stack_(nullptr) {}
+ explicit LeakDetectorValueType(size_t size)
+ : type_(TYPE_SIZE), size_(size), call_stack_(nullptr) {}
+ explicit LeakDetectorValueType(const CallStack* call_stack)
+ : type_(TYPE_CALL_STACK), size_(0), call_stack_(call_stack) {}
+
+ // Accessors.
+ Type type() const { return type_; }
+ size_t size() const { return size_; }
+ const CallStack* call_stack() const { return call_stack_; }
+
+ // Comparators.
+ bool operator==(const LeakDetectorValueType& other) const;
+ bool operator<(const LeakDetectorValueType& other) const;
+
+ private:
+ Type type_;
+
+ size_t size_;
+ const CallStack* call_stack_;
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_LEAK_DETECTOR_VALUE_TYPE_
diff --git a/chromium/components/metrics/leak_detector/ranked_set.cc b/chromium/components/metrics/leak_detector/ranked_set.cc
new file mode 100644
index 00000000000..5725fb5c01c
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/ranked_set.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/ranked_set.h"
+
+#include <algorithm>
+
+namespace metrics {
+namespace leak_detector {
+
+RankedSet::RankedSet(size_t max_size) : max_size_(max_size) {}
+
+RankedSet::~RankedSet() {}
+
+RankedSet::RankedSet(RankedSet&& other) : max_size_(other.max_size_) {
+ entries_ = std::move(other.entries_);
+}
+
+RankedSet& RankedSet::operator=(RankedSet&& other) {
+ max_size_ = other.max_size_;
+ entries_ = std::move(other.entries_);
+ return *this;
+}
+
+bool RankedSet::Entry::operator<(const RankedSet::Entry& other) const {
+ if (count == other.count)
+ return value < other.value;
+
+ return count > other.count;
+}
+
+void RankedSet::Add(const ValueType& value, int count) {
+ // If the container is full, do not add any entry with |count| if does not
+ // exceed the lowest count of the entries in the list.
+ if (size() == max_size_ && count < min_count())
+ return;
+
+ Entry new_entry;
+ new_entry.value = value;
+ new_entry.count = count;
+ entries_.insert(new_entry);
+
+ // Limit the container size if it exceeds the maximum allowed size, by
+ // deleting the last element. This should only iterate once because the size
+ // can only have increased by 1, but use a while loop just to be safe.
+ while (entries_.size() > max_size_)
+ entries_.erase(--entries_.end());
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/ranked_set.h b/chromium/components/metrics/leak_detector/ranked_set.h
new file mode 100644
index 00000000000..f91251593b0
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/ranked_set.h
@@ -0,0 +1,96 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_
+
+#include <stddef.h>
+
+#include <functional> // for std::less
+#include <set>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+#include "components/metrics/leak_detector/stl_allocator.h"
+
+namespace metrics {
+namespace leak_detector {
+
+// RankedSet lets you add entries consisting of a value-count pair, and
+// automatically sorts them internally by count in descending order. This allows
+// for the user of this container to insert value-count pairs without having to
+// explicitly sort them by count.
+class RankedSet {
+ public:
+ using ValueType = LeakDetectorValueType;
+
+ // A single entry in the RankedSet. The RankedSet sorts entries by |count|
+ // in descending order.
+ struct Entry {
+ ValueType value;
+ int count;
+
+ // This less-than comparator is used for sorting Entries within a sorted
+ // container. It internally reverses the comparison so that higher-count
+ // entries are sorted ahead of lower-count entries.
+ bool operator<(const Entry& other) const;
+ };
+
+ // This class uses CustomAllocator to avoid recursive malloc hook invocation
+ // when analyzing allocs and frees.
+ using EntrySet =
+ std::set<Entry, std::less<Entry>, STLAllocator<Entry, CustomAllocator>>;
+ using const_iterator = EntrySet::const_iterator;
+
+ explicit RankedSet(size_t max_size);
+ ~RankedSet();
+
+ // For move semantics.
+ RankedSet(RankedSet&& other);
+ RankedSet& operator=(RankedSet&& other);
+
+ // Accessors for begin() and end() const iterators.
+ const_iterator begin() const { return entries_.begin(); }
+ const_iterator end() const { return entries_.end(); }
+
+ size_t size() const { return entries_.size(); }
+ size_t max_size() const { return max_size_; }
+
+ // Add a new value-count pair to the container. Will overwrite any existing
+ // entry with the same value and count. Will not overwrite an existing entry
+ // with the same value but a different count, or different values with the
+ // same count.
+ //
+ // Time complexity is O(log n).
+ void Add(const ValueType& value, int count);
+
+ // Helper functions to directly add a size or call stack to the RankedSet.
+ void AddSize(size_t size, int count) { Add(ValueType(size), count); }
+ void AddCallStack(const CallStack* call_stack, int count) {
+ Add(ValueType(call_stack), count);
+ }
+
+ private:
+ // Max and min counts. Returns 0 if the list is empty.
+ int max_count() const {
+ return entries_.empty() ? 0 : entries_.begin()->count;
+ }
+ int min_count() const {
+ return entries_.empty() ? 0 : entries_.rbegin()->count;
+ }
+
+ // Max number of items that can be stored in the list.
+ size_t max_size_;
+
+ // Actual storage container for entries.
+ EntrySet entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(RankedSet);
+};
+
+} // namespace leak_detector
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_RANKED_SET_H_
diff --git a/chromium/components/metrics/leak_detector/ranked_set_unittest.cc b/chromium/components/metrics/leak_detector/ranked_set_unittest.cc
new file mode 100644
index 00000000000..2e2de01c8b7
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/ranked_set_unittest.cc
@@ -0,0 +1,324 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/leak_detector/ranked_set.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "components/metrics/leak_detector/custom_allocator.h"
+#include "components/metrics/leak_detector/leak_detector_value_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace leak_detector {
+
+namespace {
+
+// Makes it easier to instantiate LeakDetectorValueTypes.
+LeakDetectorValueType Value(uint32_t value) {
+ return LeakDetectorValueType(value);
+}
+
+} // namespace
+
+class RankedSetTest : public ::testing::Test {
+ public:
+ RankedSetTest() {}
+
+ void SetUp() override { CustomAllocator::Initialize(); }
+ void TearDown() override { EXPECT_TRUE(CustomAllocator::Shutdown()); }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RankedSetTest);
+};
+
+TEST_F(RankedSetTest, Iterators) {
+ RankedSet set(10);
+ EXPECT_TRUE(set.begin() == set.end());
+
+ set.Add(Value(0x1234), 100);
+ EXPECT_FALSE(set.begin() == set.end());
+}
+
+TEST_F(RankedSetTest, SingleInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 100);
+ EXPECT_EQ(1U, set.size());
+
+ auto iter = set.begin();
+ EXPECT_EQ(0x1234U, iter->value.size());
+ EXPECT_EQ(100, iter->count);
+}
+
+TEST_F(RankedSetTest, InOrderInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 100);
+ EXPECT_EQ(1U, set.size());
+ set.Add(Value(0x2345), 95);
+ EXPECT_EQ(2U, set.size());
+ set.Add(Value(0x3456), 90);
+ EXPECT_EQ(3U, set.size());
+ set.Add(Value(0x4567), 85);
+ EXPECT_EQ(4U, set.size());
+ set.Add(Value(0x5678), 80);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues[] = {
+ {Value(0x1234), 100}, {Value(0x2345), 95}, {Value(0x3456), 90},
+ {Value(0x4567), 85}, {Value(0x5678), 80},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues));
+ EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, ReverseOrderInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 0);
+ EXPECT_EQ(1U, set.size());
+ set.Add(Value(0x2345), 5);
+ EXPECT_EQ(2U, set.size());
+ set.Add(Value(0x3456), 10);
+ EXPECT_EQ(3U, set.size());
+ set.Add(Value(0x4567), 15);
+ EXPECT_EQ(4U, set.size());
+ set.Add(Value(0x5678), 20);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues[] = {
+ {Value(0x5678), 20}, {Value(0x4567), 15}, {Value(0x3456), 10},
+ {Value(0x2345), 5}, {Value(0x1234), 0},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues));
+ EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, UnorderedInsertion) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 15);
+ set.Add(Value(0x2345), 20);
+ set.Add(Value(0x3456), 10);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x5678), 25);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+
+ // Add more items.
+ set.Add(Value(0x6789), 35);
+ set.Add(Value(0x789a), 40);
+ set.Add(Value(0x89ab), 50);
+ set.Add(Value(0x9abc), 5);
+ set.Add(Value(0xabcd), 0);
+ EXPECT_EQ(10U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues2[] = {
+ {Value(0x89ab), 50}, {Value(0x789a), 40}, {Value(0x6789), 35},
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10}, {Value(0x9abc), 5},
+ {Value(0xabcd), 0},
+ };
+
+ index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues2));
+ EXPECT_EQ(kExpectedValues2[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues2[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, UnorderedInsertionWithSameCounts) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 20);
+ set.Add(Value(0x2345), 20);
+ set.Add(Value(0x3456), 30);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x5678), 20);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in. Entries
+ // with the same count should be ordered from lowest value to highest value.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x3456), 30}, {Value(0x4567), 30}, {Value(0x1234), 20},
+ {Value(0x2345), 20}, {Value(0x5678), 20},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, RepeatedEntries) {
+ RankedSet set(10);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 20);
+ set.Add(Value(0x3456), 30);
+ set.Add(Value(0x1234), 20);
+ set.Add(Value(0x3456), 30);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x4567), 30);
+ EXPECT_EQ(3U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x3456), 30}, {Value(0x4567), 30}, {Value(0x1234), 20},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, InsertionWithOverflow) {
+ RankedSet set(5);
+ EXPECT_EQ(0U, set.size());
+
+ set.Add(Value(0x1234), 15);
+ set.Add(Value(0x2345), 20);
+ set.Add(Value(0x3456), 10);
+ set.Add(Value(0x4567), 30);
+ set.Add(Value(0x5678), 25);
+ EXPECT_EQ(5U, set.size());
+
+ // These values will not make it into the set, which is now full.
+ set.Add(Value(0x6789), 0);
+ EXPECT_EQ(5U, set.size());
+ set.Add(Value(0x789a), 5);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues1[] = {
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10},
+ };
+
+ size_t index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues1));
+ EXPECT_EQ(kExpectedValues1[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues1[index].count, entry.count);
+ ++index;
+ }
+
+ // Insert some more values that go in the middle of the set.
+ set.Add(Value(0x89ab), 27);
+ EXPECT_EQ(5U, set.size());
+ set.Add(Value(0x9abc), 22);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues2[] = {
+ {Value(0x4567), 30}, {Value(0x89ab), 27}, {Value(0x5678), 25},
+ {Value(0x9abc), 22}, {Value(0x2345), 20},
+ };
+
+ index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues2));
+ EXPECT_EQ(kExpectedValues2[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues2[index].count, entry.count);
+ ++index;
+ }
+
+ // Insert some more values at the front of the set.
+ set.Add(Value(0xabcd), 40);
+ EXPECT_EQ(5U, set.size());
+ set.Add(Value(0xbcde), 35);
+ EXPECT_EQ(5U, set.size());
+
+ // Iterate through the contents to make sure they match what went in.
+ const RankedSet::Entry kExpectedValues3[] = {
+ {Value(0xabcd), 40}, {Value(0xbcde), 35}, {Value(0x4567), 30},
+ {Value(0x89ab), 27}, {Value(0x5678), 25},
+ };
+
+ index = 0;
+ for (const auto& entry : set) {
+ EXPECT_LT(index, arraysize(kExpectedValues3));
+ EXPECT_EQ(kExpectedValues3[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues3[index].count, entry.count);
+ ++index;
+ }
+}
+
+TEST_F(RankedSetTest, MoveOperation) {
+ const RankedSet::Entry kExpectedValues[] = {
+ {Value(0x89ab), 50}, {Value(0x789a), 40}, {Value(0x6789), 35},
+ {Value(0x4567), 30}, {Value(0x5678), 25}, {Value(0x2345), 20},
+ {Value(0x1234), 15}, {Value(0x3456), 10}, {Value(0x9abc), 5},
+ {Value(0xabcd), 0},
+ };
+
+ RankedSet source(10);
+ for (const RankedSet::Entry& entry : kExpectedValues) {
+ source.Add(entry.value, entry.count);
+ }
+ EXPECT_EQ(10U, source.size());
+
+ RankedSet dest(25); // This should be changed by the move.
+ dest = std::move(source);
+ EXPECT_EQ(10U, dest.size());
+ EXPECT_EQ(10U, dest.max_size());
+
+ size_t index = 0;
+ for (const auto& entry : dest) {
+ EXPECT_LT(index, arraysize(kExpectedValues));
+ EXPECT_EQ(kExpectedValues[index].value.size(), entry.value.size());
+ EXPECT_EQ(kExpectedValues[index].count, entry.count);
+ ++index;
+ }
+}
+
+} // namespace leak_detector
+} // namespace metrics
diff --git a/chromium/components/metrics/leak_detector/stl_allocator.h b/chromium/components/metrics/leak_detector/stl_allocator.h
new file mode 100644
index 00000000000..bfef0a38244
--- /dev/null
+++ b/chromium/components/metrics/leak_detector/stl_allocator.h
@@ -0,0 +1,61 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_
+#define COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_
+
+#include <stddef.h>
+
+#include <limits>
+#include <memory>
+
+#include "base/logging.h"
+
+// Generic allocator class for STL objects.
+// deallocate() to use the template class Alloc's allocation.
+// that uses a given type-less allocator Alloc, which must provide:
+// static void* Alloc::Allocate(size_t size);
+// static void Alloc::Free(void* ptr, size_t size);
+//
+// Inherits from the default allocator, std::allocator. Overrides allocate() and
+// deallocate() and some other functions.
+//
+// STLAllocator<T, MyAlloc> provides the same thread-safety guarantees as
+// MyAlloc.
+//
+// Usage example:
+// set<T, less<T>, STLAllocator<T, MyAlloc> > my_set;
+
+template <typename T, class Alloc>
+class STLAllocator : public std::allocator<T> {
+ public:
+ typedef size_t size_type;
+ typedef T* pointer;
+
+ template <class T1>
+ struct rebind {
+ typedef STLAllocator<T1, Alloc> other;
+ };
+
+ STLAllocator() {}
+ explicit STLAllocator(const STLAllocator&) {}
+ template <class T1>
+ STLAllocator(const STLAllocator<T1, Alloc>&) {}
+ ~STLAllocator() {}
+
+ pointer allocate(size_type n, const void* = 0) {
+ // Make sure the computation of the total allocation size does not cause an
+ // integer overflow.
+ RAW_CHECK(n < max_size());
+ return static_cast<T*>(Alloc::Allocate(n * sizeof(T)));
+ }
+
+ void deallocate(pointer p, size_type n) { Alloc::Free(p, n * sizeof(T)); }
+
+ size_type max_size() const {
+ return std::numeric_limits<size_t>::max() / sizeof(T);
+ }
+};
+
+#endif // COMPONENTS_METRICS_LEAK_DETECTOR_STL_ALLOCATOR_H_
diff --git a/chromium/components/metrics/machine_id_provider.h b/chromium/components/metrics/machine_id_provider.h
new file mode 100644
index 00000000000..d7fcc447639
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+#define COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace metrics {
+
+// Provides machine characteristics used as a machine id. The implementation is
+// platform specific with a default implementation that gives an empty id. The
+// class is ref-counted thread safe so it can be used to post to the FILE thread
+// and communicate back to the UI thread.
+// This raw machine id should not be stored or transmitted over the network.
+// TODO(jwd): Simplify implementation to get rid of the need for
+// RefCountedThreadSafe (crbug.com/354882).
+class MachineIdProvider : public base::RefCountedThreadSafe<MachineIdProvider> {
+ public:
+ // Get a string containing machine characteristics, to be used as a machine
+ // id. The implementation is platform specific, with a default implementation
+ // returning an empty string.
+ // The return value should not be stored to disk or transmitted.
+ std::string GetMachineId();
+
+ // Returns a pointer to a new MachineIdProvider or NULL if there is no
+ // provider implemented on a given platform. This is done to avoid posting a
+ // task to the FILE thread on platforms with no implementation.
+ static MachineIdProvider* CreateInstance();
+
+ private:
+ friend class base::RefCountedThreadSafe<MachineIdProvider>;
+
+ MachineIdProvider();
+ virtual ~MachineIdProvider();
+
+ DISALLOW_COPY_AND_ASSIGN(MachineIdProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
diff --git a/chromium/components/metrics/machine_id_provider_stub.cc b/chromium/components/metrics/machine_id_provider_stub.cc
new file mode 100644
index 00000000000..626f2b7cdc8
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider_stub.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+namespace metrics {
+
+MachineIdProvider::MachineIdProvider() {
+}
+
+MachineIdProvider::~MachineIdProvider() {
+}
+
+// static
+MachineIdProvider* MachineIdProvider::CreateInstance() {
+ return NULL;
+}
+
+std::string MachineIdProvider::GetMachineId() {
+ return std::string();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/machine_id_provider_win.cc b/chromium/components/metrics/machine_id_provider_win.cc
new file mode 100644
index 00000000000..41079a7dc2c
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider_win.cc
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include <windows.h>
+#include <stdint.h>
+#include <winioctl.h>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/win/scoped_handle.h"
+
+namespace metrics {
+
+MachineIdProvider::MachineIdProvider() {
+}
+
+MachineIdProvider::~MachineIdProvider() {
+}
+
+// On windows, the machine id is based on the serial number of the drive Chrome
+// is running from.
+std::string MachineIdProvider::GetMachineId() {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ // Use the program's path to get the drive used for the machine id. This means
+ // that whenever the underlying drive changes, it's considered a new machine.
+ // This is fine as we do not support migrating Chrome installs to new drives.
+ base::FilePath executable_path;
+
+ if (!PathService::Get(base::FILE_EXE, &executable_path)) {
+ NOTREACHED();
+ return std::string();
+ }
+
+ std::vector<base::FilePath::StringType> path_components;
+ executable_path.GetComponents(&path_components);
+ if (path_components.empty()) {
+ NOTREACHED();
+ return std::string();
+ }
+ base::FilePath::StringType drive_name = L"\\\\.\\" + path_components[0];
+
+ base::win::ScopedHandle drive_handle(
+ CreateFile(drive_name.c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL));
+
+ STORAGE_PROPERTY_QUERY query = {};
+ query.PropertyId = StorageDeviceProperty;
+ query.QueryType = PropertyStandardQuery;
+
+ // Perform an initial query to get the number of bytes being returned.
+ DWORD bytes_returned;
+ STORAGE_DESCRIPTOR_HEADER header = {};
+ BOOL status = DeviceIoControl(drive_handle.Get(),
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &header,
+ sizeof(STORAGE_DESCRIPTOR_HEADER),
+ &bytes_returned,
+ NULL);
+
+ if (!status)
+ return std::string();
+
+ // Query for the actual serial number.
+ std::vector<int8_t> output_buf(header.Size);
+ status = DeviceIoControl(drive_handle.Get(),
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &query,
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &output_buf[0],
+ output_buf.size(),
+ &bytes_returned,
+ NULL);
+
+ if (!status)
+ return std::string();
+
+ const STORAGE_DEVICE_DESCRIPTOR* device_descriptor =
+ reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(&output_buf[0]);
+
+ // The serial number is stored in the |output_buf| as a null-terminated
+ // string starting at the specified offset.
+ const DWORD offset = device_descriptor->SerialNumberOffset;
+ if (offset >= output_buf.size())
+ return std::string();
+
+ // Make sure that the null-terminator exists.
+ const std::vector<int8_t>::iterator serial_number_begin =
+ output_buf.begin() + offset;
+ const std::vector<int8_t>::iterator null_location =
+ std::find(serial_number_begin, output_buf.end(), '\0');
+ if (null_location == output_buf.end())
+ return std::string();
+
+ const char* serial_number =
+ reinterpret_cast<const char*>(&output_buf[offset]);
+
+ return std::string(serial_number);
+}
+
+// static
+MachineIdProvider* MachineIdProvider::CreateInstance() {
+ return new MachineIdProvider();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/machine_id_provider_win_unittest.cc b/chromium/components/metrics/machine_id_provider_win_unittest.cc
new file mode 100644
index 00000000000..11ffd8f73df
--- /dev/null
+++ b/chromium/components/metrics/machine_id_provider_win_unittest.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/win/windows_version.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(MachineIdProviderTest, GetId) {
+ scoped_refptr<MachineIdProvider> provider(
+ MachineIdProvider::CreateInstance());
+ std::string id1 = provider->GetMachineId();
+
+ // TODO(rpaquay): See crbug/458230
+ if (base::win::GetVersion() <= base::win::VERSION_XP)
+ return;
+
+ EXPECT_NE(std::string(), id1);
+
+ std::string id2 = provider->GetMachineId();
+ EXPECT_EQ(id1, id2);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log.cc b/chromium/components/metrics/metrics_log.cc
new file mode 100644
index 00000000000..32db4184462
--- /dev/null
+++ b/chromium/components/metrics/metrics_log.cc
@@ -0,0 +1,423 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/build_time.h"
+#include "base/cpu.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/histogram_encoder.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/proto/histogram_event.pb.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/metrics/proto/user_action_event.pb.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/active_field_trials.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/current_module.h"
+#endif
+
+using base::SampleCountIterator;
+typedef variations::ActiveGroupId ActiveGroupId;
+
+namespace metrics {
+
+namespace {
+
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+ return id.size() < 16;
+}
+
+// Computes a SHA-1 hash of |data| and returns it as a hex string.
+std::string ComputeSHA1(const std::string& data) {
+ const std::string sha1 = base::SHA1HashString(data);
+ return base::HexEncode(sha1.data(), sha1.size());
+}
+
+void WriteFieldTrials(const std::vector<ActiveGroupId>& field_trial_ids,
+ SystemProfileProto* system_profile) {
+ for (std::vector<ActiveGroupId>::const_iterator it =
+ field_trial_ids.begin(); it != field_trial_ids.end(); ++it) {
+ SystemProfileProto::FieldTrial* field_trial =
+ system_profile->add_field_trial();
+ field_trial->set_name_id(it->name);
+ field_trial->set_group_id(it->group);
+ }
+}
+
+// Round a timestamp measured in seconds since epoch to one with a granularity
+// of an hour. This can be used before uploaded potentially sensitive
+// timestamps.
+int64_t RoundSecondsToHour(int64_t time_in_seconds) {
+ return 3600 * (time_in_seconds / 3600);
+}
+
+} // namespace
+
+MetricsLog::MetricsLog(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : closed_(false),
+ log_type_(log_type),
+ client_(client),
+ creation_time_(base::TimeTicks::Now()),
+ local_state_(local_state) {
+ if (IsTestingID(client_id))
+ uma_proto_.set_client_id(0);
+ else
+ uma_proto_.set_client_id(Hash(client_id));
+
+ uma_proto_.set_session_id(session_id);
+
+ const int32_t product = client_->GetProduct();
+ // Only set the product if it differs from the default value.
+ if (product != uma_proto_.product())
+ uma_proto_.set_product(product);
+
+ SystemProfileProto* system_profile = uma_proto_.mutable_system_profile();
+ system_profile->set_build_timestamp(GetBuildTime());
+ system_profile->set_app_version(client_->GetVersionString());
+ system_profile->set_channel(client_->GetChannel());
+#if defined(SYZYASAN)
+ system_profile->set_is_asan_build(true);
+#endif
+}
+
+MetricsLog::~MetricsLog() {
+}
+
+// static
+void MetricsLog::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityIncompleteSessionEndCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationFail, 0);
+ registry->RegisterIntegerPref(
+ prefs::kStabilityBreakpadRegistrationSuccess, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityDebuggerPresent, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityDebuggerNotPresent, 0);
+ registry->RegisterStringPref(prefs::kStabilitySavedSystemProfile,
+ std::string());
+ registry->RegisterStringPref(prefs::kStabilitySavedSystemProfileHash,
+ std::string());
+}
+
+// static
+uint64_t MetricsLog::Hash(const std::string& value) {
+ uint64_t hash = base::HashMetricName(value);
+
+ // The following log is VERY helpful when folks add some named histogram into
+ // the code, but forgot to update the descriptive list of histograms. When
+ // that happens, all we get to see (server side) is a hash of the histogram
+ // name. We can then use this logging to find out what histogram name was
+ // being hashed to a given MD5 value by just running the version of Chromium
+ // in question with --enable-logging.
+ DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
+
+ return hash;
+}
+
+// static
+int64_t MetricsLog::GetBuildTime() {
+ static int64_t integral_build_time = 0;
+ if (!integral_build_time)
+ integral_build_time = static_cast<int64_t>(base::GetBuildTime().ToTimeT());
+ return integral_build_time;
+}
+
+// static
+int64_t MetricsLog::GetCurrentTime() {
+ return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLog::RecordUserAction(const std::string& key) {
+ DCHECK(!closed_);
+
+ UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+ user_action->set_name_hash(Hash(key));
+ user_action->set_time(GetCurrentTime());
+}
+
+void MetricsLog::RecordHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot) {
+ DCHECK(!closed_);
+ EncodeHistogramDelta(histogram_name, snapshot, &uma_proto_);
+}
+
+void MetricsLog::RecordStabilityMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime) {
+ DCHECK(!closed_);
+ DCHECK(HasEnvironment());
+ DCHECK(!HasStabilityMetrics());
+
+ PrefService* pref = local_state_;
+ DCHECK(pref);
+
+ // Get stability attributes out of Local State, zeroing out stored values.
+ // NOTE: This could lead to some data loss if this report isn't successfully
+ // sent, but that's true for all the metrics.
+
+ WriteRequiredStabilityAttributes(pref);
+
+ // Record recent delta for critical stability metrics. We can't wait for a
+ // restart to gather these, as that delay biases our observation away from
+ // users that run happily for a looooong time. We send increments with each
+ // uma log upload, just as we send histogram data.
+ WriteRealtimeStabilityAttributes(pref, incremental_uptime, uptime);
+
+ SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+ for (size_t i = 0; i < metrics_providers.size(); ++i) {
+ if (log_type() == INITIAL_STABILITY_LOG)
+ metrics_providers[i]->ProvideInitialStabilityMetrics(system_profile);
+ metrics_providers[i]->ProvideStabilityMetrics(system_profile);
+ }
+
+ // Omit some stats unless this is the initial stability log.
+ if (log_type() != INITIAL_STABILITY_LOG)
+ return;
+
+ int incomplete_shutdown_count =
+ pref->GetInteger(prefs::kStabilityIncompleteSessionEndCount);
+ pref->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0);
+ int breakpad_registration_success_count =
+ pref->GetInteger(prefs::kStabilityBreakpadRegistrationSuccess);
+ pref->SetInteger(prefs::kStabilityBreakpadRegistrationSuccess, 0);
+ int breakpad_registration_failure_count =
+ pref->GetInteger(prefs::kStabilityBreakpadRegistrationFail);
+ pref->SetInteger(prefs::kStabilityBreakpadRegistrationFail, 0);
+ int debugger_present_count =
+ pref->GetInteger(prefs::kStabilityDebuggerPresent);
+ pref->SetInteger(prefs::kStabilityDebuggerPresent, 0);
+ int debugger_not_present_count =
+ pref->GetInteger(prefs::kStabilityDebuggerNotPresent);
+ pref->SetInteger(prefs::kStabilityDebuggerNotPresent, 0);
+
+ // TODO(jar): The following are all optional, so we *could* optimize them for
+ // values of zero (and not include them).
+ SystemProfileProto::Stability* stability =
+ system_profile->mutable_stability();
+ stability->set_incomplete_shutdown_count(incomplete_shutdown_count);
+ stability->set_breakpad_registration_success_count(
+ breakpad_registration_success_count);
+ stability->set_breakpad_registration_failure_count(
+ breakpad_registration_failure_count);
+ stability->set_debugger_present_count(debugger_present_count);
+ stability->set_debugger_not_present_count(debugger_not_present_count);
+}
+
+void MetricsLog::RecordGeneralMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers) {
+ for (size_t i = 0; i < metrics_providers.size(); ++i)
+ metrics_providers[i]->ProvideGeneralMetrics(uma_proto());
+}
+
+void MetricsLog::GetFieldTrialIds(
+ std::vector<ActiveGroupId>* field_trial_ids) const {
+ variations::GetFieldTrialActiveGroupIds(field_trial_ids);
+}
+
+bool MetricsLog::HasEnvironment() const {
+ return uma_proto()->system_profile().has_uma_enabled_date();
+}
+
+void MetricsLog::WriteMetricsEnableDefault(
+ MetricsServiceClient::EnableMetricsDefault metrics_default,
+ SystemProfileProto* system_profile) {
+ if (client_->IsReportingPolicyManaged()) {
+ // If it's managed, then it must be reporting, otherwise we wouldn't be
+ // sending metrics.
+ system_profile->set_uma_default_state(
+ SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED);
+ return;
+ }
+
+ switch (metrics_default) {
+ case MetricsServiceClient::DEFAULT_UNKNOWN:
+ // Don't set the field if it's unknown.
+ break;
+ case MetricsServiceClient::OPT_IN:
+ system_profile->set_uma_default_state(
+ SystemProfileProto_UmaDefaultState_OPT_IN);
+ break;
+ case MetricsServiceClient::OPT_OUT:
+ system_profile->set_uma_default_state(
+ SystemProfileProto_UmaDefaultState_OPT_OUT);
+ }
+}
+
+bool MetricsLog::HasStabilityMetrics() const {
+ return uma_proto()->system_profile().stability().has_launch_count();
+}
+
+// The server refuses data that doesn't have certain values. crashcount and
+// launchcount are currently "required" in the "stability" group.
+// TODO(isherman): Stop writing these attributes specially once the migration to
+// protobufs is complete.
+void MetricsLog::WriteRequiredStabilityAttributes(PrefService* pref) {
+ int launch_count = pref->GetInteger(prefs::kStabilityLaunchCount);
+ pref->SetInteger(prefs::kStabilityLaunchCount, 0);
+ int crash_count = pref->GetInteger(prefs::kStabilityCrashCount);
+ pref->SetInteger(prefs::kStabilityCrashCount, 0);
+
+ SystemProfileProto::Stability* stability =
+ uma_proto()->mutable_system_profile()->mutable_stability();
+ stability->set_launch_count(launch_count);
+ stability->set_crash_count(crash_count);
+}
+
+void MetricsLog::WriteRealtimeStabilityAttributes(
+ PrefService* pref,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime) {
+ // Update the stats which are critical for real-time stability monitoring.
+ // Since these are "optional," only list ones that are non-zero, as the counts
+ // are aggregated (summed) server side.
+
+ SystemProfileProto::Stability* stability =
+ uma_proto()->mutable_system_profile()->mutable_stability();
+
+ const uint64_t incremental_uptime_sec = incremental_uptime.InSeconds();
+ if (incremental_uptime_sec)
+ stability->set_incremental_uptime_sec(incremental_uptime_sec);
+ const uint64_t uptime_sec = uptime.InSeconds();
+ if (uptime_sec)
+ stability->set_uptime_sec(uptime_sec);
+}
+
+void MetricsLog::RecordEnvironment(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ const std::vector<variations::ActiveGroupId>& synthetic_trials,
+ int64_t install_date,
+ int64_t metrics_reporting_enabled_date) {
+ DCHECK(!HasEnvironment());
+
+ SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+
+ WriteMetricsEnableDefault(client_->GetDefaultOptIn(), system_profile);
+
+ std::string brand_code;
+ if (client_->GetBrand(&brand_code))
+ system_profile->set_brand_code(brand_code);
+
+ // Reduce granularity of the enabled_date field to nearest hour.
+ system_profile->set_uma_enabled_date(
+ RoundSecondsToHour(metrics_reporting_enabled_date));
+
+ // Reduce granularity of the install_date field to nearest hour.
+ system_profile->set_install_date(RoundSecondsToHour(install_date));
+
+ system_profile->set_application_locale(client_->GetApplicationLocale());
+
+ SystemProfileProto::Hardware* hardware = system_profile->mutable_hardware();
+
+ // HardwareModelName() will return an empty string on platforms where it's
+ // not implemented or if an error occured.
+ hardware->set_hardware_class(base::SysInfo::HardwareModelName());
+
+ hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture());
+ hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB());
+#if defined(OS_WIN)
+ hardware->set_dll_base(reinterpret_cast<uint64_t>(CURRENT_MODULE()));
+#endif
+
+#if defined(OVERRIDE_OS_NAME_TO_BLIMP)
+ os->set_name("Blimp");
+#else
+ SystemProfileProto::OS* os = system_profile->mutable_os();
+ std::string os_name = base::SysInfo::OperatingSystemName();
+ os->set_name(os_name);
+#endif
+
+ os->set_version(base::SysInfo::OperatingSystemVersion());
+#if defined(OS_ANDROID)
+ os->set_fingerprint(
+ base::android::BuildInfo::GetInstance()->android_build_fp());
+#endif
+
+ base::CPU cpu_info;
+ SystemProfileProto::Hardware::CPU* cpu = hardware->mutable_cpu();
+ cpu->set_vendor_name(cpu_info.vendor_name());
+ cpu->set_signature(cpu_info.signature());
+ cpu->set_num_cores(base::SysInfo::NumberOfProcessors());
+
+ std::vector<ActiveGroupId> field_trial_ids;
+ GetFieldTrialIds(&field_trial_ids);
+ WriteFieldTrials(field_trial_ids, system_profile);
+ WriteFieldTrials(synthetic_trials, system_profile);
+
+ for (size_t i = 0; i < metrics_providers.size(); ++i)
+ metrics_providers[i]->ProvideSystemProfileMetrics(system_profile);
+
+ std::string serialied_system_profile;
+ std::string base64_system_profile;
+ if (system_profile->SerializeToString(&serialied_system_profile)) {
+ base::Base64Encode(serialied_system_profile, &base64_system_profile);
+ PrefService* local_state = local_state_;
+ local_state->SetString(prefs::kStabilitySavedSystemProfile,
+ base64_system_profile);
+ local_state->SetString(prefs::kStabilitySavedSystemProfileHash,
+ ComputeSHA1(serialied_system_profile));
+ }
+}
+
+bool MetricsLog::LoadSavedEnvironmentFromPrefs() {
+ PrefService* local_state = local_state_;
+ const std::string base64_system_profile =
+ local_state->GetString(prefs::kStabilitySavedSystemProfile);
+ if (base64_system_profile.empty())
+ return false;
+
+ const std::string system_profile_hash =
+ local_state->GetString(prefs::kStabilitySavedSystemProfileHash);
+ local_state->ClearPref(prefs::kStabilitySavedSystemProfile);
+ local_state->ClearPref(prefs::kStabilitySavedSystemProfileHash);
+
+ SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+ std::string serialied_system_profile;
+ return base::Base64Decode(base64_system_profile, &serialied_system_profile) &&
+ ComputeSHA1(serialied_system_profile) == system_profile_hash &&
+ system_profile->ParseFromString(serialied_system_profile);
+}
+
+void MetricsLog::CloseLog() {
+ DCHECK(!closed_);
+ closed_ = true;
+}
+
+void MetricsLog::GetEncodedLog(std::string* encoded_log) {
+ DCHECK(closed_);
+ uma_proto_.SerializeToString(encoded_log);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log.h b/chromium/components/metrics/metrics_log.h
new file mode 100644
index 00000000000..e2324f8ac36
--- /dev/null
+++ b/chromium/components/metrics/metrics_log.h
@@ -0,0 +1,209 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService. This is the unit of data that is sent to the server.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_H_
+#define COMPONENTS_METRICS_METRICS_LOG_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class DictionaryValue;
+class HistogramSamples;
+}
+
+namespace content {
+struct WebPluginInfo;
+}
+
+namespace variations {
+struct ActiveGroupId;
+}
+
+namespace metrics {
+
+class MetricsProvider;
+class MetricsServiceClient;
+
+class MetricsLog {
+ public:
+ enum LogType {
+ INITIAL_STABILITY_LOG, // The initial log containing stability stats.
+ ONGOING_LOG, // Subsequent logs in a session.
+ };
+
+ // Creates a new metrics log of the specified type.
+ // |client_id| is the identifier for this profile on this installation
+ // |session_id| is an integer that's incremented on each application launch
+ // |client| is used to interact with the embedder.
+ // |local_state| is the PrefService that this instance should use.
+ // Note: |this| instance does not take ownership of the |client|, but rather
+ // stores a weak pointer to it. The caller should ensure that the |client| is
+ // valid for the lifetime of this class.
+ MetricsLog(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ MetricsServiceClient* client,
+ PrefService* local_state);
+ virtual ~MetricsLog();
+
+ // Registers local state prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // Computes the MD5 hash of the given string, and returns the first 8 bytes of
+ // the hash.
+ static uint64_t Hash(const std::string& value);
+
+ // Get the GMT buildtime for the current binary, expressed in seconds since
+ // January 1, 1970 GMT.
+ // The value is used to identify when a new build is run, so that previous
+ // reliability stats, from other builds, can be abandoned.
+ static int64_t GetBuildTime();
+
+ // Convenience function to return the current time at a resolution in seconds.
+ // This wraps base::TimeTicks, and hence provides an abstract time that is
+ // always incrementing for use in measuring time durations.
+ static int64_t GetCurrentTime();
+
+ // Records a user-initiated action.
+ void RecordUserAction(const std::string& key);
+
+ // Record any changes in a given histogram for transmission.
+ void RecordHistogramDelta(const std::string& histogram_name,
+ const base::HistogramSamples& snapshot);
+
+ // Records the current operating environment, including metrics provided by
+ // the specified set of |metrics_providers|. Takes the list of installed
+ // plugins, Google Update statistics, and synthetic trial IDs as parameters
+ // because those can't be obtained synchronously from the UI thread.
+ // A synthetic trial is one that is set up dynamically by code in Chrome. For
+ // example, a pref may be mapped to a synthetic trial such that the group
+ // is determined by the pref value.
+ void RecordEnvironment(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ const std::vector<variations::ActiveGroupId>& synthetic_trials,
+ int64_t install_date,
+ int64_t metrics_reporting_enabled_date);
+
+ // Loads the environment proto that was saved by the last RecordEnvironment()
+ // call from prefs and clears the pref value. Returns true on success or false
+ // if there was no saved environment in prefs or it could not be decoded.
+ bool LoadSavedEnvironmentFromPrefs();
+
+ // Writes application stability metrics, including stability metrics provided
+ // by the specified set of |metrics_providers|. The system profile portion of
+ // the log must have already been filled in by a call to RecordEnvironment()
+ // or LoadSavedEnvironmentFromPrefs().
+ // NOTE: Has the side-effect of clearing the stability prefs..
+ //
+ // If this log is of type INITIAL_STABILITY_LOG, records additional info such
+ // as number of incomplete shutdowns as well as extra breakpad and debugger
+ // stats.
+ void RecordStabilityMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime);
+
+ // Records general metrics based on the specified |metrics_providers|.
+ void RecordGeneralMetrics(
+ const std::vector<MetricsProvider*>& metrics_providers);
+
+ // Stop writing to this record and generate the encoded representation.
+ // None of the Record* methods can be called after this is called.
+ void CloseLog();
+
+ // Fills |encoded_log| with the serialized protobuf representation of the
+ // record. Must only be called after CloseLog() has been called.
+ void GetEncodedLog(std::string* encoded_log);
+
+ const base::TimeTicks& creation_time() const {
+ return creation_time_;
+ }
+
+ int num_events() const {
+ return uma_proto_.omnibox_event_size() +
+ uma_proto_.user_action_event_size();
+ }
+
+ LogType log_type() const { return log_type_; }
+
+ protected:
+ // Exposed for the sake of mocking/accessing in test code.
+
+ // Fills |field_trial_ids| with the list of initialized field trials name and
+ // group ids.
+ virtual void GetFieldTrialIds(
+ std::vector<variations::ActiveGroupId>* field_trial_ids) const;
+
+ ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+
+ // Exposed to allow subclass to access to export the uma_proto. Can be used
+ // by external components to export logs to Chrome.
+ const ChromeUserMetricsExtension* uma_proto() const {
+ return &uma_proto_;
+ }
+
+ private:
+ // Returns true if the environment has already been filled in by a call to
+ // RecordEnvironment() or LoadSavedEnvironmentFromPrefs().
+ bool HasEnvironment() const;
+
+ // Write the default state of the enable metrics checkbox.
+ void WriteMetricsEnableDefault(
+ MetricsServiceClient::EnableMetricsDefault metrics_default,
+ SystemProfileProto* system_profile);
+
+ // Returns true if the stability metrics have already been filled in by a
+ // call to RecordStabilityMetrics().
+ bool HasStabilityMetrics() const;
+
+ // Within the stability group, write required attributes.
+ void WriteRequiredStabilityAttributes(PrefService* pref);
+
+ // Within the stability group, write attributes that need to be updated asap
+ // and can't be delayed until the user decides to restart chromium.
+ // Delaying these stats would bias metrics away from happy long lived
+ // chromium processes (ones that don't crash, and keep on running).
+ void WriteRealtimeStabilityAttributes(PrefService* pref,
+ base::TimeDelta incremental_uptime,
+ base::TimeDelta uptime);
+
+ // closed_ is true when record has been packed up for sending, and should
+ // no longer be written to. It is only used for sanity checking.
+ bool closed_;
+
+ // The type of the log, i.e. initial or ongoing.
+ const LogType log_type_;
+
+ // Stores the protocol buffer representation for this log.
+ ChromeUserMetricsExtension uma_proto_;
+
+ // Used to interact with the embedder. Weak pointer; must outlive |this|
+ // instance.
+ MetricsServiceClient* const client_;
+
+ // The time when the current log was created.
+ const base::TimeTicks creation_time_;
+
+ PrefService* local_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_LOG_H_
diff --git a/chromium/components/metrics/metrics_log_manager.cc b/chromium/components/metrics/metrics_log_manager.cc
new file mode 100644
index 00000000000..661dacfa55f
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_manager.cc
@@ -0,0 +1,130 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_manager.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+
+namespace {
+
+// The number of "initial" logs to save, and hope to send during a future Chrome
+// session. Initial logs contain crash stats, and are pretty small.
+const size_t kInitialLogsPersistLimit = 20;
+
+// The number of ongoing logs to save persistently, and hope to
+// send during a this or future sessions. Note that each log may be pretty
+// large, as presumably the related "initial" log wasn't sent (probably nothing
+// was, as the user was probably off-line). As a result, the log probably kept
+// accumulating while the "initial" log was stalled, and couldn't be sent. As a
+// result, we don't want to save too many of these mega-logs.
+// A "standard shutdown" will create a small log, including just the data that
+// was not yet been transmitted, and that is normal (to have exactly one
+// ongoing_log_ at startup).
+const size_t kOngoingLogsPersistLimit = 8;
+
+// The number of bytes each of initial and ongoing logs that must be stored.
+// This ensures that a reasonable amount of history will be stored even if there
+// is a long series of very small logs.
+const size_t kStorageByteLimitPerLogType = 300000;
+
+} // namespace
+
+MetricsLogManager::MetricsLogManager(PrefService* local_state,
+ size_t max_ongoing_log_size)
+ : unsent_logs_loaded_(false),
+ initial_log_queue_(local_state,
+ prefs::kMetricsInitialLogs,
+ kInitialLogsPersistLimit,
+ kStorageByteLimitPerLogType,
+ 0),
+ ongoing_log_queue_(local_state,
+ prefs::kMetricsOngoingLogs,
+ kOngoingLogsPersistLimit,
+ kStorageByteLimitPerLogType,
+ max_ongoing_log_size) {}
+
+MetricsLogManager::~MetricsLogManager() {}
+
+void MetricsLogManager::BeginLoggingWithLog(scoped_ptr<MetricsLog> log) {
+ DCHECK(!current_log_);
+ current_log_ = std::move(log);
+}
+
+void MetricsLogManager::FinishCurrentLog() {
+ DCHECK(current_log_.get());
+ current_log_->CloseLog();
+ std::string log_data;
+ current_log_->GetEncodedLog(&log_data);
+ if (!log_data.empty())
+ StoreLog(log_data, current_log_->log_type());
+ current_log_.reset();
+}
+
+void MetricsLogManager::StageNextLogForUpload() {
+ DCHECK(!has_staged_log());
+ if (!initial_log_queue_.empty())
+ initial_log_queue_.StageLog();
+ else
+ ongoing_log_queue_.StageLog();
+}
+
+void MetricsLogManager::DiscardStagedLog() {
+ DCHECK(has_staged_log());
+ if (initial_log_queue_.has_staged_log())
+ initial_log_queue_.DiscardStagedLog();
+ else
+ ongoing_log_queue_.DiscardStagedLog();
+ DCHECK(!has_staged_log());
+}
+
+void MetricsLogManager::DiscardCurrentLog() {
+ current_log_->CloseLog();
+ current_log_.reset();
+}
+
+void MetricsLogManager::PauseCurrentLog() {
+ DCHECK(!paused_log_.get());
+ paused_log_.reset(current_log_.release());
+}
+
+void MetricsLogManager::ResumePausedLog() {
+ DCHECK(!current_log_.get());
+ current_log_.reset(paused_log_.release());
+}
+
+void MetricsLogManager::StoreLog(const std::string& log_data,
+ MetricsLog::LogType log_type) {
+ switch (log_type) {
+ case MetricsLog::INITIAL_STABILITY_LOG:
+ initial_log_queue_.StoreLog(log_data);
+ break;
+ case MetricsLog::ONGOING_LOG:
+ ongoing_log_queue_.StoreLog(log_data);
+ break;
+ }
+}
+
+void MetricsLogManager::PersistUnsentLogs() {
+ DCHECK(unsent_logs_loaded_);
+ if (!unsent_logs_loaded_)
+ return;
+
+ initial_log_queue_.SerializeLogs();
+ ongoing_log_queue_.SerializeLogs();
+}
+
+void MetricsLogManager::LoadPersistedUnsentLogs() {
+ initial_log_queue_.DeserializeLogs();
+ ongoing_log_queue_.DeserializeLogs();
+ unsent_logs_loaded_ = true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_manager.h b/chromium/components/metrics/metrics_log_manager.h
new file mode 100644
index 00000000000..c4f2082bfdf
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_manager.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
+#define COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/persisted_logs.h"
+
+namespace metrics {
+
+// Manages all the log objects used by a MetricsService implementation. Keeps
+// track of both an in progress log and a log that is staged for uploading as
+// text, as well as saving logs to, and loading logs from, persistent storage.
+class MetricsLogManager {
+ public:
+ // The metrics log manager will persist it's unsent logs by storing them in
+ // |local_state|, and will not persist ongoing logs over
+ // |max_ongoing_log_size|.
+ MetricsLogManager(PrefService* local_state, size_t max_ongoing_log_size);
+ ~MetricsLogManager();
+
+ // Makes |log| the current_log. This should only be called if there is not a
+ // current log.
+ void BeginLoggingWithLog(scoped_ptr<MetricsLog> log);
+
+ // Returns the in-progress log.
+ MetricsLog* current_log() { return current_log_.get(); }
+
+ // Closes current_log(), compresses it, and stores the compressed log for
+ // later, leaving current_log() NULL.
+ void FinishCurrentLog();
+
+ // Returns true if there are any logs waiting to be uploaded.
+ bool has_unsent_logs() const {
+ return initial_log_queue_.size() || ongoing_log_queue_.size();
+ }
+
+ // Populates staged_log_text() with the next stored log to send.
+ // Should only be called if has_unsent_logs() is true.
+ void StageNextLogForUpload();
+
+ // Returns true if there is a log that needs to be, or is being, uploaded.
+ bool has_staged_log() const {
+ return initial_log_queue_.has_staged_log() ||
+ ongoing_log_queue_.has_staged_log();
+ }
+
+ // The text of the staged log, as a serialized protobuf.
+ // Will trigger a DCHECK if there is no staged log.
+ const std::string& staged_log() const {
+ return initial_log_queue_.has_staged_log() ?
+ initial_log_queue_.staged_log() : ongoing_log_queue_.staged_log();
+ }
+
+ // The SHA1 hash of the staged log.
+ // Will trigger a DCHECK if there is no staged log.
+ const std::string& staged_log_hash() const {
+ return initial_log_queue_.has_staged_log() ?
+ initial_log_queue_.staged_log_hash() :
+ ongoing_log_queue_.staged_log_hash();
+ }
+
+ // Discards the staged log.
+ void DiscardStagedLog();
+
+ // Closes and discards |current_log|.
+ void DiscardCurrentLog();
+
+ // Sets current_log to NULL, but saves the current log for future use with
+ // ResumePausedLog(). Only one log may be paused at a time.
+ // TODO(stuartmorgan): Pause/resume support is really a workaround for a
+ // design issue in initial log writing; that should be fixed, and pause/resume
+ // removed.
+ void PauseCurrentLog();
+
+ // Restores the previously paused log (if any) to current_log().
+ // This should only be called if there is not a current log.
+ void ResumePausedLog();
+
+ // Saves any unsent logs to persistent storage.
+ void PersistUnsentLogs();
+
+ // Loads any unsent logs from persistent storage.
+ void LoadPersistedUnsentLogs();
+
+ // Saves |log_data| as the given type. Public to allow to push log created by
+ // external components.
+ void StoreLog(const std::string& log_data, MetricsLog::LogType log_type);
+
+ private:
+ // Tracks whether unsent logs (if any) have been loaded from the serializer.
+ bool unsent_logs_loaded_;
+
+ // The log that we are still appending to.
+ scoped_ptr<MetricsLog> current_log_;
+
+ // A paused, previously-current log.
+ scoped_ptr<MetricsLog> paused_log_;
+
+ // Logs that have not yet been sent.
+ PersistedLogs initial_log_queue_;
+ PersistedLogs ongoing_log_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogManager);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
diff --git a/chromium/components/metrics/metrics_log_manager_unittest.cc b/chromium/components/metrics/metrics_log_manager_unittest.cc
new file mode 100644
index 00000000000..c35d10043b7
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_manager_unittest.cc
@@ -0,0 +1,306 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_manager.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+// Dummy serializer that just stores logs in memory.
+class TestLogPrefService : public TestingPrefServiceSimple {
+ public:
+ TestLogPrefService() {
+ registry()->RegisterListPref(prefs::kMetricsInitialLogs);
+ registry()->RegisterListPref(prefs::kMetricsOngoingLogs);
+ }
+
+ // Returns the number of logs of the given type.
+ size_t TypeCount(MetricsLog::LogType log_type) {
+ int list_length = 0;
+ if (log_type == MetricsLog::INITIAL_STABILITY_LOG)
+ list_length = GetList(prefs::kMetricsInitialLogs)->GetSize();
+ else
+ list_length = GetList(prefs::kMetricsOngoingLogs)->GetSize();
+ return list_length / 2;
+ }
+};
+
+} // namespace
+
+TEST(MetricsLogManagerTest, StandardFlow) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+
+ // Make sure a new manager has a clean slate.
+ EXPECT_EQ(NULL, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+
+ // Check that the normal flow works.
+ MetricsLog* initial_log = new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service);
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(initial_log));
+ EXPECT_EQ(initial_log, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+
+ log_manager.FinishCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+ EXPECT_FALSE(log_manager.has_staged_log());
+
+ MetricsLog* second_log =
+ new MetricsLog("id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service);
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(second_log));
+ EXPECT_EQ(second_log, log_manager.current_log());
+
+ log_manager.StageNextLogForUpload();
+ EXPECT_TRUE(log_manager.has_staged_log());
+ EXPECT_FALSE(log_manager.staged_log().empty());
+
+ log_manager.DiscardStagedLog();
+ EXPECT_EQ(second_log, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+}
+
+TEST(MetricsLogManagerTest, AbandonedLog) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+
+ MetricsLog* dummy_log = new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service);
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(dummy_log));
+ EXPECT_EQ(dummy_log, log_manager.current_log());
+
+ log_manager.DiscardCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+ EXPECT_FALSE(log_manager.has_staged_log());
+}
+
+TEST(MetricsLogManagerTest, InterjectedLog) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+
+ MetricsLog* ongoing_log =
+ new MetricsLog("id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service);
+ MetricsLog* temp_log = new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service);
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(ongoing_log));
+ EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+ log_manager.PauseCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(temp_log));
+ EXPECT_EQ(temp_log, log_manager.current_log());
+ log_manager.FinishCurrentLog();
+ EXPECT_EQ(NULL, log_manager.current_log());
+
+ log_manager.ResumePausedLog();
+ EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+ EXPECT_FALSE(log_manager.has_staged_log());
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+}
+
+TEST(MetricsLogManagerTest, InterjectedLogPreservesType) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.PauseCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.ResumePausedLog();
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+
+ // Verify that the remaining log (which is the original ongoing log) still
+ // has the right type.
+ log_manager.FinishCurrentLog();
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST(MetricsLogManagerTest, StoreAndLoad) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ // Set up some in-progress logging in a scoped log manager simulating the
+ // leadup to quitting, then persist as would be done on quit.
+ {
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ // Simulate a log having already been unsent from a previous session.
+ {
+ std::string log("proto");
+ PersistedLogs ongoing_logs(&pref_service, prefs::kMetricsOngoingLogs, 1,
+ 1, 0);
+ ongoing_logs.StoreLog(log);
+ ongoing_logs.SerializeLogs();
+ }
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+ log_manager.LoadPersistedUnsentLogs();
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.StageNextLogForUpload();
+ log_manager.FinishCurrentLog();
+
+ // Nothing should be written out until PersistUnsentLogs is called.
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+
+ // Now simulate the relaunch, ensure that the log manager restores
+ // everything correctly, and verify that once the are handled they are not
+ // re-persisted.
+ {
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ // The initial log should be sent first; update the persisted storage to
+ // verify.
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+
+ // Handle the first ongoing log.
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ EXPECT_TRUE(log_manager.has_unsent_logs());
+
+ // Handle the last log.
+ log_manager.StageNextLogForUpload();
+ log_manager.DiscardStagedLog();
+ EXPECT_FALSE(log_manager.has_unsent_logs());
+
+ // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been
+ // called again.
+ EXPECT_EQ(2U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ // Persist, and make sure nothing is left.
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+}
+
+TEST(MetricsLogManagerTest, StoreStagedLogTypes) {
+ TestMetricsServiceClient client;
+
+ // Ensure that types are preserved when storing staged logs.
+ {
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.StageNextLogForUpload();
+ log_manager.PersistUnsentLogs();
+
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+
+ {
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.StageNextLogForUpload();
+ log_manager.PersistUnsentLogs();
+
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+}
+
+TEST(MetricsLogManagerTest, LargeLogDiscarding) {
+ TestMetricsServiceClient client;
+ TestLogPrefService pref_service;
+ // Set the size threshold very low, to verify that it's honored.
+ MetricsLogManager log_manager(&pref_service, 1);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+
+ // Only the ongoing log should be written out, due to the threshold.
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST(MetricsLogManagerTest, DiscardOrder) {
+ // Ensure that the correct log is discarded if new logs are pushed while
+ // a log is staged.
+ TestMetricsServiceClient client;
+ {
+ TestLogPrefService pref_service;
+ MetricsLogManager log_manager(&pref_service, 0);
+ log_manager.LoadPersistedUnsentLogs();
+
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
+ log_manager.FinishCurrentLog();
+ log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
+ "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
+ log_manager.StageNextLogForUpload();
+ log_manager.FinishCurrentLog();
+ log_manager.DiscardStagedLog();
+
+ log_manager.PersistUnsentLogs();
+ EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+ EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_unittest.cc b/chromium/components/metrics/metrics_log_unittest.cc
new file mode 100644
index 00000000000..8eea886855a
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_unittest.cc
@@ -0,0 +1,465 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/active_field_trials.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kClientId[] = "bogus client ID";
+const int64_t kInstallDate = 1373051956;
+const int64_t kInstallDateExpected = 1373050800; // Computed from kInstallDate.
+const int64_t kEnabledDate = 1373001211;
+const int64_t kEnabledDateExpected = 1373000400; // Computed from kEnabledDate.
+const int kSessionId = 127;
+const variations::ActiveGroupId kFieldTrialIds[] = {
+ {37, 43},
+ {13, 47},
+ {23, 17}
+};
+const variations::ActiveGroupId kSyntheticTrials[] = {
+ {55, 15},
+ {66, 16}
+};
+
+class TestMetricsLog : public MetricsLog {
+ public:
+ TestMetricsLog(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ MetricsServiceClient* client,
+ TestingPrefServiceSimple* prefs)
+ : MetricsLog(client_id, session_id, log_type, client, prefs),
+ prefs_(prefs) {
+ InitPrefs();
+ }
+
+ ~TestMetricsLog() override {}
+
+ const ChromeUserMetricsExtension& uma_proto() const {
+ return *MetricsLog::uma_proto();
+ }
+
+ const SystemProfileProto& system_profile() const {
+ return uma_proto().system_profile();
+ }
+
+ private:
+ void InitPrefs() {
+ prefs_->SetString(prefs::kMetricsReportingEnabledTimestamp,
+ base::Int64ToString(kEnabledDate));
+ }
+
+ void GetFieldTrialIds(
+ std::vector<variations::ActiveGroupId>* field_trial_ids) const override {
+ ASSERT_TRUE(field_trial_ids->empty());
+
+ for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) {
+ field_trial_ids->push_back(kFieldTrialIds[i]);
+ }
+ }
+
+ // Weak pointer to the PrefsService used by this log.
+ TestingPrefServiceSimple* prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsLog);
+};
+
+} // namespace
+
+class MetricsLogTest : public testing::Test {
+ public:
+ MetricsLogTest() {
+ MetricsLog::RegisterPrefs(prefs_.registry());
+ MetricsStateManager::RegisterPrefs(prefs_.registry());
+ }
+
+ ~MetricsLogTest() override {}
+
+ protected:
+ // Check that the values in |system_values| correspond to the test data
+ // defined at the top of this file.
+ void CheckSystemProfile(const SystemProfileProto& system_profile) {
+ EXPECT_EQ(kInstallDateExpected, system_profile.install_date());
+ EXPECT_EQ(kEnabledDateExpected, system_profile.uma_enabled_date());
+
+ ASSERT_EQ(arraysize(kFieldTrialIds) + arraysize(kSyntheticTrials),
+ static_cast<size_t>(system_profile.field_trial_size()));
+ for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) {
+ const SystemProfileProto::FieldTrial& field_trial =
+ system_profile.field_trial(i);
+ EXPECT_EQ(kFieldTrialIds[i].name, field_trial.name_id());
+ EXPECT_EQ(kFieldTrialIds[i].group, field_trial.group_id());
+ }
+ // Verify the right data is present for the synthetic trials.
+ for (size_t i = 0; i < arraysize(kSyntheticTrials); ++i) {
+ const SystemProfileProto::FieldTrial& field_trial =
+ system_profile.field_trial(i + arraysize(kFieldTrialIds));
+ EXPECT_EQ(kSyntheticTrials[i].name, field_trial.name_id());
+ EXPECT_EQ(kSyntheticTrials[i].group, field_trial.group_id());
+ }
+
+ EXPECT_EQ(TestMetricsServiceClient::kBrandForTesting,
+ system_profile.brand_code());
+
+ const SystemProfileProto::Hardware& hardware =
+ system_profile.hardware();
+
+ EXPECT_TRUE(hardware.has_cpu());
+ EXPECT_TRUE(hardware.cpu().has_vendor_name());
+ EXPECT_TRUE(hardware.cpu().has_signature());
+ EXPECT_TRUE(hardware.cpu().has_num_cores());
+
+ // TODO(isherman): Verify other data written into the protobuf as a result
+ // of this call.
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogTest);
+};
+
+TEST_F(MetricsLogTest, LogType) {
+ TestMetricsServiceClient client;
+ TestingPrefServiceSimple prefs;
+
+ MetricsLog log1("id", 0, MetricsLog::ONGOING_LOG, &client, &prefs);
+ EXPECT_EQ(MetricsLog::ONGOING_LOG, log1.log_type());
+
+ MetricsLog log2("id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &prefs);
+ EXPECT_EQ(MetricsLog::INITIAL_STABILITY_LOG, log2.log_type());
+}
+
+TEST_F(MetricsLogTest, EmptyRecord) {
+ TestMetricsServiceClient client;
+ client.set_version_string("bogus version");
+ TestingPrefServiceSimple prefs;
+ MetricsLog log("totally bogus client ID", 137, MetricsLog::ONGOING_LOG,
+ &client, &prefs);
+ log.CloseLog();
+
+ std::string encoded;
+ log.GetEncodedLog(&encoded);
+
+ // A couple of fields are hard to mock, so these will be copied over directly
+ // for the expected output.
+ ChromeUserMetricsExtension parsed;
+ ASSERT_TRUE(parsed.ParseFromString(encoded));
+
+ ChromeUserMetricsExtension expected;
+ expected.set_client_id(5217101509553811875); // Hashed bogus client ID
+ expected.set_session_id(137);
+ expected.mutable_system_profile()->set_build_timestamp(
+ parsed.system_profile().build_timestamp());
+ expected.mutable_system_profile()->set_app_version("bogus version");
+ expected.mutable_system_profile()->set_channel(client.GetChannel());
+
+ EXPECT_EQ(expected.SerializeAsString(), encoded);
+}
+
+TEST_F(MetricsLogTest, HistogramBucketFields) {
+ // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+ base::BucketRanges ranges(8);
+ ranges.set_range(0, 1);
+ ranges.set_range(1, 5);
+ ranges.set_range(2, 7);
+ ranges.set_range(3, 8);
+ ranges.set_range(4, 9);
+ ranges.set_range(5, 10);
+ ranges.set_range(6, 11);
+ ranges.set_range(7, 12);
+
+ base::SampleVector samples(1, &ranges);
+ samples.Accumulate(3, 1); // Bucket 1-5.
+ samples.Accumulate(6, 1); // Bucket 5-7.
+ samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped)
+ samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped)
+ samples.Accumulate(11, 1); // Bucket 11-12.
+
+ TestMetricsServiceClient client;
+ TestingPrefServiceSimple prefs;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ log.RecordHistogramDelta("Test", samples);
+
+ const ChromeUserMetricsExtension& uma_proto = log.uma_proto();
+ const HistogramEventProto& histogram_proto =
+ uma_proto.histogram_event(uma_proto.histogram_event_size() - 1);
+
+ // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+ // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+ ASSERT_EQ(5, histogram_proto.bucket_size());
+
+ // 1-5 becomes 1-/ (max is same as next min).
+ EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+ EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+ // 5-7 stays 5-7 (no optimization possible).
+ EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+ EXPECT_EQ(5, histogram_proto.bucket(1).min());
+ EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+ // 8-9 becomes /-9 (min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+ EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+ // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+ EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+ EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+ EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+ // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+ EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+ EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+ EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+TEST_F(MetricsLogTest, RecordEnvironment) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+ // Add two synthetic trials.
+ synthetic_trials.push_back(kSyntheticTrials[0]);
+ synthetic_trials.push_back(kSyntheticTrials[1]);
+
+ log.RecordEnvironment(std::vector<MetricsProvider*>(), synthetic_trials,
+ kInstallDate, kEnabledDate);
+ // Check that the system profile on the log has the correct values set.
+ CheckSystemProfile(log.system_profile());
+
+ // Check that the system profile has also been written to prefs.
+ const std::string base64_system_profile =
+ prefs_.GetString(prefs::kStabilitySavedSystemProfile);
+ EXPECT_FALSE(base64_system_profile.empty());
+ std::string serialied_system_profile;
+ EXPECT_TRUE(base::Base64Decode(base64_system_profile,
+ &serialied_system_profile));
+ SystemProfileProto decoded_system_profile;
+ EXPECT_TRUE(decoded_system_profile.ParseFromString(serialied_system_profile));
+ CheckSystemProfile(decoded_system_profile);
+}
+
+TEST_F(MetricsLogTest, LoadSavedEnvironmentFromPrefs) {
+ const char* kSystemProfilePref = prefs::kStabilitySavedSystemProfile;
+ const char* kSystemProfileHashPref =
+ prefs::kStabilitySavedSystemProfileHash;
+
+ TestMetricsServiceClient client;
+
+ // The pref value is empty, so loading it from prefs should fail.
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_FALSE(log.LoadSavedEnvironmentFromPrefs());
+ }
+
+ // Do a RecordEnvironment() call and check whether the pref is recorded.
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(),
+ kInstallDate, kEnabledDate);
+ EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty());
+ EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty());
+ }
+
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_TRUE(log.LoadSavedEnvironmentFromPrefs());
+ // Check some values in the system profile.
+ EXPECT_EQ(kInstallDateExpected, log.system_profile().install_date());
+ EXPECT_EQ(kEnabledDateExpected, log.system_profile().uma_enabled_date());
+ // Ensure that the call cleared the prefs.
+ EXPECT_TRUE(prefs_.GetString(kSystemProfilePref).empty());
+ EXPECT_TRUE(prefs_.GetString(kSystemProfileHashPref).empty());
+ }
+
+ // Ensure that a non-matching hash results in the pref being invalid.
+ {
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ // Call RecordEnvironment() to record the pref again.
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(),
+ kInstallDate, kEnabledDate);
+ }
+
+ {
+ // Set the hash to a bad value.
+ prefs_.SetString(kSystemProfileHashPref, "deadbeef");
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_FALSE(log.LoadSavedEnvironmentFromPrefs());
+ // Ensure that the prefs are cleared, even if the call failed.
+ EXPECT_TRUE(prefs_.GetString(kSystemProfilePref).empty());
+ EXPECT_TRUE(prefs_.GetString(kSystemProfileHashPref).empty());
+ }
+}
+
+TEST_F(MetricsLogTest, RecordEnvironmentEnableDefault) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log_unknown(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+
+ log_unknown.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_FALSE(log_unknown.system_profile().has_uma_default_state());
+
+ client.set_enable_default(MetricsServiceClient::OPT_IN);
+ TestMetricsLog log_opt_in(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+ log_opt_in.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_TRUE(log_opt_in.system_profile().has_uma_default_state());
+ EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_IN,
+ log_opt_in.system_profile().uma_default_state());
+
+ client.set_enable_default(MetricsServiceClient::OPT_OUT);
+ TestMetricsLog log_opt_out(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+ log_opt_out.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_TRUE(log_opt_out.system_profile().has_uma_default_state());
+ EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_OUT,
+ log_opt_out.system_profile().uma_default_state());
+
+ client.set_reporting_is_managed(true);
+ TestMetricsLog log_managed(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+ &client, &prefs_);
+ log_managed.RecordEnvironment(std::vector<MetricsProvider*>(),
+ synthetic_trials, kInstallDate, kEnabledDate);
+ EXPECT_TRUE(log_managed.system_profile().has_uma_default_state());
+ EXPECT_EQ(SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED,
+ log_managed.system_profile().uma_default_state());
+}
+
+TEST_F(MetricsLogTest, InitialLogStabilityMetrics) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(kClientId,
+ kSessionId,
+ MetricsLog::INITIAL_STABILITY_LOG,
+ &client,
+ &prefs_);
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ ScopedVector<MetricsProvider> metrics_providers;
+ metrics_providers.push_back(test_provider);
+ log.RecordEnvironment(metrics_providers.get(),
+ std::vector<variations::ActiveGroupId>(), kInstallDate,
+ kEnabledDate);
+ log.RecordStabilityMetrics(metrics_providers.get(), base::TimeDelta(),
+ base::TimeDelta());
+ const SystemProfileProto_Stability& stability =
+ log.system_profile().stability();
+ // Required metrics:
+ EXPECT_TRUE(stability.has_launch_count());
+ EXPECT_TRUE(stability.has_crash_count());
+ // Initial log metrics:
+ EXPECT_TRUE(stability.has_incomplete_shutdown_count());
+ EXPECT_TRUE(stability.has_breakpad_registration_success_count());
+ EXPECT_TRUE(stability.has_breakpad_registration_failure_count());
+ EXPECT_TRUE(stability.has_debugger_present_count());
+ EXPECT_TRUE(stability.has_debugger_not_present_count());
+
+ // The test provider should have been called upon to provide initial
+ // stability and regular stability metrics.
+ EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsLogTest, OngoingLogStabilityMetrics) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ ScopedVector<MetricsProvider> metrics_providers;
+ metrics_providers.push_back(test_provider);
+ log.RecordEnvironment(metrics_providers.get(),
+ std::vector<variations::ActiveGroupId>(), kInstallDate,
+ kEnabledDate);
+ log.RecordStabilityMetrics(metrics_providers.get(), base::TimeDelta(),
+ base::TimeDelta());
+ const SystemProfileProto_Stability& stability =
+ log.system_profile().stability();
+ // Required metrics:
+ EXPECT_TRUE(stability.has_launch_count());
+ EXPECT_TRUE(stability.has_crash_count());
+ // Initial log metrics:
+ EXPECT_FALSE(stability.has_incomplete_shutdown_count());
+ EXPECT_FALSE(stability.has_breakpad_registration_success_count());
+ EXPECT_FALSE(stability.has_breakpad_registration_failure_count());
+ EXPECT_FALSE(stability.has_debugger_present_count());
+ EXPECT_FALSE(stability.has_debugger_not_present_count());
+
+ // The test provider should have been called upon to provide regular but not
+ // initial stability metrics.
+ EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsLogTest, ChromeChannelWrittenToProtobuf) {
+ TestMetricsServiceClient client;
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ EXPECT_TRUE(log.uma_proto().system_profile().has_channel());
+}
+
+TEST_F(MetricsLogTest, ProductNotSetIfDefault) {
+ TestMetricsServiceClient client;
+ EXPECT_EQ(ChromeUserMetricsExtension::CHROME, client.GetProduct());
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ // Check that the product isn't set, since it's default and also verify the
+ // default value is indeed equal to Chrome.
+ EXPECT_FALSE(log.uma_proto().has_product());
+ EXPECT_EQ(ChromeUserMetricsExtension::CHROME, log.uma_proto().product());
+}
+
+TEST_F(MetricsLogTest, ProductSetIfNotDefault) {
+ const int32_t kTestProduct = 100;
+ EXPECT_NE(ChromeUserMetricsExtension::CHROME, kTestProduct);
+
+ TestMetricsServiceClient client;
+ client.set_product(kTestProduct);
+ TestMetricsLog log(
+ kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client, &prefs_);
+ // Check that the product is set to |kTestProduct|.
+ EXPECT_TRUE(log.uma_proto().has_product());
+ EXPECT_EQ(kTestProduct, log.uma_proto().product());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_uploader.cc b/chromium/components/metrics/metrics_log_uploader.cc
new file mode 100644
index 00000000000..41b83ed9249
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_uploader.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_uploader.h"
+
+namespace metrics {
+
+MetricsLogUploader::MetricsLogUploader(
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete)
+ : server_url_(server_url),
+ mime_type_(mime_type),
+ on_upload_complete_(on_upload_complete) {
+}
+
+MetricsLogUploader::~MetricsLogUploader() {
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_log_uploader.h b/chromium/components/metrics/metrics_log_uploader.h
new file mode 100644
index 00000000000..2ef0cf2577e
--- /dev/null
+++ b/chromium/components/metrics/metrics_log_uploader.h
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace metrics {
+
+// MetricsLogUploader is an abstract base class for uploading UMA logs on behalf
+// of MetricsService.
+class MetricsLogUploader {
+ public:
+ // Constructs the uploader that will upload logs to the specified |server_url|
+ // with the given |mime_type|. The |on_upload_complete| callback will be
+ // called with the HTTP response code of the upload or with -1 on an error.
+ MetricsLogUploader(const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete);
+ virtual ~MetricsLogUploader();
+
+ // Uploads a log with the specified |compressed_log_data| and |log_hash|.
+ // |log_hash| is expected to be the hex-encoded SHA1 hash of the log data
+ // before compression.
+ virtual void UploadLog(const std::string& compressed_log_data,
+ const std::string& log_hash) = 0;
+
+ protected:
+ const std::string server_url_;
+ const std::string mime_type_;
+ const base::Callback<void(int)> on_upload_complete_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogUploader);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
diff --git a/chromium/components/metrics/metrics_pref_names.cc b/chromium/components/metrics/metrics_pref_names.cc
new file mode 100644
index 00000000000..3872061fb3e
--- /dev/null
+++ b/chromium/components/metrics/metrics_pref_names.cc
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+namespace prefs {
+
+// Set once, to the current epoch time, on the first run of chrome on this
+// machine. Attached to metrics reports forever thereafter.
+const char kInstallDate[] = "uninstall_metrics.installation_date2";
+
+// The metrics client GUID.
+// Note: The name client_id2 is a result of creating
+// new prefs to do a one-time reset of the previous values.
+const char kMetricsClientID[] = "user_experience_metrics.client_id2";
+
+// Array of strings that are each UMA logs that were supposed to be sent in the
+// first minute of a browser session. These logs include things like crash count
+// info, etc.
+const char kMetricsInitialLogs[] =
+ "user_experience_metrics.initial_logs_list";
+
+// The metrics entropy source.
+// Note: The name low_entropy_source2 is a result of creating
+// new prefs to do a one-time reset of the previous values.
+const char kMetricsLowEntropySource[] =
+ "user_experience_metrics.low_entropy_source2";
+
+// A machine ID used to detect when underlying hardware changes. It is only
+// stored locally and never transmitted in metrics reports.
+const char kMetricsMachineId[] = "user_experience_metrics.machine_id";
+
+// Array of strings that are each UMA logs that were not sent because the
+// browser terminated before these accumulated metrics could be sent. These
+// logs typically include histograms and memory reports, as well as ongoing
+// user activities.
+const char kMetricsOngoingLogs[] =
+ "user_experience_metrics.ongoing_logs_list";
+
+// Boolean that indicates a cloned install has been detected and the metrics
+// client id and low entropy source should be reset.
+const char kMetricsResetIds[] = "user_experience_metrics.reset_metrics_ids";
+
+// Boolean that specifies whether or not crash reporting and metrics reporting
+// are sent over the network for analysis.
+const char kMetricsReportingEnabled[] =
+ "user_experience_metrics.reporting_enabled";
+
+// Date/time when the user opted in to UMA and generated the client id for the
+// very first time (local machine time, stored as a 64-bit time_t value).
+const char kMetricsReportingEnabledTimestamp[] =
+ "user_experience_metrics.client_id_timestamp";
+
+// The metrics client session ID.
+const char kMetricsSessionID[] = "user_experience_metrics.session_id";
+
+// The prefix of the last-seen timestamp for persistent histogram files.
+// Values are named for the files themselves.
+const char kMetricsLastSeenPrefix[] =
+ "user_experience_metrics.last_seen.";
+
+// Number of times the browser has been able to register crash reporting.
+const char kStabilityBreakpadRegistrationSuccess[] =
+ "user_experience_metrics.stability.breakpad_registration_ok";
+
+// Number of times the browser has failed to register crash reporting.
+const char kStabilityBreakpadRegistrationFail[] =
+ "user_experience_metrics.stability.breakpad_registration_fail";
+
+// Total number of child process crashes (other than renderer / extension
+// renderer ones, and plugin children, which are counted separately) since the
+// last report.
+const char kStabilityChildProcessCrashCount[] =
+ "user_experience_metrics.stability.child_process_crash_count";
+
+// Number of times the application exited uncleanly since the last report.
+const char kStabilityCrashCount[] =
+ "user_experience_metrics.stability.crash_count";
+
+// Number of times the browser has been run under a debugger.
+const char kStabilityDebuggerPresent[] =
+ "user_experience_metrics.stability.debugger_present";
+
+// Number of times the browser has not been run under a debugger.
+const char kStabilityDebuggerNotPresent[] =
+ "user_experience_metrics.stability.debugger_not_present";
+
+// An enum value to indicate the execution phase the browser was in.
+const char kStabilityExecutionPhase[] =
+ "user_experience_metrics.stability.execution_phase";
+
+// True if the previous run of the program exited cleanly.
+const char kStabilityExitedCleanly[] =
+ "user_experience_metrics.stability.exited_cleanly";
+
+// Number of times an extension renderer process crashed since the last report.
+const char kStabilityExtensionRendererCrashCount[] =
+ "user_experience_metrics.stability.extension_renderer_crash_count";
+
+// Number of times an extension renderer process failed to launch since the last
+// report.
+const char kStabilityExtensionRendererFailedLaunchCount[] =
+ "user_experience_metrics.stability.extension_renderer_failed_launch_count";
+
+// Number of times the session end did not complete.
+const char kStabilityIncompleteSessionEndCount[] =
+ "user_experience_metrics.stability.incomplete_session_end_count";
+
+// Time when the app was last known to be running, in seconds since
+// the epoch.
+const char kStabilityLastTimestampSec[] =
+ "user_experience_metrics.stability.last_timestamp_sec";
+
+// Number of times the application was launched since last report.
+const char kStabilityLaunchCount[] =
+ "user_experience_metrics.stability.launch_count";
+
+// Time when the app was last launched, in seconds since the epoch.
+const char kStabilityLaunchTimeSec[] =
+ "user_experience_metrics.stability.launch_time_sec";
+
+// Number of times a page load event occurred since the last report.
+const char kStabilityPageLoadCount[] =
+ "user_experience_metrics.stability.page_load_count";
+
+// Number of times a renderer process crashed since the last report.
+const char kStabilityRendererCrashCount[] =
+ "user_experience_metrics.stability.renderer_crash_count";
+
+// Number of times a renderer process failed to launch since the last report.
+const char kStabilityRendererFailedLaunchCount[] =
+ "user_experience_metrics.stability.renderer_failed_launch_count";
+
+// Number of times the renderer has become non-responsive since the last
+// report.
+const char kStabilityRendererHangCount[] =
+ "user_experience_metrics.stability.renderer_hang_count";
+
+// Base64 encoded serialized UMA system profile proto from the previous session.
+const char kStabilitySavedSystemProfile[] =
+ "user_experience_metrics.stability.saved_system_profile";
+
+// SHA-1 hash of the serialized UMA system profile proto (hex encoded).
+const char kStabilitySavedSystemProfileHash[] =
+ "user_experience_metrics.stability.saved_system_profile_hash";
+
+// False if we received a session end and either we crashed during processing
+// the session end or ran out of time and windows terminated us.
+const char kStabilitySessionEndCompleted[] =
+ "user_experience_metrics.stability.session_end_completed";
+
+// Build time, in seconds since an epoch, which is used to assure that stability
+// metrics reported reflect stability of the same build.
+const char kStabilityStatsBuildTime[] =
+ "user_experience_metrics.stability.stats_buildtime";
+
+// Version string of previous run, which is used to assure that stability
+// metrics reported under current version reflect stability of the same version.
+const char kStabilityStatsVersion[] =
+ "user_experience_metrics.stability.stats_version";
+
+// The keys below are strictly increasing counters over the lifetime of
+// a chrome installation. They are (optionally) sent up to the uninstall
+// survey in the event of uninstallation.
+const char kUninstallLaunchCount[] = "uninstall_metrics.launch_count";
+const char kUninstallMetricsPageLoadCount[] =
+ "uninstall_metrics.page_load_count";
+const char kUninstallMetricsUptimeSec[] = "uninstall_metrics.uptime_sec";
+
+// Dictionary for measuring cellular data used by UMA service during last 7
+// days.
+const char kUmaCellDataUse[] = "user_experience_metrics.uma_cell_datause";
+
+// Dictionary for measuring cellular data used by user including chrome services
+// per day.
+const char kUserCellDataUse[] = "user_experience_metrics.user_call_datause";
+
+} // namespace prefs
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_pref_names.h b/chromium/components/metrics/metrics_pref_names.h
new file mode 100644
index 00000000000..52733611995
--- /dev/null
+++ b/chromium/components/metrics/metrics_pref_names.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
+#define COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
+
+namespace metrics {
+namespace prefs {
+
+// Alphabetical list of preference names specific to the metrics
+// component. Document each in the .cc file.
+extern const char kInstallDate[];
+extern const char kMetricsClientID[];
+extern const char kMetricsInitialLogs[];
+extern const char kMetricsLowEntropySource[];
+extern const char kMetricsMachineId[];
+extern const char kMetricsOngoingLogs[];
+extern const char kMetricsResetIds[];
+
+// For finding out whether metrics and crash reporting is enabled use the
+// relevant embedder-specific subclass of MetricsServiceAccessor instead of
+// reading this pref directly; see the comments on metrics_service_accessor.h.
+// (NOTE: If within //chrome, use
+// ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled()).
+extern const char kMetricsReportingEnabled[];
+extern const char kMetricsReportingEnabledTimestamp[];
+extern const char kMetricsSessionID[];
+extern const char kMetricsLastSeenPrefix[];
+extern const char kStabilityBreakpadRegistrationSuccess[];
+extern const char kStabilityBreakpadRegistrationFail[];
+extern const char kStabilityChildProcessCrashCount[];
+extern const char kStabilityCrashCount[];
+extern const char kStabilityDebuggerPresent[];
+extern const char kStabilityDebuggerNotPresent[];
+extern const char kStabilityExecutionPhase[];
+extern const char kStabilityExtensionRendererCrashCount[];
+extern const char kStabilityExtensionRendererFailedLaunchCount[];
+extern const char kStabilityExitedCleanly[];
+extern const char kStabilityIncompleteSessionEndCount[];
+extern const char kStabilityLastTimestampSec[];
+extern const char kStabilityLaunchCount[];
+extern const char kStabilityLaunchTimeSec[];
+extern const char kStabilityPageLoadCount[];
+extern const char kStabilityRendererCrashCount[];
+extern const char kStabilityRendererFailedLaunchCount[];
+extern const char kStabilityRendererHangCount[];
+extern const char kStabilitySavedSystemProfile[];
+extern const char kStabilitySavedSystemProfileHash[];
+extern const char kStabilitySessionEndCompleted[];
+extern const char kStabilityStatsBuildTime[];
+extern const char kStabilityStatsVersion[];
+extern const char kUninstallLaunchCount[];
+extern const char kUninstallMetricsPageLoadCount[];
+extern const char kUninstallMetricsUptimeSec[];
+
+// For measuring data use for throttling UMA log uploads on cellular.
+extern const char kUmaCellDataUse[];
+extern const char kUserCellDataUse[];
+
+} // namespace prefs
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
diff --git a/chromium/components/metrics/metrics_provider.cc b/chromium/components/metrics/metrics_provider.cc
new file mode 100644
index 00000000000..5ba7382be5e
--- /dev/null
+++ b/chromium/components/metrics/metrics_provider.cc
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+MetricsProvider::MetricsProvider() {
+}
+
+MetricsProvider::~MetricsProvider() {
+}
+
+void MetricsProvider::Init() {
+}
+
+void MetricsProvider::OnDidCreateMetricsLog() {
+}
+
+void MetricsProvider::OnRecordingEnabled() {
+}
+
+void MetricsProvider::OnRecordingDisabled() {
+}
+
+void MetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) {
+}
+
+bool MetricsProvider::HasInitialStabilityMetrics() {
+ return false;
+}
+
+void MetricsProvider::ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+}
+
+void MetricsProvider::ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+}
+
+void MetricsProvider::ClearSavedStabilityMetrics() {
+}
+
+void MetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto) {
+}
+
+void MetricsProvider::RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager) {
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_provider.h b/chromium/components/metrics/metrics_provider.h
new file mode 100644
index 00000000000..664e5dbc539
--- /dev/null
+++ b/chromium/components/metrics/metrics_provider.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+
+namespace base {
+class HistogramSnapshotManager;
+} // namespace base
+
+namespace metrics {
+
+class ChromeUserMetricsExtension;
+class SystemProfileProto;
+class SystemProfileProto_Stability;
+
+// MetricsProvider is an interface allowing different parts of the UMA protos to
+// be filled out by different classes.
+class MetricsProvider {
+ public:
+ MetricsProvider();
+ virtual ~MetricsProvider();
+
+ // Called after initialiazation of MetricsService and field trials.
+ virtual void Init();
+
+ // Called when a new MetricsLog is created.
+ virtual void OnDidCreateMetricsLog();
+
+ // Called when metrics recording has been enabled.
+ virtual void OnRecordingEnabled();
+
+ // Called when metrics recording has been disabled.
+ virtual void OnRecordingDisabled();
+
+ // Provides additional metrics into the system profile.
+ virtual void ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto);
+
+ // Called once at startup to see whether this provider has critical stability
+ // events to share in an initial stability log.
+ // Returning true can trigger ProvideInitialStabilityMetrics and
+ // ProvideStabilityMetrics on all other registered metrics providers.
+ // Default implementation always returns false.
+ virtual bool HasInitialStabilityMetrics();
+
+ // Called at most once at startup when an initial stability log is created.
+ // It provides critical statiblity metrics that need to be reported in an
+ // initial stability log.
+ // Default implementation is a no-op.
+ virtual void ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto);
+
+ // Provides additional stability metrics. Stability metrics can be provided
+ // directly into |stability_proto| fields or by logging stability histograms
+ // via the UMA_STABILITY_HISTOGRAM_ENUMERATION() macro.
+ virtual void ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto);
+
+ // Called to indicate that saved stability prefs should be cleared, e.g.
+ // because they are from an old version and should not be kept.
+ virtual void ClearSavedStabilityMetrics();
+
+ // Provides general metrics that are neither system profile nor stability
+ // metrics. May also be used to add histograms when final metrics are
+ // collected right before upload.
+ virtual void ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto);
+
+ // Called during collection to explicitly load histogram snapshots using a
+ // snapshot manager. PrepareDeltas() will have already been called and
+ // FinishDeltas() will be called later; calls to only PrepareDelta(), not
+ // PrepareDeltas (plural), should be made.
+ virtual void RecordHistogramSnapshots(
+ base::HistogramSnapshotManager* snapshot_manager);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/metrics_reporting_scheduler.cc b/chromium/components/metrics/metrics_reporting_scheduler.cc
new file mode 100644
index 00000000000..ab117ad01e9
--- /dev/null
+++ b/chromium/components/metrics/metrics_reporting_scheduler.cc
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_reporting_scheduler.h"
+
+#include <stdint.h>
+
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/variations/variations_associated_data.h"
+
+using base::TimeDelta;
+
+namespace metrics {
+
+namespace {
+
+// The delay, in seconds, after startup before sending the first log message.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// Sessions are more likely to be short on a mobile device, so handle the
+// initial log quickly.
+const int kInitialUploadIntervalSeconds = 15;
+#else
+const int kInitialUploadIntervalSeconds = 60;
+#endif
+
+// The delay, in seconds, between uploading when there are queued logs from
+// previous sessions to send.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// Sending in a burst is better on a mobile device, since keeping the radio on
+// is very expensive.
+const int kUnsentLogsIntervalSeconds = 3;
+#else
+const int kUnsentLogsIntervalSeconds = 15;
+#endif
+
+// When uploading metrics to the server fails, we progressively wait longer and
+// longer before sending the next log. This backoff process helps reduce load
+// on a server that is having issues.
+// The following is the multiplier we use to expand that inter-log duration.
+const double kBackoffMultiplier = 1.1;
+
+// The maximum backoff multiplier.
+const int kMaxBackoffMultiplier = 10;
+
+enum InitSequence {
+ TIMER_FIRED_FIRST,
+ INIT_TASK_COMPLETED_FIRST,
+ INIT_SEQUENCE_ENUM_SIZE,
+};
+
+void LogMetricsInitSequence(InitSequence sequence) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence,
+ INIT_SEQUENCE_ENUM_SIZE);
+}
+
+void LogActualUploadInterval(TimeDelta interval) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.ActualLogUploadInterval",
+ interval.InMinutes(),
+ 1,
+ base::TimeDelta::FromHours(12).InMinutes(),
+ 50);
+}
+
+} // anonymous namespace
+
+MetricsReportingScheduler::MetricsReportingScheduler(
+ const base::Closure& upload_callback,
+ const base::Callback<base::TimeDelta(void)>& upload_interval_callback)
+ : upload_callback_(upload_callback),
+ upload_interval_(TimeDelta::FromSeconds(kInitialUploadIntervalSeconds)),
+ running_(false),
+ callback_pending_(false),
+ init_task_complete_(false),
+ waiting_for_init_task_complete_(false),
+ upload_interval_callback_(upload_interval_callback) {
+}
+
+MetricsReportingScheduler::~MetricsReportingScheduler() {}
+
+void MetricsReportingScheduler::Start() {
+ running_ = true;
+ ScheduleNextUpload();
+}
+
+void MetricsReportingScheduler::Stop() {
+ running_ = false;
+ if (upload_timer_.IsRunning())
+ upload_timer_.Stop();
+}
+
+// Callback from MetricsService when the startup init task has completed.
+void MetricsReportingScheduler::InitTaskComplete() {
+ DCHECK(!init_task_complete_);
+ init_task_complete_ = true;
+ if (waiting_for_init_task_complete_) {
+ waiting_for_init_task_complete_ = false;
+ TriggerUpload();
+ } else {
+ LogMetricsInitSequence(INIT_TASK_COMPLETED_FIRST);
+ }
+}
+
+void MetricsReportingScheduler::UploadFinished(bool server_is_healthy,
+ bool more_logs_remaining) {
+ DCHECK(callback_pending_);
+ callback_pending_ = false;
+ // If the server is having issues, back off. Otherwise, reset to default
+ // (unless there are more logs to send, in which case the next upload should
+ // happen sooner).
+ if (!server_is_healthy) {
+ BackOffUploadInterval();
+ } else if (more_logs_remaining) {
+ upload_interval_ = TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds);
+ } else {
+ upload_interval_ = GetStandardUploadInterval();
+ last_upload_finish_time_ = base::TimeTicks::Now();
+ }
+
+ if (running_)
+ ScheduleNextUpload();
+}
+
+void MetricsReportingScheduler::UploadCancelled() {
+ DCHECK(callback_pending_);
+ callback_pending_ = false;
+ if (running_)
+ ScheduleNextUpload();
+}
+
+void MetricsReportingScheduler::SetUploadIntervalForTesting(
+ base::TimeDelta interval) {
+ upload_interval_ = interval;
+}
+
+void MetricsReportingScheduler::TriggerUpload() {
+ // If the timer fired before the init task has completed, don't trigger the
+ // upload yet - wait for the init task to complete and do it then.
+ if (!init_task_complete_) {
+ LogMetricsInitSequence(TIMER_FIRED_FIRST);
+ waiting_for_init_task_complete_ = true;
+ return;
+ }
+
+ if (!last_upload_finish_time_.is_null()) {
+ LogActualUploadInterval(base::TimeTicks::Now() - last_upload_finish_time_);
+ last_upload_finish_time_ = base::TimeTicks();
+ }
+
+ callback_pending_ = true;
+ upload_callback_.Run();
+}
+
+void MetricsReportingScheduler::ScheduleNextUpload() {
+ DCHECK(running_);
+ if (upload_timer_.IsRunning() || callback_pending_)
+ return;
+
+ upload_timer_.Start(FROM_HERE, upload_interval_, this,
+ &MetricsReportingScheduler::TriggerUpload);
+}
+
+void MetricsReportingScheduler::BackOffUploadInterval() {
+ DCHECK_GT(kBackoffMultiplier, 1.0);
+ upload_interval_ = TimeDelta::FromMicroseconds(static_cast<int64_t>(
+ kBackoffMultiplier * upload_interval_.InMicroseconds()));
+
+ TimeDelta max_interval = kMaxBackoffMultiplier * GetStandardUploadInterval();
+ if (upload_interval_ > max_interval || upload_interval_.InSeconds() < 0) {
+ upload_interval_ = max_interval;
+ }
+}
+
+base::TimeDelta MetricsReportingScheduler::GetStandardUploadInterval() {
+ return upload_interval_callback_.Run();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_reporting_scheduler.h b/chromium/components/metrics/metrics_reporting_scheduler.h
new file mode 100644
index 00000000000..f4e3dd11b3c
--- /dev/null
+++ b/chromium/components/metrics/metrics_reporting_scheduler.h
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_
+#define COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/metrics/net/network_metrics_provider.h"
+
+namespace metrics {
+
+// Scheduler task to drive a MetricsService object's uploading.
+class MetricsReportingScheduler {
+ public:
+ // Creates MetricsServiceScheduler object with the given |upload_callback|
+ // callback to call when uploading should happen and
+ // |upload_interval_callback| to determine the interval between uploads
+ // in steady state.
+ MetricsReportingScheduler(
+ const base::Closure& upload_callback,
+ const base::Callback<base::TimeDelta(void)>& upload_interval_callback);
+ ~MetricsReportingScheduler();
+
+ // Starts scheduling uploads. This in a no-op if the scheduler is already
+ // running, so it is safe to call more than once.
+ void Start();
+
+ // Stops scheduling uploads.
+ void Stop();
+
+ // Callback from MetricsService when the startup init task has completed.
+ void InitTaskComplete();
+
+ // Callback from MetricsService when a triggered upload finishes.
+ void UploadFinished(bool server_is_healthy, bool more_logs_remaining);
+
+ // Callback from MetricsService when a triggered upload is cancelled by the
+ // MetricsService.
+ void UploadCancelled();
+
+ // Sets the upload interval to a specific value, exposed for unit tests.
+ void SetUploadIntervalForTesting(base::TimeDelta interval);
+
+ private:
+ // Timer callback indicating it's time for the MetricsService to upload
+ // metrics.
+ void TriggerUpload();
+
+ // Schedules a future call to TriggerUpload if one isn't already pending.
+ void ScheduleNextUpload();
+
+ // Increases the upload interval each time it's called, to handle the case
+ // where the server is having issues.
+ void BackOffUploadInterval();
+
+ // Returns upload interval to use in steady state.
+ base::TimeDelta GetStandardUploadInterval();
+
+ // The MetricsService method to call when uploading should happen.
+ const base::Closure upload_callback_;
+
+ base::OneShotTimer upload_timer_;
+
+ // The interval between being told an upload is done and starting the next
+ // upload.
+ base::TimeDelta upload_interval_;
+
+ // The tick count of the last time log upload has been finished and null if no
+ // upload has been done yet.
+ base::TimeTicks last_upload_finish_time_;
+
+ // Indicates that the scheduler is running (i.e., that Start has been called
+ // more recently than Stop).
+ bool running_;
+
+ // Indicates that the last triggered upload hasn't resolved yet.
+ bool callback_pending_;
+
+ // Whether the InitTaskComplete() callback has been called.
+ bool init_task_complete_;
+
+ // Whether the initial scheduled upload timer has fired before the init task
+ // has been completed.
+ bool waiting_for_init_task_complete_;
+
+ // Callback function used to get the standard upload time.
+ base::Callback<base::TimeDelta(void)> upload_interval_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsReportingScheduler);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_REPORTING_SCHEDULER_H_
diff --git a/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc b/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc
new file mode 100644
index 00000000000..f5b9b5b0180
--- /dev/null
+++ b/chromium/components/metrics/metrics_reporting_scheduler_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_reporting_scheduler.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class MetricsReportingSchedulerTest : public testing::Test {
+ public:
+ MetricsReportingSchedulerTest() : callback_call_count_(0) {}
+ ~MetricsReportingSchedulerTest() override {}
+
+ base::Closure GetCallback() {
+ return base::Bind(&MetricsReportingSchedulerTest::SchedulerCallback,
+ base::Unretained(this));
+ }
+
+ base::Callback<base::TimeDelta(void)> GetConnectionCallback() {
+ return base::Bind(&MetricsReportingSchedulerTest::GetStandardUploadInterval,
+ base::Unretained(this));
+ }
+
+ int callback_call_count() const { return callback_call_count_; }
+
+ private:
+ void SchedulerCallback() {
+ ++callback_call_count_;
+ }
+
+ base::TimeDelta GetStandardUploadInterval() {
+ return base::TimeDelta::FromMinutes(5);
+ }
+
+ int callback_call_count_;
+
+ base::MessageLoopForUI message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsReportingSchedulerTest);
+};
+
+
+TEST_F(MetricsReportingSchedulerTest, InitTaskCompleteBeforeTimer) {
+ MetricsReportingScheduler scheduler(GetCallback(), GetConnectionCallback());
+ scheduler.SetUploadIntervalForTesting(base::TimeDelta());
+ scheduler.InitTaskComplete();
+ scheduler.Start();
+ EXPECT_EQ(0, callback_call_count());
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, callback_call_count());
+}
+
+TEST_F(MetricsReportingSchedulerTest, InitTaskCompleteAfterTimer) {
+ MetricsReportingScheduler scheduler(GetCallback(), GetConnectionCallback());
+ scheduler.SetUploadIntervalForTesting(base::TimeDelta());
+ scheduler.Start();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, callback_call_count());
+
+ scheduler.InitTaskComplete();
+ EXPECT_EQ(1, callback_call_count());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service.cc b/chromium/components/metrics/metrics_service.cc
new file mode 100644
index 00000000000..38ef6e45ab1
--- /dev/null
+++ b/chromium/components/metrics/metrics_service.cc
@@ -0,0 +1,1193 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//------------------------------------------------------------------------------
+// Description of the life cycle of a instance of MetricsService.
+//
+// OVERVIEW
+//
+// A MetricsService instance is typically created at application startup. It is
+// the central controller for the acquisition of log data, and the automatic
+// transmission of that log data to an external server. Its major job is to
+// manage logs, grouping them for transmission, and transmitting them. As part
+// of its grouping, MS finalizes logs by including some just-in-time gathered
+// memory statistics, snapshotting the current stats of numerous histograms,
+// closing the logs, translating to protocol buffer format, and compressing the
+// results for transmission. Transmission includes submitting a compressed log
+// as data in a URL-post, and retransmitting (or retaining at process
+// termination) if the attempted transmission failed. Retention across process
+// terminations is done using the the PrefServices facilities. The retained logs
+// (the ones that never got transmitted) are compressed and base64-encoded
+// before being persisted.
+//
+// Logs fall into one of two categories: "initial logs," and "ongoing logs."
+// There is at most one initial log sent for each complete run of Chrome (from
+// startup, to browser shutdown). An initial log is generally transmitted some
+// short time (1 minute?) after startup, and includes stats such as recent crash
+// info, the number and types of plugins, etc. The external server's response
+// to the initial log conceptually tells this MS if it should continue
+// transmitting logs (during this session). The server response can actually be
+// much more detailed, and always includes (at a minimum) how often additional
+// ongoing logs should be sent.
+//
+// After the above initial log, a series of ongoing logs will be transmitted.
+// The first ongoing log actually begins to accumulate information stating when
+// the MS was first constructed. Note that even though the initial log is
+// commonly sent a full minute after startup, the initial log does not include
+// much in the way of user stats. The most common interlog period (delay)
+// is 30 minutes. That time period starts when the first user action causes a
+// logging event. This means that if there is no user action, there may be long
+// periods without any (ongoing) log transmissions. Ongoing logs typically
+// contain very detailed records of user activities (ex: opened tab, closed
+// tab, fetched URL, maximized window, etc.) In addition, just before an
+// ongoing log is closed out, a call is made to gather memory statistics. Those
+// memory statistics are deposited into a histogram, and the log finalization
+// code is then called. In the finalization, a call to a Histogram server
+// acquires a list of all local histograms that have been flagged for upload
+// to the UMA server. The finalization also acquires the most recent number
+// of page loads, along with any counts of renderer or plugin crashes.
+//
+// When the browser shuts down, there will typically be a fragment of an ongoing
+// log that has not yet been transmitted. At shutdown time, that fragment is
+// closed (including snapshotting histograms), and persisted, for potential
+// transmission during a future run of the product.
+//
+// There are two slightly abnormal shutdown conditions. There is a
+// "disconnected scenario," and a "really fast startup and shutdown" scenario.
+// In the "never connected" situation, the user has (during the running of the
+// process) never established an internet connection. As a result, attempts to
+// transmit the initial log have failed, and a lot(?) of data has accumulated in
+// the ongoing log (which didn't yet get closed, because there was never even a
+// contemplation of sending it). There is also a kindred "lost connection"
+// situation, where a loss of connection prevented an ongoing log from being
+// transmitted, and a (still open) log was stuck accumulating a lot(?) of data,
+// while the earlier log retried its transmission. In both of these
+// disconnected situations, two logs need to be, and are, persistently stored
+// for future transmission.
+//
+// The other unusual shutdown condition, termed "really fast startup and
+// shutdown," involves the deliberate user termination of the process before
+// the initial log is even formed or transmitted. In that situation, no logging
+// is done, but the historical crash statistics remain (unlogged) for inclusion
+// in a future run's initial log. (i.e., we don't lose crash stats).
+//
+// With the above overview, we can now describe the state machine's various
+// states, based on the State enum specified in the state_ member. Those states
+// are:
+//
+// INITIALIZED, // Constructor was called.
+// INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish.
+// INIT_TASK_DONE, // Waiting for timer to send initial log.
+// SENDING_LOGS, // Sending logs and creating new ones when we run out.
+//
+// In more detail, we have:
+//
+// INITIALIZED, // Constructor was called.
+// The MS has been constructed, but has taken no actions to compose the
+// initial log.
+//
+// INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish.
+// Typically about 30 seconds after startup, a task is sent to a second thread
+// (the file thread) to perform deferred (lower priority and slower)
+// initialization steps such as getting the list of plugins. That task will
+// (when complete) make an async callback (via a Task) to indicate the
+// completion.
+//
+// INIT_TASK_DONE, // Waiting for timer to send initial log.
+// The callback has arrived, and it is now possible for an initial log to be
+// created. This callback typically arrives back less than one second after
+// the deferred init task is dispatched.
+//
+// SENDING_LOGS, // Sending logs an creating new ones when we run out.
+// Logs from previous sessions have been loaded, and initial logs have been
+// created (an optional stability log and the first metrics log). We will
+// send all of these logs, and when run out, we will start cutting new logs
+// to send. We will also cut a new log if we expect a shutdown.
+//
+// The progression through the above states is simple, and sequential.
+// States proceed from INITIAL to SENDING_LOGS, and remain in the latter until
+// shutdown.
+//
+// Also note that whenever we successfully send a log, we mirror the list
+// of logs into the PrefService. This ensures that IF we crash, we won't start
+// up and retransmit our old logs again.
+//
+// Due to race conditions, it is always possible that a log file could be sent
+// twice. For example, if a log file is sent, but not yet acknowledged by
+// the external server, and the user shuts down, then a copy of the log may be
+// saved for re-transmission. These duplicates could be filtered out server
+// side, but are not expected to be a significant problem.
+//
+//
+//------------------------------------------------------------------------------
+
+#include "components/metrics/metrics_service.h"
+
+#include <stddef.h>
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/rand_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/tracked_objects.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/metrics/data_use_tracker.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_manager.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_reporting_scheduler.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/entropy_provider.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace metrics {
+
+namespace {
+
+// Check to see that we're being called on only one thread.
+bool IsSingleThreaded() {
+ static base::PlatformThreadId thread_id = 0;
+ if (!thread_id)
+ thread_id = base::PlatformThread::CurrentId();
+ return base::PlatformThread::CurrentId() == thread_id;
+}
+
+// The delay, in seconds, after starting recording before doing expensive
+// initialization work.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// On mobile devices, a significant portion of sessions last less than a minute.
+// Use a shorter timer on these platforms to avoid losing data.
+// TODO(dfalcantara): To avoid delaying startup, tighten up initialization so
+// that it occurs after the user gets their initial page.
+const int kInitializationDelaySeconds = 5;
+#else
+const int kInitializationDelaySeconds = 30;
+#endif
+
+// The maximum number of events in a log uploaded to the UMA server.
+const int kEventLimit = 2400;
+
+// If an upload fails, and the transmission was over this byte count, then we
+// will discard the log, and not try to retransmit it. We also don't persist
+// the log to the prefs for transmission during the next chrome session if this
+// limit is exceeded.
+const size_t kUploadLogAvoidRetransmitSize = 100 * 1024;
+
+// Interval, in minutes, between state saves.
+const int kSaveStateIntervalMinutes = 5;
+
+enum ResponseStatus {
+ UNKNOWN_FAILURE,
+ SUCCESS,
+ BAD_REQUEST, // Invalid syntax or log too large.
+ NO_RESPONSE,
+ NUM_RESPONSE_STATUSES
+};
+
+ResponseStatus ResponseCodeToStatus(int response_code) {
+ switch (response_code) {
+ case -1:
+ return NO_RESPONSE;
+ case 200:
+ return SUCCESS;
+ case 400:
+ return BAD_REQUEST;
+ default:
+ return UNKNOWN_FAILURE;
+ }
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void MarkAppCleanShutdownAndCommit(CleanExitBeacon* clean_exit_beacon,
+ PrefService* local_state) {
+ clean_exit_beacon->WriteBeaconValue(true);
+ local_state->SetInteger(prefs::kStabilityExecutionPhase,
+ MetricsService::SHUTDOWN_COMPLETE);
+ // Start writing right away (write happens on a different thread).
+ local_state->CommitPendingWrite();
+}
+#endif // defined(OS_ANDROID) || defined(OS_IOS)
+
+// Determines if current log should be sent based on sampling rate. Returns true
+// if the sampling rate is not set.
+bool ShouldUploadLog() {
+ std::string probability_str = variations::GetVariationParamValue(
+ "UMA_EnableCellularLogUpload", "Sample_Probability");
+ if (probability_str.empty())
+ return true;
+
+ int probability;
+ // In case specified sampling rate is invalid.
+ if (!base::StringToInt(probability_str, &probability))
+ return true;
+ return base::RandInt(1, 100) <= probability;
+}
+
+} // namespace
+
+// static
+MetricsService::ShutdownCleanliness MetricsService::clean_shutdown_status_ =
+ MetricsService::CLEANLY_SHUTDOWN;
+
+MetricsService::ExecutionPhase MetricsService::execution_phase_ =
+ MetricsService::UNINITIALIZED_PHASE;
+
+// static
+void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
+ DCHECK(IsSingleThreaded());
+ MetricsStateManager::RegisterPrefs(registry);
+ MetricsLog::RegisterPrefs(registry);
+ DataUseTracker::RegisterPrefs(registry);
+
+ registry->RegisterInt64Pref(prefs::kInstallDate, 0);
+
+ registry->RegisterInt64Pref(prefs::kStabilityLaunchTimeSec, 0);
+ registry->RegisterInt64Pref(prefs::kStabilityLastTimestampSec, 0);
+ registry->RegisterStringPref(prefs::kStabilityStatsVersion, std::string());
+ registry->RegisterInt64Pref(prefs::kStabilityStatsBuildTime, 0);
+ registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, true);
+ registry->RegisterIntegerPref(prefs::kStabilityExecutionPhase,
+ UNINITIALIZED_PHASE);
+ registry->RegisterBooleanPref(prefs::kStabilitySessionEndCompleted, true);
+ registry->RegisterIntegerPref(prefs::kMetricsSessionID, -1);
+
+ registry->RegisterListPref(prefs::kMetricsInitialLogs);
+ registry->RegisterListPref(prefs::kMetricsOngoingLogs);
+
+ registry->RegisterInt64Pref(prefs::kUninstallLaunchCount, 0);
+ registry->RegisterInt64Pref(prefs::kUninstallMetricsUptimeSec, 0);
+}
+
+MetricsService::MetricsService(MetricsStateManager* state_manager,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : log_manager_(local_state, kUploadLogAvoidRetransmitSize),
+ histogram_snapshot_manager_(this),
+ state_manager_(state_manager),
+ client_(client),
+ local_state_(local_state),
+ clean_exit_beacon_(client->GetRegistryBackupKey(), local_state),
+ recording_state_(UNSET),
+ reporting_active_(false),
+ test_mode_active_(false),
+ state_(INITIALIZED),
+ log_upload_in_progress_(false),
+ idle_since_last_transmission_(false),
+ session_id_(-1),
+ data_use_tracker_(DataUseTracker::Create(local_state_)),
+ self_ptr_factory_(this),
+ state_saver_factory_(this) {
+ DCHECK(IsSingleThreaded());
+ DCHECK(state_manager_);
+ DCHECK(client_);
+ DCHECK(local_state_);
+
+ // Set the install date if this is our first run.
+ int64_t install_date = local_state_->GetInt64(prefs::kInstallDate);
+ if (install_date == 0)
+ local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT());
+}
+
+MetricsService::~MetricsService() {
+ DisableRecording();
+}
+
+void MetricsService::InitializeMetricsRecordingState() {
+ InitializeMetricsState();
+
+ base::Closure upload_callback =
+ base::Bind(&MetricsService::StartScheduledUpload,
+ self_ptr_factory_.GetWeakPtr());
+ scheduler_.reset(
+ new MetricsReportingScheduler(
+ upload_callback,
+ // MetricsServiceClient outlives MetricsService, and
+ // MetricsReportingScheduler is tied to the lifetime of |this|.
+ base::Bind(&MetricsServiceClient::GetStandardUploadInterval,
+ base::Unretained(client_))));
+
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->Init();
+}
+
+void MetricsService::Start() {
+ HandleIdleSinceLastTransmission(false);
+ EnableRecording();
+ EnableReporting();
+}
+
+void MetricsService::StartRecordingForTests() {
+ test_mode_active_ = true;
+ EnableRecording();
+ DisableReporting();
+}
+
+void MetricsService::Stop() {
+ HandleIdleSinceLastTransmission(false);
+ DisableReporting();
+ DisableRecording();
+}
+
+void MetricsService::EnableReporting() {
+ if (reporting_active_)
+ return;
+ reporting_active_ = true;
+ StartSchedulerIfNecessary();
+}
+
+void MetricsService::DisableReporting() {
+ reporting_active_ = false;
+}
+
+std::string MetricsService::GetClientId() {
+ return state_manager_->client_id();
+}
+
+int64_t MetricsService::GetInstallDate() {
+ return local_state_->GetInt64(prefs::kInstallDate);
+}
+
+int64_t MetricsService::GetMetricsReportingEnabledDate() {
+ return local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
+}
+
+bool MetricsService::WasLastShutdownClean() const {
+ return clean_exit_beacon_.exited_cleanly();
+}
+
+scoped_ptr<const base::FieldTrial::EntropyProvider>
+MetricsService::CreateEntropyProvider() {
+ // TODO(asvitkine): Refactor the code so that MetricsService does not expose
+ // this method.
+ return state_manager_->CreateEntropyProvider();
+}
+
+void MetricsService::EnableRecording() {
+ DCHECK(IsSingleThreaded());
+
+ if (recording_state_ == ACTIVE)
+ return;
+ recording_state_ = ACTIVE;
+
+ state_manager_->ForceClientIdCreation();
+ client_->SetMetricsClientId(state_manager_->client_id());
+ if (!log_manager_.current_log())
+ OpenNewLog();
+
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->OnRecordingEnabled();
+
+ base::RemoveActionCallback(action_callback_);
+ action_callback_ = base::Bind(&MetricsService::OnUserAction,
+ base::Unretained(this));
+ base::AddActionCallback(action_callback_);
+}
+
+void MetricsService::DisableRecording() {
+ DCHECK(IsSingleThreaded());
+
+ if (recording_state_ == INACTIVE)
+ return;
+ recording_state_ = INACTIVE;
+
+ client_->OnRecordingDisabled();
+
+ base::RemoveActionCallback(action_callback_);
+
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->OnRecordingDisabled();
+
+ PushPendingLogsToPersistentStorage();
+}
+
+bool MetricsService::recording_active() const {
+ DCHECK(IsSingleThreaded());
+ return recording_state_ == ACTIVE;
+}
+
+bool MetricsService::reporting_active() const {
+ DCHECK(IsSingleThreaded());
+ return reporting_active_;
+}
+
+void MetricsService::RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) {
+ log_manager_.current_log()->RecordHistogramDelta(histogram.histogram_name(),
+ snapshot);
+}
+
+void MetricsService::InconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) {
+ UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowser",
+ problem, base::HistogramBase::NEVER_EXCEEDED_VALUE);
+}
+
+void MetricsService::UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) {
+ UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowserUnique",
+ problem, base::HistogramBase::NEVER_EXCEEDED_VALUE);
+}
+
+void MetricsService::InconsistencyDetectedInLoggedCount(int amount) {
+ UMA_HISTOGRAM_COUNTS("Histogram.InconsistentSnapshotBrowser",
+ std::abs(amount));
+}
+
+void MetricsService::HandleIdleSinceLastTransmission(bool in_idle) {
+ // If there wasn't a lot of action, maybe the computer was asleep, in which
+ // case, the log transmissions should have stopped. Here we start them up
+ // again.
+ if (!in_idle && idle_since_last_transmission_)
+ StartSchedulerIfNecessary();
+ idle_since_last_transmission_ = in_idle;
+}
+
+void MetricsService::OnApplicationNotIdle() {
+ if (recording_state_ == ACTIVE)
+ HandleIdleSinceLastTransmission(false);
+}
+
+void MetricsService::RecordStartOfSessionEnd() {
+ LogCleanShutdown();
+ RecordBooleanPrefValue(prefs::kStabilitySessionEndCompleted, false);
+}
+
+void MetricsService::RecordCompletedSessionEnd() {
+ LogCleanShutdown();
+ RecordBooleanPrefValue(prefs::kStabilitySessionEndCompleted, true);
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void MetricsService::OnAppEnterBackground() {
+ scheduler_->Stop();
+
+ MarkAppCleanShutdownAndCommit(&clean_exit_beacon_, local_state_);
+
+ // At this point, there's no way of knowing when the process will be
+ // killed, so this has to be treated similar to a shutdown, closing and
+ // persisting all logs. Unlinke a shutdown, the state is primed to be ready
+ // to continue logging and uploading if the process does return.
+ if (recording_active() && state_ >= SENDING_LOGS) {
+ PushPendingLogsToPersistentStorage();
+ // Persisting logs closes the current log, so start recording a new log
+ // immediately to capture any background work that might be done before the
+ // process is killed.
+ OpenNewLog();
+ }
+}
+
+void MetricsService::OnAppEnterForeground() {
+ clean_exit_beacon_.WriteBeaconValue(false);
+ StartSchedulerIfNecessary();
+}
+#else
+void MetricsService::LogNeedForCleanShutdown() {
+ clean_exit_beacon_.WriteBeaconValue(false);
+ // Redundant setting to be sure we call for a clean shutdown.
+ clean_shutdown_status_ = NEED_TO_SHUTDOWN;
+}
+#endif // defined(OS_ANDROID) || defined(OS_IOS)
+
+// static
+void MetricsService::SetExecutionPhase(ExecutionPhase execution_phase,
+ PrefService* local_state) {
+ execution_phase_ = execution_phase;
+ local_state->SetInteger(prefs::kStabilityExecutionPhase, execution_phase_);
+}
+
+void MetricsService::RecordBreakpadRegistration(bool success) {
+ if (!success)
+ IncrementPrefValue(prefs::kStabilityBreakpadRegistrationFail);
+ else
+ IncrementPrefValue(prefs::kStabilityBreakpadRegistrationSuccess);
+}
+
+void MetricsService::RecordBreakpadHasDebugger(bool has_debugger) {
+ if (!has_debugger)
+ IncrementPrefValue(prefs::kStabilityDebuggerNotPresent);
+ else
+ IncrementPrefValue(prefs::kStabilityDebuggerPresent);
+}
+
+void MetricsService::ClearSavedStabilityMetrics() {
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->ClearSavedStabilityMetrics();
+
+ // Reset the prefs that are managed by MetricsService/MetricsLog directly.
+ local_state_->SetInteger(prefs::kStabilityCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+ UNINITIALIZED_PHASE);
+ local_state_->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0);
+ local_state_->SetInteger(prefs::kStabilityLaunchCount, 0);
+ local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true);
+}
+
+void MetricsService::PushExternalLog(const std::string& log) {
+ log_manager_.StoreLog(log, MetricsLog::ONGOING_LOG);
+}
+
+UpdateUsagePrefCallbackType MetricsService::GetDataUseForwardingCallback() {
+ DCHECK(IsSingleThreaded());
+
+ if (data_use_tracker_) {
+ return data_use_tracker_->GetDataUseForwardingCallback(
+ base::ThreadTaskRunnerHandle::Get());
+ }
+ return UpdateUsagePrefCallbackType();
+}
+
+//------------------------------------------------------------------------------
+// private methods
+//------------------------------------------------------------------------------
+
+
+//------------------------------------------------------------------------------
+// Initialization methods
+
+void MetricsService::InitializeMetricsState() {
+ const int64_t buildtime = MetricsLog::GetBuildTime();
+ const std::string version = client_->GetVersionString();
+ bool version_changed = false;
+ if (local_state_->GetInt64(prefs::kStabilityStatsBuildTime) != buildtime ||
+ local_state_->GetString(prefs::kStabilityStatsVersion) != version) {
+ local_state_->SetString(prefs::kStabilityStatsVersion, version);
+ local_state_->SetInt64(prefs::kStabilityStatsBuildTime, buildtime);
+ version_changed = true;
+ }
+
+ log_manager_.LoadPersistedUnsentLogs();
+
+ session_id_ = local_state_->GetInteger(prefs::kMetricsSessionID);
+
+ if (!clean_exit_beacon_.exited_cleanly()) {
+ IncrementPrefValue(prefs::kStabilityCrashCount);
+ // Reset flag, and wait until we call LogNeedForCleanShutdown() before
+ // monitoring.
+ clean_exit_beacon_.WriteBeaconValue(true);
+ }
+
+ bool has_initial_stability_log = false;
+ if (!clean_exit_beacon_.exited_cleanly() ||
+ ProvidersHaveInitialStabilityMetrics()) {
+ // TODO(rtenneti): On windows, consider saving/getting execution_phase from
+ // the registry.
+ int execution_phase =
+ local_state_->GetInteger(prefs::kStabilityExecutionPhase);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Chrome.Browser.CrashedExecutionPhase",
+ execution_phase);
+
+ // If the previous session didn't exit cleanly, or if any provider
+ // explicitly requests it, prepare an initial stability log -
+ // provided UMA is enabled.
+ if (state_manager_->IsMetricsReportingEnabled())
+ has_initial_stability_log = PrepareInitialStabilityLog();
+ }
+
+ // If no initial stability log was generated and there was a version upgrade,
+ // clear the stability stats from the previous version (so that they don't get
+ // attributed to the current version). This could otherwise happen due to a
+ // number of different edge cases, such as if the last version crashed before
+ // it could save off a system profile or if UMA reporting is disabled (which
+ // normally results in stats being accumulated).
+ if (!has_initial_stability_log && version_changed)
+ ClearSavedStabilityMetrics();
+
+ // Update session ID.
+ ++session_id_;
+ local_state_->SetInteger(prefs::kMetricsSessionID, session_id_);
+
+ // Stability bookkeeping
+ IncrementPrefValue(prefs::kStabilityLaunchCount);
+
+ DCHECK_EQ(UNINITIALIZED_PHASE, execution_phase_);
+ SetExecutionPhase(START_METRICS_RECORDING, local_state_);
+
+ if (!local_state_->GetBoolean(prefs::kStabilitySessionEndCompleted)) {
+ IncrementPrefValue(prefs::kStabilityIncompleteSessionEndCount);
+ // This is marked false when we get a WM_ENDSESSION.
+ local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true);
+ }
+
+ // Call GetUptimes() for the first time, thus allowing all later calls
+ // to record incremental uptimes accurately.
+ base::TimeDelta ignored_uptime_parameter;
+ base::TimeDelta startup_uptime;
+ GetUptimes(local_state_, &startup_uptime, &ignored_uptime_parameter);
+ DCHECK_EQ(0, startup_uptime.InMicroseconds());
+ // For backwards compatibility, leave this intact in case Omaha is checking
+ // them. prefs::kStabilityLastTimestampSec may also be useless now.
+ // TODO(jar): Delete these if they have no uses.
+ local_state_->SetInt64(prefs::kStabilityLaunchTimeSec,
+ base::Time::Now().ToTimeT());
+
+ // Bookkeeping for the uninstall metrics.
+ IncrementLongPrefsValue(prefs::kUninstallLaunchCount);
+
+ // Kick off the process of saving the state (so the uptime numbers keep
+ // getting updated) every n minutes.
+ ScheduleNextStateSave();
+}
+
+void MetricsService::OnUserAction(const std::string& action) {
+ if (!ShouldLogEvents())
+ return;
+
+ log_manager_.current_log()->RecordUserAction(action);
+ HandleIdleSinceLastTransmission(false);
+}
+
+void MetricsService::FinishedInitTask() {
+ DCHECK_EQ(INIT_TASK_SCHEDULED, state_);
+ state_ = INIT_TASK_DONE;
+
+ // Create the initial log.
+ if (!initial_metrics_log_.get()) {
+ initial_metrics_log_ = CreateLog(MetricsLog::ONGOING_LOG);
+ NotifyOnDidCreateMetricsLog();
+ }
+
+ scheduler_->InitTaskComplete();
+}
+
+void MetricsService::GetUptimes(PrefService* pref,
+ base::TimeDelta* incremental_uptime,
+ base::TimeDelta* uptime) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ // If this is the first call, init |first_updated_time_| and
+ // |last_updated_time_|.
+ if (last_updated_time_.is_null()) {
+ first_updated_time_ = now;
+ last_updated_time_ = now;
+ }
+ *incremental_uptime = now - last_updated_time_;
+ *uptime = now - first_updated_time_;
+ last_updated_time_ = now;
+
+ const int64_t incremental_time_secs = incremental_uptime->InSeconds();
+ if (incremental_time_secs > 0) {
+ int64_t metrics_uptime = pref->GetInt64(prefs::kUninstallMetricsUptimeSec);
+ metrics_uptime += incremental_time_secs;
+ pref->SetInt64(prefs::kUninstallMetricsUptimeSec, metrics_uptime);
+ }
+}
+
+void MetricsService::NotifyOnDidCreateMetricsLog() {
+ DCHECK(IsSingleThreaded());
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->OnDidCreateMetricsLog();
+}
+
+//------------------------------------------------------------------------------
+// State save methods
+
+void MetricsService::ScheduleNextStateSave() {
+ state_saver_factory_.InvalidateWeakPtrs();
+
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&MetricsService::SaveLocalState,
+ state_saver_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMinutes(kSaveStateIntervalMinutes));
+}
+
+void MetricsService::SaveLocalState() {
+ RecordCurrentState(local_state_);
+
+ // TODO(jar):110021 Does this run down the batteries????
+ ScheduleNextStateSave();
+}
+
+
+//------------------------------------------------------------------------------
+// Recording control methods
+
+void MetricsService::OpenNewLog() {
+ DCHECK(!log_manager_.current_log());
+
+ log_manager_.BeginLoggingWithLog(CreateLog(MetricsLog::ONGOING_LOG));
+ NotifyOnDidCreateMetricsLog();
+ if (state_ == INITIALIZED) {
+ // We only need to schedule that run once.
+ state_ = INIT_TASK_SCHEDULED;
+
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&MetricsService::StartInitTask,
+ self_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kInitializationDelaySeconds));
+ }
+}
+
+void MetricsService::StartInitTask() {
+ client_->InitializeSystemProfileMetrics(
+ base::Bind(&MetricsService::FinishedInitTask,
+ self_ptr_factory_.GetWeakPtr()));
+}
+
+void MetricsService::CloseCurrentLog() {
+ if (!log_manager_.current_log())
+ return;
+
+ // TODO(jar): Integrate bounds on log recording more consistently, so that we
+ // can stop recording logs that are too big much sooner.
+ if (log_manager_.current_log()->num_events() > kEventLimit) {
+ UMA_HISTOGRAM_COUNTS("UMA.Discarded Log Events",
+ log_manager_.current_log()->num_events());
+ log_manager_.DiscardCurrentLog();
+ OpenNewLog(); // Start trivial log to hold our histograms.
+ }
+
+ // If a persistent allocator is in use, update its internal histograms (such
+ // as how much memory is being used) before reporting.
+ base::PersistentHistogramAllocator* allocator =
+ base::GlobalHistogramAllocator::Get();
+ if (allocator)
+ allocator->UpdateTrackingHistograms();
+
+ // Put incremental data (histogram deltas, and realtime stats deltas) at the
+ // end of all log transmissions (initial log handles this separately).
+ // RecordIncrementalStabilityElements only exists on the derived
+ // MetricsLog class.
+ MetricsLog* current_log = log_manager_.current_log();
+ DCHECK(current_log);
+ RecordCurrentEnvironment(current_log);
+ base::TimeDelta incremental_uptime;
+ base::TimeDelta uptime;
+ GetUptimes(local_state_, &incremental_uptime, &uptime);
+ current_log->RecordStabilityMetrics(metrics_providers_.get(),
+ incremental_uptime, uptime);
+
+ current_log->RecordGeneralMetrics(metrics_providers_.get());
+ RecordCurrentHistograms();
+
+ log_manager_.FinishCurrentLog();
+}
+
+void MetricsService::PushPendingLogsToPersistentStorage() {
+ if (state_ < SENDING_LOGS)
+ return; // We didn't and still don't have time to get plugin list etc.
+
+ CloseCurrentLog();
+ log_manager_.PersistUnsentLogs();
+}
+
+//------------------------------------------------------------------------------
+// Transmission of logs methods
+
+void MetricsService::StartSchedulerIfNecessary() {
+ // Never schedule cutting or uploading of logs in test mode.
+ if (test_mode_active_)
+ return;
+
+ // Even if reporting is disabled, the scheduler is needed to trigger the
+ // creation of the initial log, which must be done in order for any logs to be
+ // persisted on shutdown or backgrounding.
+ if (recording_active() &&
+ (reporting_active() || state_ < SENDING_LOGS)) {
+ scheduler_->Start();
+ }
+}
+
+void MetricsService::StartScheduledUpload() {
+ DCHECK(state_ >= INIT_TASK_DONE);
+ // If we're getting no notifications, then the log won't have much in it, and
+ // it's possible the computer is about to go to sleep, so don't upload and
+ // stop the scheduler.
+ // If recording has been turned off, the scheduler doesn't need to run.
+ // If reporting is off, proceed if the initial log hasn't been created, since
+ // that has to happen in order for logs to be cut and stored when persisting.
+ // TODO(stuartmorgan): Call Stop() on the scheduler when reporting and/or
+ // recording are turned off instead of letting it fire and then aborting.
+ if (idle_since_last_transmission_ ||
+ !recording_active() ||
+ (!reporting_active() && state_ >= SENDING_LOGS)) {
+ scheduler_->Stop();
+ scheduler_->UploadCancelled();
+ return;
+ }
+
+ // If there are unsent logs, send the next one. If not, start the asynchronous
+ // process of finalizing the current log for upload.
+ if (state_ == SENDING_LOGS && log_manager_.has_unsent_logs()) {
+ SendNextLog();
+ } else {
+ // There are no logs left to send, so start creating a new one.
+ client_->CollectFinalMetricsForLog(
+ base::Bind(&MetricsService::OnFinalLogInfoCollectionDone,
+ self_ptr_factory_.GetWeakPtr()));
+ }
+}
+
+void MetricsService::OnFinalLogInfoCollectionDone() {
+ // If somehow there is a log upload in progress, we return and hope things
+ // work out. The scheduler isn't informed since if this happens, the scheduler
+ // will get a response from the upload.
+ DCHECK(!log_upload_in_progress_);
+ if (log_upload_in_progress_)
+ return;
+
+ // Abort if metrics were turned off during the final info gathering.
+ if (!recording_active()) {
+ scheduler_->Stop();
+ scheduler_->UploadCancelled();
+ return;
+ }
+
+ if (state_ == INIT_TASK_DONE) {
+ PrepareInitialMetricsLog();
+ } else {
+ DCHECK_EQ(SENDING_LOGS, state_);
+ CloseCurrentLog();
+ OpenNewLog();
+ }
+ SendNextLog();
+}
+
+void MetricsService::SendNextLog() {
+ DCHECK_EQ(SENDING_LOGS, state_);
+ if (!reporting_active()) {
+ scheduler_->Stop();
+ scheduler_->UploadCancelled();
+ return;
+ }
+ if (!log_manager_.has_unsent_logs()) {
+ // Should only get here if serializing the log failed somehow.
+ // Just tell the scheduler it was uploaded and wait for the next log
+ // interval.
+ scheduler_->UploadFinished(true, log_manager_.has_unsent_logs());
+ return;
+ }
+ if (!log_manager_.has_staged_log())
+ log_manager_.StageNextLogForUpload();
+
+ // Proceed to stage the log for upload if log size satisfies cellular log
+ // upload constrains.
+ bool is_cellular_logic = client_->IsUMACellularUploadLogicEnabled();
+ if (is_cellular_logic && data_use_tracker_ &&
+ !data_use_tracker_->ShouldUploadLogOnCellular(
+ log_manager_.staged_log_hash().size())) {
+ scheduler_->UploadCancelled();
+ } else {
+ SendStagedLog();
+ }
+}
+
+bool MetricsService::ProvidersHaveInitialStabilityMetrics() {
+ // Check whether any metrics provider has initial stability metrics.
+ for (MetricsProvider* provider : metrics_providers_) {
+ if (provider->HasInitialStabilityMetrics())
+ return true;
+ }
+
+ return false;
+}
+
+bool MetricsService::PrepareInitialStabilityLog() {
+ DCHECK_EQ(INITIALIZED, state_);
+
+ scoped_ptr<MetricsLog> initial_stability_log(
+ CreateLog(MetricsLog::INITIAL_STABILITY_LOG));
+
+ // Do not call NotifyOnDidCreateMetricsLog here because the stability
+ // log describes stats from the _previous_ session.
+
+ if (!initial_stability_log->LoadSavedEnvironmentFromPrefs())
+ return false;
+
+ log_manager_.PauseCurrentLog();
+ log_manager_.BeginLoggingWithLog(std::move(initial_stability_log));
+
+ // Note: Some stability providers may record stability stats via histograms,
+ // so this call has to be after BeginLoggingWithLog().
+ log_manager_.current_log()->RecordStabilityMetrics(
+ metrics_providers_.get(), base::TimeDelta(), base::TimeDelta());
+ RecordCurrentStabilityHistograms();
+
+ // Note: RecordGeneralMetrics() intentionally not called since this log is for
+ // stability stats from a previous session only.
+
+ log_manager_.FinishCurrentLog();
+ log_manager_.ResumePausedLog();
+
+ // Store unsent logs, including the stability log that was just saved, so
+ // that they're not lost in case of a crash before upload time.
+ log_manager_.PersistUnsentLogs();
+
+ return true;
+}
+
+void MetricsService::PrepareInitialMetricsLog() {
+ DCHECK_EQ(INIT_TASK_DONE, state_);
+
+ RecordCurrentEnvironment(initial_metrics_log_.get());
+ base::TimeDelta incremental_uptime;
+ base::TimeDelta uptime;
+ GetUptimes(local_state_, &incremental_uptime, &uptime);
+
+ // Histograms only get written to the current log, so make the new log current
+ // before writing them.
+ log_manager_.PauseCurrentLog();
+ log_manager_.BeginLoggingWithLog(std::move(initial_metrics_log_));
+
+ // Note: Some stability providers may record stability stats via histograms,
+ // so this call has to be after BeginLoggingWithLog().
+ MetricsLog* current_log = log_manager_.current_log();
+ current_log->RecordStabilityMetrics(metrics_providers_.get(),
+ base::TimeDelta(), base::TimeDelta());
+ current_log->RecordGeneralMetrics(metrics_providers_.get());
+ RecordCurrentHistograms();
+
+ log_manager_.FinishCurrentLog();
+ log_manager_.ResumePausedLog();
+
+ // Store unsent logs, including the initial log that was just saved, so
+ // that they're not lost in case of a crash before upload time.
+ log_manager_.PersistUnsentLogs();
+
+ state_ = SENDING_LOGS;
+}
+
+void MetricsService::SendStagedLog() {
+ DCHECK(log_manager_.has_staged_log());
+ if (!log_manager_.has_staged_log())
+ return;
+
+ DCHECK(!log_upload_in_progress_);
+ log_upload_in_progress_ = true;
+
+ if (!ShouldUploadLog()) {
+ SkipAndDiscardUpload();
+ return;
+ }
+
+ if (!log_uploader_) {
+ log_uploader_ = client_->CreateUploader(
+ base::Bind(&MetricsService::OnLogUploadComplete,
+ self_ptr_factory_.GetWeakPtr()));
+ }
+
+ const std::string hash =
+ base::HexEncode(log_manager_.staged_log_hash().data(),
+ log_manager_.staged_log_hash().size());
+ log_uploader_->UploadLog(log_manager_.staged_log(), hash);
+
+ HandleIdleSinceLastTransmission(true);
+}
+
+
+void MetricsService::OnLogUploadComplete(int response_code) {
+ DCHECK_EQ(SENDING_LOGS, state_);
+ DCHECK(log_upload_in_progress_);
+ log_upload_in_progress_ = false;
+
+ // Log a histogram to track response success vs. failure rates.
+ UMA_HISTOGRAM_ENUMERATION("UMA.UploadResponseStatus.Protobuf",
+ ResponseCodeToStatus(response_code),
+ NUM_RESPONSE_STATUSES);
+
+ bool upload_succeeded = response_code == 200;
+
+ // Provide boolean for error recovery (allow us to ignore response_code).
+ bool discard_log = false;
+ const size_t log_size = log_manager_.staged_log().length();
+ if (upload_succeeded) {
+ UMA_HISTOGRAM_COUNTS_10000("UMA.LogSize.OnSuccess", log_size / 1024);
+ } else if (log_size > kUploadLogAvoidRetransmitSize) {
+ UMA_HISTOGRAM_COUNTS("UMA.Large Rejected Log was Discarded",
+ static_cast<int>(log_size));
+ discard_log = true;
+ } else if (response_code == 400) {
+ // Bad syntax. Retransmission won't work.
+ discard_log = true;
+ }
+
+ if (upload_succeeded || discard_log) {
+ log_manager_.DiscardStagedLog();
+ // Store the updated list to disk now that the removed log is uploaded.
+ log_manager_.PersistUnsentLogs();
+ }
+
+ // Error 400 indicates a problem with the log, not with the server, so
+ // don't consider that a sign that the server is in trouble.
+ bool server_is_healthy = upload_succeeded || response_code == 400;
+ scheduler_->UploadFinished(server_is_healthy, log_manager_.has_unsent_logs());
+
+ if (server_is_healthy)
+ client_->OnLogUploadComplete();
+}
+
+void MetricsService::IncrementPrefValue(const char* path) {
+ int value = local_state_->GetInteger(path);
+ local_state_->SetInteger(path, value + 1);
+}
+
+void MetricsService::IncrementLongPrefsValue(const char* path) {
+ int64_t value = local_state_->GetInt64(path);
+ local_state_->SetInt64(path, value + 1);
+}
+
+bool MetricsService::UmaMetricsProperlyShutdown() {
+ CHECK(clean_shutdown_status_ == CLEANLY_SHUTDOWN ||
+ clean_shutdown_status_ == NEED_TO_SHUTDOWN);
+ return clean_shutdown_status_ == CLEANLY_SHUTDOWN;
+}
+
+void MetricsService::AddSyntheticTrialObserver(
+ variations::SyntheticTrialObserver* observer) {
+ synthetic_trial_observer_list_.AddObserver(observer);
+ if (!synthetic_trial_groups_.empty())
+ observer->OnSyntheticTrialsChanged(synthetic_trial_groups_);
+}
+
+void MetricsService::RemoveSyntheticTrialObserver(
+ variations::SyntheticTrialObserver* observer) {
+ synthetic_trial_observer_list_.RemoveObserver(observer);
+}
+
+void MetricsService::RegisterSyntheticFieldTrial(
+ const variations::SyntheticTrialGroup& trial) {
+ for (size_t i = 0; i < synthetic_trial_groups_.size(); ++i) {
+ if (synthetic_trial_groups_[i].id.name == trial.id.name) {
+ if (synthetic_trial_groups_[i].id.group != trial.id.group) {
+ synthetic_trial_groups_[i].id.group = trial.id.group;
+ synthetic_trial_groups_[i].start_time = base::TimeTicks::Now();
+ NotifySyntheticTrialObservers();
+ }
+ return;
+ }
+ }
+
+ variations::SyntheticTrialGroup trial_group = trial;
+ trial_group.start_time = base::TimeTicks::Now();
+ synthetic_trial_groups_.push_back(trial_group);
+ NotifySyntheticTrialObservers();
+}
+
+void MetricsService::GetCurrentSyntheticFieldTrialsForTesting(
+ std::vector<variations::ActiveGroupId>* synthetic_trials) {
+ GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(), synthetic_trials);
+}
+
+void MetricsService::RegisterMetricsProvider(
+ scoped_ptr<MetricsProvider> provider) {
+ DCHECK_EQ(INITIALIZED, state_);
+ metrics_providers_.push_back(std::move(provider));
+}
+
+void MetricsService::CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ state_manager_->CheckForClonedInstall(task_runner);
+}
+
+void MetricsService::NotifySyntheticTrialObservers() {
+ FOR_EACH_OBSERVER(variations::SyntheticTrialObserver,
+ synthetic_trial_observer_list_,
+ OnSyntheticTrialsChanged(synthetic_trial_groups_));
+}
+
+void MetricsService::GetSyntheticFieldTrialsOlderThan(
+ base::TimeTicks time,
+ std::vector<variations::ActiveGroupId>* synthetic_trials) {
+ DCHECK(synthetic_trials);
+ synthetic_trials->clear();
+ for (size_t i = 0; i < synthetic_trial_groups_.size(); ++i) {
+ if (synthetic_trial_groups_[i].start_time <= time)
+ synthetic_trials->push_back(synthetic_trial_groups_[i].id);
+ }
+}
+
+scoped_ptr<MetricsLog> MetricsService::CreateLog(MetricsLog::LogType log_type) {
+ return make_scoped_ptr(new MetricsLog(state_manager_->client_id(),
+ session_id_,
+ log_type,
+ client_,
+ local_state_));
+}
+
+void MetricsService::RecordCurrentEnvironment(MetricsLog* log) {
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+ GetSyntheticFieldTrialsOlderThan(log->creation_time(), &synthetic_trials);
+ log->RecordEnvironment(metrics_providers_.get(), synthetic_trials,
+ GetInstallDate(), GetMetricsReportingEnabledDate());
+}
+
+void MetricsService::RecordCurrentHistograms() {
+ DCHECK(log_manager_.current_log());
+ histogram_snapshot_manager_.StartDeltas();
+ // "true" to the begin() call indicates that StatisticsRecorder should include
+ // histograms held in persistent storage.
+ auto end = base::StatisticsRecorder::end();
+ for (auto it = base::StatisticsRecorder::begin(true); it != end; ++it) {
+ if ((*it)->flags() & base::Histogram::kUmaTargetedHistogramFlag)
+ histogram_snapshot_manager_.PrepareDelta(*it);
+ }
+ for (MetricsProvider* provider : metrics_providers_)
+ provider->RecordHistogramSnapshots(&histogram_snapshot_manager_);
+ histogram_snapshot_manager_.FinishDeltas();
+}
+
+void MetricsService::RecordCurrentStabilityHistograms() {
+ DCHECK(log_manager_.current_log());
+ // "true" indicates that StatisticsRecorder should include histograms in
+ // persistent storage.
+ histogram_snapshot_manager_.PrepareDeltas(
+ base::StatisticsRecorder::begin(true), base::StatisticsRecorder::end(),
+ base::Histogram::kNoFlags, base::Histogram::kUmaStabilityHistogramFlag);
+}
+
+void MetricsService::LogCleanShutdown() {
+ // Redundant setting to assure that we always reset this value at shutdown
+ // (and that we don't use some alternate path, and not call LogCleanShutdown).
+ clean_shutdown_status_ = CLEANLY_SHUTDOWN;
+
+ clean_exit_beacon_.WriteBeaconValue(true);
+ RecordCurrentState(local_state_);
+ local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+ MetricsService::SHUTDOWN_COMPLETE);
+}
+
+bool MetricsService::ShouldLogEvents() {
+ // We simply don't log events to UMA if there is a single incognito
+ // session visible. The problem is that we always notify using the original
+ // profile in order to simplify notification processing.
+ return !client_->IsOffTheRecordSessionActive();
+}
+
+void MetricsService::RecordBooleanPrefValue(const char* path, bool value) {
+ DCHECK(IsSingleThreaded());
+ local_state_->SetBoolean(path, value);
+ RecordCurrentState(local_state_);
+}
+
+void MetricsService::RecordCurrentState(PrefService* pref) {
+ pref->SetInt64(prefs::kStabilityLastTimestampSec,
+ base::Time::Now().ToTimeT());
+}
+
+void MetricsService::SkipAndDiscardUpload() {
+ log_manager_.DiscardStagedLog();
+ scheduler_->UploadCancelled();
+ log_upload_in_progress_ = false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service.h b/chromium/components/metrics/metrics_service.h
new file mode 100644
index 00000000000..f02df4bcabf
--- /dev/null
+++ b/chromium/components/metrics/metrics_service.h
@@ -0,0 +1,501 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a service that collects information about the user
+// experience in order to help improve future versions of the app.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/user_metrics.h"
+#include "base/observer_list.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/clean_exit_beacon.h"
+#include "components/metrics/data_use_tracker.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_manager.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/net/network_metrics_provider.h"
+#include "components/variations/synthetic_trials.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class DictionaryValue;
+class HistogramSamples;
+class PrefService;
+}
+
+namespace variations {
+struct ActiveGroupId;
+}
+
+namespace net {
+class URLFetcher;
+}
+
+namespace metrics {
+
+class MetricsLogUploader;
+class MetricsReportingScheduler;
+class MetricsServiceAccessor;
+class MetricsServiceClient;
+class MetricsStateManager;
+
+// See metrics_service.cc for a detailed description.
+class MetricsService : public base::HistogramFlattener {
+ public:
+ // The execution phase of the browser.
+ enum ExecutionPhase {
+ UNINITIALIZED_PHASE = 0,
+ START_METRICS_RECORDING = 100,
+ CREATE_PROFILE = 200,
+ STARTUP_TIMEBOMB_ARM = 300,
+ THREAD_WATCHER_START = 400,
+ MAIN_MESSAGE_LOOP_RUN = 500,
+ SHUTDOWN_TIMEBOMB_ARM = 600,
+ SHUTDOWN_COMPLETE = 700,
+ };
+
+ // Creates the MetricsService with the given |state_manager|, |client|, and
+ // |local_state|. Does not take ownership of the paramaters; instead stores
+ // a weak pointer to each. Caller should ensure that the parameters are valid
+ // for the lifetime of this class.
+ MetricsService(MetricsStateManager* state_manager,
+ MetricsServiceClient* client,
+ PrefService* local_state);
+ ~MetricsService() override;
+
+ // Initializes metrics recording state. Updates various bookkeeping values in
+ // prefs and sets up the scheduler. This is a separate function rather than
+ // being done by the constructor so that field trials could be created before
+ // this is run.
+ void InitializeMetricsRecordingState();
+
+ // Starts the metrics system, turning on recording and uploading of metrics.
+ // Should be called when starting up with metrics enabled, or when metrics
+ // are turned on.
+ void Start();
+
+ // Starts the metrics system in a special test-only mode. Metrics won't ever
+ // be uploaded or persisted in this mode, but metrics will be recorded in
+ // memory.
+ void StartRecordingForTests();
+
+ // Shuts down the metrics system. Should be called at shutdown, or if metrics
+ // are turned off.
+ void Stop();
+
+ // Enable/disable transmission of accumulated logs and crash reports (dumps).
+ // Calling Start() automatically enables reporting, but sending is
+ // asyncronous so this can be called immediately after Start() to prevent
+ // any uploading.
+ void EnableReporting();
+ void DisableReporting();
+
+ // Returns the client ID for this client, or the empty string if metrics
+ // recording is not currently running.
+ std::string GetClientId();
+
+ // Returns the install date of the application, in seconds since the epoch.
+ int64_t GetInstallDate();
+
+ // Returns the date at which the current metrics client ID was created as
+ // an int64_t containing seconds since the epoch.
+ int64_t GetMetricsReportingEnabledDate();
+
+ // Returns true if the last session exited cleanly.
+ bool WasLastShutdownClean() const;
+
+ // Returns the preferred entropy provider used to seed persistent activities
+ // based on whether or not metrics reporting will be permitted on this client.
+ //
+ // If metrics reporting is enabled, this method returns an entropy provider
+ // that has a high source of entropy, partially based on the client ID.
+ // Otherwise, it returns an entropy provider that is based on a low entropy
+ // source.
+ scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider();
+
+ // At startup, prefs needs to be called with a list of all the pref names and
+ // types we'll be using.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // HistogramFlattener:
+ void RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) override;
+ void InconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override;
+ void UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override;
+ void InconsistencyDetectedInLoggedCount(int amount) override;
+
+ // This should be called when the application is not idle, i.e. the user seems
+ // to be interacting with the application.
+ void OnApplicationNotIdle();
+
+ // Invoked when we get a WM_SESSIONEND. This places a value in prefs that is
+ // reset when RecordCompletedSessionEnd is invoked.
+ void RecordStartOfSessionEnd();
+
+ // This should be called when the application is shutting down. It records
+ // that session end was successful.
+ void RecordCompletedSessionEnd();
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+ // Called when the application is going into background mode.
+ void OnAppEnterBackground();
+
+ // Called when the application is coming out of background mode.
+ void OnAppEnterForeground();
+#else
+ // Set the dirty flag, which will require a later call to LogCleanShutdown().
+ void LogNeedForCleanShutdown();
+#endif // defined(OS_ANDROID) || defined(OS_IOS)
+
+ static void SetExecutionPhase(ExecutionPhase execution_phase,
+ PrefService* local_state);
+
+ // Saves in the preferences if the crash report registration was successful.
+ // This count is eventually send via UMA logs.
+ void RecordBreakpadRegistration(bool success);
+
+ // Saves in the preferences if the browser is running under a debugger.
+ // This count is eventually send via UMA logs.
+ void RecordBreakpadHasDebugger(bool has_debugger);
+
+ bool recording_active() const;
+ bool reporting_active() const;
+
+ // Redundant test to ensure that we are notified of a clean exit.
+ // This value should be true when process has completed shutdown.
+ static bool UmaMetricsProperlyShutdown();
+
+ // Public accessor that returns the list of synthetic field trials. It must
+ // only be used for testing.
+ void GetCurrentSyntheticFieldTrialsForTesting(
+ std::vector<variations::ActiveGroupId>* synthetic_trials);
+
+ // Adds an observer to be notified when the synthetic trials list changes.
+ void AddSyntheticTrialObserver(variations::SyntheticTrialObserver* observer);
+
+ // Removes an existing observer of synthetic trials list changes.
+ void RemoveSyntheticTrialObserver(
+ variations::SyntheticTrialObserver* observer);
+
+ // Register the specified |provider| to provide additional metrics into the
+ // UMA log. Should be called during MetricsService initialization only.
+ void RegisterMetricsProvider(scoped_ptr<MetricsProvider> provider);
+
+ // Check if this install was cloned or imaged from another machine. If a
+ // clone is detected, reset the client id and low entropy source. This
+ // should not be called more than once.
+ void CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Clears the stability metrics that are saved in local state.
+ void ClearSavedStabilityMetrics();
+
+ // Pushes a log that has been generated by an external component.
+ void PushExternalLog(const std::string& log);
+
+ // Returns a callback to data use pref updating function which can be called
+ // from any thread, but this function should be called on UI thread.
+ UpdateUsagePrefCallbackType GetDataUseForwardingCallback();
+
+ protected:
+ // Exposed for testing.
+ MetricsLogManager* log_manager() { return &log_manager_; }
+
+ private:
+ friend class MetricsServiceAccessor;
+
+ // The MetricsService has a lifecycle that is stored as a state.
+ // See metrics_service.cc for description of this lifecycle.
+ enum State {
+ INITIALIZED, // Constructor was called.
+ INIT_TASK_SCHEDULED, // Waiting for deferred init tasks to finish.
+ INIT_TASK_DONE, // Waiting for timer to send initial log.
+ SENDING_LOGS, // Sending logs an creating new ones when we run out.
+ };
+
+ enum ShutdownCleanliness {
+ CLEANLY_SHUTDOWN = 0xdeadbeef,
+ NEED_TO_SHUTDOWN = ~CLEANLY_SHUTDOWN
+ };
+
+ // The current state of recording for the MetricsService. The state is UNSET
+ // until set to something else, at which point it remains INACTIVE or ACTIVE
+ // for the lifetime of the object.
+ enum RecordingState {
+ INACTIVE,
+ ACTIVE,
+ UNSET
+ };
+
+ typedef std::vector<variations::SyntheticTrialGroup> SyntheticTrialGroups;
+
+ // Registers a field trial name and group to be used to annotate a UMA report
+ // with a particular Chrome configuration state. A UMA report will be
+ // annotated with this trial group if and only if all events in the report
+ // were created after the trial is registered. Only one group name may be
+ // registered at a time for a given trial_name. Only the last group name that
+ // is registered for a given trial name will be recorded. The values passed
+ // in must not correspond to any real field trial in the code.
+ void RegisterSyntheticFieldTrial(
+ const variations::SyntheticTrialGroup& trial_group);
+
+ // Calls into the client to initialize some system profile metrics.
+ void StartInitTask();
+
+ // Callback that moves the state to INIT_TASK_DONE. When this is called, the
+ // state should be INIT_TASK_SCHEDULED.
+ void FinishedInitTask();
+
+ void OnUserAction(const std::string& action);
+
+ // Get the amount of uptime since this process started and since the last
+ // call to this function. Also updates the cumulative uptime metric (stored
+ // as a pref) for uninstall. Uptimes are measured using TimeTicks, which
+ // guarantees that it is monotonic and does not jump if the user changes
+ // his/her clock. The TimeTicks implementation also makes the clock not
+ // count time the computer is suspended.
+ void GetUptimes(PrefService* pref,
+ base::TimeDelta* incremental_uptime,
+ base::TimeDelta* uptime);
+
+ // Turns recording on or off.
+ // DisableRecording() also forces a persistent save of logging state (if
+ // anything has been recorded, or transmitted).
+ void EnableRecording();
+ void DisableRecording();
+
+ // If in_idle is true, sets idle_since_last_transmission to true.
+ // If in_idle is false and idle_since_last_transmission_ is true, sets
+ // idle_since_last_transmission to false and starts the timer (provided
+ // starting the timer is permitted).
+ void HandleIdleSinceLastTransmission(bool in_idle);
+
+ // Set up client ID, session ID, etc.
+ void InitializeMetricsState();
+
+ // Notifies providers when a new metrics log is created.
+ void NotifyOnDidCreateMetricsLog();
+
+ // Schedule the next save of LocalState information. This is called
+ // automatically by the task that performs each save to schedule the next one.
+ void ScheduleNextStateSave();
+
+ // Save the LocalState information immediately. This should not be called by
+ // anybody other than the scheduler to avoid doing too many writes. When you
+ // make a change, call ScheduleNextStateSave() instead.
+ void SaveLocalState();
+
+ // Opens a new log for recording user experience metrics.
+ void OpenNewLog();
+
+ // Closes out the current log after adding any last information.
+ void CloseCurrentLog();
+
+ // Pushes the text of the current and staged logs into persistent storage.
+ // Called when Chrome shuts down.
+ void PushPendingLogsToPersistentStorage();
+
+ // Ensures that scheduler is running, assuming the current settings are such
+ // that metrics should be reported. If not, this is a no-op.
+ void StartSchedulerIfNecessary();
+
+ // Starts the process of uploading metrics data.
+ void StartScheduledUpload();
+
+ // Called by the client via a callback when final log info collection is
+ // complete.
+ void OnFinalLogInfoCollectionDone();
+
+ // If recording is enabled, begins uploading the next completed log from
+ // the log manager, staging it if necessary.
+ void SendNextLog();
+
+ // Returns true if any of the registered metrics providers have critical
+ // stability metrics to report in an initial stability log.
+ bool ProvidersHaveInitialStabilityMetrics();
+
+ // Prepares the initial stability log, which is only logged when the previous
+ // run of Chrome crashed. This log contains any stability metrics left over
+ // from that previous run, and only these stability metrics. It uses the
+ // system profile from the previous session. Returns true if a log was
+ // created.
+ bool PrepareInitialStabilityLog();
+
+ // Prepares the initial metrics log, which includes startup histograms and
+ // profiler data, as well as incremental stability-related metrics.
+ void PrepareInitialMetricsLog();
+
+ // Uploads the currently staged log (which must be non-null).
+ void SendStagedLog();
+
+ // Called after transmission completes (either successfully or with failure).
+ void OnLogUploadComplete(int response_code);
+
+ // Reads, increments and then sets the specified integer preference.
+ void IncrementPrefValue(const char* path);
+
+ // Reads, increments and then sets the specified long preference that is
+ // stored as a string.
+ void IncrementLongPrefsValue(const char* path);
+
+ // Records that the browser was shut down cleanly.
+ void LogCleanShutdown();
+
+ // Records state that should be periodically saved, like uptime and
+ // buffered plugin stability statistics.
+ void RecordCurrentState(PrefService* pref);
+
+ // Checks whether events should currently be logged.
+ bool ShouldLogEvents();
+
+ // Sets the value of the specified path in prefs and schedules a save.
+ void RecordBooleanPrefValue(const char* path, bool value);
+
+ // Notifies observers on a synthetic trial list change.
+ void NotifySyntheticTrialObservers();
+
+ // Returns a list of synthetic field trials that are older than |time|.
+ void GetSyntheticFieldTrialsOlderThan(
+ base::TimeTicks time,
+ std::vector<variations::ActiveGroupId>* synthetic_trials);
+
+ // Creates a new MetricsLog instance with the given |log_type|.
+ scoped_ptr<MetricsLog> CreateLog(MetricsLog::LogType log_type);
+
+ // Records the current environment (system profile) in |log|.
+ void RecordCurrentEnvironment(MetricsLog* log);
+
+ // Record complete list of histograms into the current log.
+ // Called when we close a log.
+ void RecordCurrentHistograms();
+
+ // Record complete list of stability histograms into the current log,
+ // i.e., histograms with the |kUmaStabilityHistogramFlag| flag set.
+ void RecordCurrentStabilityHistograms();
+
+ // Skips staged upload and discards the log. Used in case of unsuccessful
+ // upload or intentional sampling of logs.
+ void SkipAndDiscardUpload();
+
+ // Manager for the various in-flight logs.
+ MetricsLogManager log_manager_;
+
+ // |histogram_snapshot_manager_| prepares histogram deltas for transmission.
+ base::HistogramSnapshotManager histogram_snapshot_manager_;
+
+ // Used to manage various metrics reporting state prefs, such as client id,
+ // low entropy source and whether metrics reporting is enabled. Weak pointer.
+ MetricsStateManager* const state_manager_;
+
+ // Used to interact with the embedder. Weak pointer; must outlive |this|
+ // instance.
+ MetricsServiceClient* const client_;
+
+ // Registered metrics providers.
+ ScopedVector<MetricsProvider> metrics_providers_;
+
+ PrefService* local_state_;
+
+ CleanExitBeacon clean_exit_beacon_;
+
+ base::ActionCallback action_callback_;
+
+ // Indicate whether recording and reporting are currently happening.
+ // These should not be set directly, but by calling SetRecording and
+ // SetReporting.
+ RecordingState recording_state_;
+ bool reporting_active_;
+
+ // Indicate whether test mode is enabled, where the initial log should never
+ // be cut, and logs are neither persisted nor uploaded.
+ bool test_mode_active_;
+
+ // The progression of states made by the browser are recorded in the following
+ // state.
+ State state_;
+
+ // The initial metrics log, used to record startup metrics (histograms and
+ // profiler data). Note that if a crash occurred in the previous session, an
+ // initial stability log may be sent before this.
+ scoped_ptr<MetricsLog> initial_metrics_log_;
+
+ // Instance of the helper class for uploading logs.
+ scoped_ptr<MetricsLogUploader> log_uploader_;
+
+ // Whether there is a current log upload in progress.
+ bool log_upload_in_progress_;
+
+ // Whether the MetricsService object has received any notifications since
+ // the last time a transmission was sent.
+ bool idle_since_last_transmission_;
+
+ // A number that identifies the how many times the app has been launched.
+ int session_id_;
+
+ // The scheduler for determining when uploads should happen.
+ scoped_ptr<MetricsReportingScheduler> scheduler_;
+
+ // Stores the time of the first call to |GetUptimes()|.
+ base::TimeTicks first_updated_time_;
+
+ // Stores the time of the last call to |GetUptimes()|.
+ base::TimeTicks last_updated_time_;
+
+ // Field trial groups that map to Chrome configuration states.
+ SyntheticTrialGroups synthetic_trial_groups_;
+
+ // List of observers of |synthetic_trial_groups_| changes.
+ base::ObserverList<variations::SyntheticTrialObserver>
+ synthetic_trial_observer_list_;
+
+ // Execution phase the browser is in.
+ static ExecutionPhase execution_phase_;
+
+ // Redundant marker to check that we completed our shutdown, and set the
+ // exited-cleanly bit in the prefs.
+ static ShutdownCleanliness clean_shutdown_status_;
+
+ FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, IsPluginProcess);
+ FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest,
+ PermutedEntropyCacheClearedWhenLowEntropyReset);
+ FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, RegisterSyntheticTrial);
+
+ // Pointer used for obtaining data use pref updater callback on above layers.
+ scoped_ptr<DataUseTracker> data_use_tracker_;
+
+ // Weak pointers factory used to post task on different threads. All weak
+ // pointers managed by this factory have the same lifetime as MetricsService.
+ base::WeakPtrFactory<MetricsService> self_ptr_factory_;
+
+ // Weak pointers factory used for saving state. All weak pointers managed by
+ // this factory are invalidated in ScheduleNextStateSave.
+ base::WeakPtrFactory<MetricsService> state_saver_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsService);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SERVICE_H_
diff --git a/chromium/components/metrics/metrics_service_accessor.cc b/chromium/components/metrics/metrics_service_accessor.cc
new file mode 100644
index 00000000000..ac04ba9884d
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_accessor.cc
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service_accessor.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/metrics_util.h"
+
+namespace metrics {
+
+// static
+bool MetricsServiceAccessor::IsMetricsReportingEnabled(
+ PrefService* pref_service) {
+ return IsMetricsReportingEnabledWithPrefValue(
+ pref_service->GetBoolean(prefs::kMetricsReportingEnabled));
+}
+
+// static
+bool MetricsServiceAccessor::IsMetricsReportingEnabledWithPrefValue(
+ bool enabled_in_prefs) {
+#if defined(GOOGLE_CHROME_BUILD)
+ // In official builds, disable metrics when reporting field trials are
+ // forced; otherwise, use the value of the user's preference to determine
+ // whether to enable metrics reporting.
+ return !base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceFieldTrials) &&
+ enabled_in_prefs;
+#else
+ // In non-official builds, disable metrics reporting completely.
+ return false;
+#endif // defined(GOOGLE_CHROME_BUILD)
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrial(
+ MetricsService* metrics_service,
+ const std::string& trial_name,
+ const std::string& group_name) {
+ return RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ metrics_service, HashName(trial_name), HashName(group_name));
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ const std::string& group_name) {
+ return RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ metrics_service, trial_name_hash, HashName(group_name));
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ uint32_t group_name_hash) {
+ if (!metrics_service)
+ return false;
+
+ variations::SyntheticTrialGroup trial_group(trial_name_hash, group_name_hash);
+ metrics_service->RegisterSyntheticFieldTrial(trial_group);
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service_accessor.h b/chromium/components/metrics/metrics_service_accessor.h
new file mode 100644
index 00000000000..8d34675b5fe
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_accessor.h
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "base/macros.h"
+
+class PrefService;
+
+namespace metrics {
+
+class MetricsService;
+
+// This class limits and documents access to metrics service helper methods.
+// These methods are protected so each user has to inherit own program-specific
+// specialization and enable access there by declaring friends.
+class MetricsServiceAccessor {
+ protected:
+ // Constructor declared as protected to enable inheritance. Descendants should
+ // disallow instantiation.
+ MetricsServiceAccessor() {}
+
+ // Returns whether metrics reporting is enabled, using the value of the
+ // kMetricsReportingEnabled pref in |pref_service| to determine whether user
+ // has enabled reporting.
+ // NOTE: This method currently does not return the correct value on ChromeOS
+ // and Android due to http://crbug.com/362192 and http://crbug.com/532084. See
+ // ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled().
+ static bool IsMetricsReportingEnabled(PrefService* pref_service);
+
+ // Returns whether metrics reporting is enabled, using the value of
+ // |enabled_in_prefs| to determine whether the user has enabled reporting.
+ // Exists because kMetricsReportingEnabled is currently not used on all
+ // platforms.
+ // TODO(gayane): Consolidate metric prefs on all platforms and eliminate this
+ // method. http://crbug.com/362192, http://crbug.com/532084
+ static bool IsMetricsReportingEnabledWithPrefValue(bool enabled_in_prefs);
+
+ // Registers a field trial name and group with |metrics_service| (if not
+ // null), to be used to annotate a UMA report with a particular configuration
+ // state. A UMA report will be annotated with this trial group if and only if
+ // all events in the report were created after the trial is registered. Only
+ // one group name may be registered at a time for a given trial name. Only the
+ // last group name that is registered for a given trial name will be recorded.
+ // The values passed in must not correspond to any real field trial in the
+ // code. Returns true on success.
+ // See the comment on MetricsService::RegisterSyntheticFieldTrial for details.
+ static bool RegisterSyntheticFieldTrial(MetricsService* metrics_service,
+ const std::string& trial_name,
+ const std::string& group_name);
+
+ // Same as RegisterSyntheticFieldTrial above, but takes in the trial name as a
+ // hash rather than computing the hash from the string.
+ static bool RegisterSyntheticFieldTrialWithNameHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ const std::string& group_name);
+
+ // Same as RegisterSyntheticFieldTrial above, but takes in the trial and group
+ // names as hashes rather than computing those hashes from the strings.
+ static bool RegisterSyntheticFieldTrialWithNameAndGroupHash(
+ MetricsService* metrics_service,
+ uint32_t trial_name_hash,
+ uint32_t group_name_hash);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MetricsServiceAccessor);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
diff --git a/chromium/components/metrics/metrics_service_client.cc b/chromium/components/metrics/metrics_service_client.cc
new file mode 100644
index 00000000000..cd5b53ca802
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_client.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service_client.h"
+
+namespace metrics {
+
+base::string16 MetricsServiceClient::GetRegistryBackupKey() {
+ return base::string16();
+}
+
+bool MetricsServiceClient::IsReportingPolicyManaged() {
+ return false;
+}
+
+MetricsServiceClient::EnableMetricsDefault
+MetricsServiceClient::GetDefaultOptIn() {
+ return DEFAULT_UNKNOWN;
+}
+
+bool MetricsServiceClient::IsUMACellularUploadLogicEnabled() {
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_service_client.h b/chromium/components/metrics/metrics_service_client.h
new file mode 100644
index 00000000000..43c5d136ca8
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_client.h
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace metrics {
+
+class MetricsLogUploader;
+class MetricsService;
+
+// An abstraction of operations that depend on the embedder's (e.g. Chrome)
+// environment.
+class MetricsServiceClient {
+ public:
+ // Default value of the enable metrics recording. This relates to the state of
+ // the enable checkbox shown on first-run. This enum is used to store values
+ // in a pref, and shouldn't be renumbered.
+ enum EnableMetricsDefault {
+ // We only record the value during first-run. The default of existing
+ // installs is considered unknown.
+ DEFAULT_UNKNOWN,
+ // The first-run checkbox was unchecked by default.
+ OPT_IN,
+ // The first-run checkbox was checked by default.
+ OPT_OUT,
+ };
+
+ virtual ~MetricsServiceClient() {}
+
+ // Returns the MetricsService instance that this client is associated with.
+ // With the exception of testing contexts, the returned instance must be valid
+ // for the lifetime of this object (typically, the embedder's client
+ // implementation will own the MetricsService instance being returned).
+ virtual MetricsService* GetMetricsService() = 0;
+
+ // Registers the client id with other services (e.g. crash reporting), called
+ // when metrics recording gets enabled.
+ virtual void SetMetricsClientId(const std::string& client_id) = 0;
+
+ // Notifies the client that recording is disabled, so that other services
+ // (such as crash reporting) can clear any association with metrics.
+ virtual void OnRecordingDisabled() = 0;
+
+ // Whether there's an "off the record" (aka "Incognito") session active.
+ virtual bool IsOffTheRecordSessionActive() = 0;
+
+ // Returns the product value to use in uploaded reports, which will be used to
+ // set the ChromeUserMetricsExtension.product field. See comments on that
+ // field on why it's an int32_t rather than an enum.
+ virtual int32_t GetProduct() = 0;
+
+ // Returns the current application locale (e.g. "en-US").
+ virtual std::string GetApplicationLocale() = 0;
+
+ // Retrieves the brand code string associated with the install, returning
+ // false if no brand code is available.
+ virtual bool GetBrand(std::string* brand_code) = 0;
+
+ // Returns the release channel (e.g. stable, beta, etc) of the application.
+ virtual SystemProfileProto::Channel GetChannel() = 0;
+
+ // Returns the version of the application as a string.
+ virtual std::string GetVersionString() = 0;
+
+ // Called by the metrics service when a log has been uploaded.
+ virtual void OnLogUploadComplete() = 0;
+
+ // Gathers metrics that will be filled into the system profile protobuf,
+ // calling |done_callback| when complete.
+ virtual void InitializeSystemProfileMetrics(
+ const base::Closure& done_callback) = 0;
+
+ // Called prior to a metrics log being closed, allowing the client to collect
+ // extra histograms that will go in that log. Asynchronous API - the client
+ // implementation should call |done_callback| when complete.
+ virtual void CollectFinalMetricsForLog(
+ const base::Closure& done_callback) = 0;
+
+ // Creates a MetricsLogUploader with the specified parameters (see comments on
+ // MetricsLogUploader for details).
+ virtual scoped_ptr<MetricsLogUploader> CreateUploader(
+ const base::Callback<void(int)>& on_upload_complete) = 0;
+
+ // Returns the standard interval between upload attempts.
+ virtual base::TimeDelta GetStandardUploadInterval() = 0;
+
+ // Returns the name of a key under HKEY_CURRENT_USER that can be used to store
+ // backups of metrics data. Unused except on Windows.
+ virtual base::string16 GetRegistryBackupKey();
+
+ // Called on plugin loading errors.
+ virtual void OnPluginLoadingError(const base::FilePath& plugin_path) {}
+
+ // Called on renderer crashes in some embedders (e.g., those that do not use
+ // //content and thus do not have //content's notification system available
+ // as a mechanism for observing renderer crashes).
+ virtual void OnRendererProcessCrash() {}
+
+ // Returns whether metrics reporting is managed by policy.
+ virtual bool IsReportingPolicyManaged();
+
+ // Gets information about the default value for the enable metrics reporting
+ // checkbox shown during first-run.
+ virtual EnableMetricsDefault GetDefaultOptIn();
+
+ // Returns whether cellular logic is enabled for metrics reporting.
+ virtual bool IsUMACellularUploadLogicEnabled();
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
diff --git a/chromium/components/metrics/metrics_service_unittest.cc b/chromium/components/metrics/metrics_service_unittest.cc
new file mode 100644
index 00000000000..4a8aea770e9
--- /dev/null
+++ b/chromium/components/metrics/metrics_service_unittest.cc
@@ -0,0 +1,413 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/threading/platform_thread.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/metrics_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+void StoreNoClientInfoBackup(const ClientInfo& /* client_info */) {
+}
+
+scoped_ptr<ClientInfo> ReturnNoBackup() {
+ return scoped_ptr<ClientInfo>();
+}
+
+class TestMetricsService : public MetricsService {
+ public:
+ TestMetricsService(MetricsStateManager* state_manager,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : MetricsService(state_manager, client, local_state) {}
+ ~TestMetricsService() override {}
+
+ using MetricsService::log_manager;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsService);
+};
+
+class TestMetricsLog : public MetricsLog {
+ public:
+ TestMetricsLog(const std::string& client_id,
+ int session_id,
+ MetricsServiceClient* client,
+ PrefService* local_state)
+ : MetricsLog(client_id,
+ session_id,
+ MetricsLog::ONGOING_LOG,
+ client,
+ local_state) {}
+
+ ~TestMetricsLog() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsLog);
+};
+
+class MetricsServiceTest : public testing::Test {
+ public:
+ MetricsServiceTest() : is_metrics_reporting_enabled_(false) {
+ MetricsService::RegisterPrefs(testing_local_state_.registry());
+ metrics_state_manager_ = MetricsStateManager::Create(
+ GetLocalState(),
+ base::Bind(&MetricsServiceTest::is_metrics_reporting_enabled,
+ base::Unretained(this)),
+ base::Bind(&StoreNoClientInfoBackup),
+ base::Bind(&ReturnNoBackup));
+ }
+
+ ~MetricsServiceTest() override {
+ MetricsService::SetExecutionPhase(MetricsService::UNINITIALIZED_PHASE,
+ GetLocalState());
+ }
+
+ MetricsStateManager* GetMetricsStateManager() {
+ return metrics_state_manager_.get();
+ }
+
+ PrefService* GetLocalState() { return &testing_local_state_; }
+
+ // Sets metrics reporting as enabled for testing.
+ void EnableMetricsReporting() {
+ is_metrics_reporting_enabled_ = true;
+ }
+
+ // Waits until base::TimeTicks::Now() no longer equals |value|. This should
+ // take between 1-15ms per the documented resolution of base::TimeTicks.
+ void WaitUntilTimeChanges(const base::TimeTicks& value) {
+ while (base::TimeTicks::Now() == value) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+ }
+ }
+
+ // Returns true if there is a synthetic trial in the given vector that matches
+ // the given trial name and trial group; returns false otherwise.
+ bool HasSyntheticTrial(
+ const std::vector<variations::ActiveGroupId>& synthetic_trials,
+ const std::string& trial_name,
+ const std::string& trial_group) {
+ uint32_t trial_name_hash = HashName(trial_name);
+ uint32_t trial_group_hash = HashName(trial_group);
+ for (const variations::ActiveGroupId& trial : synthetic_trials) {
+ if (trial.name == trial_name_hash && trial.group == trial_group_hash)
+ return true;
+ }
+ return false;
+ }
+
+ // Finds a histogram with the specified |name_hash| in |histograms|.
+ const base::HistogramBase* FindHistogram(
+ const base::StatisticsRecorder::Histograms& histograms,
+ uint64_t name_hash) {
+ for (const base::HistogramBase* histogram : histograms) {
+ if (name_hash == base::HashMetricName(histogram->histogram_name()))
+ return histogram;
+ }
+ return nullptr;
+ }
+
+ // Checks whether |uma_log| contains any histograms that are not flagged
+ // with kUmaStabilityHistogramFlag. Stability logs should only contain such
+ // histograms.
+ void CheckForNonStabilityHistograms(
+ const ChromeUserMetricsExtension& uma_log) {
+ const int kStabilityFlags = base::HistogramBase::kUmaStabilityHistogramFlag;
+ base::StatisticsRecorder::Histograms histograms;
+ base::StatisticsRecorder::GetHistograms(&histograms);
+ for (int i = 0; i < uma_log.histogram_event_size(); ++i) {
+ const uint64_t hash = uma_log.histogram_event(i).name_hash();
+
+ const base::HistogramBase* histogram = FindHistogram(histograms, hash);
+ EXPECT_TRUE(histogram) << hash;
+
+ EXPECT_EQ(kStabilityFlags, histogram->flags() & kStabilityFlags) << hash;
+ }
+ }
+
+ private:
+ bool is_metrics_reporting_enabled() const {
+ return is_metrics_reporting_enabled_;
+ }
+
+ bool is_metrics_reporting_enabled_;
+ TestingPrefServiceSimple testing_local_state_;
+ scoped_ptr<MetricsStateManager> metrics_state_manager_;
+ base::MessageLoop message_loop;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsServiceTest);
+};
+
+} // namespace
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAfterCleanShutDown) {
+ EnableMetricsReporting();
+ GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true);
+
+ TestMetricsServiceClient client;
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+ // No initial stability log should be generated.
+ EXPECT_FALSE(service.log_manager()->has_unsent_logs());
+ EXPECT_FALSE(service.log_manager()->has_staged_log());
+
+ // The test provider should not have been called upon to provide initial
+ // stability nor regular stability metrics.
+ EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_FALSE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAtProviderRequest) {
+ EnableMetricsReporting();
+
+ // Save an existing system profile to prefs, to correspond to what would be
+ // saved from a previous session.
+ TestMetricsServiceClient client;
+ TestMetricsLog log("client", 1, &client, GetLocalState());
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(), 0, 0);
+
+ // Record stability build time and version from previous session, so that
+ // stability metrics (including exited cleanly flag) won't be cleared.
+ GetLocalState()->SetInt64(prefs::kStabilityStatsBuildTime,
+ MetricsLog::GetBuildTime());
+ GetLocalState()->SetString(prefs::kStabilityStatsVersion,
+ client.GetVersionString());
+
+ // Set the clean exit flag, as that will otherwise cause a stabilty
+ // log to be produced, irrespective provider requests.
+ GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true);
+
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+ // Add a metrics provider that requests a stability log.
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ test_provider->set_has_initial_stability_metrics(true);
+ service.RegisterMetricsProvider(
+ scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+
+ // The initial stability log should be generated and persisted in unsent logs.
+ MetricsLogManager* log_manager = service.log_manager();
+ EXPECT_TRUE(log_manager->has_unsent_logs());
+ EXPECT_FALSE(log_manager->has_staged_log());
+
+ // The test provider should have been called upon to provide initial
+ // stability and regular stability metrics.
+ EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+
+ // Stage the log and retrieve it.
+ log_manager->StageNextLogForUpload();
+ EXPECT_TRUE(log_manager->has_staged_log());
+
+ std::string uncompressed_log;
+ EXPECT_TRUE(compression::GzipUncompress(log_manager->staged_log(),
+ &uncompressed_log));
+
+ ChromeUserMetricsExtension uma_log;
+ EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log));
+
+ EXPECT_TRUE(uma_log.has_client_id());
+ EXPECT_TRUE(uma_log.has_session_id());
+ EXPECT_TRUE(uma_log.has_system_profile());
+ EXPECT_EQ(0, uma_log.user_action_event_size());
+ EXPECT_EQ(0, uma_log.omnibox_event_size());
+ EXPECT_EQ(0, uma_log.profiler_event_size());
+ EXPECT_EQ(0, uma_log.perf_data_size());
+ CheckForNonStabilityHistograms(uma_log);
+
+ // As there wasn't an unclean shutdown, this log has zero crash count.
+ EXPECT_EQ(0, uma_log.system_profile().stability().crash_count());
+}
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) {
+ EnableMetricsReporting();
+ GetLocalState()->ClearPref(prefs::kStabilityExitedCleanly);
+
+ // Set up prefs to simulate restarting after a crash.
+
+ // Save an existing system profile to prefs, to correspond to what would be
+ // saved from a previous session.
+ TestMetricsServiceClient client;
+ TestMetricsLog log("client", 1, &client, GetLocalState());
+ log.RecordEnvironment(std::vector<MetricsProvider*>(),
+ std::vector<variations::ActiveGroupId>(), 0, 0);
+
+ // Record stability build time and version from previous session, so that
+ // stability metrics (including exited cleanly flag) won't be cleared.
+ GetLocalState()->SetInt64(prefs::kStabilityStatsBuildTime,
+ MetricsLog::GetBuildTime());
+ GetLocalState()->SetString(prefs::kStabilityStatsVersion,
+ client.GetVersionString());
+
+ GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, false);
+
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+ // Add a provider.
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+ service.InitializeMetricsRecordingState();
+
+ // The initial stability log should be generated and persisted in unsent logs.
+ MetricsLogManager* log_manager = service.log_manager();
+ EXPECT_TRUE(log_manager->has_unsent_logs());
+ EXPECT_FALSE(log_manager->has_staged_log());
+
+ // The test provider should have been called upon to provide initial
+ // stability and regular stability metrics.
+ EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+ EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+
+ // Stage the log and retrieve it.
+ log_manager->StageNextLogForUpload();
+ EXPECT_TRUE(log_manager->has_staged_log());
+
+ std::string uncompressed_log;
+ EXPECT_TRUE(compression::GzipUncompress(log_manager->staged_log(),
+ &uncompressed_log));
+
+ ChromeUserMetricsExtension uma_log;
+ EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log));
+
+ EXPECT_TRUE(uma_log.has_client_id());
+ EXPECT_TRUE(uma_log.has_session_id());
+ EXPECT_TRUE(uma_log.has_system_profile());
+ EXPECT_EQ(0, uma_log.user_action_event_size());
+ EXPECT_EQ(0, uma_log.omnibox_event_size());
+ EXPECT_EQ(0, uma_log.profiler_event_size());
+ EXPECT_EQ(0, uma_log.perf_data_size());
+ CheckForNonStabilityHistograms(uma_log);
+
+ EXPECT_EQ(1, uma_log.system_profile().stability().crash_count());
+}
+
+TEST_F(MetricsServiceTest, RegisterSyntheticTrial) {
+ TestMetricsServiceClient client;
+ MetricsService service(GetMetricsStateManager(), &client, GetLocalState());
+
+ // Add two synthetic trials and confirm that they show up in the list.
+ variations::SyntheticTrialGroup trial1(HashName("TestTrial1"),
+ HashName("Group1"));
+ service.RegisterSyntheticFieldTrial(trial1);
+
+ variations::SyntheticTrialGroup trial2(HashName("TestTrial2"),
+ HashName("Group2"));
+ service.RegisterSyntheticFieldTrial(trial2);
+ // Ensure that time has advanced by at least a tick before proceeding.
+ WaitUntilTimeChanges(base::TimeTicks::Now());
+
+ service.log_manager_.BeginLoggingWithLog(scoped_ptr<MetricsLog>(
+ new MetricsLog("clientID",
+ 1,
+ MetricsLog::INITIAL_STABILITY_LOG,
+ &client,
+ GetLocalState())));
+ // Save the time when the log was started (it's okay for this to be greater
+ // than the time recorded by the above call since it's used to ensure the
+ // value changes).
+ const base::TimeTicks begin_log_time = base::TimeTicks::Now();
+
+ std::vector<variations::ActiveGroupId> synthetic_trials;
+ service.GetSyntheticFieldTrialsOlderThan(base::TimeTicks::Now(),
+ &synthetic_trials);
+ EXPECT_EQ(2U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial1", "Group1"));
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+
+ // Ensure that time has advanced by at least a tick before proceeding.
+ WaitUntilTimeChanges(begin_log_time);
+
+ // Change the group for the first trial after the log started.
+ variations::SyntheticTrialGroup trial3(HashName("TestTrial1"),
+ HashName("Group2"));
+ service.RegisterSyntheticFieldTrial(trial3);
+ service.GetSyntheticFieldTrialsOlderThan(begin_log_time, &synthetic_trials);
+ EXPECT_EQ(1U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+
+ // Add a new trial after the log started and confirm that it doesn't show up.
+ variations::SyntheticTrialGroup trial4(HashName("TestTrial3"),
+ HashName("Group3"));
+ service.RegisterSyntheticFieldTrial(trial4);
+ service.GetSyntheticFieldTrialsOlderThan(begin_log_time, &synthetic_trials);
+ EXPECT_EQ(1U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+
+ // Ensure that time has advanced by at least a tick before proceeding.
+ WaitUntilTimeChanges(base::TimeTicks::Now());
+
+ // Start a new log and ensure all three trials appear in it.
+ service.log_manager_.FinishCurrentLog();
+ service.log_manager_.BeginLoggingWithLog(
+ scoped_ptr<MetricsLog>(new MetricsLog(
+ "clientID", 1, MetricsLog::ONGOING_LOG, &client, GetLocalState())));
+ service.GetSyntheticFieldTrialsOlderThan(
+ service.log_manager_.current_log()->creation_time(), &synthetic_trials);
+ EXPECT_EQ(3U, synthetic_trials.size());
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial1", "Group2"));
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial2", "Group2"));
+ EXPECT_TRUE(HasSyntheticTrial(synthetic_trials, "TestTrial3", "Group3"));
+ service.log_manager_.FinishCurrentLog();
+}
+
+TEST_F(MetricsServiceTest,
+ MetricsProviderOnRecordingDisabledCalledOnInitialStop) {
+ TestMetricsServiceClient client;
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+ service.Stop();
+
+ EXPECT_TRUE(test_provider->on_recording_disabled_called());
+}
+
+TEST_F(MetricsServiceTest, MetricsProvidersInitialized) {
+ TestMetricsServiceClient client;
+ TestMetricsService service(
+ GetMetricsStateManager(), &client, GetLocalState());
+
+ TestMetricsProvider* test_provider = new TestMetricsProvider();
+ service.RegisterMetricsProvider(scoped_ptr<MetricsProvider>(test_provider));
+
+ service.InitializeMetricsRecordingState();
+
+ EXPECT_TRUE(test_provider->init_called());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_state_manager.cc b/chromium/components/metrics/metrics_state_manager.cc
new file mode 100644
index 00000000000..89817aa5992
--- /dev/null
+++ b/chromium/components/metrics/metrics_state_manager.cc
@@ -0,0 +1,314 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_state_manager.h"
+
+#include <stddef.h>
+
+#include "base/command_line.h"
+#include "base/guid.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/cloned_install_detector.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/caching_permuted_entropy_provider.h"
+
+namespace metrics {
+
+namespace {
+
+// The argument used to generate a non-identifying entropy source. We want no
+// more than 13 bits of entropy, so use this max to return a number in the range
+// [0, 7999] as the entropy source (12.97 bits of entropy).
+const int kMaxLowEntropySize = 8000;
+
+// Default prefs value for prefs::kMetricsLowEntropySource to indicate that
+// the value has not yet been set.
+const int kLowEntropySourceNotSet = -1;
+
+// Generates a new non-identifying entropy source used to seed persistent
+// activities.
+int GenerateLowEntropySource() {
+ return base::RandInt(0, kMaxLowEntropySize - 1);
+}
+
+} // namespace
+
+// static
+bool MetricsStateManager::instance_exists_ = false;
+
+MetricsStateManager::MetricsStateManager(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& retrieve_client_info)
+ : local_state_(local_state),
+ is_reporting_enabled_callback_(is_reporting_enabled_callback),
+ store_client_info_(store_client_info),
+ load_client_info_(retrieve_client_info),
+ low_entropy_source_(kLowEntropySourceNotSet),
+ entropy_source_returned_(ENTROPY_SOURCE_NONE) {
+ ResetMetricsIDsIfNecessary();
+ if (IsMetricsReportingEnabled())
+ ForceClientIdCreation();
+
+ DCHECK(!instance_exists_);
+ instance_exists_ = true;
+}
+
+MetricsStateManager::~MetricsStateManager() {
+ DCHECK(instance_exists_);
+ instance_exists_ = false;
+}
+
+bool MetricsStateManager::IsMetricsReportingEnabled() {
+ return is_reporting_enabled_callback_.Run();
+}
+
+void MetricsStateManager::ForceClientIdCreation() {
+ if (!client_id_.empty())
+ return;
+
+ client_id_ = local_state_->GetString(prefs::kMetricsClientID);
+ if (!client_id_.empty()) {
+ // It is technically sufficient to only save a backup of the client id when
+ // it is initially generated below, but since the backup was only introduced
+ // in M38, seed it explicitly from here for some time.
+ BackUpCurrentClientInfo();
+ return;
+ }
+
+ const scoped_ptr<ClientInfo> client_info_backup =
+ LoadClientInfoAndMaybeMigrate();
+ if (client_info_backup) {
+ client_id_ = client_info_backup->client_id;
+
+ const base::Time now = base::Time::Now();
+
+ // Save the recovered client id and also try to reinstantiate the backup
+ // values for the dates corresponding with that client id in order to avoid
+ // weird scenarios where we could report an old client id with a recent
+ // install date.
+ local_state_->SetString(prefs::kMetricsClientID, client_id_);
+ local_state_->SetInt64(prefs::kInstallDate,
+ client_info_backup->installation_date != 0
+ ? client_info_backup->installation_date
+ : now.ToTimeT());
+ local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+ client_info_backup->reporting_enabled_date != 0
+ ? client_info_backup->reporting_enabled_date
+ : now.ToTimeT());
+
+ base::TimeDelta recovered_installation_age;
+ if (client_info_backup->installation_date != 0) {
+ recovered_installation_age =
+ now - base::Time::FromTimeT(client_info_backup->installation_date);
+ }
+ UMA_HISTOGRAM_COUNTS_10000("UMA.ClientIdBackupRecoveredWithAge",
+ recovered_installation_age.InHours());
+
+ // Flush the backup back to persistent storage in case we re-generated
+ // missing data above.
+ BackUpCurrentClientInfo();
+ return;
+ }
+
+ // Failing attempts at getting an existing client ID, generate a new one.
+ client_id_ = base::GenerateGUID();
+ local_state_->SetString(prefs::kMetricsClientID, client_id_);
+
+ // Record the timestamp of when the user opted in to UMA.
+ local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+ base::Time::Now().ToTimeT());
+
+ BackUpCurrentClientInfo();
+}
+
+void MetricsStateManager::CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+ DCHECK(!cloned_install_detector_);
+
+ MachineIdProvider* provider = MachineIdProvider::CreateInstance();
+ if (!provider)
+ return;
+
+ cloned_install_detector_.reset(new ClonedInstallDetector(provider));
+ cloned_install_detector_->CheckForClonedInstall(local_state_, task_runner);
+}
+
+scoped_ptr<const base::FieldTrial::EntropyProvider>
+MetricsStateManager::CreateEntropyProvider() {
+ // For metrics reporting-enabled users, we combine the client ID and low
+ // entropy source to get the final entropy source. Otherwise, only use the low
+ // entropy source.
+ // This has two useful properties:
+ // 1) It makes the entropy source less identifiable for parties that do not
+ // know the low entropy source.
+ // 2) It makes the final entropy source resettable.
+ const int low_entropy_source_value = GetLowEntropySource();
+ UMA_HISTOGRAM_SPARSE_SLOWLY("UMA.LowEntropySourceValue",
+ low_entropy_source_value);
+ if (IsMetricsReportingEnabled()) {
+ UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH);
+ const std::string high_entropy_source =
+ client_id_ + base::IntToString(low_entropy_source_value);
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>(
+ new SHA1EntropyProvider(high_entropy_source));
+ }
+
+ UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW);
+#if defined(OS_ANDROID) || defined(OS_IOS)
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>(
+ new CachingPermutedEntropyProvider(local_state_,
+ low_entropy_source_value,
+ kMaxLowEntropySize));
+#else
+ return scoped_ptr<const base::FieldTrial::EntropyProvider>(
+ new PermutedEntropyProvider(low_entropy_source_value,
+ kMaxLowEntropySize));
+#endif
+}
+
+// static
+scoped_ptr<MetricsStateManager> MetricsStateManager::Create(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& retrieve_client_info) {
+ scoped_ptr<MetricsStateManager> result;
+ // Note: |instance_exists_| is updated in the constructor and destructor.
+ if (!instance_exists_) {
+ result.reset(new MetricsStateManager(local_state,
+ is_reporting_enabled_callback,
+ store_client_info,
+ retrieve_client_info));
+ }
+ return result;
+}
+
+// static
+void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterBooleanPref(prefs::kMetricsResetIds, false);
+ // registry->RegisterIntegerPref(prefs::kMetricsDefaultOptIn,
+ // DEFAULT_UNKNOWN);
+ registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
+ registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
+ registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
+ kLowEntropySourceNotSet);
+
+ ClonedInstallDetector::RegisterPrefs(registry);
+ CachingPermutedEntropyProvider::RegisterPrefs(registry);
+}
+
+void MetricsStateManager::BackUpCurrentClientInfo() {
+ ClientInfo client_info;
+ client_info.client_id = client_id_;
+ client_info.installation_date = local_state_->GetInt64(prefs::kInstallDate);
+ client_info.reporting_enabled_date =
+ local_state_->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
+ store_client_info_.Run(client_info);
+}
+
+scoped_ptr<ClientInfo> MetricsStateManager::LoadClientInfoAndMaybeMigrate() {
+ scoped_ptr<ClientInfo> client_info = load_client_info_.Run();
+
+ // Prior to 2014-07, the client ID was stripped of its dashes before being
+ // saved. Migrate back to a proper GUID if this is the case. This migration
+ // code can be removed in M41+.
+ const size_t kGUIDLengthWithoutDashes = 32U;
+ if (client_info &&
+ client_info->client_id.length() == kGUIDLengthWithoutDashes) {
+ DCHECK(client_info->client_id.find('-') == std::string::npos);
+
+ std::string client_id_with_dashes;
+ client_id_with_dashes.reserve(kGUIDLengthWithoutDashes + 4U);
+ std::string::const_iterator client_id_it = client_info->client_id.begin();
+ for (size_t i = 0; i < kGUIDLengthWithoutDashes + 4U; ++i) {
+ if (i == 8U || i == 13U || i == 18U || i == 23U) {
+ client_id_with_dashes.push_back('-');
+ } else {
+ client_id_with_dashes.push_back(*client_id_it);
+ ++client_id_it;
+ }
+ }
+ DCHECK(client_id_it == client_info->client_id.end());
+ client_info->client_id.assign(client_id_with_dashes);
+ }
+
+ // The GUID retrieved (and possibly fixed above) should be valid unless
+ // retrieval failed.
+ DCHECK(!client_info || base::IsValidGUID(client_info->client_id));
+
+ return client_info;
+}
+
+int MetricsStateManager::GetLowEntropySource() {
+ UpdateLowEntropySource();
+ return low_entropy_source_;
+}
+
+void MetricsStateManager::UpdateLowEntropySource() {
+ // Note that the default value for the low entropy source and the default pref
+ // value are both kLowEntropySourceNotSet, which is used to identify if the
+ // value has been set or not.
+ if (low_entropy_source_ != kLowEntropySourceNotSet)
+ return;
+
+ const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
+ // Only try to load the value from prefs if the user did not request a
+ // reset.
+ // Otherwise, skip to generating a new value.
+ if (!command_line->HasSwitch(switches::kResetVariationState)) {
+ int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
+ // If the value is outside the [0, kMaxLowEntropySize) range, re-generate
+ // it below.
+ if (value >= 0 && value < kMaxLowEntropySize) {
+ low_entropy_source_ = value;
+ return;
+ }
+ }
+
+ low_entropy_source_ = GenerateLowEntropySource();
+ local_state_->SetInteger(prefs::kMetricsLowEntropySource,
+ low_entropy_source_);
+ CachingPermutedEntropyProvider::ClearCache(local_state_);
+}
+
+void MetricsStateManager::UpdateEntropySourceReturnedValue(
+ EntropySourceType type) {
+ if (entropy_source_returned_ != ENTROPY_SOURCE_NONE)
+ return;
+
+ entropy_source_returned_ = type;
+ UMA_HISTOGRAM_ENUMERATION("UMA.EntropySourceType", type,
+ ENTROPY_SOURCE_ENUM_SIZE);
+}
+
+void MetricsStateManager::ResetMetricsIDsIfNecessary() {
+ if (!local_state_->GetBoolean(prefs::kMetricsResetIds))
+ return;
+
+ UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true);
+
+ DCHECK(client_id_.empty());
+ DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_);
+
+ local_state_->ClearPref(prefs::kMetricsClientID);
+ local_state_->ClearPref(prefs::kMetricsLowEntropySource);
+ local_state_->ClearPref(prefs::kMetricsResetIds);
+
+ // Also clear the backed up client info.
+ store_client_info_.Run(ClientInfo());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_state_manager.h b/chromium/components/metrics/metrics_state_manager.h
new file mode 100644
index 00000000000..cbf5bf71b6b
--- /dev/null
+++ b/chromium/components/metrics/metrics_state_manager.h
@@ -0,0 +1,173 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
+#define COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "components/metrics/client_info.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class ClonedInstallDetector;
+
+// Responsible for managing MetricsService state prefs, specifically the UMA
+// client id and low entropy source. Code outside the metrics directory should
+// not be instantiating or using this class directly.
+class MetricsStateManager {
+ public:
+ // A callback that can be invoked to store client info to persistent storage.
+ // Storing an empty client_id will resulted in the backup being voided.
+ typedef base::Callback<void(const ClientInfo& client_info)>
+ StoreClientInfoCallback;
+
+ // A callback that can be invoked to load client info stored through the
+ // StoreClientInfoCallback.
+ typedef base::Callback<scoped_ptr<ClientInfo>(void)> LoadClientInfoCallback;
+
+ virtual ~MetricsStateManager();
+
+ // Returns true if the user opted in to sending metric reports.
+ bool IsMetricsReportingEnabled();
+
+ // Returns the client ID for this client, or the empty string if the user is
+ // not opted in to metrics reporting.
+ const std::string& client_id() const { return client_id_; }
+
+ // Forces the client ID to be generated. This is useful in case it's needed
+ // before recording.
+ void ForceClientIdCreation();
+
+ // Checks if this install was cloned or imaged from another machine. If a
+ // clone is detected, resets the client id and low entropy source. This
+ // should not be called more than once.
+ void CheckForClonedInstall(
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
+ // Returns the preferred entropy provider used to seed persistent activities
+ // based on whether or not metrics reporting is permitted on this client.
+ //
+ // If metrics reporting is enabled, this method returns an entropy provider
+ // that has a high source of entropy, partially based on the client ID.
+ // Otherwise, it returns an entropy provider that is based on a low entropy
+ // source.
+ scoped_ptr<const base::FieldTrial::EntropyProvider> CreateEntropyProvider();
+
+ // Creates the MetricsStateManager, enforcing that only a single instance
+ // of the class exists at a time. Returns NULL if an instance exists already.
+ static scoped_ptr<MetricsStateManager> Create(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& load_client_info);
+
+ // Registers local state prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_Low);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_High);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, LowEntropySource0NotReset);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest,
+ PermutedEntropyCacheClearedWhenLowEntropyReset);
+ FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs);
+
+ // Designates which entropy source was returned from this class.
+ // This is used for testing to validate that we return the correct source
+ // depending on the state of the service.
+ enum EntropySourceType {
+ ENTROPY_SOURCE_NONE,
+ ENTROPY_SOURCE_LOW,
+ ENTROPY_SOURCE_HIGH,
+ ENTROPY_SOURCE_ENUM_SIZE,
+ };
+
+ // Creates the MetricsStateManager with the given |local_state|. Calls
+ // |is_reporting_enabled_callback| to query whether metrics reporting is
+ // enabled. Clients should instead use Create(), which enforces that a single
+ // instance of this class be alive at any given time.
+ // |store_client_info| should back up client info to persistent storage such
+ // that it is later retrievable by |load_client_info|.
+ MetricsStateManager(
+ PrefService* local_state,
+ const base::Callback<bool(void)>& is_reporting_enabled_callback,
+ const StoreClientInfoCallback& store_client_info,
+ const LoadClientInfoCallback& load_client_info);
+
+ // Backs up the current client info via |store_client_info_|.
+ void BackUpCurrentClientInfo();
+
+ // Loads the client info via |load_client_info_| and potentially migrates it
+ // before returning it if it comes back in its old form.
+ scoped_ptr<ClientInfo> LoadClientInfoAndMaybeMigrate();
+
+ // Returns the low entropy source for this client. This is a random value
+ // that is non-identifying amongst browser clients. This method will
+ // generate the entropy source value if it has not been called before.
+ int GetLowEntropySource();
+
+ // Generates the low entropy source value for this client if it is not
+ // already set.
+ void UpdateLowEntropySource();
+
+ // Updates |entropy_source_returned_| with |type| iff the current value is
+ // ENTROPY_SOURCE_NONE and logs the new value in a histogram.
+ void UpdateEntropySourceReturnedValue(EntropySourceType type);
+
+ // Returns the first entropy source that was returned by this service since
+ // start up, or NONE if neither was returned yet. This is exposed for testing
+ // only.
+ EntropySourceType entropy_source_returned() const {
+ return entropy_source_returned_;
+ }
+
+ // Reset the client id and low entropy source if the kMetricsResetMetricIDs
+ // pref is true.
+ void ResetMetricsIDsIfNecessary();
+
+ // Whether an instance of this class exists. Used to enforce that there aren't
+ // multiple instances of this class at a given time.
+ static bool instance_exists_;
+
+ // Weak pointer to the local state prefs store.
+ PrefService* const local_state_;
+
+ // A callback run by this MetricsStateManager to know whether reporting is
+ // enabled.
+ const base::Callback<bool(void)> is_reporting_enabled_callback_;
+
+ // A callback run during client id creation so this MetricsStateManager can
+ // store a backup of the newly generated ID.
+ const StoreClientInfoCallback store_client_info_;
+
+ // A callback run if this MetricsStateManager can't get the client id from
+ // its typical location and wants to attempt loading it from this backup.
+ const LoadClientInfoCallback load_client_info_;
+
+ // The identifier that's sent to the server with the log reports.
+ std::string client_id_;
+
+ // The non-identifying low entropy source value.
+ int low_entropy_source_;
+
+ // The last entropy source returned by this service, used for testing.
+ EntropySourceType entropy_source_returned_;
+
+ scoped_ptr<ClonedInstallDetector> cloned_install_detector_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsStateManager);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
diff --git a/chromium/components/metrics/metrics_state_manager_unittest.cc b/chromium/components/metrics/metrics_state_manager_unittest.cc
new file mode 100644
index 00000000000..72280aea534
--- /dev/null
+++ b/chromium/components/metrics/metrics_state_manager_unittest.cc
@@ -0,0 +1,386 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_state_manager.h"
+
+#include <ctype.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/caching_permuted_entropy_provider.h"
+#include "components/variations/pref_names.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class MetricsStateManagerTest : public testing::Test {
+ public:
+ MetricsStateManagerTest() : is_metrics_reporting_enabled_(false) {
+ MetricsService::RegisterPrefs(prefs_.registry());
+ }
+
+ scoped_ptr<MetricsStateManager> CreateStateManager() {
+ return MetricsStateManager::Create(
+ &prefs_,
+ base::Bind(&MetricsStateManagerTest::is_metrics_reporting_enabled,
+ base::Unretained(this)),
+ base::Bind(&MetricsStateManagerTest::MockStoreClientInfoBackup,
+ base::Unretained(this)),
+ base::Bind(&MetricsStateManagerTest::LoadFakeClientInfoBackup,
+ base::Unretained(this)));
+ }
+
+ // Sets metrics reporting as enabled for testing.
+ void EnableMetricsReporting() {
+ is_metrics_reporting_enabled_ = true;
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+
+ // Last ClientInfo stored by the MetricsStateManager via
+ // MockStoreClientInfoBackup.
+ scoped_ptr<ClientInfo> stored_client_info_backup_;
+
+ // If set, will be returned via LoadFakeClientInfoBackup if requested by the
+ // MetricsStateManager.
+ scoped_ptr<ClientInfo> fake_client_info_backup_;
+
+ private:
+ bool is_metrics_reporting_enabled() const {
+ return is_metrics_reporting_enabled_;
+ }
+
+ // Stores the |client_info| in |stored_client_info_backup_| for verification
+ // by the tests later.
+ void MockStoreClientInfoBackup(const ClientInfo& client_info) {
+ stored_client_info_backup_.reset(new ClientInfo);
+ stored_client_info_backup_->client_id = client_info.client_id;
+ stored_client_info_backup_->installation_date =
+ client_info.installation_date;
+ stored_client_info_backup_->reporting_enabled_date =
+ client_info.reporting_enabled_date;
+
+ // Respect the contract that storing an empty client_id voids the existing
+ // backup (required for the last section of the ForceClientIdCreation test
+ // below).
+ if (client_info.client_id.empty())
+ fake_client_info_backup_.reset();
+ }
+
+ // Hands out a copy of |fake_client_info_backup_| if it is set.
+ scoped_ptr<ClientInfo> LoadFakeClientInfoBackup() {
+ if (!fake_client_info_backup_)
+ return scoped_ptr<ClientInfo>();
+
+ scoped_ptr<ClientInfo> backup_copy(new ClientInfo);
+ backup_copy->client_id = fake_client_info_backup_->client_id;
+ backup_copy->installation_date =
+ fake_client_info_backup_->installation_date;
+ backup_copy->reporting_enabled_date =
+ fake_client_info_backup_->reporting_enabled_date;
+ return backup_copy;
+ }
+
+ bool is_metrics_reporting_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsStateManagerTest);
+};
+
+// Ensure the ClientId is formatted as expected.
+TEST_F(MetricsStateManagerTest, ClientIdCorrectlyFormatted) {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->ForceClientIdCreation();
+
+ const std::string client_id = state_manager->client_id();
+ EXPECT_EQ(36U, client_id.length());
+
+ for (size_t i = 0; i < client_id.length(); ++i) {
+ char current = client_id[i];
+ if (i == 8 || i == 13 || i == 18 || i == 23)
+ EXPECT_EQ('-', current);
+ else
+ EXPECT_TRUE(isxdigit(current));
+ }
+}
+
+TEST_F(MetricsStateManagerTest, EntropySourceUsed_Low) {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->CreateEntropyProvider();
+ EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW,
+ state_manager->entropy_source_returned());
+}
+
+TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) {
+ EnableMetricsReporting();
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->CreateEntropyProvider();
+ EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH,
+ state_manager->entropy_source_returned());
+}
+
+TEST_F(MetricsStateManagerTest, LowEntropySource0NotReset) {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // Get the low entropy source once, to initialize it.
+ state_manager->GetLowEntropySource();
+
+ // Now, set it to 0 and ensure it doesn't get reset.
+ state_manager->low_entropy_source_ = 0;
+ EXPECT_EQ(0, state_manager->GetLowEntropySource());
+ // Call it another time, just to make sure.
+ EXPECT_EQ(0, state_manager->GetLowEntropySource());
+}
+
+TEST_F(MetricsStateManagerTest,
+ PermutedEntropyCacheClearedWhenLowEntropyReset) {
+ const PrefService::Preference* low_entropy_pref =
+ prefs_.FindPreference(prefs::kMetricsLowEntropySource);
+ const char* kCachePrefName =
+ variations::prefs::kVariationsPermutedEntropyCache;
+ int low_entropy_value = -1;
+
+ // First, generate an initial low entropy source value.
+ {
+ EXPECT_TRUE(low_entropy_pref->IsDefaultValue());
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->GetLowEntropySource();
+
+ EXPECT_FALSE(low_entropy_pref->IsDefaultValue());
+ EXPECT_TRUE(low_entropy_pref->GetValue()->GetAsInteger(&low_entropy_value));
+ }
+
+ // Now, set a dummy value in the permuted entropy cache pref and verify that
+ // another call to GetLowEntropySource() doesn't clobber it when
+ // --reset-variation-state wasn't specified.
+ {
+ prefs_.SetString(kCachePrefName, "test");
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->GetLowEntropySource();
+
+ EXPECT_EQ("test", prefs_.GetString(kCachePrefName));
+ EXPECT_EQ(low_entropy_value,
+ prefs_.GetInteger(prefs::kMetricsLowEntropySource));
+ }
+
+ // Verify that the cache does get reset if --reset-variations-state is passed.
+ {
+ base::CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kResetVariationState);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->GetLowEntropySource();
+
+ EXPECT_TRUE(prefs_.GetString(kCachePrefName).empty());
+ }
+}
+
+// Check that setting the kMetricsResetIds pref to true causes the client id to
+// be reset. We do not check that the low entropy source is reset because we
+// cannot ensure that metrics state manager won't generate the same id again.
+TEST_F(MetricsStateManagerTest, ResetMetricsIDs) {
+ // Set an initial client id in prefs. It should not be possible for the
+ // metrics state manager to generate this id randomly.
+ const std::string kInitialClientId = "initial client id";
+ prefs_.SetString(prefs::kMetricsClientID, kInitialClientId);
+
+ // Make sure the initial client id isn't reset by the metrics state manager.
+ {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->ForceClientIdCreation();
+ EXPECT_EQ(kInitialClientId, state_manager->client_id());
+ }
+
+ // Set the reset pref to cause the IDs to be reset.
+ prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+
+ // Cause the actual reset to happen.
+ {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ state_manager->ForceClientIdCreation();
+ EXPECT_NE(kInitialClientId, state_manager->client_id());
+
+ state_manager->GetLowEntropySource();
+
+ EXPECT_FALSE(prefs_.GetBoolean(prefs::kMetricsResetIds));
+ }
+
+ EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID));
+}
+
+TEST_F(MetricsStateManagerTest, ForceClientIdCreation) {
+ const int64_t kFakeInstallationDate = 12345;
+ prefs_.SetInt64(prefs::kInstallDate, kFakeInstallationDate);
+
+ const int64_t test_begin_time = base::Time::Now().ToTimeT();
+
+ // Holds ClientInfo from previous scoped test for extra checks.
+ scoped_ptr<ClientInfo> previous_client_info;
+
+ {
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // client_id shouldn't be auto-generated if metrics reporting is not
+ // enabled.
+ EXPECT_EQ(std::string(), state_manager->client_id());
+ EXPECT_EQ(0, prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+
+ // Confirm that the initial ForceClientIdCreation call creates the client id
+ // and backs it up via MockStoreClientInfoBackup.
+ EXPECT_FALSE(stored_client_info_backup_);
+ state_manager->ForceClientIdCreation();
+ EXPECT_NE(std::string(), state_manager->client_id());
+ EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ test_begin_time);
+
+ ASSERT_TRUE(stored_client_info_backup_);
+ EXPECT_EQ(state_manager->client_id(),
+ stored_client_info_backup_->client_id);
+ EXPECT_EQ(kFakeInstallationDate,
+ stored_client_info_backup_->installation_date);
+ EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ stored_client_info_backup_->reporting_enabled_date);
+
+ previous_client_info = std::move(stored_client_info_backup_);
+ }
+
+ EnableMetricsReporting();
+
+ {
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // client_id should be auto-obtained from the constructor when metrics
+ // reporting is enabled.
+ EXPECT_EQ(previous_client_info->client_id, state_manager->client_id());
+
+ // The backup should also be refreshed when the client id re-initialized.
+ ASSERT_TRUE(stored_client_info_backup_);
+ EXPECT_EQ(previous_client_info->client_id,
+ stored_client_info_backup_->client_id);
+ EXPECT_EQ(kFakeInstallationDate,
+ stored_client_info_backup_->installation_date);
+ EXPECT_EQ(previous_client_info->reporting_enabled_date,
+ stored_client_info_backup_->reporting_enabled_date);
+
+ // Re-forcing client id creation shouldn't cause another backup and
+ // shouldn't affect the existing client id.
+ stored_client_info_backup_.reset();
+ state_manager->ForceClientIdCreation();
+ EXPECT_FALSE(stored_client_info_backup_);
+ EXPECT_EQ(previous_client_info->client_id, state_manager->client_id());
+ }
+
+ const int64_t kBackupInstallationDate = 1111;
+ const int64_t kBackupReportingEnabledDate = 2222;
+ const char kBackupClientId[] = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+ fake_client_info_backup_.reset(new ClientInfo);
+ fake_client_info_backup_->client_id = kBackupClientId;
+ fake_client_info_backup_->installation_date = kBackupInstallationDate;
+ fake_client_info_backup_->reporting_enabled_date =
+ kBackupReportingEnabledDate;
+
+ {
+ // The existence of a backup should result in the same behaviour as
+ // before if we already have a client id.
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ EXPECT_EQ(previous_client_info->client_id, state_manager->client_id());
+
+ // The backup should also be refreshed when the client id re-initialized.
+ ASSERT_TRUE(stored_client_info_backup_);
+ EXPECT_EQ(previous_client_info->client_id,
+ stored_client_info_backup_->client_id);
+ EXPECT_EQ(kFakeInstallationDate,
+ stored_client_info_backup_->installation_date);
+ EXPECT_EQ(previous_client_info->reporting_enabled_date,
+ stored_client_info_backup_->reporting_enabled_date);
+ stored_client_info_backup_.reset();
+ }
+
+ prefs_.ClearPref(prefs::kMetricsClientID);
+ prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp);
+
+ {
+ // The backup should kick in if the client id has gone missing. It should
+ // replace remaining and missing dates as well.
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ EXPECT_EQ(kBackupClientId, state_manager->client_id());
+ EXPECT_EQ(kBackupInstallationDate, prefs_.GetInt64(prefs::kInstallDate));
+ EXPECT_EQ(kBackupReportingEnabledDate,
+ prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+
+ EXPECT_TRUE(stored_client_info_backup_);
+ stored_client_info_backup_.reset();
+ }
+
+ const char kNoDashesBackupClientId[] = "AAAAAAAABBBBCCCCDDDDEEEEEEEEEEEE";
+ fake_client_info_backup_.reset(new ClientInfo);
+ fake_client_info_backup_->client_id = kNoDashesBackupClientId;
+
+ prefs_.ClearPref(prefs::kInstallDate);
+ prefs_.ClearPref(prefs::kMetricsClientID);
+ prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp);
+
+ {
+ // When running the backup from old-style client ids, dashes should be
+ // re-added. And missing dates in backup should be replaced by Time::Now().
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+ EXPECT_EQ(kBackupClientId, state_manager->client_id());
+ EXPECT_GE(prefs_.GetInt64(prefs::kInstallDate), test_begin_time);
+ EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ test_begin_time);
+
+ EXPECT_TRUE(stored_client_info_backup_);
+ previous_client_info = std::move(stored_client_info_backup_);
+ }
+
+ prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+
+ {
+ // Upon request to reset metrics ids, the existing backup should not be
+ // restored.
+
+ EXPECT_FALSE(stored_client_info_backup_);
+
+ scoped_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+ // A brand new client id should have been generated.
+ EXPECT_NE(std::string(), state_manager->client_id());
+ EXPECT_NE(previous_client_info->client_id, state_manager->client_id());
+
+ // The installation date should not have been affected.
+ EXPECT_EQ(previous_client_info->installation_date,
+ prefs_.GetInt64(prefs::kInstallDate));
+
+ // The metrics-reporting-enabled date will be reset to Now().
+ EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+ previous_client_info->reporting_enabled_date);
+
+ stored_client_info_backup_.reset();
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_switches.cc b/chromium/components/metrics/metrics_switches.cc
new file mode 100644
index 00000000000..1aef619d6a4
--- /dev/null
+++ b/chromium/components/metrics/metrics_switches.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_switches.h"
+
+namespace metrics {
+namespace switches {
+
+// Forces a reset of the one-time-randomized FieldTrials on this client, also
+// known as the Chrome Variations state.
+const char kResetVariationState[] = "reset-variation-state";
+
+} // namespace switches
+} // namespace metrics
diff --git a/chromium/components/metrics/metrics_switches.h b/chromium/components/metrics/metrics_switches.h
new file mode 100644
index 00000000000..b3fe7fd9c03
--- /dev/null
+++ b/chromium/components/metrics/metrics_switches.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SWITCHES_H_
+#define COMPONENTS_METRICS_METRICS_SWITCHES_H_
+
+namespace metrics {
+namespace switches {
+
+// Alphabetical list of switches specific to the metrics component. Document
+// each in the .cc file.
+extern const char kResetVariationState[];
+
+} // namespace switches
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_METRICS_SWITCHES_H_
diff --git a/chromium/components/metrics/net/DEPS b/chromium/components/metrics/net/DEPS
new file mode 100644
index 00000000000..c7c67e4830e
--- /dev/null
+++ b/chromium/components/metrics/net/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+chromeos/network",
+ "+components/data_use_measurement/core",
+ "+net",
+ "+third_party/cros_system_api",
+]
diff --git a/chromium/components/metrics/net/net_metrics_log_uploader.cc b/chromium/components/metrics/net/net_metrics_log_uploader.cc
new file mode 100644
index 00000000000..0ded667b787
--- /dev/null
+++ b/chromium/components/metrics/net/net_metrics_log_uploader.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/net_metrics_log_uploader.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "net/base/load_flags.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_fetcher.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Records the network connection type if upload was successful.
+void RecordConnectionType(int response_code) {
+ if (response_code == 200) {
+ UMA_HISTOGRAM_ENUMERATION("UMA.LogUpload.ConnetionType",
+ net::NetworkChangeNotifier::GetConnectionType(),
+ net::NetworkChangeNotifier::CONNECTION_LAST);
+ }
+}
+
+} // namespace
+
+namespace metrics {
+
+NetMetricsLogUploader::NetMetricsLogUploader(
+ net::URLRequestContextGetter* request_context_getter,
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete)
+ : MetricsLogUploader(server_url, mime_type, on_upload_complete),
+ request_context_getter_(request_context_getter) {
+}
+
+NetMetricsLogUploader::~NetMetricsLogUploader() {
+}
+
+void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data,
+ const std::string& log_hash) {
+ current_fetch_ =
+ net::URLFetcher::Create(GURL(server_url_), net::URLFetcher::POST, this);
+ data_use_measurement::DataUseUserData::AttachToFetcher(
+ current_fetch_.get(), data_use_measurement::DataUseUserData::UMA);
+ current_fetch_->SetRequestContext(request_context_getter_);
+ current_fetch_->SetUploadData(mime_type_, compressed_log_data);
+
+ // Tell the server that we're uploading gzipped protobufs.
+ current_fetch_->SetExtraRequestHeaders("content-encoding: gzip");
+
+ DCHECK(!log_hash.empty());
+ current_fetch_->AddExtraRequestHeader("X-Chrome-UMA-Log-SHA1: " + log_hash);
+
+ // We already drop cookies server-side, but we might as well strip them out
+ // client-side as well.
+ current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES);
+ current_fetch_->Start();
+}
+
+void NetMetricsLogUploader::OnURLFetchComplete(const net::URLFetcher* source) {
+ // We're not allowed to re-use the existing |URLFetcher|s, so free them here.
+ // Note however that |source| is aliased to the fetcher, so we should be
+ // careful not to delete it too early.
+ DCHECK_EQ(current_fetch_.get(), source);
+
+ int response_code = source->GetResponseCode();
+ if (response_code == net::URLFetcher::RESPONSE_CODE_INVALID)
+ response_code = -1;
+ current_fetch_.reset();
+ RecordConnectionType(response_code);
+ on_upload_complete_.Run(response_code);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/net_metrics_log_uploader.h b/chromium/components/metrics/net/net_metrics_log_uploader.h
new file mode 100644
index 00000000000..a62b8988f3c
--- /dev/null
+++ b/chromium/components/metrics/net/net_metrics_log_uploader.h
@@ -0,0 +1,55 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
+namespace metrics {
+
+// Implementation of MetricsLogUploader using the Chrome network stack.
+class NetMetricsLogUploader : public MetricsLogUploader,
+ public net::URLFetcherDelegate {
+ public:
+ // Constructs a NetMetricsLogUploader with the specified request context and
+ // other params (see comments on MetricsLogUploader for details). The caller
+ // must ensure that |request_context_getter| remains valid for the lifetime
+ // of this class.
+ NetMetricsLogUploader(net::URLRequestContextGetter* request_context_getter,
+ const std::string& server_url,
+ const std::string& mime_type,
+ const base::Callback<void(int)>& on_upload_complete);
+ ~NetMetricsLogUploader() override;
+
+ // MetricsLogUploader:
+ void UploadLog(const std::string& compressed_log_data,
+ const std::string& log_hash) override;
+
+ private:
+ // net::URLFetcherDelegate:
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // The request context for fetches done using the network stack.
+ net::URLRequestContextGetter* const request_context_getter_;
+
+ // The outstanding transmission appears as a URL Fetch operation.
+ scoped_ptr<net::URLFetcher> current_fetch_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploader);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
diff --git a/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc b/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc
new file mode 100644
index 00000000000..a2d0ecd166b
--- /dev/null
+++ b/chromium/components/metrics/net/net_metrics_log_uploader_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/net_metrics_log_uploader.h"
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class NetMetricsLogUploaderTest : public testing::Test {
+ public:
+ NetMetricsLogUploaderTest() : on_upload_complete_count_(0) {
+ }
+
+ void CreateAndOnUploadCompleteReuseUploader() {
+ uploader_.reset(new NetMetricsLogUploader(
+ NULL, "http://dummy_server", "dummy_mime",
+ base::Bind(&NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader,
+ base::Unretained(this))));
+ uploader_->UploadLog("initial_dummy_data", "initial_dummy_hash");
+ }
+
+ void OnUploadCompleteReuseUploader(int response_code) {
+ ++on_upload_complete_count_;
+ if (on_upload_complete_count_ == 1)
+ uploader_->UploadLog("dummy_data", "dummy_hash");
+ }
+
+ int on_upload_complete_count() const {
+ return on_upload_complete_count_;
+ }
+
+ private:
+ scoped_ptr<NetMetricsLogUploader> uploader_;
+ int on_upload_complete_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploaderTest);
+};
+
+TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) {
+ net::TestURLFetcherFactory factory;
+ CreateAndOnUploadCompleteReuseUploader();
+
+ // Mimic the initial fetcher callback.
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ // Mimic the second fetcher callback.
+ fetcher = factory.GetFetcherByID(0);
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+
+ EXPECT_EQ(on_upload_complete_count(), 2);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/network_metrics_provider.cc b/chromium/components/metrics/net/network_metrics_provider.cc
new file mode 100644
index 00000000000..92540893984
--- /dev/null
+++ b/chromium/components/metrics/net/network_metrics_provider.cc
@@ -0,0 +1,265 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/network_metrics_provider.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/task_runner_util.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+
+#if defined(OS_CHROMEOS)
+#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h"
+#endif // OS_CHROMEOS
+
+namespace metrics {
+
+NetworkMetricsProvider::NetworkMetricsProvider(base::TaskRunner* io_task_runner)
+ : io_task_runner_(io_task_runner),
+ connection_type_is_ambiguous_(false),
+ wifi_phy_layer_protocol_is_ambiguous_(false),
+ wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN),
+ total_aborts_(0),
+ total_codes_(0),
+ weak_ptr_factory_(this) {
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
+ connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
+ ProbeWifiPHYLayerProtocol();
+}
+
+NetworkMetricsProvider::~NetworkMetricsProvider() {
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+}
+
+void NetworkMetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension*) {
+ // ProvideGeneralMetrics is called on the main thread, at the time a metrics
+ // record is being finalized.
+ net::NetworkChangeNotifier::FinalizingMetricsLogRecord();
+ LogAggregatedMetrics();
+}
+
+void NetworkMetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile) {
+ SystemProfileProto::Network* network = system_profile->mutable_network();
+ network->set_connection_type_is_ambiguous(connection_type_is_ambiguous_);
+ network->set_connection_type(GetConnectionType());
+ network->set_wifi_phy_layer_protocol_is_ambiguous(
+ wifi_phy_layer_protocol_is_ambiguous_);
+ network->set_wifi_phy_layer_protocol(GetWifiPHYLayerProtocol());
+
+ // Update the connection type. Note that this is necessary to set the network
+ // type to "none" if there is no network connection for an entire UMA logging
+ // window, since OnConnectionTypeChanged() ignores transitions to the "none"
+ // state.
+ connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
+ // Reset the "ambiguous" flags, since a new metrics log session has started.
+ connection_type_is_ambiguous_ = false;
+ wifi_phy_layer_protocol_is_ambiguous_ = false;
+
+ if (!wifi_access_point_info_provider_.get()) {
+#if defined(OS_CHROMEOS)
+ wifi_access_point_info_provider_.reset(
+ new WifiAccessPointInfoProviderChromeos());
+#else
+ wifi_access_point_info_provider_.reset(
+ new WifiAccessPointInfoProvider());
+#endif // OS_CHROMEOS
+ }
+
+ // Connected wifi access point information.
+ WifiAccessPointInfoProvider::WifiAccessPointInfo info;
+ if (wifi_access_point_info_provider_->GetInfo(&info))
+ WriteWifiAccessPointProto(info, network);
+}
+
+void NetworkMetricsProvider::OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ // To avoid reporting an ambiguous connection type for users on flaky
+ // connections, ignore transitions to the "none" state. Note that the
+ // connection type is refreshed in ProvideSystemProfileMetrics() each time a
+ // new UMA logging window begins, so users who genuinely transition to offline
+ // mode for an extended duration will still be at least partially represented
+ // in the metrics logs.
+ if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
+ return;
+
+ if (type != connection_type_ &&
+ connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE) {
+ connection_type_is_ambiguous_ = true;
+ }
+ connection_type_ = type;
+
+ ProbeWifiPHYLayerProtocol();
+}
+
+SystemProfileProto::Network::ConnectionType
+NetworkMetricsProvider::GetConnectionType() const {
+ switch (connection_type_) {
+ case net::NetworkChangeNotifier::CONNECTION_NONE:
+ return SystemProfileProto::Network::CONNECTION_NONE;
+ case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ return SystemProfileProto::Network::CONNECTION_UNKNOWN;
+ case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
+ return SystemProfileProto::Network::CONNECTION_ETHERNET;
+ case net::NetworkChangeNotifier::CONNECTION_WIFI:
+ return SystemProfileProto::Network::CONNECTION_WIFI;
+ case net::NetworkChangeNotifier::CONNECTION_2G:
+ return SystemProfileProto::Network::CONNECTION_2G;
+ case net::NetworkChangeNotifier::CONNECTION_3G:
+ return SystemProfileProto::Network::CONNECTION_3G;
+ case net::NetworkChangeNotifier::CONNECTION_4G:
+ return SystemProfileProto::Network::CONNECTION_4G;
+ case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
+ return SystemProfileProto::Network::CONNECTION_BLUETOOTH;
+ }
+ NOTREACHED();
+ return SystemProfileProto::Network::CONNECTION_UNKNOWN;
+}
+
+SystemProfileProto::Network::WifiPHYLayerProtocol
+NetworkMetricsProvider::GetWifiPHYLayerProtocol() const {
+ switch (wifi_phy_layer_protocol_) {
+ case net::WIFI_PHY_LAYER_PROTOCOL_NONE:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_NONE;
+ case net::WIFI_PHY_LAYER_PROTOCOL_ANCIENT:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_ANCIENT;
+ case net::WIFI_PHY_LAYER_PROTOCOL_A:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_A;
+ case net::WIFI_PHY_LAYER_PROTOCOL_B:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_B;
+ case net::WIFI_PHY_LAYER_PROTOCOL_G:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_G;
+ case net::WIFI_PHY_LAYER_PROTOCOL_N:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_N;
+ case net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN:
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+ }
+ NOTREACHED();
+ return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+}
+
+void NetworkMetricsProvider::ProbeWifiPHYLayerProtocol() {
+ PostTaskAndReplyWithResult(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&net::GetWifiPHYLayerProtocol),
+ base::Bind(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult(
+ net::WifiPHYLayerProtocol mode) {
+ if (wifi_phy_layer_protocol_ != net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN &&
+ mode != wifi_phy_layer_protocol_) {
+ wifi_phy_layer_protocol_is_ambiguous_ = true;
+ }
+ wifi_phy_layer_protocol_ = mode;
+}
+
+void NetworkMetricsProvider::WriteWifiAccessPointProto(
+ const WifiAccessPointInfoProvider::WifiAccessPointInfo& info,
+ SystemProfileProto::Network* network_proto) {
+ SystemProfileProto::Network::WifiAccessPoint* access_point_info =
+ network_proto->mutable_access_point_info();
+ SystemProfileProto::Network::WifiAccessPoint::SecurityMode security =
+ SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN;
+ switch (info.security) {
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_NONE:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_NONE;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_WPA:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WPA;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_WEP:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WEP;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_RSN:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_RSN;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_802_1X:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_802_1X;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_PSK:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_PSK;
+ break;
+ case WifiAccessPointInfoProvider::WIFI_SECURITY_UNKNOWN:
+ security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN;
+ break;
+ }
+ access_point_info->set_security_mode(security);
+
+ // |bssid| is xx:xx:xx:xx:xx:xx, extract the first three components and
+ // pack into a uint32_t.
+ std::string bssid = info.bssid;
+ if (bssid.size() == 17 && bssid[2] == ':' && bssid[5] == ':' &&
+ bssid[8] == ':' && bssid[11] == ':' && bssid[14] == ':') {
+ std::string vendor_prefix_str;
+ uint32_t vendor_prefix;
+
+ base::RemoveChars(bssid.substr(0, 9), ":", &vendor_prefix_str);
+ DCHECK_EQ(6U, vendor_prefix_str.size());
+ if (base::HexStringToUInt(vendor_prefix_str, &vendor_prefix))
+ access_point_info->set_vendor_prefix(vendor_prefix);
+ else
+ NOTREACHED();
+ }
+
+ // Return if vendor information is not provided.
+ if (info.model_number.empty() && info.model_name.empty() &&
+ info.device_name.empty() && info.oui_list.empty())
+ return;
+
+ SystemProfileProto::Network::WifiAccessPoint::VendorInformation* vendor =
+ access_point_info->mutable_vendor_info();
+ if (!info.model_number.empty())
+ vendor->set_model_number(info.model_number);
+ if (!info.model_name.empty())
+ vendor->set_model_name(info.model_name);
+ if (!info.device_name.empty())
+ vendor->set_device_name(info.device_name);
+
+ // Return if OUI list is not provided.
+ if (info.oui_list.empty())
+ return;
+
+ // Parse OUI list.
+ for (const base::StringPiece& oui_str : base::SplitStringPiece(
+ info.oui_list, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+ uint32_t oui;
+ if (base::HexStringToUInt(oui_str, &oui))
+ vendor->add_element_identifier(oui);
+ else
+ NOTREACHED();
+ }
+}
+
+void NetworkMetricsProvider::LogAggregatedMetrics() {
+ base::HistogramBase* error_codes = base::SparseHistogram::FactoryGet(
+ "Net.ErrorCodesForMainFrame3",
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ scoped_ptr<base::HistogramSamples> samples = error_codes->SnapshotSamples();
+ base::HistogramBase::Count new_aborts =
+ samples->GetCount(-net::ERR_ABORTED) - total_aborts_;
+ base::HistogramBase::Count new_codes = samples->TotalCount() - total_codes_;
+ if (new_codes > 0) {
+ UMA_HISTOGRAM_COUNTS("Net.ErrAborted.CountPerUpload", new_aborts);
+ UMA_HISTOGRAM_PERCENTAGE("Net.ErrAborted.ProportionPerUpload",
+ (100 * new_aborts) / new_codes);
+ total_codes_ += new_codes;
+ total_aborts_ += new_aborts;
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/network_metrics_provider.h b/chromium/components/metrics/net/network_metrics_provider.h
new file mode 100644
index 00000000000..5dbb5593b24
--- /dev/null
+++ b/chromium/components/metrics/net/network_metrics_provider.h
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_base.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_interfaces.h"
+
+namespace metrics {
+
+// Registers as observer with net::NetworkChangeNotifier and keeps track of
+// the network environment.
+class NetworkMetricsProvider
+ : public MetricsProvider,
+ public net::NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ // Creates a NetworkMetricsProvider, where |io_task_runner| is used to post
+ // network info collection tasks.
+ explicit NetworkMetricsProvider(base::TaskRunner* io_task_runner);
+ ~NetworkMetricsProvider() override;
+
+ private:
+ // MetricsProvider:
+ void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
+ void ProvideSystemProfileMetrics(SystemProfileProto* system_profile) override;
+
+ // ConnectionTypeObserver:
+ void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) override;
+
+ SystemProfileProto::Network::ConnectionType GetConnectionType() const;
+ SystemProfileProto::Network::WifiPHYLayerProtocol GetWifiPHYLayerProtocol()
+ const;
+
+ // Posts a call to net::GetWifiPHYLayerProtocol on the blocking pool.
+ void ProbeWifiPHYLayerProtocol();
+ // Callback from the blocking pool with the result of
+ // net::GetWifiPHYLayerProtocol.
+ void OnWifiPHYLayerProtocolResult(net::WifiPHYLayerProtocol mode);
+
+ // Writes info about the wireless access points that this system is
+ // connected to.
+ void WriteWifiAccessPointProto(
+ const WifiAccessPointInfoProvider::WifiAccessPointInfo& info,
+ SystemProfileProto::Network* network_proto);
+
+ // Logs metrics that are functions of other metrics being uploaded.
+ void LogAggregatedMetrics();
+
+ // Task runner used for blocking file I/O.
+ base::TaskRunner* io_task_runner_;
+
+ // True if |connection_type_| changed during the lifetime of the log.
+ bool connection_type_is_ambiguous_;
+ // The connection type according to net::NetworkChangeNotifier.
+ net::NetworkChangeNotifier::ConnectionType connection_type_;
+
+ // True if |wifi_phy_layer_protocol_| changed during the lifetime of the log.
+ bool wifi_phy_layer_protocol_is_ambiguous_;
+ // The PHY mode of the currently associated access point obtained via
+ // net::GetWifiPHYLayerProtocol.
+ net::WifiPHYLayerProtocol wifi_phy_layer_protocol_;
+
+ // Helper object for retrieving connected wifi access point information.
+ scoped_ptr<WifiAccessPointInfoProvider> wifi_access_point_info_provider_;
+
+ // These metrics track histogram totals for the Net.ErrorCodesForMainFrame3
+ // histogram. They are used to compute deltas at upload time.
+ base::HistogramBase::Count total_aborts_;
+ base::HistogramBase::Count total_codes_;
+
+ base::WeakPtrFactory<NetworkMetricsProvider> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/net/version_utils.cc b/chromium/components/metrics/net/version_utils.cc
new file mode 100644
index 00000000000..37452330d9e
--- /dev/null
+++ b/chromium/components/metrics/net/version_utils.cc
@@ -0,0 +1,41 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/version_utils.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/version_info/version_info.h"
+
+namespace metrics {
+
+std::string GetVersionString() {
+ std::string version = version_info::GetVersionNumber();
+#if defined(ARCH_CPU_64_BITS)
+ version += "-64";
+#endif // defined(ARCH_CPU_64_BITS)
+ if (!version_info::IsOfficialBuild())
+ version.append("-devel");
+ return version;
+}
+
+SystemProfileProto::Channel AsProtobufChannel(
+ version_info::Channel channel) {
+ switch (channel) {
+ case version_info::Channel::UNKNOWN:
+ return SystemProfileProto::CHANNEL_UNKNOWN;
+ case version_info::Channel::CANARY:
+ return SystemProfileProto::CHANNEL_CANARY;
+ case version_info::Channel::DEV:
+ return SystemProfileProto::CHANNEL_DEV;
+ case version_info::Channel::BETA:
+ return SystemProfileProto::CHANNEL_BETA;
+ case version_info::Channel::STABLE:
+ return SystemProfileProto::CHANNEL_STABLE;
+ }
+ NOTREACHED();
+ return SystemProfileProto::CHANNEL_UNKNOWN;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/version_utils.h b/chromium/components/metrics/net/version_utils.h
new file mode 100644
index 00000000000..853c846a875
--- /dev/null
+++ b/chromium/components/metrics/net/version_utils.h
@@ -0,0 +1,29 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_VERSION_UTILS_H_
+#define COMPONENTS_METRICS_NET_VERSION_UTILS_H_
+
+#include <string>
+
+#include "components/metrics/proto/system_profile.pb.h"
+
+namespace version_info {
+enum class Channel;
+}
+
+namespace metrics {
+
+// Build a string including the Chrome app version, suffixed by "-64" on 64-bit
+// platforms, and "-devel" on developer builds.
+std::string GetVersionString();
+
+// Translates version_info::Channel to the equivalent
+// SystemProfileProto::Channel.
+SystemProfileProto::Channel AsProtobufChannel(
+ version_info::Channel channel);
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_VERSION_UTILS_H_
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider.cc b/chromium/components/metrics/net/wifi_access_point_info_provider.cc
new file mode 100644
index 00000000000..21e04b181d2
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+
+namespace metrics {
+
+WifiAccessPointInfoProvider::WifiAccessPointInfo::WifiAccessPointInfo() {
+}
+
+WifiAccessPointInfoProvider::WifiAccessPointInfo::~WifiAccessPointInfo() {
+}
+
+WifiAccessPointInfoProvider::WifiAccessPointInfoProvider() {
+}
+
+WifiAccessPointInfoProvider::~WifiAccessPointInfoProvider() {
+}
+
+bool WifiAccessPointInfoProvider::GetInfo(WifiAccessPointInfo *info) {
+ return false;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider.h b/chromium/components/metrics/net/wifi_access_point_info_provider.h
new file mode 100644
index 00000000000..3a761da1b12
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
+#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// Interface for accessing connected wireless access point information.
+class WifiAccessPointInfoProvider {
+ public:
+ // Wifi access point security mode definitions.
+ enum WifiSecurityMode {
+ WIFI_SECURITY_UNKNOWN = 0,
+ WIFI_SECURITY_WPA = 1,
+ WIFI_SECURITY_WEP = 2,
+ WIFI_SECURITY_RSN = 3,
+ WIFI_SECURITY_802_1X = 4,
+ WIFI_SECURITY_PSK = 5,
+ WIFI_SECURITY_NONE
+ };
+
+ // Information of the currently connected wifi access point.
+ struct WifiAccessPointInfo {
+ WifiAccessPointInfo();
+ ~WifiAccessPointInfo();
+ WifiSecurityMode security;
+ std::string bssid;
+ std::string model_number;
+ std::string model_name;
+ std::string device_name;
+ std::string oui_list;
+ };
+
+ WifiAccessPointInfoProvider();
+ virtual ~WifiAccessPointInfoProvider();
+
+ // Fill in the wifi access point info if device is currently connected to a
+ // wifi access point. Return true if device is connected to a wifi access
+ // point, false otherwise.
+ virtual bool GetInfo(WifiAccessPointInfo *info);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc
new file mode 100644
index 00000000000..3654e93ef4f
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.cc
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/strings/string_number_conversions.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/shill_property_util.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using chromeos::NetworkHandler;
+
+namespace metrics {
+
+WifiAccessPointInfoProviderChromeos::WifiAccessPointInfoProviderChromeos() {
+ NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
+
+ // Update initial connection state.
+ DefaultNetworkChanged(
+ NetworkHandler::Get()->network_state_handler()->DefaultNetwork());
+}
+
+WifiAccessPointInfoProviderChromeos::~WifiAccessPointInfoProviderChromeos() {
+ NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
+ FROM_HERE);
+}
+
+bool WifiAccessPointInfoProviderChromeos::GetInfo(WifiAccessPointInfo* info) {
+ // Wifi access point information is not provided if the BSSID is empty.
+ // This assumes the BSSID is never empty when access point information exists.
+ if (wifi_access_point_info_.bssid.empty())
+ return false;
+
+ *info = wifi_access_point_info_;
+ return true;
+}
+
+void WifiAccessPointInfoProviderChromeos::DefaultNetworkChanged(
+ const chromeos::NetworkState* default_network) {
+ // Reset access point info to prevent reporting of out-dated data.
+ wifi_access_point_info_ = WifiAccessPointInfo();
+
+ // Skip non-wifi connections
+ if (!default_network || default_network->type() != shill::kTypeWifi)
+ return;
+
+ // Retrieve access point info for wifi connection.
+ NetworkHandler::Get()->network_configuration_handler()->GetShillProperties(
+ default_network->path(),
+ base::Bind(&WifiAccessPointInfoProviderChromeos::ParseInfo, AsWeakPtr()),
+ chromeos::network_handler::ErrorCallback());
+}
+
+void WifiAccessPointInfoProviderChromeos::ParseInfo(
+ const std::string &service_path,
+ const base::DictionaryValue& properties) {
+ // Skip services that contain "_nomap" in the SSID.
+ std::string ssid = chromeos::shill_property_util::GetSSIDFromProperties(
+ properties, false /* verbose_logging */, nullptr);
+ if (ssid.find("_nomap", 0) != std::string::npos)
+ return;
+
+ std::string bssid;
+ if (!properties.GetStringWithoutPathExpansion(shill::kWifiBSsid, &bssid) ||
+ bssid.empty())
+ return;
+
+ // Filter out BSSID with local bit set in the first byte.
+ uint32_t first_octet;
+ if (!base::HexStringToUInt(bssid.substr(0, 2), &first_octet))
+ NOTREACHED();
+ if (first_octet & 0x2)
+ return;
+ wifi_access_point_info_.bssid = bssid;
+
+ // Parse security info.
+ std::string security;
+ properties.GetStringWithoutPathExpansion(
+ shill::kSecurityProperty, &security);
+ wifi_access_point_info_.security = WIFI_SECURITY_UNKNOWN;
+ if (security == shill::kSecurityWpa)
+ wifi_access_point_info_.security = WIFI_SECURITY_WPA;
+ else if (security == shill::kSecurityWep)
+ wifi_access_point_info_.security = WIFI_SECURITY_WEP;
+ else if (security == shill::kSecurityRsn)
+ wifi_access_point_info_.security = WIFI_SECURITY_RSN;
+ else if (security == shill::kSecurity8021x)
+ wifi_access_point_info_.security = WIFI_SECURITY_802_1X;
+ else if (security == shill::kSecurityPsk)
+ wifi_access_point_info_.security = WIFI_SECURITY_PSK;
+ else if (security == shill::kSecurityNone)
+ wifi_access_point_info_.security = WIFI_SECURITY_NONE;
+
+ properties.GetStringWithoutPathExpansion(
+ shill::kWifiBSsid, &wifi_access_point_info_.bssid);
+ const base::DictionaryValue* vendor_dict = NULL;
+ if (!properties.GetDictionaryWithoutPathExpansion(
+ shill::kWifiVendorInformationProperty,
+ &vendor_dict))
+ return;
+
+ vendor_dict->GetStringWithoutPathExpansion(
+ shill::kVendorWPSModelNumberProperty,
+ &wifi_access_point_info_.model_number);
+ vendor_dict->GetStringWithoutPathExpansion(
+ shill::kVendorWPSModelNameProperty,
+ &wifi_access_point_info_.model_name);
+ vendor_dict->GetStringWithoutPathExpansion(
+ shill::kVendorWPSDeviceNameProperty,
+ &wifi_access_point_info_.device_name);
+ vendor_dict->GetStringWithoutPathExpansion(shill::kVendorOUIListProperty,
+ &wifi_access_point_info_.oui_list);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h
new file mode 100644
index 00000000000..d3102397741
--- /dev/null
+++ b/chromium/components/metrics/net/wifi_access_point_info_provider_chromeos.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
+#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chromeos/network/network_state_handler_observer.h"
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+
+namespace metrics {
+
+// WifiAccessPointInfoProviderChromeos provides the connected wifi
+// acccess point information for chromeos.
+class WifiAccessPointInfoProviderChromeos
+ : public WifiAccessPointInfoProvider,
+ public chromeos::NetworkStateHandlerObserver,
+ public base::SupportsWeakPtr<WifiAccessPointInfoProviderChromeos> {
+ public:
+ WifiAccessPointInfoProviderChromeos();
+ ~WifiAccessPointInfoProviderChromeos() override;
+
+ // WifiAccessPointInfoProvider:
+ bool GetInfo(WifiAccessPointInfo* info) override;
+
+ // NetworkStateHandlerObserver:
+ void DefaultNetworkChanged(
+ const chromeos::NetworkState* default_network) override;
+
+ private:
+ // Callback from Shill.Service.GetProperties. Parses |properties| to obtain
+ // the wifi access point information.
+ void ParseInfo(const std::string& service_path,
+ const base::DictionaryValue& properties);
+
+ WifiAccessPointInfo wifi_access_point_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProviderChromeos);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
diff --git a/chromium/components/metrics/persisted_logs.cc b/chromium/components/metrics/persisted_logs.cc
new file mode 100644
index 00000000000..c78ecec2596
--- /dev/null
+++ b/chromium/components/metrics/persisted_logs.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/md5.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sha1.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+PersistedLogs::LogReadStatus MakeRecallStatusHistogram(
+ PersistedLogs::LogReadStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs",
+ status, PersistedLogs::END_RECALL_STATUS);
+ return status;
+}
+
+// Reads the value at |index| from |list_value| as a string and Base64-decodes
+// it into |result|. Returns true on success.
+bool ReadBase64String(const base::ListValue& list_value,
+ size_t index,
+ std::string* result) {
+ std::string base64_result;
+ if (!list_value.GetString(index, &base64_result))
+ return false;
+ return base::Base64Decode(base64_result, result);
+}
+
+// Base64-encodes |str| and appends the result to |list_value|.
+void AppendBase64String(const std::string& str, base::ListValue* list_value) {
+ std::string base64_str;
+ base::Base64Encode(str, &base64_str);
+ list_value->AppendString(base64_str);
+}
+
+} // namespace
+
+void PersistedLogs::LogHashPair::Init(const std::string& log_data) {
+ DCHECK(!log_data.empty());
+
+ if (!compression::GzipCompress(log_data, &compressed_log_data)) {
+ NOTREACHED();
+ return;
+ }
+
+ UMA_HISTOGRAM_PERCENTAGE(
+ "UMA.ProtoCompressionRatio",
+ static_cast<int>(100 * compressed_log_data.size() / log_data.size()));
+
+ hash = base::SHA1HashString(log_data);
+}
+
+PersistedLogs::PersistedLogs(PrefService* local_state,
+ const char* pref_name,
+ size_t min_log_count,
+ size_t min_log_bytes,
+ size_t max_log_size)
+ : local_state_(local_state),
+ pref_name_(pref_name),
+ min_log_count_(min_log_count),
+ min_log_bytes_(min_log_bytes),
+ max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)),
+ staged_log_index_(-1) {
+ DCHECK(local_state_);
+ // One of the limit arguments must be non-zero.
+ DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0);
+}
+
+PersistedLogs::~PersistedLogs() {}
+
+void PersistedLogs::SerializeLogs() const {
+ ListPrefUpdate update(local_state_, pref_name_);
+ WriteLogsToPrefList(update.Get());
+}
+
+PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() {
+ return ReadLogsFromPrefList(*local_state_->GetList(pref_name_));
+}
+
+void PersistedLogs::StoreLog(const std::string& log_data) {
+ list_.push_back(LogHashPair());
+ list_.back().Init(log_data);
+}
+
+void PersistedLogs::StageLog() {
+ // CHECK, rather than DCHECK, because swap()ing with an empty list causes
+ // hard-to-identify crashes much later.
+ CHECK(!list_.empty());
+ DCHECK(!has_staged_log());
+ staged_log_index_ = list_.size() - 1;
+ DCHECK(has_staged_log());
+}
+
+void PersistedLogs::DiscardStagedLog() {
+ DCHECK(has_staged_log());
+ DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size());
+ list_.erase(list_.begin() + staged_log_index_);
+ staged_log_index_ = -1;
+}
+
+void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const {
+ list_value->Clear();
+
+ // Keep the most recent logs which are smaller than |max_log_size_|.
+ // We keep at least |min_log_bytes_| and |min_log_count_| of logs before
+ // discarding older logs.
+ size_t start = list_.size();
+ size_t saved_log_count = 0;
+ size_t bytes_used = 0;
+ for (; start > 0; --start) {
+ size_t log_size = list_[start - 1].compressed_log_data.length();
+ if (bytes_used >= min_log_bytes_ &&
+ saved_log_count >= min_log_count_) {
+ break;
+ }
+ // Oversized logs won't be persisted, so don't count them.
+ if (log_size > max_log_size_)
+ continue;
+ bytes_used += log_size;
+ ++saved_log_count;
+ }
+
+ for (size_t i = start; i < list_.size(); ++i) {
+ size_t log_size = list_[i].compressed_log_data.length();
+ if (log_size > max_log_size_) {
+ UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
+ static_cast<int>(log_size));
+ continue;
+ }
+ AppendBase64String(list_[i].compressed_log_data, list_value);
+ AppendBase64String(list_[i].hash, list_value);
+ }
+}
+
+PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList(
+ const base::ListValue& list_value) {
+ if (list_value.empty())
+ return MakeRecallStatusHistogram(LIST_EMPTY);
+
+ // For each log, there's two entries in the list (the data and the hash).
+ DCHECK_EQ(0U, list_value.GetSize() % 2);
+ const size_t log_count = list_value.GetSize() / 2;
+
+ // Resize |list_| ahead of time, so that values can be decoded directly into
+ // the elements of the list.
+ DCHECK(list_.empty());
+ list_.resize(log_count);
+
+ for (size_t i = 0; i < log_count; ++i) {
+ if (!ReadBase64String(list_value, i * 2, &list_[i].compressed_log_data) ||
+ !ReadBase64String(list_value, i * 2 + 1, &list_[i].hash)) {
+ list_.clear();
+ return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
+ }
+ }
+
+ return MakeRecallStatusHistogram(RECALL_SUCCESS);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/persisted_logs.h b/chromium/components/metrics/persisted_logs.h
new file mode 100644
index 00000000000..73ad79a5b06
--- /dev/null
+++ b/chromium/components/metrics/persisted_logs.h
@@ -0,0 +1,143 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PERSISTED_LOGS_H_
+#define COMPONENTS_METRICS_PERSISTED_LOGS_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/values.h"
+
+class PrefService;
+
+namespace metrics {
+
+// Maintains a list of unsent logs that are written and restored from disk.
+class PersistedLogs {
+ public:
+ // Used to produce a histogram that keeps track of the status of recalling
+ // persisted per logs.
+ enum LogReadStatus {
+ RECALL_SUCCESS, // We were able to correctly recall a persisted log.
+ LIST_EMPTY, // Attempting to recall from an empty list.
+ LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger().
+ LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3).
+ LIST_SIZE_CORRUPTION, // List size is not as expected.
+ LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString().
+ CHECKSUM_CORRUPTION, // Failed to verify checksum.
+ CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using
+ // GetAsString().
+ DECODE_FAIL, // Failed to decode log.
+ DEPRECATED_XML_PROTO_MISMATCH, // The XML and protobuf logs have
+ // inconsistent data.
+ END_RECALL_STATUS // Number of bins to use to create the histogram.
+ };
+
+ // Constructs a PersistedLogs that stores data in |local_state| under the
+ // preference |pref_name|.
+ // Calling code is responsible for ensuring that the lifetime of |local_state|
+ // is longer than the lifetime of PersistedLogs.
+ //
+ // When saving logs to disk, stores either the first |min_log_count| logs, or
+ // at least |min_log_bytes| bytes of logs, whichever is greater.
+ //
+ // If the optional |max_log_size| parameter is non-zero, all logs larger than
+ // that limit will be skipped when writing to disk.
+ PersistedLogs(PrefService* local_state,
+ const char* pref_name,
+ size_t min_log_count,
+ size_t min_log_bytes,
+ size_t max_log_size);
+ ~PersistedLogs();
+
+ // Write list to storage.
+ void SerializeLogs() const;
+
+ // Reads the list from the preference.
+ LogReadStatus DeserializeLogs();
+
+ // Adds a log to the list.
+ void StoreLog(const std::string& log_data);
+
+ // Stages the most recent log. The staged_log will remain the same even if
+ // additional logs are added.
+ void StageLog();
+
+ // Remove the staged log.
+ void DiscardStagedLog();
+
+ // True if a log has been staged.
+ bool has_staged_log() const { return staged_log_index_ != -1; }
+
+ // Returns the element in the front of the list.
+ const std::string& staged_log() const {
+ DCHECK(has_staged_log());
+ return list_[staged_log_index_].compressed_log_data;
+ }
+
+ // Returns the element in the front of the list.
+ const std::string& staged_log_hash() const {
+ DCHECK(has_staged_log());
+ return list_[staged_log_index_].hash;
+ }
+
+ // The number of elements currently stored.
+ size_t size() const { return list_.size(); }
+
+ // True if there are no stored logs.
+ bool empty() const { return list_.empty(); }
+
+ private:
+ // Writes the list to the ListValue.
+ void WriteLogsToPrefList(base::ListValue* list) const;
+
+ // Reads the list from the ListValue.
+ LogReadStatus ReadLogsFromPrefList(const base::ListValue& list);
+
+ // A weak pointer to the PrefService object to read and write the preference
+ // from. Calling code should ensure this object continues to exist for the
+ // lifetime of the PersistedLogs object.
+ PrefService* local_state_;
+
+ // The name of the preference to serialize logs to/from.
+ const char* pref_name_;
+
+ // We will keep at least this |min_log_count_| logs or |min_log_bytes_| bytes
+ // of logs, whichever is greater, when writing to disk. These apply after
+ // skipping logs greater than |max_log_size_|.
+ const size_t min_log_count_;
+ const size_t min_log_bytes_;
+
+ // Logs greater than this size will not be written to disk.
+ const size_t max_log_size_;
+
+ struct LogHashPair {
+ // Initializes the members based on uncompressed |log_data|.
+ void Init(const std::string& log_data);
+
+ // Compressed log data - a serialized protobuf that's been gzipped.
+ std::string compressed_log_data;
+
+ // The SHA1 hash of log, stored to catch errors from memory corruption.
+ std::string hash;
+ };
+ // A list of all of the stored logs, stored with SHA1 hashes to check for
+ // corruption while they are stored in memory.
+ std::vector<LogHashPair> list_;
+
+ // The index and type of the log staged for upload. If nothing has been
+ // staged, the index will be -1.
+ int staged_log_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistedLogs);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PERSISTED_LOGS_H_
diff --git a/chromium/components/metrics/persisted_logs_unittest.cc b/chromium/components/metrics/persisted_logs_unittest.cc
new file mode 100644
index 00000000000..b4a79a73ea6
--- /dev/null
+++ b/chromium/components/metrics/persisted_logs_unittest.cc
@@ -0,0 +1,289 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs.h"
+
+#include <stddef.h>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/rand_util.h"
+#include "base/sha1.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestPrefName[] = "TestPref";
+const size_t kLogCountLimit = 3;
+const size_t kLogByteLimit = 1000;
+
+// Compresses |log_data| and returns the result.
+std::string Compress(const std::string& log_data) {
+ std::string compressed_log_data;
+ EXPECT_TRUE(compression::GzipCompress(log_data, &compressed_log_data));
+ return compressed_log_data;
+}
+
+// Generates and returns log data such that its size after compression is at
+// least |min_compressed_size|.
+std::string GenerateLogWithMinCompressedSize(size_t min_compressed_size) {
+ // Since the size check is done against a compressed log, generate enough
+ // data that compresses to larger than |log_size|.
+ std::string rand_bytes = base::RandBytesAsString(min_compressed_size);
+ while (Compress(rand_bytes).size() < min_compressed_size)
+ rand_bytes.append(base::RandBytesAsString(min_compressed_size));
+ std::string base64_data_for_logging;
+ base::Base64Encode(rand_bytes, &base64_data_for_logging);
+ SCOPED_TRACE(testing::Message() << "Using random data "
+ << base64_data_for_logging);
+ return rand_bytes;
+}
+
+class PersistedLogsTest : public testing::Test {
+ public:
+ PersistedLogsTest() {
+ prefs_.registry()->RegisterListPref(kTestPrefName);
+ }
+
+ protected:
+ TestingPrefServiceSimple prefs_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest);
+};
+
+class TestPersistedLogs : public PersistedLogs {
+ public:
+ TestPersistedLogs(PrefService* service, size_t min_log_bytes)
+ : PersistedLogs(service, kTestPrefName, kLogCountLimit, min_log_bytes,
+ 0) {
+ }
+
+ // Stages and removes the next log, while testing it's value.
+ void ExpectNextLog(const std::string& expected_log) {
+ StageLog();
+ EXPECT_EQ(staged_log(), Compress(expected_log));
+ DiscardStagedLog();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestPersistedLogs);
+};
+
+} // namespace
+
+// Store and retrieve empty list_value.
+TEST_F(PersistedLogsTest, EmptyLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ persisted_logs.SerializeLogs();
+ const base::ListValue* list_value = prefs_.GetList(kTestPrefName);
+ EXPECT_EQ(0U, list_value->GetSize());
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::LIST_EMPTY, result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(0U, result_persisted_logs.size());
+}
+
+// Store and retrieve a single log value.
+TEST_F(PersistedLogsTest, SingleElementLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ persisted_logs.StoreLog("Hello world!");
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(1U, result_persisted_logs.size());
+
+ // Verify that the result log matches the initial log.
+ persisted_logs.StageLog();
+ result_persisted_logs.StageLog();
+ EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log());
+ EXPECT_EQ(persisted_logs.staged_log_hash(),
+ result_persisted_logs.staged_log_hash());
+}
+
+// Store a set of logs over the length limit, but smaller than the min number of
+// bytes.
+TEST_F(PersistedLogsTest, LongButTinyLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ size_t log_count = kLogCountLimit * 5;
+ for (size_t i = 0; i < log_count; ++i)
+ persisted_logs.StoreLog("x");
+
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
+
+ result_persisted_logs.ExpectNextLog("x");
+}
+
+// Store a set of logs over the length limit, but that doesn't reach the minimum
+// number of bytes until after passing the length limit.
+TEST_F(PersistedLogsTest, LongButSmallLogList) {
+ size_t log_count = kLogCountLimit * 5;
+ size_t log_size = 50;
+
+ std::string first_kept = "First to keep";
+ first_kept.resize(log_size, ' ');
+
+ std::string blank_log = std::string(log_size, ' ');
+
+ std::string last_kept = "Last to keep";
+ last_kept.resize(log_size, ' ');
+
+ // Set the byte limit enough to keep everything but the first two logs.
+ const size_t min_log_bytes =
+ Compress(first_kept).length() + Compress(last_kept).length() +
+ (log_count - 4) * Compress(blank_log).length();
+ TestPersistedLogs persisted_logs(&prefs_, min_log_bytes);
+
+ persisted_logs.StoreLog("one");
+ persisted_logs.StoreLog("two");
+ persisted_logs.StoreLog(first_kept);
+ for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) {
+ persisted_logs.StoreLog(blank_log);
+ }
+ persisted_logs.StoreLog(last_kept);
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size());
+
+ result_persisted_logs.ExpectNextLog(last_kept);
+ while (result_persisted_logs.size() > 1) {
+ result_persisted_logs.ExpectNextLog(blank_log);
+ }
+ result_persisted_logs.ExpectNextLog(first_kept);
+}
+
+// Store a set of logs within the length limit, but well over the minimum
+// number of bytes.
+TEST_F(PersistedLogsTest, ShortButLargeLogList) {
+ // Make the total byte count about twice the minimum.
+ size_t log_count = kLogCountLimit;
+ size_t log_size = (kLogByteLimit / log_count) * 2;
+ std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+ for (size_t i = 0; i < log_count; ++i) {
+ persisted_logs.StoreLog(log_data);
+ }
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
+}
+
+// Store a set of logs over the length limit, and over the minimum number of
+// bytes.
+TEST_F(PersistedLogsTest, LongAndLargeLogList) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ // Include twice the max number of logs.
+ size_t log_count = kLogCountLimit * 2;
+ // Make the total byte count about four times the minimum.
+ size_t log_size = (kLogByteLimit / log_count) * 4;
+
+ std::string target_log = "First to keep";
+ target_log += GenerateLogWithMinCompressedSize(log_size);
+
+ std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+ for (size_t i = 0; i < log_count; ++i) {
+ if (i == log_count - kLogCountLimit)
+ persisted_logs.StoreLog(target_log);
+ else
+ persisted_logs.StoreLog(log_data);
+ }
+
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(kLogCountLimit, result_persisted_logs.size());
+
+ while (result_persisted_logs.size() > 1) {
+ result_persisted_logs.ExpectNextLog(log_data);
+ }
+ result_persisted_logs.ExpectNextLog(target_log);
+}
+
+// Check that the store/stage/discard functions work as expected.
+TEST_F(PersistedLogsTest, Staging) {
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+ std::string tmp;
+
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ persisted_logs.StoreLog("one");
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ persisted_logs.StoreLog("two");
+ persisted_logs.StageLog();
+ EXPECT_TRUE(persisted_logs.has_staged_log());
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
+ persisted_logs.StoreLog("three");
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
+ EXPECT_EQ(persisted_logs.size(), 3U);
+ persisted_logs.DiscardStagedLog();
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ EXPECT_EQ(persisted_logs.size(), 2U);
+ persisted_logs.StageLog();
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("three"));
+ persisted_logs.DiscardStagedLog();
+ persisted_logs.StageLog();
+ EXPECT_EQ(persisted_logs.staged_log(), Compress("one"));
+ persisted_logs.DiscardStagedLog();
+ EXPECT_FALSE(persisted_logs.has_staged_log());
+ EXPECT_EQ(persisted_logs.size(), 0U);
+}
+
+TEST_F(PersistedLogsTest, DiscardOrder) {
+ // Ensure that the correct log is discarded if new logs are pushed while
+ // a log is staged.
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+ persisted_logs.StoreLog("one");
+ persisted_logs.StageLog();
+ persisted_logs.StoreLog("two");
+ persisted_logs.DiscardStagedLog();
+ persisted_logs.SerializeLogs();
+
+ TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+ EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
+ result_persisted_logs.DeserializeLogs());
+ EXPECT_EQ(1U, result_persisted_logs.size());
+ result_persisted_logs.ExpectNextLog("two");
+}
+
+
+TEST_F(PersistedLogsTest, Hashes) {
+ const char kFooText[] = "foo";
+ const std::string foo_hash = base::SHA1HashString(kFooText);
+
+ TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+ persisted_logs.StoreLog(kFooText);
+ persisted_logs.StageLog();
+
+ EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log());
+ EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/content/DEPS b/chromium/components/metrics/profiler/content/DEPS
new file mode 100644
index 00000000000..c74cf95b862
--- /dev/null
+++ b/chromium/components/metrics/profiler/content/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/nacl/common",
+ "+content/public/browser",
+ "+content/public/common",
+ "+content/public/test",
+]
diff --git a/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc
new file mode 100644
index 00000000000..e1449e30572
--- /dev/null
+++ b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.cc
@@ -0,0 +1,97 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/content/content_tracking_synchronizer_delegate.h"
+
+#include "components/metrics/profiler/tracking_synchronizer.h"
+#include "components/nacl/common/nacl_process_type.h"
+#include "content/public/browser/profiler_controller.h"
+#include "content/public/common/process_type.h"
+
+namespace metrics {
+
+namespace {
+
+ProfilerEventProto::TrackedObject::ProcessType AsProtobufProcessType(
+ int process_type) {
+ switch (process_type) {
+ case content::PROCESS_TYPE_BROWSER:
+ return ProfilerEventProto::TrackedObject::BROWSER;
+ case content::PROCESS_TYPE_RENDERER:
+ return ProfilerEventProto::TrackedObject::RENDERER;
+ case content::PROCESS_TYPE_UTILITY:
+ return ProfilerEventProto::TrackedObject::UTILITY;
+ case content::PROCESS_TYPE_ZYGOTE:
+ return ProfilerEventProto::TrackedObject::ZYGOTE;
+ case content::PROCESS_TYPE_SANDBOX_HELPER:
+ return ProfilerEventProto::TrackedObject::SANDBOX_HELPER;
+ case content::PROCESS_TYPE_GPU:
+ return ProfilerEventProto::TrackedObject::GPU;
+ case content::PROCESS_TYPE_PPAPI_PLUGIN:
+ return ProfilerEventProto::TrackedObject::PPAPI_PLUGIN;
+ case content::PROCESS_TYPE_PPAPI_BROKER:
+ return ProfilerEventProto::TrackedObject::PPAPI_BROKER;
+ case PROCESS_TYPE_NACL_LOADER:
+ return ProfilerEventProto::TrackedObject::NACL_LOADER;
+ case PROCESS_TYPE_NACL_BROKER:
+ return ProfilerEventProto::TrackedObject::NACL_BROKER;
+ default:
+ NOTREACHED();
+ return ProfilerEventProto::TrackedObject::UNKNOWN;
+ }
+}
+
+} // namespace
+
+// static
+scoped_ptr<TrackingSynchronizerDelegate>
+ContentTrackingSynchronizerDelegate::Create(
+ TrackingSynchronizer* synchronizer) {
+ return make_scoped_ptr(new ContentTrackingSynchronizerDelegate(synchronizer));
+}
+
+ContentTrackingSynchronizerDelegate::ContentTrackingSynchronizerDelegate(
+ TrackingSynchronizer* synchronizer)
+ : synchronizer_(synchronizer) {
+ DCHECK(synchronizer_);
+ content::ProfilerController::GetInstance()->Register(this);
+}
+
+ContentTrackingSynchronizerDelegate::~ContentTrackingSynchronizerDelegate() {
+ content::ProfilerController::GetInstance()->Unregister(this);
+}
+
+void ContentTrackingSynchronizerDelegate::GetProfilerDataForChildProcesses(
+ int sequence_number,
+ int current_profiling_phase) {
+ // Get profiler data from renderer and browser child processes.
+ content::ProfilerController::GetInstance()->GetProfilerData(
+ sequence_number, current_profiling_phase);
+}
+
+void ContentTrackingSynchronizerDelegate::OnProfilingPhaseCompleted(
+ int profiling_phase) {
+ // Notify renderer and browser child processes.
+ content::ProfilerController::GetInstance()->OnProfilingPhaseCompleted(
+ profiling_phase);
+}
+
+void ContentTrackingSynchronizerDelegate::OnPendingProcesses(
+ int sequence_number,
+ int pending_processes,
+ bool end) {
+ // Notify |synchronizer_|.
+ synchronizer_->OnPendingProcesses(sequence_number, pending_processes, end);
+}
+
+void ContentTrackingSynchronizerDelegate::OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ content::ProcessType process_type) {
+ // Notify |synchronizer_|.
+ synchronizer_->OnProfilerDataCollected(sequence_number, profiler_data,
+ AsProtobufProcessType(process_type));
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h
new file mode 100644
index 00000000000..c648347c8cf
--- /dev/null
+++ b/chromium/components/metrics/profiler/content/content_tracking_synchronizer_delegate.h
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_
+#define COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+#include "content/public/browser/profiler_subscriber.h"
+
+namespace metrics {
+
+// Provides an implementation of TrackingSynchronizerDelegate for use on
+// //content-based platforms. Specifically, drives the tracking of profiler data
+// for child processes by interacting with content::ProfilerController.
+class ContentTrackingSynchronizerDelegate : public TrackingSynchronizerDelegate,
+ public content::ProfilerSubscriber {
+ public:
+ ~ContentTrackingSynchronizerDelegate() override;
+
+ // Creates a ContentTrackingSynchronizerDelegate that is associated with
+ // |synchronizer_|.
+ static scoped_ptr<TrackingSynchronizerDelegate> Create(
+ TrackingSynchronizer* synchronizer);
+
+ private:
+ ContentTrackingSynchronizerDelegate(TrackingSynchronizer* synchronizer);
+
+ // TrackingSynchronizerDelegate:
+ void GetProfilerDataForChildProcesses(int sequence_number,
+ int current_profiling_phase) override;
+ void OnProfilingPhaseCompleted(int profiling_phase) override;
+
+ // content::ProfilerSubscriber:
+ void OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) override;
+ void OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ content::ProcessType process_type) override;
+
+ TrackingSynchronizer* const synchronizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentTrackingSynchronizerDelegate);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_CONTENT_CONTENT_TRACKING_SYNCHRONIZER_DELEGATE_H_
diff --git a/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc
new file mode 100644
index 00000000000..881d92f3e6f
--- /dev/null
+++ b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h"
+#include "components/metrics/profiler/tracking_synchronizer.h"
+
+namespace metrics {
+
+// static
+scoped_ptr<TrackingSynchronizerDelegate>
+IOSTrackingSynchronizerDelegate::Create(TrackingSynchronizer* synchronizer) {
+ return make_scoped_ptr(new IOSTrackingSynchronizerDelegate(synchronizer));
+}
+
+IOSTrackingSynchronizerDelegate::IOSTrackingSynchronizerDelegate(
+ TrackingSynchronizer* synchronizer)
+ : synchronizer_(synchronizer) {
+ DCHECK(synchronizer_);
+}
+
+IOSTrackingSynchronizerDelegate::~IOSTrackingSynchronizerDelegate() {}
+
+void IOSTrackingSynchronizerDelegate::GetProfilerDataForChildProcesses(
+ int sequence_number,
+ int current_profiling_phase) {
+ // Notify |synchronizer_| that there are no processes pending (on iOS, there
+ // is only the browser process).
+ synchronizer_->OnPendingProcesses(sequence_number, 0, true);
+}
+
+void IOSTrackingSynchronizerDelegate::OnProfilingPhaseCompleted(
+ int profiling_phase) {}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h
new file mode 100644
index 00000000000..d08476d839d
--- /dev/null
+++ b/chromium/components/metrics/profiler/ios/ios_tracking_synchronizer_delegate.h
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_
+#define COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+
+namespace metrics {
+
+// Provides an implementation of TrackingSynchronizerDelegate for usage on iOS.
+// This implementation is minimal, as on iOS there are no child processes.
+class IOSTrackingSynchronizerDelegate : public TrackingSynchronizerDelegate {
+ public:
+ ~IOSTrackingSynchronizerDelegate() override;
+
+ // Creates an IOSTrackingSynchronizerDelegate that is associated with
+ // |synchronizer_|.
+ static scoped_ptr<TrackingSynchronizerDelegate> Create(
+ TrackingSynchronizer* synchronizer);
+
+ private:
+ IOSTrackingSynchronizerDelegate(TrackingSynchronizer* synchronizer);
+
+ // TrackingSynchronizerDelegate:
+ void GetProfilerDataForChildProcesses(int sequence_number,
+ int current_profiling_phase) override;
+ void OnProfilingPhaseCompleted(int profiling_phase) override;
+
+ TrackingSynchronizer* const synchronizer_;
+
+ DISALLOW_COPY_AND_ASSIGN(IOSTrackingSynchronizerDelegate);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_IOS_IOS_TRACKING_SYNCHRONIZER_DELEGATE_H_
diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider.cc b/chromium/components/metrics/profiler/profiler_metrics_provider.cc
new file mode 100644
index 00000000000..df64e293aee
--- /dev/null
+++ b/chromium/components/metrics/profiler/profiler_metrics_provider.cc
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/profiler_metrics_provider.h"
+
+#include <ctype.h>
+#include <stddef.h>
+#include <string>
+#include <vector>
+
+#include "base/stl_util.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/metrics_log.h"
+
+namespace metrics {
+namespace {
+
+// Maps a thread name by replacing trailing sequence of digits with "*".
+// Examples:
+// 1. "BrowserBlockingWorker1/23857" => "BrowserBlockingWorker1/*"
+// 2. "Chrome_IOThread" => "Chrome_IOThread"
+std::string MapThreadName(const std::string& thread_name) {
+ size_t i = thread_name.length();
+
+ while (i > 0 && isdigit(thread_name[i - 1])) {
+ --i;
+ }
+
+ if (i == thread_name.length())
+ return thread_name;
+
+ return thread_name.substr(0, i) + '*';
+}
+
+// Normalizes a source filename (which is platform- and build-method-dependent)
+// by extracting the last component of the full file name.
+// Example: "c:\b\build\slave\win\build\src\chrome\app\chrome_main.cc" =>
+// "chrome_main.cc".
+std::string NormalizeFileName(const std::string& file_name) {
+ const size_t offset = file_name.find_last_of("\\/");
+ return offset != std::string::npos ? file_name.substr(offset + 1) : file_name;
+}
+
+void WriteProfilerData(
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ ProfilerEventProto* performance_profile) {
+ for (const auto& task : process_data_phase.tasks) {
+ const tracked_objects::DeathDataSnapshot& death_data = task.death_data;
+ ProfilerEventProto::TrackedObject* tracked_object =
+ performance_profile->add_tracked_object();
+ tracked_object->set_birth_thread_name_hash(
+ MetricsLog::Hash(MapThreadName(task.birth.thread_name)));
+ tracked_object->set_exec_thread_name_hash(
+ MetricsLog::Hash(MapThreadName(task.death_thread_name)));
+ tracked_object->set_source_file_name_hash(
+ MetricsLog::Hash(NormalizeFileName(task.birth.location.file_name)));
+ tracked_object->set_source_function_name_hash(
+ MetricsLog::Hash(task.birth.location.function_name));
+ tracked_object->set_source_line_number(task.birth.location.line_number);
+ tracked_object->set_exec_count(death_data.count);
+ tracked_object->set_exec_time_total(death_data.run_duration_sum);
+ tracked_object->set_exec_time_sampled(death_data.run_duration_sample);
+ tracked_object->set_queue_time_total(death_data.queue_duration_sum);
+ tracked_object->set_queue_time_sampled(death_data.queue_duration_sample);
+ tracked_object->set_process_type(process_type);
+ tracked_object->set_process_id(process_id);
+ }
+}
+
+} // namespace
+
+ProfilerMetricsProvider::ProfilerMetricsProvider() {
+}
+
+ProfilerMetricsProvider::ProfilerMetricsProvider(
+ const base::Callback<bool(void)>& cellular_callback)
+ : cellular_callback_(cellular_callback) {
+}
+
+ProfilerMetricsProvider::~ProfilerMetricsProvider() {
+}
+
+void ProfilerMetricsProvider::ProvideGeneralMetrics(
+ ChromeUserMetricsExtension* uma_proto) {
+ DCHECK_EQ(0, uma_proto->profiler_event_size());
+
+ for (auto& event : profiler_events_cache_) {
+ uma_proto->add_profiler_event()->Swap(&event.second);
+ }
+
+ profiler_events_cache_.clear();
+}
+
+void ProfilerMetricsProvider::RecordProfilerData(
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ int profiling_phase,
+ base::TimeDelta phase_start,
+ base::TimeDelta phase_finish,
+ const ProfilerEvents& past_events) {
+ // Omit profiler data on connections where it's likely to cost the user money
+ // for us to upload it.
+ if (IsCellularLogicEnabled())
+ return;
+
+ const bool new_phase = !ContainsKey(profiler_events_cache_, profiling_phase);
+ ProfilerEventProto* profiler_event = &profiler_events_cache_[profiling_phase];
+
+ if (new_phase) {
+ profiler_event->set_profile_version(
+ ProfilerEventProto::VERSION_SPLIT_PROFILE);
+ profiler_event->set_time_source(ProfilerEventProto::WALL_CLOCK_TIME);
+ profiler_event->set_profiling_start_ms(phase_start.InMilliseconds());
+ profiler_event->set_profiling_finish_ms(phase_finish.InMilliseconds());
+ for (const auto& event : past_events) {
+ profiler_event->add_past_session_event(event);
+ }
+ }
+
+ WriteProfilerData(process_data_phase, process_id, process_type,
+ profiler_event);
+}
+
+bool ProfilerMetricsProvider::IsCellularLogicEnabled() {
+ if (cellular_callback_.is_null())
+ return false;
+
+ return cellular_callback_.Run();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider.h b/chromium/components/metrics/profiler/profiler_metrics_provider.h
new file mode 100644
index 00000000000..8585fce5549
--- /dev/null
+++ b/chromium/components/metrics/profiler/profiler_metrics_provider.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace tracked_objects {
+struct ProcessDataPhaseSnapshot;
+}
+
+namespace metrics {
+
+// ProfilerMetricsProvider is responsible for filling out the |profiler_event|
+// section of the UMA proto.
+class ProfilerMetricsProvider : public MetricsProvider {
+ public:
+ explicit ProfilerMetricsProvider(
+ const base::Callback<bool(void)>& cellular_callback);
+ // Creates profiler metrics provider with a null callback.
+ ProfilerMetricsProvider();
+ ~ProfilerMetricsProvider() override;
+
+ // MetricsDataProvider:
+ void ProvideGeneralMetrics(ChromeUserMetricsExtension* uma_proto) override;
+
+ // Records the passed profiled data, which should be a snapshot of the
+ // browser's profiled performance during startup for a single process.
+ void RecordProfilerData(
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ int profiling_phase,
+ base::TimeDelta phase_start,
+ base::TimeDelta phase_finish,
+ const ProfilerEvents& past_events);
+
+ private:
+ // Returns true if current connection type is cellular and user is assigned to
+ // experimental group for enabled cellular uploads according to
+ // |cellular_callback_|.
+ bool IsCellularLogicEnabled();
+
+ // Saved cache of generated Profiler event protos, to be copied into the UMA
+ // proto when ProvideGeneralMetrics() is called. The map is from a profiling
+ // phase id to the profiler event proto that represents profiler data for the
+ // profiling phase.
+ std::map<int, ProfilerEventProto> profiler_events_cache_;
+
+ // Returns true if current connection type is cellular and user is assigned to
+ // experimental group for enabled cellular uploads.
+ base::Callback<bool(void)> cellular_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfilerMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_PROFILER_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc b/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..e2e31b922eb
--- /dev/null
+++ b/chromium/components/metrics/profiler/profiler_metrics_provider_unittest.cc
@@ -0,0 +1,277 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/profiler_metrics_provider.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/tracked_objects.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using tracked_objects::ProcessDataPhaseSnapshot;
+using tracked_objects::TaskSnapshot;
+
+namespace metrics {
+
+TEST(ProfilerMetricsProviderTest, RecordData) {
+ // WARNING: If you broke the below check, you've modified how
+ // HashMetricName works. Please also modify all server-side code that
+ // relies on the existing way of hashing.
+ EXPECT_EQ(UINT64_C(1518842999910132863),
+ base::HashMetricName("birth_thread*"));
+
+ ProfilerMetricsProvider profiler_metrics_provider;
+
+ {
+ // Add data from the browser process.
+ ProcessDataPhaseSnapshot process_data_phase;
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "a/b/file.h";
+ process_data_phase.tasks.back().birth.location.function_name = "function";
+ process_data_phase.tasks.back().birth.location.line_number = 1337;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread";
+ process_data_phase.tasks.back().death_data.count = 37;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 31;
+ process_data_phase.tasks.back().death_data.run_duration_max = 17;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 13;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 8;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 5;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 3;
+ process_data_phase.tasks.back().death_thread_name = "Still_Alive";
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "c\\d\\file2";
+ process_data_phase.tasks.back().birth.location.function_name = "function2";
+ process_data_phase.tasks.back().birth.location.line_number = 1773;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread2";
+ process_data_phase.tasks.back().death_data.count = 19;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 23;
+ process_data_phase.tasks.back().death_data.run_duration_max = 11;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 7;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 0;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 0;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 0;
+ process_data_phase.tasks.back().death_thread_name = "death_thread";
+
+ profiler_metrics_provider.RecordProfilerData(
+ process_data_phase, 177, ProfilerEventProto::TrackedObject::BROWSER, 0,
+ base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(2),
+ ProfilerEvents());
+ }
+
+ {
+ // Add second phase from the browser process.
+ ProcessDataPhaseSnapshot process_data_phase;
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "a/b/file10.h";
+ process_data_phase.tasks.back().birth.location.function_name = "function10";
+ process_data_phase.tasks.back().birth.location.line_number = 101337;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread_ten";
+ process_data_phase.tasks.back().death_data.count = 1037;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 1031;
+ process_data_phase.tasks.back().death_data.run_duration_max = 1017;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 1013;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 108;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 105;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 103;
+ process_data_phase.tasks.back().death_thread_name = "Already_Dead";
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "c\\d\\file210";
+ process_data_phase.tasks.back().birth.location.function_name =
+ "function210";
+ process_data_phase.tasks.back().birth.location.line_number = 101773;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread_ten2";
+ process_data_phase.tasks.back().death_data.count = 1019;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 1023;
+ process_data_phase.tasks.back().death_data.run_duration_max = 1011;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 107;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 100;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 100;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 100;
+ process_data_phase.tasks.back().death_thread_name = "death_thread_ten";
+
+ profiler_metrics_provider.RecordProfilerData(
+ process_data_phase, 177, ProfilerEventProto::TrackedObject::BROWSER, 1,
+ base::TimeDelta::FromMinutes(10), base::TimeDelta::FromMinutes(20),
+ ProfilerEvents(1, ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT));
+ }
+
+ {
+ // Add data from a renderer process.
+ ProcessDataPhaseSnapshot process_data_phase;
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "file3";
+ process_data_phase.tasks.back().birth.location.function_name = "function3";
+ process_data_phase.tasks.back().birth.location.line_number = 7331;
+ process_data_phase.tasks.back().birth.thread_name = "birth_thread3";
+ process_data_phase.tasks.back().death_data.count = 137;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 131;
+ process_data_phase.tasks.back().death_data.run_duration_max = 117;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 113;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 108;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 105;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 103;
+ process_data_phase.tasks.back().death_thread_name = "death_thread3";
+ process_data_phase.tasks.push_back(TaskSnapshot());
+ process_data_phase.tasks.back().birth.location.file_name = "";
+ process_data_phase.tasks.back().birth.location.function_name = "";
+ process_data_phase.tasks.back().birth.location.line_number = 7332;
+ process_data_phase.tasks.back().birth.thread_name = "";
+ process_data_phase.tasks.back().death_data.count = 138;
+ process_data_phase.tasks.back().death_data.run_duration_sum = 132;
+ process_data_phase.tasks.back().death_data.run_duration_max = 118;
+ process_data_phase.tasks.back().death_data.run_duration_sample = 114;
+ process_data_phase.tasks.back().death_data.queue_duration_sum = 109;
+ process_data_phase.tasks.back().death_data.queue_duration_max = 106;
+ process_data_phase.tasks.back().death_data.queue_duration_sample = 104;
+ process_data_phase.tasks.back().death_thread_name = "";
+
+ profiler_metrics_provider.RecordProfilerData(
+ process_data_phase, 1177, ProfilerEventProto::TrackedObject::RENDERER,
+ 0, base::TimeDelta::FromMinutes(1), base::TimeDelta::FromMinutes(2),
+ ProfilerEvents());
+ }
+
+ // Capture the data and verify that it is as expected.
+ ChromeUserMetricsExtension uma_proto;
+ profiler_metrics_provider.ProvideGeneralMetrics(&uma_proto);
+
+ // Phase 0
+ ASSERT_EQ(2, uma_proto.profiler_event_size());
+
+ EXPECT_EQ(ProfilerEventProto::VERSION_SPLIT_PROFILE,
+ uma_proto.profiler_event(0).profile_version());
+ EXPECT_EQ(ProfilerEventProto::WALL_CLOCK_TIME,
+ uma_proto.profiler_event(0).time_source());
+ ASSERT_EQ(0, uma_proto.profiler_event(0).past_session_event_size());
+ ASSERT_EQ(60000, uma_proto.profiler_event(0).profiling_start_ms());
+ ASSERT_EQ(120000, uma_proto.profiler_event(0).profiling_finish_ms());
+ ASSERT_EQ(4, uma_proto.profiler_event(0).tracked_object_size());
+
+ const ProfilerEventProto::TrackedObject* tracked_object =
+ &uma_proto.profiler_event(0).tracked_object(0);
+ EXPECT_EQ(base::HashMetricName("file.h"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(1337, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(37, tracked_object->exec_count());
+ EXPECT_EQ(31, tracked_object->exec_time_total());
+ EXPECT_EQ(13, tracked_object->exec_time_sampled());
+ EXPECT_EQ(8, tracked_object->queue_time_total());
+ EXPECT_EQ(3, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("Still_Alive"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(0).tracked_object(1);
+ EXPECT_EQ(base::HashMetricName("file2"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function2"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(1773, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread*"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(19, tracked_object->exec_count());
+ EXPECT_EQ(23, tracked_object->exec_time_total());
+ EXPECT_EQ(7, tracked_object->exec_time_sampled());
+ EXPECT_EQ(0, tracked_object->queue_time_total());
+ EXPECT_EQ(0, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("death_thread"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(0).tracked_object(2);
+ EXPECT_EQ(base::HashMetricName("file3"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function3"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(7331, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread*"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(137, tracked_object->exec_count());
+ EXPECT_EQ(131, tracked_object->exec_time_total());
+ EXPECT_EQ(113, tracked_object->exec_time_sampled());
+ EXPECT_EQ(108, tracked_object->queue_time_total());
+ EXPECT_EQ(103, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("death_thread*"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(1177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::RENDERER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(0).tracked_object(3);
+ EXPECT_EQ(base::HashMetricName(""), tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName(""),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(7332, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName(""), tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(138, tracked_object->exec_count());
+ EXPECT_EQ(132, tracked_object->exec_time_total());
+ EXPECT_EQ(114, tracked_object->exec_time_sampled());
+ EXPECT_EQ(109, tracked_object->queue_time_total());
+ EXPECT_EQ(104, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName(""), tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::RENDERER,
+ tracked_object->process_type());
+
+ // Phase 1
+ EXPECT_EQ(ProfilerEventProto::VERSION_SPLIT_PROFILE,
+ uma_proto.profiler_event(1).profile_version());
+ EXPECT_EQ(ProfilerEventProto::WALL_CLOCK_TIME,
+ uma_proto.profiler_event(1).time_source());
+ ASSERT_EQ(1, uma_proto.profiler_event(1).past_session_event_size());
+ ASSERT_EQ(ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT,
+ uma_proto.profiler_event(1).past_session_event(0));
+ ASSERT_EQ(600000, uma_proto.profiler_event(1).profiling_start_ms());
+ ASSERT_EQ(1200000, uma_proto.profiler_event(1).profiling_finish_ms());
+ ASSERT_EQ(2, uma_proto.profiler_event(1).tracked_object_size());
+
+ tracked_object = &uma_proto.profiler_event(1).tracked_object(0);
+ EXPECT_EQ(base::HashMetricName("file10.h"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function10"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(101337, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread_ten"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(1037, tracked_object->exec_count());
+ EXPECT_EQ(1031, tracked_object->exec_time_total());
+ EXPECT_EQ(1013, tracked_object->exec_time_sampled());
+ EXPECT_EQ(108, tracked_object->queue_time_total());
+ EXPECT_EQ(103, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("Already_Dead"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+
+ tracked_object = &uma_proto.profiler_event(1).tracked_object(1);
+ EXPECT_EQ(base::HashMetricName("file210"),
+ tracked_object->source_file_name_hash());
+ EXPECT_EQ(base::HashMetricName("function210"),
+ tracked_object->source_function_name_hash());
+ EXPECT_EQ(101773, tracked_object->source_line_number());
+ EXPECT_EQ(base::HashMetricName("birth_thread_ten*"),
+ tracked_object->birth_thread_name_hash());
+ EXPECT_EQ(1019, tracked_object->exec_count());
+ EXPECT_EQ(1023, tracked_object->exec_time_total());
+ EXPECT_EQ(107, tracked_object->exec_time_sampled());
+ EXPECT_EQ(100, tracked_object->queue_time_total());
+ EXPECT_EQ(100, tracked_object->queue_time_sampled());
+ EXPECT_EQ(base::HashMetricName("death_thread_ten"),
+ tracked_object->exec_thread_name_hash());
+ EXPECT_EQ(177U, tracked_object->process_id());
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::BROWSER,
+ tracked_object->process_type());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer.cc b/chromium/components/metrics/profiler/tracking_synchronizer.cc
new file mode 100644
index 00000000000..67c8361cf72
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer.cc
@@ -0,0 +1,378 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/tracking_synchronizer.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/histogram.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+#include "components/variations/variations_associated_data.h"
+
+using base::TimeTicks;
+
+namespace metrics {
+
+namespace {
+
+// Negative numbers are never used as sequence numbers. We explicitly pick a
+// negative number that is "so negative" that even when we add one (as is done
+// when we generated the next sequence number) that it will still be negative.
+// We have code that handles wrapping around on an overflow into negative
+// territory.
+const int kNeverUsableSequenceNumber = -2;
+
+// This singleton instance should be started during the single threaded
+// portion of main(). It initializes globals to provide support for all future
+// calls. This object is created on the UI thread, and it is destroyed after
+// all the other threads have gone away. As a result, it is ok to call it
+// from the UI thread, or for about:profiler.
+static TrackingSynchronizer* g_tracking_synchronizer = NULL;
+
+} // namespace
+
+// The "RequestContext" structure describes an individual request received
+// from the UI. All methods are accessible on UI thread.
+class TrackingSynchronizer::RequestContext {
+ public:
+ // A map from sequence_number_ to the actual RequestContexts.
+ typedef std::map<int, RequestContext*> RequestContextMap;
+
+ RequestContext(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object,
+ int sequence_number)
+ : callback_object_(callback_object),
+ sequence_number_(sequence_number),
+ received_process_group_count_(0),
+ processes_pending_(0) {
+ }
+ ~RequestContext() {}
+
+ void SetReceivedProcessGroupCount(bool done) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ received_process_group_count_ = done;
+ }
+
+ // Methods for book keeping of processes_pending_.
+ void IncrementProcessesPending() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ ++processes_pending_;
+ }
+
+ void AddProcessesPending(int processes_pending) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ processes_pending_ += processes_pending;
+ }
+
+ void DecrementProcessesPending() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ --processes_pending_;
+ }
+
+ // Records that we are waiting for one less tracking data from a process for
+ // the given sequence number. If |received_process_group_count_| and
+ // |processes_pending_| are zero, then delete the current object by calling
+ // Unregister.
+ void DeleteIfAllDone() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (processes_pending_ <= 0 && received_process_group_count_)
+ RequestContext::Unregister(sequence_number_);
+ }
+
+ // Register |callback_object| in |outstanding_requests_| map for the given
+ // |sequence_number|.
+ static RequestContext* Register(
+ int sequence_number,
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
+ RequestContext* request = new RequestContext(
+ callback_object, sequence_number);
+ outstanding_requests_.Get()[sequence_number] = request;
+
+ return request;
+ }
+
+ // Find the |RequestContext| in |outstanding_requests_| map for the given
+ // |sequence_number|.
+ static RequestContext* GetRequestContext(int sequence_number) {
+ RequestContextMap::iterator it =
+ outstanding_requests_.Get().find(sequence_number);
+ if (it == outstanding_requests_.Get().end())
+ return NULL;
+
+ RequestContext* request = it->second;
+ DCHECK_EQ(sequence_number, request->sequence_number_);
+ return request;
+ }
+
+ // Delete the entry for the given |sequence_number| from
+ // |outstanding_requests_| map. This method is called when all changes have
+ // been acquired, or when the wait time expires (whichever is sooner).
+ static void Unregister(int sequence_number) {
+ RequestContextMap::iterator it =
+ outstanding_requests_.Get().find(sequence_number);
+ if (it == outstanding_requests_.Get().end())
+ return;
+
+ RequestContext* request = it->second;
+ DCHECK_EQ(sequence_number, request->sequence_number_);
+ bool received_process_group_count = request->received_process_group_count_;
+ int unresponsive_processes = request->processes_pending_;
+
+ if (request->callback_object_.get())
+ request->callback_object_->FinishedReceivingProfilerData();
+
+ delete request;
+ outstanding_requests_.Get().erase(it);
+
+ UMA_HISTOGRAM_BOOLEAN("Profiling.ReceivedProcessGroupCount",
+ received_process_group_count);
+ UMA_HISTOGRAM_COUNTS("Profiling.PendingProcessNotResponding",
+ unresponsive_processes);
+ }
+
+ // Delete all the entries in |outstanding_requests_| map.
+ static void OnShutdown() {
+ // Just in case we have any pending tasks, clear them out.
+ while (!outstanding_requests_.Get().empty()) {
+ RequestContextMap::iterator it = outstanding_requests_.Get().begin();
+ delete it->second;
+ outstanding_requests_.Get().erase(it);
+ }
+ }
+
+ // Used to verify that methods are called on the UI thread (the thread on
+ // which this object was created).
+ base::ThreadChecker thread_checker_;
+
+ // Requests are made to asynchronously send data to the |callback_object_|.
+ base::WeakPtr<TrackingSynchronizerObserver> callback_object_;
+
+ // The sequence number used by the most recent update request to contact all
+ // processes.
+ int sequence_number_;
+
+ // Indicates if we have received all pending processes count.
+ bool received_process_group_count_;
+
+ // The number of pending processes (browser, all renderer processes and
+ // browser child processes) that have not yet responded to requests.
+ int processes_pending_;
+
+ // Map of all outstanding RequestContexts, from sequence_number_ to
+ // RequestContext.
+ static base::LazyInstance<RequestContextMap>::Leaky outstanding_requests_;
+};
+
+// static
+base::LazyInstance
+ <TrackingSynchronizer::RequestContext::RequestContextMap>::Leaky
+ TrackingSynchronizer::RequestContext::outstanding_requests_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+// TrackingSynchronizer methods and members.
+
+TrackingSynchronizer::TrackingSynchronizer(
+ scoped_ptr<base::TickClock> clock,
+ const TrackingSynchronizerDelegateFactory& delegate_factory)
+ : last_used_sequence_number_(kNeverUsableSequenceNumber),
+ clock_(std::move(clock)) {
+ DCHECK(!g_tracking_synchronizer);
+ g_tracking_synchronizer = this;
+ phase_start_times_.push_back(clock_->NowTicks());
+ delegate_ = delegate_factory.Run(this);
+}
+
+TrackingSynchronizer::~TrackingSynchronizer() {
+ // Just in case we have any pending tasks, clear them out.
+ RequestContext::OnShutdown();
+
+ g_tracking_synchronizer = NULL;
+}
+
+// static
+void TrackingSynchronizer::FetchProfilerDataAsynchronously(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
+ if (!g_tracking_synchronizer) {
+ // System teardown is happening.
+ return;
+ }
+
+ int sequence_number = g_tracking_synchronizer->RegisterAndNotifyAllProcesses(
+ callback_object);
+
+ // Post a task that would be called after waiting for wait_time. This acts
+ // as a watchdog, to cancel the requests for non-responsive processes.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RequestContext::Unregister, sequence_number),
+ base::TimeDelta::FromMinutes(1));
+}
+
+// static
+void TrackingSynchronizer::OnProfilingPhaseCompleted(
+ ProfilerEventProto::ProfilerEvent profiling_event) {
+ if (!g_tracking_synchronizer) {
+ // System teardown is happening.
+ return;
+ }
+
+ g_tracking_synchronizer->NotifyAllProcessesOfProfilingPhaseCompletion(
+ profiling_event);
+}
+
+void TrackingSynchronizer::OnPendingProcesses(int sequence_number,
+ int pending_processes,
+ bool end) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RequestContext* request = RequestContext::GetRequestContext(sequence_number);
+ if (!request)
+ return;
+ request->AddProcessesPending(pending_processes);
+ request->SetReceivedProcessGroupCount(end);
+ request->DeleteIfAllDone();
+}
+
+void TrackingSynchronizer::OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DecrementPendingProcessesAndSendData(sequence_number, profiler_data,
+ process_type);
+}
+
+int TrackingSynchronizer::RegisterAndNotifyAllProcesses(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ int sequence_number = GetNextAvailableSequenceNumber();
+
+ RequestContext* request =
+ RequestContext::Register(sequence_number, callback_object);
+
+ // Increment pending process count for sending browser's profiler data.
+ request->IncrementProcessesPending();
+
+ const int current_profiling_phase = phase_completion_events_sequence_.size();
+
+ delegate_->GetProfilerDataForChildProcesses(sequence_number,
+ current_profiling_phase);
+
+ // Send process data snapshot from browser process.
+ tracked_objects::ProcessDataSnapshot process_data_snapshot;
+ tracked_objects::ThreadData::Snapshot(current_profiling_phase,
+ &process_data_snapshot);
+
+ DecrementPendingProcessesAndSendData(
+ sequence_number, process_data_snapshot,
+ ProfilerEventProto::TrackedObject::BROWSER);
+
+ return sequence_number;
+}
+
+void TrackingSynchronizer::RegisterPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event) {
+ phase_completion_events_sequence_.push_back(profiling_event);
+ phase_start_times_.push_back(clock_->NowTicks());
+}
+
+void TrackingSynchronizer::NotifyAllProcessesOfProfilingPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (variations::GetVariationParamValue("UMALogPhasedProfiling",
+ "send_split_profiles") == "false") {
+ return;
+ }
+
+ int profiling_phase = phase_completion_events_sequence_.size();
+
+ // If you hit this check, stop and think. You just added a new profiling
+ // phase. Each profiling phase takes additional memory in DeathData's list of
+ // snapshots. We cannot grow it indefinitely. Consider collapsing older phases
+ // after they were sent to UMA server, or other ways to save memory.
+ DCHECK_LT(profiling_phase, 1);
+
+ RegisterPhaseCompletion(profiling_event);
+
+ delegate_->OnProfilingPhaseCompleted(profiling_phase);
+
+ // Notify browser process.
+ tracked_objects::ThreadData::OnProfilingPhaseCompleted(profiling_phase);
+}
+
+void TrackingSynchronizer::SendData(
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ TrackingSynchronizerObserver* observer) const {
+ // We are going to loop though past profiling phases and notify the request
+ // about each phase that is contained in profiler_data. past_events
+ // will track the set of past profiling events as we go.
+ ProfilerEvents past_events;
+
+ // Go through all completed phases, and through the current one. The current
+ // one is not in phase_completion_events_sequence_, but note the <=
+ // comparison.
+ for (size_t phase = 0; phase <= phase_completion_events_sequence_.size();
+ ++phase) {
+ auto it = profiler_data.phased_snapshots.find(phase);
+
+ if (it != profiler_data.phased_snapshots.end()) {
+ // If the phase is contained in the received snapshot, notify the
+ // request.
+ const base::TimeTicks phase_start = phase_start_times_[phase];
+ const base::TimeTicks phase_finish = phase + 1 < phase_start_times_.size()
+ ? phase_start_times_[phase + 1]
+ : clock_->NowTicks();
+ observer->ReceivedProfilerData(
+ ProfilerDataAttributes(phase, profiler_data.process_id, process_type,
+ phase_start, phase_finish),
+ it->second, past_events);
+ }
+
+ if (phase < phase_completion_events_sequence_.size()) {
+ past_events.push_back(phase_completion_events_sequence_[phase]);
+ }
+ }
+}
+
+void TrackingSynchronizer::DecrementPendingProcessesAndSendData(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RequestContext* request = RequestContext::GetRequestContext(sequence_number);
+ if (!request)
+ return;
+
+ TrackingSynchronizerObserver* observer = request->callback_object_.get();
+ if (observer)
+ SendData(profiler_data, process_type, observer);
+
+ // Delete request if we have heard back from all child processes.
+ request->DecrementProcessesPending();
+ request->DeleteIfAllDone();
+}
+
+int TrackingSynchronizer::GetNextAvailableSequenceNumber() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ++last_used_sequence_number_;
+
+ // Watch out for wrapping to a negative number.
+ if (last_used_sequence_number_ < 0)
+ last_used_sequence_number_ = 1;
+ return last_used_sequence_number_;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer.h b/chromium/components/metrics/profiler/tracking_synchronizer.h
new file mode 100644
index 00000000000..511cf2b5971
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer.h
@@ -0,0 +1,167 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_
+#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+// This class maintains state that is used to upload profiler data from the
+// various processes, into the browser process. Such transactions are usually
+// instigated by the browser. In general, a process will respond by gathering
+// profiler data, and transmitting the pickled profiler data. We collect the
+// data in asynchronous mode that doesn't block the UI thread.
+//
+// To assure that all the processes have responded, a counter is maintained
+// to indicate the number of pending (not yet responsive) processes. We tag
+// each group of requests with a sequence number. For each group of requests, we
+// create RequestContext object which stores the sequence number, pending
+// processes and the callback_object that needs to be notified when we receive
+// an update from processes. When an update arrives we find the RequestContext
+// associated with sequence number and send the unpickled profiler data to the
+// |callback_object_|.
+
+namespace metrics {
+
+class TrackingSynchronizerObserver;
+
+typedef base::Callback<scoped_ptr<TrackingSynchronizerDelegate>(
+ TrackingSynchronizer*)> TrackingSynchronizerDelegateFactory;
+
+class TrackingSynchronizer
+ : public base::RefCountedThreadSafe<TrackingSynchronizer> {
+ public:
+ // Construction also sets up the global singleton instance. This instance is
+ // used to communicate between the IO and UI thread, and is destroyed only as
+ // the main thread (browser_main) terminates, which means the IO thread has
+ // already completed, and will not need this instance any further.
+ // |clock| is a clock used for durations of profiling phases.
+ // |delegate| is used to abstract platform-specific profiling functionality.
+ TrackingSynchronizer(
+ scoped_ptr<base::TickClock> clock,
+ const TrackingSynchronizerDelegateFactory& delegate_factory);
+
+ // Contact all processes, and get them to upload to the browser any/all
+ // changes to profiler data. It calls |callback_object|'s SetData method with
+ // the data received from each sub-process.
+ // This method is accessible on the UI thread.
+ static void FetchProfilerDataAsynchronously(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object);
+
+ // Called when a profiling phase completes. |profiling_event| is the event
+ // that triggered the completion of the current phase, and begins a new phase.
+ // This method is accessible on the UI thread.
+ static void OnProfilingPhaseCompleted(
+ ProfilerEventProto::ProfilerEvent profiling_event);
+
+ // Send profiler_data back to |callback_object_| by calling
+ // DecrementPendingProcessesAndSendData which records that we are waiting
+ // for one less profiler data from renderer or browser child process for the
+ // given sequence number. This method is accessible on UI thread.
+ void OnProfilerDataCollected(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type);
+
+ // Update the number of pending processes for the given |sequence_number|.
+ // This is called on UI thread.
+ void OnPendingProcesses(int sequence_number, int pending_processes, bool end);
+
+ protected:
+ virtual ~TrackingSynchronizer();
+
+ // Update the sequence of completed phases with a new phase completion info.
+ void RegisterPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event);
+
+ // Notify |observer| about |profiler_data| received from process of type
+ // |process_type|.
+ void SendData(const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ TrackingSynchronizerObserver* observer) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<TrackingSynchronizer>;
+
+ class RequestContext;
+
+ // Establish a new sequence_number_, and use it to notify all the processes of
+ // the need to supply, to the browser, their tracking data. It also registers
+ // |callback_object| in |outstanding_requests_| map. Return the
+ // sequence_number_ that was used. This method is accessible on UI thread.
+ int RegisterAndNotifyAllProcesses(
+ const base::WeakPtr<TrackingSynchronizerObserver>& callback_object);
+
+ // Notifies all processes of a completion of a profiling phase.
+ // |profiling_event| is the event associated with the phase change.
+ void NotifyAllProcessesOfProfilingPhaseCompletion(
+ ProfilerEventProto::ProfilerEvent profiling_event);
+
+ // It finds the RequestContext for the given |sequence_number| and notifies
+ // the RequestContext's |callback_object_| about the |value|. This is called
+ // whenever we receive profiler data from processes. It also records that we
+ // are waiting for one less profiler data from a process for the given
+ // sequence number. If we have received a response from all renderers and
+ // browser processes, then it calls RequestContext's DeleteIfAllDone to delete
+ // the entry for sequence_number. This method is accessible on UI thread.
+ void DecrementPendingProcessesAndSendData(
+ int sequence_number,
+ const tracked_objects::ProcessDataSnapshot& profiler_data,
+ ProfilerEventProto::TrackedObject::ProcessType process_type);
+
+ // Get a new sequence number to be sent to processes from browser process.
+ // This method is accessible on UI thread.
+ int GetNextAvailableSequenceNumber();
+
+ // Used to verify that certain methods are called on the UI thread (the thread
+ // on which this object was created).
+ base::ThreadChecker thread_checker_;
+
+ // We don't track the actual processes that are contacted for an update, only
+ // the count of the number of processes, and we can sometimes time-out and
+ // give up on a "slow to respond" process. We use a sequence_number to be
+ // sure a response from a process is associated with the current round of
+ // requests. All sequence numbers used are non-negative.
+ // last_used_sequence_number_ is the most recently used number (used to avoid
+ // reuse for a long time).
+ int last_used_sequence_number_;
+
+ // Sequence of events associated with already completed profiling phases. The
+ // index in the vector is the phase number. The current phase is not included.
+ std::vector<ProfilerEventProto::ProfilerEvent>
+ phase_completion_events_sequence_;
+
+ // Clock for profiling phase durations.
+ const scoped_ptr<base::TickClock> clock_;
+
+ // Times of starts of all profiling phases, including the current phase. The
+ // index in the vector is the phase number.
+ std::vector<base::TimeTicks> phase_start_times_;
+
+ // This object's delegate.
+ // NOTE: Leave this ivar last so that the delegate is torn down first at
+ // destruction, as it has a reference to this object.
+ scoped_ptr<TrackingSynchronizerDelegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackingSynchronizer);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_H_
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h b/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h
new file mode 100644
index 00000000000..23fc15fbfc0
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_delegate.h
@@ -0,0 +1,31 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_
+#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_
+
+
+namespace metrics {
+
+class TrackingSynchronizer;
+
+// An abstraction of TrackingSynchronizer-related operations that depend on the
+// platform.
+class TrackingSynchronizerDelegate {
+ public:
+ virtual ~TrackingSynchronizerDelegate() {}
+
+ // Should perform the platform-specific action that is needed to start
+ // gathering profiler data for all relevant child processes.
+ virtual void GetProfilerDataForChildProcesses(
+ int sequence_number,
+ int current_profiling_phase) = 0;
+
+ // Called when |profiling_phase| has completed.
+ virtual void OnProfilingPhaseCompleted(int profiling_phase) = 0;
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_DELEGATE_H_
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc b/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc
new file mode 100644
index 00000000000..2a8eb3d2e4b
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_observer.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+
+namespace metrics {
+
+ProfilerDataAttributes::ProfilerDataAttributes(
+ int profiling_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ base::TimeTicks phase_start,
+ base::TimeTicks phase_finish)
+ : profiling_phase(profiling_phase),
+ process_id(process_id),
+ process_type(process_type),
+ phase_start(phase_start),
+ phase_finish(phase_finish) {}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_observer.h b/chromium/components/metrics/profiler/tracking_synchronizer_observer.h
new file mode 100644
index 00000000000..c35de35cef2
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_observer.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_
+#define COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class TimeDelta;
+}
+
+namespace tracked_objects {
+struct ProcessDataPhaseSnapshot;
+}
+
+namespace metrics {
+
+// Set of profiling events, in no guaranteed order. Implemented as a vector
+// because we don't need to have an efficient .find() on it, so vector<> is more
+// efficient.
+typedef std::vector<ProfilerEventProto::ProfilerEvent> ProfilerEvents;
+
+// Attributes of profiler data passed to
+// TrackingSynchronizerObserver::ReceivedProfilerData.
+struct ProfilerDataAttributes {
+ ProfilerDataAttributes(
+ int profiling_phase,
+ base::ProcessId process_id,
+ ProfilerEventProto::TrackedObject::ProcessType process_type,
+ base::TimeTicks phase_start,
+ base::TimeTicks phase_finish);
+
+ // 0-indexed profiling phase number.
+ const int profiling_phase;
+
+ // ID of the process that reported the data.
+ const base::ProcessId process_id;
+
+ // Type of the process that reported the data.
+ const ProfilerEventProto::TrackedObject::ProcessType process_type;
+
+ // Time of the profiling phase start.
+ const base::TimeTicks phase_start;
+
+ // Time of the profiling phase finish.
+ const base::TimeTicks phase_finish;
+};
+
+// Observer for notifications from the TrackingSynchronizer class.
+class TrackingSynchronizerObserver {
+ public:
+ // Received |process_data_phase| for profiling phase and process defined by
+ // |attributes|.
+ // Each completed phase is associated with an event that triggered the
+ // completion of the phase. |past_events| contains the set of events that
+ // completed prior to the reported phase. This data structure is useful for
+ // quickly computing the full set of profiled traces that occurred before or
+ // after a given event.
+ // The observer should assume there might be more data coming until
+ // FinishedReceivingData() is called.
+ virtual void ReceivedProfilerData(
+ const ProfilerDataAttributes& attributes,
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ const ProfilerEvents& past_events) = 0;
+
+ // The observer should not expect any more calls to |ReceivedProfilerData()|
+ // (without re-registering). This is sent either when data from all processes
+ // has been gathered, or when the request times out.
+ virtual void FinishedReceivingProfilerData() {}
+
+ protected:
+ TrackingSynchronizerObserver() {}
+ virtual ~TrackingSynchronizerObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TrackingSynchronizerObserver);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_PROFILER_TRACKING_SYNCHRONIZER_OBSERVER_H_
diff --git a/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc b/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc
new file mode 100644
index 00000000000..f1df9eb4181
--- /dev/null
+++ b/chromium/components/metrics/profiler/tracking_synchronizer_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/profiler/tracking_synchronizer.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/tracked_objects.h"
+#include "components/metrics/profiler/tracking_synchronizer_delegate.h"
+#include "components/metrics/profiler/tracking_synchronizer_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using tracked_objects::ProcessDataPhaseSnapshot;
+using tracked_objects::TaskSnapshot;
+
+namespace metrics {
+
+namespace {
+
+class TestDelegate : public TrackingSynchronizerDelegate {
+ public:
+ ~TestDelegate() override {}
+
+ static scoped_ptr<TrackingSynchronizerDelegate> Create(
+ TrackingSynchronizer* synchronizer) {
+ return make_scoped_ptr(new TestDelegate());
+ }
+
+ private:
+ TestDelegate() {}
+
+ // TrackingSynchronizerDelegate:
+ void GetProfilerDataForChildProcesses(int sequence_number,
+ int current_profiling_phase) override {}
+ void OnProfilingPhaseCompleted(int profiling_phase) override {}
+};
+
+class TestObserver : public TrackingSynchronizerObserver {
+ public:
+ TestObserver() {}
+
+ ~TestObserver() override {
+ EXPECT_TRUE(got_phase_0_);
+ EXPECT_TRUE(got_phase_1_);
+ }
+
+ void ReceivedProfilerData(
+ const ProfilerDataAttributes& attributes,
+ const tracked_objects::ProcessDataPhaseSnapshot& process_data_phase,
+ const ProfilerEvents& past_events) override {
+ EXPECT_EQ(static_cast<base::ProcessId>(239), attributes.process_id);
+ EXPECT_EQ(ProfilerEventProto::TrackedObject::PPAPI_PLUGIN,
+ attributes.process_type);
+ ASSERT_EQ(1u, process_data_phase.tasks.size());
+
+ switch (attributes.profiling_phase) {
+ case 0:
+ EXPECT_FALSE(got_phase_0_);
+ got_phase_0_ = true;
+
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(111),
+ attributes.phase_start);
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(333),
+ attributes.phase_finish);
+
+ EXPECT_EQ("death_thread0",
+ process_data_phase.tasks[0].death_thread_name);
+ EXPECT_EQ(0u, past_events.size());
+ break;
+
+ case 1:
+ EXPECT_FALSE(got_phase_1_);
+ got_phase_1_ = true;
+
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(333),
+ attributes.phase_start);
+ EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromMilliseconds(777),
+ attributes.phase_finish);
+
+ EXPECT_EQ("death_thread1",
+ process_data_phase.tasks[0].death_thread_name);
+ ASSERT_EQ(1u, past_events.size());
+ EXPECT_EQ(ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT,
+ past_events[0]);
+ break;
+
+ default:
+ bool profiling_phase_is_neither_0_nor_1 = true;
+ EXPECT_FALSE(profiling_phase_is_neither_0_nor_1);
+ }
+ }
+
+ private:
+ bool got_phase_0_ = false;
+ bool got_phase_1_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+class TestTrackingSynchronizer : public TrackingSynchronizer {
+ public:
+ explicit TestTrackingSynchronizer(scoped_ptr<base::TickClock> clock)
+ : TrackingSynchronizer(std::move(clock),
+ base::Bind(&TestDelegate::Create)) {}
+
+ using TrackingSynchronizer::RegisterPhaseCompletion;
+ using TrackingSynchronizer::SendData;
+
+ private:
+ ~TestTrackingSynchronizer() override {}
+};
+
+} // namespace
+
+TEST(TrackingSynchronizerTest, ProfilerData) {
+ // Testing how TrackingSynchronizer reports 2 phases of profiling.
+ auto clock = new base::SimpleTestTickClock(); // Will be owned by
+ // |tracking_synchronizer|.
+ clock->Advance(base::TimeDelta::FromMilliseconds(111));
+
+ scoped_refptr<TestTrackingSynchronizer> tracking_synchronizer =
+ new TestTrackingSynchronizer(make_scoped_ptr(clock));
+
+ clock->Advance(base::TimeDelta::FromMilliseconds(222));
+
+ tracking_synchronizer->RegisterPhaseCompletion(
+ ProfilerEventProto::EVENT_FIRST_NONEMPTY_PAINT);
+
+ tracked_objects::ProcessDataSnapshot profiler_data;
+ ProcessDataPhaseSnapshot snapshot0;
+ tracked_objects::TaskSnapshot task_snapshot0;
+ task_snapshot0.death_thread_name = "death_thread0";
+ snapshot0.tasks.push_back(task_snapshot0);
+ ProcessDataPhaseSnapshot snapshot1;
+ profiler_data.phased_snapshots[0] = snapshot0;
+ tracked_objects::TaskSnapshot task_snapshot1;
+ task_snapshot1.death_thread_name = "death_thread1";
+ snapshot1.tasks.push_back(task_snapshot1);
+ profiler_data.phased_snapshots[1] = snapshot1;
+ profiler_data.process_id = 239;
+
+ clock->Advance(base::TimeDelta::FromMilliseconds(444));
+ TestObserver test_observer;
+ tracking_synchronizer->SendData(
+ profiler_data, ProfilerEventProto::TrackedObject::PPAPI_PLUGIN,
+ &test_observer);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/proto/BUILD.gn b/chromium/components/metrics/proto/BUILD.gn
new file mode 100644
index 00000000000..3271f184cdf
--- /dev/null
+++ b/chromium/components/metrics/proto/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+# GYP version: components/
+proto_library("proto") {
+ sources = [
+ "call_stack_profile.proto",
+ "cast_logs.proto",
+ "chrome_user_metrics_extension.proto",
+ "histogram_event.proto",
+ "memory_leak_report.proto",
+ "omnibox_event.proto",
+ "omnibox_input_type.proto",
+ "perf_data.proto",
+ "perf_stat.proto",
+ "profiler_event.proto",
+ "sampled_profile.proto",
+ "system_profile.proto",
+ "user_action_event.proto",
+ ]
+}
diff --git a/chromium/components/metrics/proto/call_stack_profile.proto b/chromium/components/metrics/proto/call_stack_profile.proto
new file mode 100644
index 00000000000..e41b3390224
--- /dev/null
+++ b/chromium/components/metrics/proto/call_stack_profile.proto
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Call stack sample data for a given profiling session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "CallStackProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 5
+message CallStackProfile {
+ // Describes an entry in the callstack.
+ message Entry {
+ // Instruction pointer subtracted by module base.
+ optional uint64 address = 1;
+
+ // Index to the module identifier in |module_ids| of CallStackProfile.
+ optional int32 module_id_index = 2;
+ }
+
+ // A sample consisting of one or more callstacks with the same stack frames
+ // and instruction pointers.
+ message Sample {
+ // The callstack. Sample.entries[0] represents the call on the top of the
+ // stack.
+ repeated Entry entry = 1;
+
+ // Number of times this stack signature occurs.
+ optional int64 count = 2;
+ }
+
+ // Uniquely identifies a module.
+ message ModuleIdentifier {
+ // A hash that uniquely identifies a particular program version with high
+ // probability. This is parsed from headers of the loaded module.
+ // For binaries generated by GNU tools:
+ // Contents of the .note.gnu.build-id field.
+ // On Windows:
+ // GUID + AGE in the debug image headers of a module.
+ optional string build_id = 1;
+
+ // MD5Sum Prefix of the module name. This is the same hashing scheme as used
+ // to hash UMA histogram names.
+ optional fixed64 name_md5_prefix = 2;
+ }
+
+ // The callstack and counts.
+ repeated Sample sample = 1;
+
+ // List of module ids found in this sample.
+ repeated ModuleIdentifier module_id = 2;
+
+ // Duration of this profile.
+ optional int32 profile_duration_ms = 3;
+
+ // Time between samples.
+ optional int32 sampling_period_ms = 4;
+}
diff --git a/chromium/components/metrics/proto/cast_logs.proto b/chromium/components/metrics/proto/cast_logs.proto
new file mode 100644
index 00000000000..0d01ef73b27
--- /dev/null
+++ b/chromium/components/metrics/proto/cast_logs.proto
@@ -0,0 +1,182 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Cast-enabled device specific log data included in ChromeUserMetricsExtension.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "CastLogsProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 7
+message CastLogsProto {
+ // Cast specific device information.
+ // Next tag: 5
+ message CastDeviceInfo {
+ // The product type of Cast device sent from Cast-enabled devices.
+ // Next tag: 5
+ enum CastProductType {
+ CAST_PRODUCT_TYPE_UNKNOWN = 0;
+ CAST_PRODUCT_TYPE_CHROMECAST = 1;
+ CAST_PRODUCT_TYPE_AUDIO = 3;
+ CAST_PRODUCT_TYPE_ANDROID_TV = 4;
+ }
+ optional CastProductType type = 1;
+
+ // The hardware revision of each product.
+ optional string hardware_revision = 2;
+
+ // The manufacturer of Cast device, this value is empty when the device
+ // is manufactured by Google.
+ optional string manufacturer = 3;
+
+ // The model of the Cast device.
+ optional string model = 4;
+ }
+ // The device sends this information at least once per day.
+ optional CastDeviceInfo cast_device_info = 1;
+
+ // Information about Cast connection between sender application and
+ // Cast-enabled device.
+ // Next tag: 4
+ message CastConnectionInfo {
+ optional fixed32 transport_connection_id = 1;
+
+ optional fixed32 virtual_connection_id = 2;
+
+ // This message describes a detail sender device and sdk. Those are
+ // parsed from the user agent string sent from sender sdk during connection.
+ // Next tag: 9
+ message SenderInfo {
+ // The identifier for the sender device, that is not tied any kind of
+ // device id outside of UMA, and this id is reset when user resets sender
+ // device.
+ optional fixed64 sender_device_id = 1;
+
+ // SDK type the sender application was using.
+ // Next tag: 3
+ enum SDKType {
+ SDK_UNKNOWN = 0;
+
+ // Native SDK type,
+ // E.G. Android sdk, iOS sdk.
+ SDK_NATIVE = 1;
+
+ // SDK via Chrome extension.
+ SDK_CHROME_EXTENSION = 2;
+ }
+ optional SDKType sdk_type = 2;
+
+ // Version of sender sdk/extension used to connection.
+ optional string version = 3;
+
+ // Chrome browser version where the Chrome extension running.
+ // Only Chrome extension sends this information.
+ optional string chrome_browser_version = 4;
+
+ // Platform of sender device.
+ // Next tag: 7
+ enum Platform {
+ // Any platform other then cases below.
+ PLATFORM_OTHER = 0;
+
+ PLATFORM_ANDROID = 1;
+ PLATFORM_IOS = 2;
+ PLATFORM_WINDOWS = 3;
+ PLATFORM_OSX = 4;
+ PLATFORM_CHROMEOS = 5;
+ PLATFORM_LINUX = 6;
+ }
+ optional Platform platform = 5;
+
+ // Sender device system version.
+ optional string system_version = 6;
+
+ // What type of connection type used to establish between sender and
+ // receiver.
+ enum ConnectionType {
+ CONNECTION_TYPE_UNKNOWN = 0;
+ CONNECTION_TYPE_LOCAL = 1;
+ CONNECTION_TYPE_RELAY = 2;
+ }
+ optional ConnectionType transport_connection_type = 7;
+
+ // Sender device model.
+ optional string model = 8;
+ }
+ optional SenderInfo sender_info = 3;
+ }
+
+ // Virtual connection established between sender application and Cast device.
+ repeated CastConnectionInfo cast_connection_info = 2;
+
+ // Stores Cast-enabled device specific events with a various context data.
+ // Next tag: 10
+ message CastEventProto {
+ // The name of the action, hashed by same logic used to hash user action
+ // event and histogram.
+ optional fixed64 name_hash = 1;
+
+ // The timestamp for the event, in milliseconds.
+ optional int64 time_msec = 2;
+
+ // The Cast receiver app ID related with this event.
+ optional fixed32 app_id = 3;
+
+ // The identifier for receiver application session.
+ optional fixed64 application_session_id = 4;
+
+ // Receiver side Cast SDK version.
+ optional fixed64 cast_receiver_version = 5;
+
+ // Cast MPL version.
+ optional fixed64 cast_mpl_version = 9;
+
+ // transport_connection_id related with this event.
+ optional fixed32 transport_connection_id = 6;
+
+ // virtual_connection_id related with this event.
+ optional fixed32 virtual_connection_id = 7;
+
+ // An optional value for the associated event
+ optional int64 value = 8;
+
+ // An optional value for the multi-room group uuid.
+ optional fixed64 group_uuid = 10;
+ }
+ repeated CastEventProto cast_event = 3;
+
+ // Virtual release track for device.
+ optional fixed32 virtual_release_track = 4;
+
+ // Cast specific device information which is expected to change over time.
+ // Next tag: 2
+ message CastDeviceMutableInfo {
+ // This is the last type of reboot the device encountered
+ // Next tag: 9
+ enum RebootType {
+ REBOOT_TYPE_UNKNOWN = 0;
+ REBOOT_TYPE_FORCED = 1;
+ REBOOT_TYPE_API = 2;
+ REBOOT_TYPE_NIGHTLY = 3;
+ REBOOT_TYPE_OTA = 4;
+ REBOOT_TYPE_WATCHDOG = 5;
+ REBOOT_TYPE_PROCESS_MANAGER = 6;
+ REBOOT_TYPE_CRASH_UPLOADER = 7;
+ REBOOT_TYPE_FDR = 8;
+ }
+ optional RebootType last_reboot_type = 1;
+ }
+ optional CastDeviceMutableInfo cast_device_mutable_info = 5;
+
+ // Unique identifier that is randomly generated on first setup, on re-setup
+ // (FDR), and when user opted-in from opted-out status. If user is opted-out
+ // then this field should not be set.
+ // This is used for joining logs from Cast sender SDK to evaluate Cast
+ // sender/receiver communication quality.
+ optional fixed64 receiver_metrics_id = 6;
+}
diff --git a/chromium/components/metrics/proto/chrome_user_metrics_extension.proto b/chromium/components/metrics/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 00000000000..a80081a774d
--- /dev/null
+++ b/chromium/components/metrics/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Protocol buffer for Chrome UMA (User Metrics Analysis).
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ChromeUserMetricsExtensionProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "cast_logs.proto";
+import "histogram_event.proto";
+import "omnibox_event.proto";
+import "profiler_event.proto";
+import "system_profile.proto";
+import "user_action_event.proto";
+import "perf_data.proto";
+import "sampled_profile.proto";
+
+// Next tag: 13
+message ChromeUserMetricsExtension {
+ // The product (i.e. end user application) for a given UMA log.
+ enum Product {
+ // Google Chrome product family.
+ CHROME = 0;
+
+ // UMA metrics from Android Webview.
+ ANDROID_WEBVIEW = 20;
+ }
+ // The product corresponding to this log. The field type is int32 instead of
+ // Product so that downstream users of the Chromium metrics component can
+ // introduce products without needing to make changes to the Chromium code
+ // (though they still need to add the new product to the server-side enum).
+ // Note: The default value is Chrome, so Chrome products will not transmit
+ // this field.
+ optional int32 product = 10 [default = 0];
+
+ // The id of the client install that generated these events.
+ //
+ // For Chrome clients, this id is unique to a top-level (one level above the
+ // "Default" directory) Chrome user data directory [1], and so is shared among
+ // all Chrome user profiles contained in this user data directory.
+ // An id of 0 is reserved for test data (monitoring and internal testing) and
+ // should normally be ignored in analysis of the data.
+ // [1] http://www.chromium.org/user-experience/user-data-directory
+ optional fixed64 client_id = 1;
+
+ // The session id for this user.
+ // Values such as tab ids are only meaningful within a particular session.
+ // The client keeps track of the session id and sends it with each event.
+ // The session id is simply an integer that is incremented each time the user
+ // relaunches Chrome.
+ optional int32 session_id = 2;
+
+ // Information about the user's browser and system configuration.
+ optional SystemProfileProto system_profile = 3;
+
+ // This message will log one or more of the following event types:
+ repeated UserActionEventProto user_action_event = 4;
+ repeated OmniboxEventProto omnibox_event = 5;
+ repeated HistogramEventProto histogram_event = 6;
+ repeated ProfilerEventProto profiler_event = 7;
+
+ // This field is no longer used. Use |sampled_profile| instead.
+ repeated PerfDataProto perf_data = 8 [deprecated=true];
+
+ // A list of all collected sample-based profiles since the last UMA upload.
+ repeated SampledProfile sampled_profile = 11;
+
+ // Additional data related with Cast-enabled devices.
+ optional CastLogsProto cast_logs = 12;
+}
diff --git a/chromium/components/metrics/proto/histogram_event.proto b/chromium/components/metrics/proto/histogram_event.proto
new file mode 100644
index 00000000000..8b054172eae
--- /dev/null
+++ b/chromium/components/metrics/proto/histogram_event.proto
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Histogram-collected metrics.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "HistogramEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 4
+message HistogramEventProto {
+ // The name of the histogram, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The sum of all the sample values.
+ // Together with the total count of the sample values, this allows us to
+ // compute the average value. The count of all sample values is just the sum
+ // of the counts of all the buckets. As of M51, when the value of this field
+ // would be 0, the field will be omitted instead.
+ optional int64 sum = 2;
+
+ // The per-bucket data.
+ message Bucket {
+ // Each bucket's range is bounded by min <= x < max.
+ // It is valid to omit one of these two fields in a bucket, but not both.
+ // If the min field is omitted, its value is assumed to be equal to max - 1.
+ // If the max field is omitted, its value is assumed to be equal to the next
+ // bucket's min value (possibly computed per above). The last bucket in a
+ // histogram should always include the max field.
+ optional int64 min = 1;
+ optional int64 max = 2;
+
+ // The bucket's index in the list of buckets, sorted in ascending order.
+ // This field was intended to provide extra redundancy to detect corrupted
+ // records, but was never used. As of M31, it is no longer sent by Chrome
+ // clients to reduce the UMA upload size.
+ optional int32 bucket_index = 3 [deprecated = true];
+
+ // The number of entries in this bucket. As of M51, when the value of this
+ // field would be 1, the field will be omitted instead.
+ optional int64 count = 4 [default = 1];
+ }
+ repeated Bucket bucket = 3;
+}
diff --git a/chromium/components/metrics/proto/memory_leak_report.proto b/chromium/components/metrics/proto/memory_leak_report.proto
new file mode 100644
index 00000000000..b465f16b4fe
--- /dev/null
+++ b/chromium/components/metrics/proto/memory_leak_report.proto
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package metrics;
+
+// Next tag: 8
+message MemoryLeakReportProto {
+ // The call stack at which the leak was found. This is a list of offsets
+ // within the program binary. The first entry is the deepest level of the call
+ // stack.
+ //
+ // Some call stack entries may not be within the Chrome binary (e.g.
+ // JavaScript code). Those entries are given as the absolute offset in memory.
+ //
+ // The offsets within Chrome are determined by whether the original call stack
+ // address was within the executable region of the Chrome binary's mapping in
+ // memory. To symbolize these results, look up these values as offsets within
+ // the Chrome debug binary. If the value doesn't fit within the Chrome
+ // binary's offset range, then it is considered to be from another binary.
+ repeated uint64 call_stack = 1;
+
+ // Size of the memory allocation involved in the leak.
+ optional uint32 size_bytes = 2;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // The rate at which allocations are pseudorandomly sampled. Ranges from 0 to
+ // 1. A rate of 1 means all incoming allocations are sampled by the leak
+ // detector, which is the maximum possible.
+ optional float sampling_rate = 3;
+
+ // The max depth to which the call stacks were unwound by the leak detector.
+ // This may be greater than the size of |call_stack|.
+ optional uint32 max_stack_depth = 4;
+
+ // The leak analysis takes place every so often, with an interval based on the
+ // number of bytes allocated. This is independent of the sampling rate as it
+ // is computed from allocation sizes before sampling.
+ optional uint64 analysis_interval_bytes = 5;
+
+ // Suspicion thresholds used in leak analysis for size and call stacks,
+ // respectively. If an allocation size or call stack is suspected this many
+ // times in a row, the leak analysis escalates to the next level. For
+ // allocation sizes, the next level is to start analyzing by call stack. For
+ // call stacks, the next level is to generate a memory leak report.
+ optional uint32 size_suspicion_threshold = 6;
+ optional uint32 call_stack_suspicion_threshold = 7;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Represents a single snapshot of the internal bookkeeping of the Runtime
+ // Memory Leak Detector, which tracks the number of extant allocations (a
+ // block of heap memory that has been allocated but not yet freed).
+ //
+ // Next tag: 3
+ message AllocationBreakdown {
+ // Table of number of extant allocations for each allocation size. The i-th
+ // entry in the vector is the net number of allocations for sizes in the
+ // range [i * 4, i * 4 + 3].
+ repeated uint32 counts_by_size = 1;
+
+ // The number of extant allocations with size = |size_bytes| and made from
+ // the call site given by |call_stack|. If it is not set, it means tracking
+ // of allocs per call site for allocation size = |size_bytes| has not yet
+ // begun at the time of this entry.
+ optional uint32 count_for_call_stack = 2;
+ }
+
+ // A record of past allocation data leading up to the circumstances that
+ // generated the current leak report.
+ //
+ // A new snapshot is taken every |analysis_interval_bytes| of memory
+ // allocation. The oldest record is at the beginning. The most recent record,
+ // taken at the time the report was generated, is at the end.
+ repeated AllocationBreakdown alloc_breakdown_history = 8;
+}
diff --git a/chromium/components/metrics/proto/omnibox_event.proto b/chromium/components/metrics/proto/omnibox_event.proto
new file mode 100644
index 00000000000..5d4f7199657
--- /dev/null
+++ b/chromium/components/metrics/proto/omnibox_event.proto
@@ -0,0 +1,286 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an omnibox interaction.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "OmniboxEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "omnibox_input_type.proto";
+
+// Next tag: 17
+message OmniboxEventProto {
+ // The timestamp for the event, in seconds since the epoch.
+ optional int64 time = 1;
+
+ // The id of the originating tab for this omnibox interaction.
+ // This is the current tab *unless* the user opened the target in a new tab.
+ // In those cases, this is unset. Tab ids are unique for a given session_id
+ // (in the containing protocol buffer ChromeUserMetricsExtensionProto).
+ optional int32 tab_id = 2;
+
+ // The number of characters the user had typed before autocompleting.
+ optional int32 typed_length = 3;
+
+ // Whether the user deleted text immediately before selecting an omnibox
+ // suggestion. This is usually the result of pressing backspace or delete.
+ optional bool just_deleted_text = 11;
+
+ // The number of terms that the user typed in the omnibox.
+ optional int32 num_typed_terms = 4;
+
+ // The index of the item that the user selected in the omnibox popup list.
+ // This corresponds the index of the |suggestion| below.
+ optional int32 selected_index = 5;
+
+ // DEPRECATED. Whether or not the top match was hidden in the omnibox
+ // suggestions dropdown.
+ optional bool DEPRECATED_is_top_result_hidden_in_dropdown = 14
+ [deprecated = true];
+
+ // Whether the omnibox popup is open. It can be closed if, for instance,
+ // the user clicks in the omnibox and hits return to reload the same page.
+ // If the popup is closed, the suggestion list will contain only one item
+ // and selected_index will be 0 (pointing to that single item). Because
+ // paste-and-search/paste-and-go actions ignore the current content of the
+ // omnibox dropdown (if it is open) when they happen, we pretend the
+ // dropdown is closed when logging these.
+ optional bool is_popup_open = 15;
+
+ // True if this is a paste-and-search or paste-and-go action. (The codebase
+ // refers to both these types as paste-and-go.)
+ optional bool is_paste_and_go = 16;
+
+ // The length of the inline autocomplete text in the omnibox.
+ // The sum |typed_length| + |completed_length| gives the full length of the
+ // user-visible text in the omnibox.
+ // This field is only set for suggestions that are allowed to be the default
+ // match and omitted otherwise. The first suggestion is always allowed to
+ // be the default match. (This is an enforced constraint.) Hence, if
+ // |selected_index| == 0, then this field will always be set.
+ optional int32 completed_length = 6;
+
+ // The amount of time, in milliseconds, since the user first began modifying
+ // the text in the omnibox. If at some point after modifying the text, the
+ // user reverts the modifications (thus seeing the current web page's URL
+ // again), then writes in the omnibox again, this elapsed time should start
+ // from the time of the second series of modification.
+ optional int64 typing_duration_ms = 7;
+
+ // The amount of time, in milliseconds, since the last time the default
+ // (inline) match changed. This may be longer than the time since the
+ // last keystroke. (The last keystroke may not have changed the default
+ // match.) It may also be shorter than the time since the last keystroke
+ // because the default match might have come from an asynchronous
+ // provider. Regardless, it should always be less than or equal to
+ // the field |typing_duration_ms|.
+ optional int64 duration_since_last_default_match_update_ms = 13;
+
+ // The type of page currently displayed when the user used the omnibox.
+ enum PageClassification {
+ // An invalid URL; shouldn't happen.
+ INVALID_SPEC = 0;
+
+ // chrome://newtab/. This can be either the built-in version or a
+ // replacement new tab page from an extension. Note that when Instant
+ // Extended is enabled, the new tab page will be reported as either
+ // INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS or
+ // INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS below,
+ // unless an extension is replacing the new tab page, in which case
+ // it will still be reported as NTP.
+ NTP = 1;
+
+ // about:blank.
+ BLANK = 2;
+
+ // The user's home page. Note that if the home page is set to any
+ // of the new tab page versions or to about:blank, then we'll
+ // classify the page into those categories, not HOME_PAGE.
+ HOME_PAGE = 3;
+
+ // The catch-all entry of everything not included somewhere else
+ // on this list.
+ OTHER = 4;
+
+ // The instant new tab page enum value was deprecated on August 2, 2013.
+ OBSOLETE_INSTANT_NTP = 5;
+
+ // The user is on a search result page that's doing search term
+ // replacement, meaning the search terms should've appeared in the omnibox
+ // before the user started editing it, not the URL of the page.
+ SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT = 6;
+
+ // The new tab page in which this omnibox interaction first started
+ // with the user having focus in the omnibox.
+ INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS = 7;
+
+ // The new tab page in which this omnibox interaction first started
+ // with the user having focus in the fakebox.
+ INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS = 8;
+
+ // The user is on a search result page that's not doing search term
+ // replacement, meaning the URL of the page should've appeared in the
+ // omnibox before the user started editing it, not the search terms.
+ SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT = 9;
+
+ // The user is on the home screen.
+ APP_HOME = 10;
+
+ // The user is in the search app.
+ APP_SEARCH = 11;
+
+ // The user is in the maps app.
+ APP_MAPS = 12;
+
+ // When adding new classifications, please consider adding them in
+ // chrome/browser/resources/omnibox/omnibox.html
+ // so that these new options are displayed on about:omnibox.
+ }
+ optional PageClassification current_page_classification = 10;
+
+ optional OmniboxInputType.Type input_type = 8;
+
+ // An enum used in multiple places below.
+ enum ProviderType {
+ UNKNOWN_PROVIDER = 0; // Unknown provider (should not reach here)
+ HISTORY_URL = 1; // URLs in history, or user-typed URLs
+ HISTORY_CONTENTS = 2; // Matches for page contents of pages in history
+ HISTORY_QUICK = 3; // Matches for recently or frequently visited pages
+ // in history
+ SEARCH = 4; // Search suggestions for the default search engine
+ KEYWORD = 5; // Keyword-triggered searches
+ BUILTIN = 6; // Built-in URLs, such as chrome://version
+ SHORTCUTS = 7; // Recently selected omnibox suggestions
+ EXTENSION_APPS = 8; // DEPRECATED. Suggestions from extensions or apps
+ CONTACT = 9; // DEPRECATED. The user's contacts
+ BOOKMARK = 10; // The user's bookmarks
+ ZERO_SUGGEST = 11; // Suggestions based on the current page
+ // This enum value is currently only used by Android GSA. It represents
+ // a suggestion from the phone.
+ ON_DEVICE = 12;
+ // This enum value is currently only used by Android GSA. It represents
+ // a suggestion powered by a Chrome content provider.
+ ON_DEVICE_CHROME = 13;
+ CLIPBOARD_URL = 14; // Suggestion coming from clipboard (iOS only).
+ }
+
+ // The result set displayed on the completion popup
+ // Next tag: 7
+ message Suggestion {
+ // Where does this result come from?
+ optional ProviderType provider = 1;
+
+ // What kind of result this is.
+ // This corresponds to the AutocompleteMatch::Type enumeration in
+ // components/omnibox/autocomplete_match.h (except for Android
+ // GSA result types).
+ enum ResultType {
+ UNKNOWN_RESULT_TYPE = 0; // Unknown type (should not reach here)
+ URL_WHAT_YOU_TYPED = 1; // The input as a URL
+ HISTORY_URL = 2; // A past page whose URL contains the input
+ HISTORY_TITLE = 3; // A past page whose title contains the input
+ HISTORY_BODY = 4; // DEPRECATED. A past page whose body
+ // contains the input
+ HISTORY_KEYWORD = 5; // A past page whose keyword contains the
+ // input
+ NAVSUGGEST = 6; // A suggested URL
+ SEARCH_WHAT_YOU_TYPED = 7; // The input as a search query (with the
+ // default engine)
+ SEARCH_HISTORY = 8; // A past search (with the default engine)
+ // containing the input
+ SEARCH_SUGGEST = 9; // A suggested search (with the default
+ // engine) for a query.
+ SEARCH_OTHER_ENGINE = 10; // A search with a non-default engine
+ EXTENSION_APP = 11; // DEPRECATED. An Extension App with a
+ // title/url that contains the input.
+ CONTACT = 12; // One of the user's contacts
+ BOOKMARK_TITLE = 13; // A bookmark whose title contains the input.
+ SEARCH_SUGGEST_ENTITY = 14; // A suggested search for an entity.
+ SEARCH_SUGGEST_TAIL = 15; // A suggested search to complete the tail
+ // of the query.
+ SEARCH_SUGGEST_PERSONALIZED = 16; // A personalized suggested search.
+ SEARCH_SUGGEST_PROFILE = 17; // A personalized suggested search for a
+ // Google+ profile.
+ APP_RESULT = 18; // Result from an installed app
+ // (eg: a gmail email).
+ // Used by Android GSA for on-device
+ // suggestion logging.
+ APP = 19; // An app result (eg: the gmail app).
+ // Used by Android GSA for on-device
+ // suggestion logging.
+ LEGACY_ON_DEVICE = 20; // An on-device result from a legacy
+ // provider. That is, this result is not
+ // from the on-device suggestion provider
+ // (go/icing). This field is
+ // used by Android GSA for on-device
+ // suggestion logging.
+ NAVSUGGEST_PERSONALIZED = 21; // A personalized url.
+ SEARCH_SUGGEST_ANSWER = 22; // DEPRECATED. Answers no longer have their
+ // own type but instead can be attached to
+ // suggestions of any type.
+ CALCULATOR = 23; // A calculator answer.
+ CLIPBOARD = 24; // An URL based on the clipboard.
+ }
+ optional ResultType result_type = 2;
+
+ // The relevance score for this suggestion.
+ optional int32 relevance = 3;
+
+ // How many times this result was typed in / selected from the omnibox.
+ // Only set for some providers and result_types. At the time of
+ // writing this comment, it is only set for HistoryURL and
+ // HistoryQuickProvider matches.
+ optional int32 typed_count = 5;
+
+ // Whether this item is starred (bookmarked) or not.
+ optional bool is_starred = 4 [deprecated=true];
+
+ // Whether this item is disabled in the UI (not clickable).
+ optional bool is_disabled = 6;
+ }
+ repeated Suggestion suggestion = 9;
+
+ // A data structure that holds per-provider information, general information
+ // not associated with a particular result.
+ // Next tag: 6
+ message ProviderInfo {
+ // Which provider generated this ProviderInfo entry.
+ optional ProviderType provider = 1;
+
+ // The provider's done() value, i.e., whether it's completed processing
+ // the query. Providers which don't do any asynchronous processing
+ // will always be done.
+ optional bool provider_done = 2;
+
+ // The set of field trials that have triggered in the most recent query,
+ // possibly affecting the shown suggestions. Each element is a hash
+ // of the corresponding field trial name.
+ // See chrome/browser/autocomplete/search_provider.cc for a specific usage
+ // example.
+ repeated fixed32 field_trial_triggered = 3;
+
+ // Same as above except that the set of field trials is a union of all field
+ // trials that have triggered within the current omnibox session including
+ // the most recent query.
+ // See AutocompleteController::ResetSession() for more details on the
+ // definition of a session.
+ // See chrome/browser/autocomplete/search_provider.cc for a specific usage
+ // example.
+ repeated fixed32 field_trial_triggered_in_session = 4;
+
+ // The number of times this provider returned a non-zero number of
+ // suggestions during this omnibox session.
+ // Note that each provider may define a session differently for its
+ // purposes.
+ optional int32 times_returned_results_in_session = 5;
+ }
+ // A list of diagnostic information about each provider. Providers
+ // will appear at most once in this list.
+ repeated ProviderInfo provider_info = 12;
+}
diff --git a/chromium/components/metrics/proto/omnibox_input_type.proto b/chromium/components/metrics/proto/omnibox_input_type.proto
new file mode 100644
index 00000000000..8d16bcb65d7
--- /dev/null
+++ b/chromium/components/metrics/proto/omnibox_input_type.proto
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an omnibox interaction.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "OmniboxInputTypeProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics.OmniboxInputType;
+
+// What kind of input the user provided.
+// Note that the type below may be misleading. For example, "http:/" alone
+// cannot be opened as a URL, so it is marked as a QUERY; yet the user
+// probably intends to type more and have it eventually become a URL, so we
+// need to make sure we still run it through inline autocomplete.
+enum Type {
+ // Empty input (should not reach here)
+ INVALID = 0;
+
+ // Valid input whose type cannot be determined
+ UNKNOWN = 1;
+
+ // DEPRECATED. Input autodetected as UNKNOWN, which the user wants to treat
+ // as an URL by specifying a desired_tld.
+ DEPRECATED_REQUESTED_URL = 2;
+
+ // Input autodetected as a URL
+ URL = 3;
+
+ // Input autodetected as a query
+ QUERY = 4;
+
+ // Input forced to be a query by an initial '?'
+ FORCED_QUERY = 5;
+}
diff --git a/chromium/components/metrics/proto/perf_data.proto b/chromium/components/metrics/proto/perf_data.proto
new file mode 100644
index 00000000000..99f67f8021b
--- /dev/null
+++ b/chromium/components/metrics/proto/perf_data.proto
@@ -0,0 +1,412 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "PerfDataProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Stores information from a perf session generated via running:
+// "perf record"
+//
+// See $kernel/tools/perf/design.txt for more details.
+
+// Please do not modify this protobuf directly, except to mirror the upstream
+// version found here:
+// https://chromium.googlesource.com/chromiumos/platform/chromiumos-wide-profiling/+/master/perf_data.proto
+// with some fields omitted for privacy reasons. Because it is a read-only copy
+// of the upstream protobuf, "Next tag:" comments are also absent.
+
+message PerfDataProto {
+
+ // Perf event attribute. Stores the event description.
+ // This data structure is defined in the linux kernel:
+ // $kernel/tools/perf/util/event.h.
+ message PerfEventAttr {
+ // Type of the event. Type is an enumeration and can be:
+ // IP: an instruction-pointer was stored in the event.
+ // MMAP: a DLL was loaded.
+ // FORK: a process was forked.
+ // etc.
+ optional uint32 type = 1;
+
+ // Size of the event data in bytes.
+ optional uint32 size = 2;
+
+ // The config stores the CPU-specific counter information.
+ optional uint64 config = 3;
+
+ // Sample period of the event. Indicates how often the event is
+ // triggered in terms of # of events. After |sample_period| events, an event
+ // will be recorded and stored.
+ optional uint64 sample_period = 4;
+
+ // Sample frequency of the event. Indicates how often the event is
+ // triggered in terms of # per second. The kernel will try to record
+ // |sample_freq| events per second.
+ optional uint64 sample_freq = 5;
+
+ // Sample type is a bitfield that records attributes of the sample. Example,
+ // whether an entire callchain was recorded, etc.
+ optional uint64 sample_type = 6;
+
+ // Bitfield that indicates whether reads on the counter will return the
+ // total time enabled and total time running.
+ optional uint64 read_format = 7;
+
+ // Indicates whether the counter starts off disabled.
+ optional bool disabled = 8;
+
+ // Indicates whether child processes inherit the counter.
+ optional bool inherit = 9;
+
+ // Indicates whether the counter is pinned to a particular CPU.
+ optional bool pinned = 10;
+
+ // Indicates whether this counter's group has exclusive access to the CPU's
+ // counters.
+ optional bool exclusive = 11;
+
+ // The following bits restrict events to be counted when the CPU is in user,
+ // kernel, hypervisor or idle modes.
+ optional bool exclude_user = 12;
+ optional bool exclude_kernel = 13;
+ optional bool exclude_hv = 14;
+ optional bool exclude_idle = 15;
+
+ // Indicates whether mmap events should be recorded.
+ optional bool mmap = 16;
+
+ // Indicates whether process comm information should be recorded upon
+ // process creation.
+ optional bool comm = 17;
+
+ // Indicates that we are in frequency mode, not period mode.
+ optional bool freq = 18;
+
+ // Indicates whether we have per-task counts.
+ optional bool inherit_stat = 19;
+
+ // Indicates whether we enable perf events after an exec() function call.
+ optional bool enable_on_exec = 20;
+
+ // Indicates whether we trace fork/exit.
+ optional bool task = 21;
+
+ // Indicates whether we are using a watermark to wake up.
+ optional bool watermark = 22;
+
+ // CPUs often "skid" when recording events. That means the instruction
+ // pointer may not be the same as the one that caused the counter overflow.
+ // Indicates the capabilities of the CPU in terms of recording precise
+ // instruction pointer.
+ optional uint32 precise_ip = 23;
+
+ // Indicates whether we have non-exec mmap data.
+ optional bool mmap_data = 24;
+
+ // If set, all the event types will have the same sample_type.
+ optional bool sample_id_all = 25;
+
+ // Indicates whether we are counting events from the host (when running a
+ // VM).
+ optional bool exclude_host = 26;
+
+ // Exclude events that happen on a guest OS.
+ optional bool exclude_guest = 27;
+
+ // Contains the number of events after which we wake up.
+ optional uint32 wakeup_events = 28;
+
+ // Contains the number of bytes after which we wake up.
+ optional uint32 wakeup_watermark = 29;
+
+ // Information about the type of the breakpoint.
+ optional uint32 bp_type = 30;
+
+ // Contains the breakpoint address.
+ optional uint64 bp_addr = 31;
+
+ // This is an extension of config (see above).
+ optional uint64 config1 = 32;
+
+ // The length of the breakpoint data in bytes.
+ optional uint64 bp_len = 33;
+
+ // This is an extension of config (see above).
+ optional uint64 config2 = 34;
+
+ // Contains the type of branch, example: user, kernel, call, return, etc.
+ optional uint64 branch_sample_type = 35;
+ }
+
+ // Describes a perf.data file attribute.
+ message PerfFileAttr {
+ optional PerfEventAttr attr = 1;
+
+ // List of perf file attribute ids. Each id describes an event.
+ repeated uint64 ids = 2;
+ }
+
+ // This message contains information about a perf sample itself, as opposed to
+ // a perf event captured by a sample.
+ message SampleInfo {
+ // Process ID / thread ID from which this sample was taken.
+ optional uint32 pid = 1;
+ optional uint32 tid = 2;
+
+ // Time this sample was taken (NOT the same as an event time).
+ // It is the number of nanoseconds since bootup.
+ optional uint64 sample_time_ns = 3;
+
+ // The ID of the sample's event type (cycles, instructions, etc).
+ // The event type IDs are defined in PerfFileAttr.
+ optional uint64 id = 4;
+
+ // The CPU on which this sample was taken.
+ optional uint32 cpu = 5;
+ }
+
+ message CommEvent {
+ // Process id.
+ optional uint32 pid = 1;
+
+ // Thread id.
+ optional uint32 tid = 2;
+
+ // Comm string's md5 prefix.
+ // The comm string was field 3 and has been intentionally left out.
+ optional uint64 comm_md5_prefix = 4;
+
+ // Time the sample was taken.
+ // Deprecated, use |sample_info| instead.
+ optional uint64 sample_time = 5 [deprecated=true];
+
+ // Info about the perf sample containing this event.
+ optional SampleInfo sample_info = 6;
+ }
+
+ message MMapEvent {
+ // Process id.
+ optional uint32 pid = 1;
+
+ // Thread id.
+ optional uint32 tid = 2;
+
+ // Start address.
+ optional uint64 start = 3;
+
+ // Length.
+ optional uint64 len = 4;
+
+ // PG Offset.
+ optional uint64 pgoff = 5;
+
+ // Filename's md5 prefix.
+ // The filename was field 6 and has been intentionally left out.
+ optional uint64 filename_md5_prefix = 7;
+
+ // Info about the perf sample containing this event.
+ optional SampleInfo sample_info = 8;
+ }
+
+ message BranchStackEntry {
+ // Branch source address.
+ optional uint64 from_ip = 1;
+
+ // Branch destination address.
+ optional uint64 to_ip = 2;
+
+ // Indicates a mispredicted branch.
+ optional bool mispredicted = 3;
+ }
+
+ message SampleEvent {
+ // Instruction pointer.
+ optional uint64 ip = 1;
+
+ // Process id.
+ optional uint32 pid = 2;
+
+ // Thread id.
+ optional uint32 tid = 3;
+
+ // The time after boot when the sample was recorded, in nanoseconds.
+ optional uint64 sample_time_ns = 4;
+
+ // The address of the sample.
+ optional uint64 addr = 5;
+
+ // The id of the sample.
+ optional uint64 id = 6;
+
+ // The stream id of the sample.
+ optional uint64 stream_id = 7;
+
+ // The period of the sample.
+ optional uint64 period = 8;
+
+ // The CPU where the event was recorded.
+ optional uint32 cpu = 9;
+
+ // The raw size of the event in bytes.
+ optional uint32 raw_size = 10;
+
+ // Sample callchain info.
+ repeated uint64 callchain = 11;
+
+ // Branch stack info.
+ repeated BranchStackEntry branch_stack = 12;
+
+ // Not added from original: fields 13 and 14.
+
+ // Sample weight for special events.
+ optional uint64 weight = 15;
+
+ // Sample data source flags.
+ // Possible flag values:
+ // http://lxr.free-electrons.com/source/include/uapi/linux/perf_event.h#L849
+ optional uint64 data_src = 16;
+
+ // Sample transaction flags for special events.
+ // Flag fields:
+ // http://lxr.free-electrons.com/source/include/uapi/linux/perf_event.h#L209
+ optional uint64 transaction = 17;
+ }
+
+ // ForkEvent is used for both FORK and EXIT events, which have the same data
+ // format. We don't want to call this "ForkOrExitEvent", in case a separate
+ // exit event is introduced in the future.
+ message ForkEvent {
+ // Forked process ID.
+ optional uint32 pid = 1;
+
+ // Parent process ID.
+ optional uint32 ppid = 2;
+
+ // Forked process thread ID.
+ optional uint32 tid = 3;
+
+ // Parent process thread ID.
+ optional uint32 ptid = 4;
+
+ // Time of fork event in nanoseconds since bootup.
+ optional uint64 fork_time_ns = 5;
+
+ // Info about the perf sample containing this event.
+ optional SampleInfo sample_info = 11;
+ }
+
+ message EventHeader {
+ // Type of event.
+ optional uint32 type = 1;
+ optional uint32 misc = 2;
+ // Size of event.
+ optional uint32 size = 3;
+ }
+
+ message PerfEvent {
+ optional EventHeader header = 1;
+
+ optional MMapEvent mmap_event = 2;
+ optional SampleEvent sample_event = 3;
+ optional CommEvent comm_event = 4;
+ // FORK and EXIT events are structurally identical. They only differ by the
+ // event type. But using two distinct fields makes things easier.
+ optional ForkEvent fork_event = 5;
+ optional ForkEvent exit_event = 9;
+
+ // Not added from original: optional LostEvent lost_event = 6;
+ // Not added from original: optional ThrottleEvent throttle_event = 7;
+ // Not added from original: optional ReadEvent read_event = 8;
+ }
+
+ message PerfEventStats {
+ // Total number of events read from perf data.
+ optional uint32 num_events_read = 1;
+
+ // Total number of various types of events.
+ optional uint32 num_sample_events = 2;
+ optional uint32 num_mmap_events = 3;
+ optional uint32 num_fork_events = 4;
+ optional uint32 num_exit_events = 5;
+
+ // Number of sample events that were successfully mapped by the address
+ // mapper, a quipper module that is used to obscure addresses and convert
+ // them to DSO name + offset. Sometimes it fails to process sample events.
+ // This field allows us to track the success rate of the address mapper.
+ optional uint32 num_sample_events_mapped = 6;
+
+ // Whether address remapping was enabled.
+ optional bool did_remap = 7;
+ }
+
+ message PerfBuildID {
+ // Misc field in perf_event_header.
+ // Indicates whether the file is mapped in kernel mode or user mode.
+ optional uint32 misc = 1;
+
+ // Process ID.
+ optional uint32 pid = 2;
+
+ // Build id. Should always contain kBuildIDArraySize bytes of data.
+ // perf_reader.h in Chrome OS defines kBuildIDArraySize = 20.
+ optional bytes build_hash = 3;
+
+ // Filename Md5sum prefix.
+ // The filename was field 4 and has been intentionally left out.
+ optional uint64 filename_md5_prefix = 5;
+ }
+
+ repeated PerfFileAttr file_attrs = 1;
+ repeated PerfEvent events = 2;
+
+ // Time when quipper generated this perf data / protobuf, given as seconds
+ // since the epoch.
+ optional uint64 timestamp_sec = 3;
+
+ // Records some stats about the serialized perf events.
+ optional PerfEventStats stats = 4;
+
+ // Not added from original: repeated uint64 metadata_mask = 5;
+
+ // Build ID metadata.
+ repeated PerfBuildID build_ids = 7;
+
+ // Not added from original: repeated PerfUint32Metadata uint32_metadata = 8;
+ // Not added from original: repeated PerfUint64Metadata uint64_metadata = 9;
+ // Not added from original:
+ // optional PerfCPUTopologyMetadata cpu_topology = 11;
+ // Not added from original:
+ // repeated PerfNodeTopologyMetadata numa_topology = 12;
+
+ message StringMetadata {
+ message StringAndMd5sumPrefix {
+ // The string value was field 1 and has been intentionally left out.
+
+ // The string value's md5sum prefix.
+ optional uint64 value_md5_prefix = 2;
+ }
+
+ // Not added from original: optional StringAndMd5sumPrefix hostname = 1;
+ // Not added from original:
+ // optional StringAndMd5sumPrefix kernel_version =2;
+ // Not added from original: optional StringAndMd5sumPrefix perf_version = 3;
+ // Not added from original: optional StringAndMd5sumPrefix architecture = 4;
+ // Not added from original:
+ // optional StringAndMd5sumPrefix cpu_description = 5;
+ // Not added from original: optional StringAndMd5sumPrefix cpu_id = 6;
+ // Not added from original:
+ // repeated StringAndMd5sumPrefix perf_command_line_token = 7;
+
+ // The command line stored as a single string.
+ optional StringAndMd5sumPrefix perf_command_line_whole = 8;
+ }
+
+ // All the string metadata from the perf data file.
+ optional StringMetadata string_metadata = 13;
+}
diff --git a/chromium/components/metrics/proto/perf_stat.proto b/chromium/components/metrics/proto/perf_stat.proto
new file mode 100644
index 00000000000..bdfb3c458bf
--- /dev/null
+++ b/chromium/components/metrics/proto/perf_stat.proto
@@ -0,0 +1,52 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package metrics;
+
+// Stores output generated by the "perf stat" command.
+//
+// See https://perf.wiki.kernel.org/index.php/Tutorial#Counting_with_perf_stat
+// for more details.
+
+// Next tag: 3
+message PerfStatProto {
+ // All lines printed by "perf stat".
+ repeated PerfStatLine line = 1;
+
+ // The command line used to run "perf stat".
+ optional string command_line = 2;
+
+ // Represents one line of "perf stat" output.
+ // Next tag: 4
+ message PerfStatLine{
+ // Time since the start of the "perf stat" command, in milliseconds.
+ //
+ // When running "perf stat" and printing the counters at the end, this is
+ // the total time taken by the run.
+ //
+ // Alternatively, "perf stat" can print its stats at regular intervals until
+ // the end of the run. For example, if "perf stat" runs for one second and
+ // prints at 200-ms intervals, it will print counter values for each event
+ // a total of five times. According to "perf stat" usage instructions, the
+ // printing interval should be no less than 100 ms.
+ optional uint64 time_ms = 1;
+
+ // Current count value of the event being counted. May be different from the
+ // nominal counter value reported by "perf stat", depending on the event.
+ // For example, memory access counters are in units of 64 bytes. A counter
+ // value of 1024 would represent 65536 bytes, and we would set this field to
+ // 65536.
+ optional uint64 count = 2;
+
+ // Name of event whose counter is listed on this line.
+ // This string should also appear as part of |PerfStatProto::command_line|.
+ // "perf stat" will preserve the event name exactly as it is passed in via
+ // the command line.
+ optional string event = 3;
+ }
+}
diff --git a/chromium/components/metrics/proto/profiler_event.proto b/chromium/components/metrics/proto/profiler_event.proto
new file mode 100644
index 00000000000..0b9d4744579
--- /dev/null
+++ b/chromium/components/metrics/proto/profiler_event.proto
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Performance metrics collected via Chrome's built-in profiler.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ProfilerEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 7
+message ProfilerEventProto {
+ // The version of this profile.
+ enum ProfileVersion {
+ VERSION_UNKNOWN = 0; // Unknown version (should not reach here).
+ VERSION_STARTUP_PROFILE = 1; // Startup profile, logged approximately 60
+ // seconds after launch.
+ VERSION_SPLIT_PROFILE = 2; // Part of a profile logged in pieces, where
+ // we finish a piece when a ProfilerEvent or a
+ // special end-of-recording event gets
+ // triggered.
+ }
+ optional ProfileVersion profile_version = 1;
+
+ // The source based upon which "time" measurements are made.
+ // We currently only measure wall clock time; but we are exploring other
+ // measurement sources as well, such as CPU time or TCMalloc statistics.
+ enum TimeSource {
+ UNKNOWN_TIME_SOURCE = 0; // Unknown type (should not reach here).
+ WALL_CLOCK_TIME = 1; // Total time elapsed between the start and end of
+ // the task's execution.
+ }
+ optional TimeSource time_source = 2;
+
+ // An event in the browser life that causes the client-side profiler framework
+ // to finish recording of its current instance of ProfilerEventProto, and
+ // start recording a new one.
+ // It's not guaranteed that the events get triggered in the order they are
+ // defined.
+ enum ProfilerEvent {
+ // The first non-empty paint of the first web contents happened.
+ // Corresponds to the Startup.FirstWebContents.NonEmptyPaint2 histogram.
+ EVENT_FIRST_NONEMPTY_PAINT = 0;
+ }
+
+ // The set of events, in no particular order, that were triggered in the
+ // current Chrome session before the recording of this ProfilerEventProto
+ // started. It doesn't include the event that triggered the end of this
+ // ProfilerEventProto. A given event will not occur twice in this set.
+ // The field can be used to find all ProfilerEventProto instances recorded
+ // before or not before a given event.
+ repeated ProfilerEvent past_session_event = 4;
+
+ // Time when profiling started. This is recorded as a time delta relative to
+ // the start time of the profiler data recording in the current browser
+ // session.
+ optional int64 profiling_start_ms = 5;
+
+ // Time when profiling finished. This is recorded as a time delta relative to
+ // the start time of the profiler data recording in the current browser
+ // session.
+ optional int64 profiling_finish_ms = 6;
+
+ // Data for a single tracked object (typically, a Task).
+ message TrackedObject {
+ // The name of the thread from which this task was posted, hashed.
+ optional fixed64 birth_thread_name_hash = 1;
+
+ // The name of the thread on which this task was executed, hashed.
+ optional fixed64 exec_thread_name_hash = 2;
+
+ // The source file name from which this task was posted, hashed.
+ optional fixed64 source_file_name_hash = 3;
+
+ // Function name from which this task was posted, hashed.
+ optional fixed64 source_function_name_hash = 4;
+
+ // The line number within the source file from which this task was posted.
+ optional int32 source_line_number = 5;
+
+ // The number of times this task was executed.
+ optional int32 exec_count = 6;
+
+ // The total execution time for instances this task.
+ optional int32 exec_time_total = 7;
+
+ // The execution time for a uniformly randomly sampled instance of this
+ // task.
+ optional int32 exec_time_sampled = 8;
+
+ // The total time instances this task spent waiting (e.g. in a message loop)
+ // before they were run.
+ optional int32 queue_time_total = 9;
+
+ // The time that a uniformly randomly sampled instance of this task spent
+ // waiting (e.g. in a message loop) before it was run.
+ optional int32 queue_time_sampled = 10;
+
+ // The type of process within which this task was executed.
+ enum ProcessType {
+ UNKNOWN = 0; // Should not reach here
+ BROWSER = 1;
+ RENDERER = 2;
+ PLUGIN = 3; // Deprecated. Should not be sent as of M51 due to
+ // NPAPI removal.
+ WORKER = 4;
+ NACL_LOADER = 5;
+ UTILITY = 6;
+ PROFILE_IMPORT = 7;
+ ZYGOTE = 8;
+ SANDBOX_HELPER = 9;
+ NACL_BROKER = 10;
+ GPU = 11;
+ PPAPI_PLUGIN = 12;
+ PPAPI_BROKER = 13;
+ }
+ optional ProcessType process_type = 11;
+
+ // The local PID for the process within which this task was executed.
+ optional uint32 process_id = 12;
+ }
+ repeated TrackedObject tracked_object = 3;
+}
diff --git a/chromium/components/metrics/proto/sampled_profile.proto b/chromium/components/metrics/proto/sampled_profile.proto
new file mode 100644
index 00000000000..b8936e114c6
--- /dev/null
+++ b/chromium/components/metrics/proto/sampled_profile.proto
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SampledProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "call_stack_profile.proto";
+import "perf_data.proto";
+import "perf_stat.proto";
+
+// Protocol buffer for collected sample-based profiling data.
+// Contains the parameters and data from a single profile collection event.
+
+// Next tag: 11
+message SampledProfile {
+ // Indicates the event that triggered this collection.
+ enum TriggerEvent {
+ UNKNOWN_TRIGGER_EVENT = 0;
+
+ // The profile was triggered by periodic sampling. Periodically sampled
+ // profiles are collected once per uniformly sized period interval. Within
+ // each interval, the sampled data is collected at a random time. For
+ // example, if the interval is 60 s, then data would be collected at a
+ // random point in each of the intervals [0, 60 s), [60 s, 120 s), etc.
+ PERIODIC_COLLECTION = 1;
+
+ // The profile was collected upon resume from suspend.
+ RESUME_FROM_SUSPEND = 2;
+
+ // The profile was collected upon restoring a previous session.
+ RESTORE_SESSION = 3;
+
+ // The profile was collected at process startup.
+ PROCESS_STARTUP = 4;
+
+ // The profile was collected after jank was detected while executing a task.
+ JANKY_TASK = 5;
+
+ // The profile was collected after a thread was determined to be hung.
+ THREAD_HUNG = 6;
+ }
+ optional TriggerEvent trigger_event = 1;
+
+ // Fields 2-3: Time durations are given in ticks, and represent system uptime
+ // rather than wall time.
+
+ // Time after system boot when the collection took place, in milliseconds.
+ optional int64 ms_after_boot = 2;
+
+ // Time after last login when the collection took place, in milliseconds.
+ optional int64 ms_after_login = 3;
+
+ // The duration for which the machine was suspended prior to collecting the
+ // sampled profile. Only set when |trigger_event| is RESUME_FROM_SUSPEND.
+ optional int64 suspend_duration_ms = 5;
+
+ // Number of milliseconds after a resume that profile was collected. Only set
+ // when |trigger_event| is RESUME_FROM_SUSPEND.
+ optional int64 ms_after_resume = 6;
+
+ // Number of tabs restored during a session restore. Only set when
+ // |trigger_event| is RESTORE_SESSION.
+ optional int32 num_tabs_restored = 7;
+
+ // Number of milliseconds after a session restore that a profile was
+ // collected. Only set when |trigger_event| is RESTORE_SESSION.
+ optional int64 ms_after_restore = 8;
+
+ // Sampled profile data collected from Linux perf tool.
+ optional PerfDataProto perf_data = 4;
+
+ // Sampled profile data collected by periodic sampling of call stacks.
+ optional CallStackProfile call_stack_profile = 9;
+
+ // Perf counter data collected using "perf stat".
+ optional PerfStatProto perf_stat = 10;
+}
diff --git a/chromium/components/metrics/proto/system_profile.proto b/chromium/components/metrics/proto/system_profile.proto
new file mode 100644
index 00000000000..d1fa799148f
--- /dev/null
+++ b/chromium/components/metrics/proto/system_profile.proto
@@ -0,0 +1,775 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about the user's brower and system configuration.
+// The system configuration fields are recorded once per client session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SystemProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 23
+message SystemProfileProto {
+ // The time when the client was compiled/linked, in seconds since the epoch.
+ optional int64 build_timestamp = 1;
+
+ // A version number string for the application.
+ // Most commonly this is the browser version number found in a user agent
+ // string, and is typically a 4-tuple of numbers separated by periods. In
+ // cases where the user agent version might be ambiguous (example: Linux 64-
+ // bit build, rather than 32-bit build, or a Windows version used in some
+ // special context, such as ChromeFrame running in IE), then this may include
+ // some additional postfix to provide clarification not available in the UA
+ // string.
+ //
+ // An example of a browser version 4-tuple is "5.0.322.0". Currently used
+ // postfixes are:
+ //
+ // "-64": a 64-bit build
+ // "-F": Chrome is running under control of ChromeFrame
+ // "-devel": this is not an official build of Chrome
+ //
+ // A full version number string could look similar to:
+ // "5.0.322.0-F-devel".
+ //
+ // This value, when available, is more trustworthy than the UA string
+ // associated with the request; and including the postfix, may be more
+ // specific.
+ optional string app_version = 2;
+
+ // The brand code or distribution tag assigned to a partner, if available.
+ // Brand codes are only available on Windows. Not every Windows install
+ // though will have a brand code.
+ optional string brand_code = 12;
+
+ // The possible channels for an installation, from least to most stable.
+ enum Channel {
+ CHANNEL_UNKNOWN = 0; // Unknown channel -- perhaps an unofficial build?
+ CHANNEL_CANARY = 1;
+ CHANNEL_DEV = 2;
+ CHANNEL_BETA = 3;
+ CHANNEL_STABLE = 4;
+ }
+ optional Channel channel = 10;
+
+ // True if Chrome build is ASan-instrumented.
+ optional bool is_asan_build = 20 [default = false];
+
+ // The date the user enabled UMA, in seconds since the epoch.
+ // If the user has toggled the UMA enabled state multiple times, this will
+ // be the most recent date on which UMA was enabled.
+ // For privacy, this is rounded to the nearest hour.
+ optional int64 uma_enabled_date = 3;
+
+ // The time when the client was installed, in seconds since the epoch.
+ // For privacy, this is rounded to the nearest hour.
+ optional int64 install_date = 16;
+
+ // The user's selected application locale, i.e. the user interface language.
+ // The locale includes a language code and, possibly, also a country code,
+ // e.g. "en-US".
+ optional string application_locale = 4;
+
+ // Information on the user's operating system.
+ message OS {
+ // The user's operating system. This should be one of:
+ // - Android
+ // - Windows NT
+ // - Linux (includes ChromeOS)
+ // - iPhone OS
+ // - Mac OS X
+ optional string name = 1;
+
+ // The version of the OS. The meaning of this field is OS-dependent.
+ optional string version = 2;
+
+ // The fingerprint of the build. This field is used only on Android.
+ optional string fingerprint = 3;
+
+ // Whether the version of iOS appears to be "jailbroken". This field is
+ // used only on iOS. Chrome for iOS detects whether device contains a
+ // DynamicLibraries/ directory. It's a necessary but insufficient indicator
+ // of whether the operating system has been jailbroken.
+ optional bool is_jailbroken = 4;
+ }
+ optional OS os = 5;
+
+ // Next tag for Hardware: 18
+ // Information on the user's hardware.
+ message Hardware {
+ // The CPU architecture (x86, PowerPC, x86_64, ...)
+ optional string cpu_architecture = 1;
+
+ // The amount of RAM present on the system, in megabytes.
+ optional int64 system_ram_mb = 2;
+
+ // The base memory address that chrome.dll was loaded at.
+ // (Logged only on Windows.)
+ optional int64 dll_base = 3;
+
+ // The hardware_class describes the current machine model, e.g. "MacPro1,1"
+ // on Mac, or "Nexus 5" on Android. Only implemented on OS X, Android, and
+ // Chrome OS.
+ //
+ // For Chrome OS, the device hardware class ID is a unique string associated
+ // with each Chrome OS device product revision generally assigned at
+ // hardware qualification time. The hardware class effectively identifies
+ // the configured system components such as CPU, WiFi adapter, etc.
+ //
+ // An example of such a hardware class is "IEC MARIO PONY 6101". An
+ // internal database associates this hardware class with the qualified
+ // device specifications including OEM information, schematics, hardware
+ // qualification reports, test device tags, etc.
+ optional string hardware_class = 4;
+
+ // The number of physical screens.
+ optional int32 screen_count = 5;
+
+ // The screen dimensions of the primary screen, in pixels.
+ optional int32 primary_screen_width = 6;
+ optional int32 primary_screen_height = 7;
+
+ // The device scale factor of the primary screen.
+ optional float primary_screen_scale_factor = 12;
+
+ // Max DPI for any attached screen. (Windows only)
+ optional float max_dpi_x = 9;
+ optional float max_dpi_y = 10;
+
+ // Information on the CPU obtained by CPUID.
+ message CPU {
+ // A 12 character string naming the vendor, e.g. "GeniuneIntel".
+ optional string vendor_name = 1;
+
+ // The signature reported by CPUID (from EAX).
+ optional uint32 signature = 2;
+
+ // Number of logical processors/cores on the current machine.
+ optional uint32 num_cores = 3;
+ }
+ optional CPU cpu = 13;
+
+ // Information on the GPU
+ message Graphics {
+ // The GPU manufacturer's vendor id.
+ optional uint32 vendor_id = 1;
+
+ // The GPU manufacturer's device id for the chip set.
+ optional uint32 device_id = 2;
+
+ // The driver version on the GPU.
+ optional string driver_version = 3;
+
+ // The driver date on the GPU.
+ optional string driver_date = 4;
+
+ // The GL_VENDOR string. An example of a gl_vendor string is
+ // "Imagination Technologies". "" if we are not using OpenGL.
+ optional string gl_vendor = 6;
+
+ // The GL_RENDERER string. An example of a gl_renderer string is
+ // "PowerVR SGX 540". "" if we are not using OpenGL.
+ optional string gl_renderer = 7;
+ }
+ optional Graphics gpu = 8;
+
+ // Information about Bluetooth devices paired with the system.
+ message Bluetooth {
+ // Whether Bluetooth is present on this system.
+ optional bool is_present = 1;
+
+ // Whether Bluetooth is enabled on this system.
+ optional bool is_enabled = 2;
+
+ // Describes a paired device.
+ message PairedDevice {
+ // Assigned class of the device. This is a bitfield according to the
+ // Bluetooth specification available at the following URL:
+ // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband
+ optional uint32 bluetooth_class = 1;
+
+ // Decoded device type.
+ enum Type {
+ DEVICE_UNKNOWN = 0;
+ DEVICE_COMPUTER = 1;
+ DEVICE_PHONE = 2;
+ DEVICE_MODEM = 3;
+ DEVICE_AUDIO = 4;
+ DEVICE_CAR_AUDIO = 5;
+ DEVICE_VIDEO = 6;
+ DEVICE_PERIPHERAL = 7;
+ DEVICE_JOYSTICK = 8;
+ DEVICE_GAMEPAD = 9;
+ DEVICE_KEYBOARD = 10;
+ DEVICE_MOUSE = 11;
+ DEVICE_TABLET = 12;
+ DEVICE_KEYBOARD_MOUSE_COMBO = 13;
+ }
+ optional Type type = 2;
+
+ // Vendor prefix of the Bluetooth address, these are OUI registered by
+ // the IEEE and are encoded with the first byte in bits 16-23, the
+ // second byte in bits 8-15 and the third byte in bits 0-7.
+ //
+ // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11
+ optional uint32 vendor_prefix = 4;
+
+ // The Vendor ID of a device, returned in vendor_id below, can be
+ // either allocated by the Bluetooth SIG or USB IF, providing two
+ // completely overlapping namespaces for identifiers.
+ //
+ // This field should be read along with vendor_id to correctly
+ // identify the vendor. For example Google is identified by either
+ // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or
+ // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1.
+ //
+ // If the device does not support the Device ID specification the
+ // unknown value will be set.
+ enum VendorIDSource {
+ VENDOR_ID_UNKNOWN = 0;
+ VENDOR_ID_BLUETOOTH = 1;
+ VENDOR_ID_USB = 2;
+ }
+ optional VendorIDSource vendor_id_source = 8;
+
+ // Vendor ID of the device, where available.
+ optional uint32 vendor_id = 5;
+
+ // Product ID of the device, where available.
+ optional uint32 product_id = 6;
+
+ // Device ID of the device, generally the release or version number in
+ // BCD format, where available.
+ optional uint32 device_id = 7;
+ }
+ repeated PairedDevice paired_device = 3;
+ }
+ optional Bluetooth bluetooth = 11;
+
+ // Whether the internal display produces touch events. Omitted if unknown.
+ // Logged on ChromeOS only.
+ optional bool internal_display_supports_touch = 14;
+
+ // Vendor ids and product ids of external touchscreens.
+ message TouchScreen {
+ // Touch screen vendor id.
+ optional uint32 vendor_id = 1;
+ // Touch screen product id.
+ optional uint32 product_id = 2;
+ }
+ // Lists vendor and product ids of external touchscreens.
+ // Logged on ChromeOS only.
+ repeated TouchScreen external_touchscreen = 15;
+
+ // Drive messages are currently logged on Windows 7+, iOS, and Android.
+ message Drive {
+ // Whether this drive incurs a time penalty when randomly accessed. This
+ // should be true for spinning disks but false for SSDs or other
+ // flash-based drives.
+ optional bool has_seek_penalty = 1;
+ }
+ // The drive that the application executable was loaded from.
+ optional Drive app_drive = 16;
+ // The drive that the current user data directory was loaded from.
+ optional Drive user_data_drive = 17;
+ }
+ optional Hardware hardware = 6;
+
+ // Information about the network connection.
+ message Network {
+ // Set to true if connection_type changed during the lifetime of the log.
+ optional bool connection_type_is_ambiguous = 1;
+
+ // Derived from net::NetworkChangeNotifier::ConnectionType translated
+ // through NetworkMetricsProvider::GetConnectionType.
+ enum ConnectionType {
+ CONNECTION_UNKNOWN = 0;
+ CONNECTION_ETHERNET = 1;
+ CONNECTION_WIFI = 2;
+ CONNECTION_2G = 3;
+ CONNECTION_3G = 4;
+ CONNECTION_4G = 5;
+ CONNECTION_BLUETOOTH = 6;
+ CONNECTION_NONE = 7;
+ }
+ // The connection type according to NetworkChangeNotifier.
+ optional ConnectionType connection_type = 2;
+
+ // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log.
+ optional bool wifi_phy_layer_protocol_is_ambiguous = 3;
+
+ // See net::WifiPHYLayerProtocol.
+ enum WifiPHYLayerProtocol {
+ WIFI_PHY_LAYER_PROTOCOL_NONE = 0;
+ WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1;
+ WIFI_PHY_LAYER_PROTOCOL_A = 2;
+ WIFI_PHY_LAYER_PROTOCOL_B = 3;
+ WIFI_PHY_LAYER_PROTOCOL_G = 4;
+ WIFI_PHY_LAYER_PROTOCOL_N = 5;
+ WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6;
+ }
+ // The physical layer mode of the associated wifi access point, if any.
+ optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4;
+
+ // Describe wifi access point information.
+ message WifiAccessPoint {
+ // Vendor prefix of the access point's BSSID, these are OUIs
+ // (Organizationally Unique Identifiers) registered by
+ // the IEEE and are encoded with the first byte in bits 16-23, the
+ // second byte in bits 8-15 and the third byte in bits 0-7.
+ optional uint32 vendor_prefix = 1;
+
+ // Access point seurity mode definitions.
+ enum SecurityMode {
+ SECURITY_UNKNOWN = 0;
+ SECURITY_WPA = 1;
+ SECURITY_WEP = 2;
+ SECURITY_RSN = 3;
+ SECURITY_802_1X = 4;
+ SECURITY_PSK = 5;
+ SECURITY_NONE = 6;
+ }
+ // The security mode of the access point.
+ optional SecurityMode security_mode = 2;
+
+ // Vendor specific information.
+ message VendorInformation {
+ // The model number, for example "0".
+ optional string model_number = 1;
+
+ // The model name (sometimes the same as the model_number),
+ // for example "WZR-HP-AG300H".
+ optional string model_name = 2;
+
+ // The device name (sometimes the same as the model_number),
+ // for example "Dummynet"
+ optional string device_name = 3;
+
+ // The list of vendor-specific OUIs (Organziationally Unqiue
+ // Identifiers). These are provided by the vendor through WPS
+ // (Wireless Provisioning Service) information elements, which
+ // identifies the content of the element.
+ repeated uint32 element_identifier = 4;
+ }
+ // The wireless access point vendor information.
+ optional VendorInformation vendor_info = 3;
+ }
+ // Information of the wireless AP that device is connected to.
+ optional WifiAccessPoint access_point_info = 5;
+ }
+ optional Network network = 13;
+
+ // Information on the Google Update install that is managing this client.
+ message GoogleUpdate {
+ // Whether the Google Update install is system-level or user-level.
+ optional bool is_system_install = 1;
+
+ // The date at which Google Update last started performing an automatic
+ // update check, in seconds since the Unix epoch.
+ optional int64 last_automatic_start_timestamp = 2;
+
+ // The date at which Google Update last successfully sent an update check
+ // and recieved an intact response from the server, in seconds since the
+ // Unix epoch. (The updates don't need to be successfully installed.)
+ optional int64 last_update_check_timestamp = 3;
+
+ // Describes a product being managed by Google Update. (This can also
+ // describe Google Update itself.)
+ message ProductInfo {
+ // The current version of the product that is installed.
+ optional string version = 1;
+
+ // The date at which Google Update successfully updated this product,
+ // stored in seconds since the Unix epoch. This is updated when an update
+ // is successfully applied, or if the server reports that no update
+ // is available.
+ optional int64 last_update_success_timestamp = 2;
+
+ // The result reported by the product updater on its last run.
+ enum InstallResult {
+ INSTALL_RESULT_SUCCESS = 0;
+ INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1;
+ INSTALL_RESULT_FAILED_MSI_ERROR = 2;
+ INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3;
+ INSTALL_RESULT_EXIT_CODE = 4;
+ }
+ optional InstallResult last_result = 3;
+
+ // The error code reported by the product updater on its last run. This
+ // will typically be a error code specific to the product installer.
+ optional int32 last_error = 4;
+
+ // The extra error code reported by the product updater on its last run.
+ // This will typically be a Win32 error code.
+ optional int32 last_extra_error = 5;
+ }
+ optional ProductInfo google_update_status = 4;
+ optional ProductInfo client_status = 5;
+ }
+ optional GoogleUpdate google_update = 11;
+
+ // Information on all installed plugins.
+ message Plugin {
+ // The plugin's self-reported name and filename (without path).
+ optional string name = 1;
+ optional string filename = 2;
+
+ // The plugin's version.
+ optional string version = 3;
+
+ // True if the plugin is disabled.
+ // If a client has multiple local Chrome user accounts, this is logged based
+ // on the first user account launched during the current session.
+ optional bool is_disabled = 4;
+
+ // True if the plugin is PPAPI.
+ optional bool is_pepper = 5;
+ }
+ repeated Plugin plugin = 7;
+
+ // Figures that can be used to generate application stability metrics.
+ // All values are counts of events since the last time that these
+ // values were reported.
+ // Next tag: 26
+ message Stability {
+ // Total amount of time that the program was running, in seconds,
+ // since the last time a log was recorded, as measured using a client-side
+ // clock implemented via TimeTicks, which guarantees that it is monotonic
+ // and does not jump if the user changes his/her clock. The TimeTicks
+ // implementation also makes the clock not count time the computer is
+ // suspended.
+ optional int64 incremental_uptime_sec = 1;
+
+ // Total amount of time that the program was running, in seconds,
+ // since startup, as measured using a client-side clock implemented
+ // via TimeTicks, which guarantees that it is monotonic and does not
+ // jump if the user changes his/her clock. The TimeTicks implementation
+ // also makes the clock not count time the computer is suspended.
+ // This field was added for M-35.
+ optional int64 uptime_sec = 23;
+
+ // Page loads along with renderer crashes, hangs and failed launches, since
+ // page load count roughly corresponds to usage.
+ optional int32 page_load_count = 2;
+ optional int32 renderer_crash_count = 3;
+ optional int32 renderer_hang_count = 4;
+ optional int32 renderer_failed_launch_count = 24;
+
+ // Number of renderer crashes and failed launches that were for extensions.
+ // These are not counted in the renderer counts above.
+ optional int32 extension_renderer_crash_count = 5;
+ optional int32 extension_renderer_failed_launch_count = 25;
+
+ // Number of non-renderer child process crashes.
+ optional int32 child_process_crash_count = 6;
+
+ // Number of times the browser has crashed while logged in as the "other
+ // user" (guest) account.
+ // Logged on ChromeOS only.
+ optional int32 other_user_crash_count = 7;
+
+ // Number of times the kernel has crashed.
+ // Logged on ChromeOS only.
+ optional int32 kernel_crash_count = 8;
+
+ // Number of times the system has shut down uncleanly.
+ // Logged on ChromeOS only.
+ optional int32 unclean_system_shutdown_count = 9;
+
+ //
+ // All the remaining fields in the Stability are recorded at most once per
+ // client session.
+ //
+
+ // The number of times the program was launched.
+ // This will typically be equal to 1. However, it is possible that Chrome
+ // was unable to upload stability metrics for previous launches (e.g. due to
+ // crashing early during startup), and hence this value might be greater
+ // than 1.
+ optional int32 launch_count = 15;
+ // The number of times that it didn't exit cleanly (which we assume to be
+ // mostly crashes).
+ optional int32 crash_count = 16;
+
+ // The number of times the program began, but did not complete, the shutdown
+ // process. (For example, this may occur when Windows is shutting down, and
+ // it only gives the process a few seconds to clean up.)
+ optional int32 incomplete_shutdown_count = 17;
+
+ // The number of times the program was able register with breakpad crash
+ // services.
+ optional int32 breakpad_registration_success_count = 18;
+
+ // The number of times the program failed to register with breakpad crash
+ // services. If crash registration fails then when the program crashes no
+ // crash report will be generated.
+ optional int32 breakpad_registration_failure_count = 19;
+
+ // The number of times the program has run under a debugger. This should
+ // be an exceptional condition. Running under a debugger prevents crash
+ // dumps from being generated.
+ optional int32 debugger_present_count = 20;
+
+ // The number of times the program has run without a debugger attached.
+ // This should be most common scenario and should be very close to
+ // |launch_count|.
+ optional int32 debugger_not_present_count = 21;
+
+ // Stability information for all installed plugins.
+ message PluginStability {
+ // The relevant plugin's information (name, etc.)
+ optional Plugin plugin = 1;
+
+ // The number of times this plugin's process was launched.
+ optional int32 launch_count = 2;
+
+ // The number of times this plugin was instantiated on a web page.
+ // This will be >= |launch_count|.
+ // (A page load with multiple sections drawn by this plugin will
+ // increase this count multiple times.)
+ optional int32 instance_count = 3;
+
+ // The number of times this plugin process crashed.
+ // This value will be <= |launch_count|.
+ optional int32 crash_count = 4;
+
+ // The number of times this plugin could not be loaded.
+ optional int32 loading_error_count = 5;
+ }
+ repeated PluginStability plugin_stability = 22;
+ }
+ optional Stability stability = 8;
+
+ // Description of a field trial or experiment that the user is currently
+ // enrolled in.
+ // All metrics reported in this upload can potentially be influenced by the
+ // field trial.
+ message FieldTrial {
+ // The name of the field trial, as a 32-bit identifier.
+ // Currently, the identifier is a hash of the field trial's name.
+ optional fixed32 name_id = 1;
+
+ // The user's group within the field trial, as a 32-bit identifier.
+ // Currently, the identifier is a hash of the group's name.
+ optional fixed32 group_id = 2;
+ }
+ repeated FieldTrial field_trial = 9;
+
+ // Information about the A/V output device(s) (typically just a TV).
+ // However, a configuration may have one or more intermediate A/V devices
+ // between the source device and the TV (e.g. an A/V receiver, video
+ // processor, etc.).
+ message ExternalAudioVideoDevice {
+ // The manufacturer name (possibly encoded as a 3-letter code, e.g. "YMH"
+ // for Yamaha).
+ optional string manufacturer_name = 1;
+
+ // The model name (e.g. "RX-V1900"). Some devices may report generic names
+ // like "receiver" or use the full manufacturer name (e.g "PHILIPS").
+ optional string model_name = 2;
+
+ // The product code (e.g. "0218").
+ optional string product_code = 3;
+
+ // The device types. A single device can have multiple types (e.g. a set-top
+ // box could be both a tuner and a player). The same type may even be
+ // repeated (e.g a device that reports two tuners).
+ enum AVDeviceType {
+ AV_DEVICE_TYPE_UNKNOWN = 0;
+ AV_DEVICE_TYPE_TV = 1;
+ AV_DEVICE_TYPE_RECORDER = 2;
+ AV_DEVICE_TYPE_TUNER = 3;
+ AV_DEVICE_TYPE_PLAYER = 4;
+ AV_DEVICE_TYPE_AUDIO_SYSTEM = 5;
+ }
+ repeated AVDeviceType av_device_type = 4;
+
+ // The year of manufacture.
+ optional int32 manufacture_year = 5;
+
+ // The week of manufacture.
+ // Note: per the Wikipedia EDID article, numbering for this field may not
+ // be consistent between manufacturers.
+ optional int32 manufacture_week = 6;
+
+ // Max horizontal resolution in pixels.
+ optional int32 horizontal_resolution = 7;
+
+ // Max vertical resolution in pixels.
+ optional int32 vertical_resolution = 8;
+
+ // Audio capabilities of the device.
+ // Ref: http://en.wikipedia.org/wiki/Extended_display_identification_data
+ // Next tag: 7
+ message AudioDescription {
+ // Audio format
+ enum AudioFormat {
+ AUDIO_FORMAT_UNKNOWN = 0;
+ AUDIO_FORMAT_LPCM = 1;
+ AUDIO_FORMAT_AC_3 = 2;
+ AUDIO_FORMAT_MPEG1 = 3;
+ AUDIO_FORMAT_MP3 = 4;
+ AUDIO_FORMAT_MPEG2 = 5;
+ AUDIO_FORMAT_AAC = 6;
+ AUDIO_FORMAT_DTS = 7;
+ AUDIO_FORMAT_ATRAC = 8;
+ AUDIO_FORMAT_ONE_BIT = 9;
+ AUDIO_FORMAT_DD_PLUS = 10;
+ AUDIO_FORMAT_DTS_HD = 11;
+ AUDIO_FORMAT_MLP_DOLBY_TRUEHD = 12;
+ AUDIO_FORMAT_DST_AUDIO = 13;
+ AUDIO_FORMAT_MICROSOFT_WMA_PRO = 14;
+ }
+ optional AudioFormat audio_format = 1;
+
+ // Number of channels (e.g. 1, 2, 8, etc.).
+ optional int32 num_channels = 2;
+
+ // Supported sample frequencies in Hz (e.g. 32000, 44100, etc.).
+ // Multiple frequencies may be specified.
+ repeated int32 sample_frequency_hz = 3;
+
+ // Maximum bit rate in bits/s.
+ optional int32 max_bit_rate_per_second = 4;
+
+ // Bit depth (e.g. 16, 20, 24, etc.).
+ optional int32 bit_depth = 5;
+
+ // Output mode: analog vs digital.
+ enum OutputMode {
+ ANALOG = 0;
+ DIGITAL = 1;
+ }
+ optional OutputMode output_mode = 6;
+
+ }
+ repeated AudioDescription audio_description = 9;
+
+ // The position in AV setup.
+ // A value of 0 means this device is the TV.
+ // A value of 1 means this device is directly connected to one of
+ // the TV's inputs.
+ // Values > 1 indicate there are 1 or more devices between this device
+ // and the TV.
+ optional int32 position_in_setup = 10;
+
+ // Whether this device is in the path to the TV.
+ optional bool is_in_path_to_tv = 11;
+
+ // The CEC version the device supports.
+ // CEC stands for Consumer Electronics Control, a part of the HDMI
+ // specification. Not all HDMI devices support CEC.
+ // Only devices that support CEC will report a value here.
+ optional int32 cec_version = 12;
+
+ // This message reports CEC commands seen by a device.
+ // After each log is sent, this information is cleared and gathered again.
+ // By collecting CEC status information by opcode we can determine
+ // which CEC features can be supported.
+ message CECCommand {
+ // The CEC command opcode. CEC supports up to 256 opcodes.
+ // We add only one CECCommand message per unique opcode. Only opcodes
+ // seen by the device will be reported. The remainder of the message
+ // accumulates status for this opcode (and device).
+ optional int32 opcode = 1;
+
+ // The total number of commands received from the external device.
+ optional int32 num_received_direct = 2;
+
+ // The number of commands received from the external device as part of a
+ // broadcast message.
+ optional int32 num_received_broadcast = 3;
+
+ // The total number of commands sent to the external device.
+ optional int32 num_sent_direct = 4;
+
+ // The number of commands sent to the external device as part of a
+ // broadcast message.
+ optional int32 num_sent_broadcast = 5;
+
+ // The number of aborted commands for unknown reasons.
+ optional int32 num_aborted_unknown_reason = 6;
+
+ // The number of aborted commands because of an unrecognized opcode.
+ optional int32 num_aborted_unrecognized = 7;
+ }
+ repeated CECCommand cec_command = 13;
+ }
+ repeated ExternalAudioVideoDevice external_audio_video_device = 14;
+
+ // Information about the current wireless access point. Collected directly
+ // from the wireless access point via standard apis if the device is
+ // connected to the Internet wirelessly. Introduced for Chrome on TV devices
+ // but also can be collected by ChromeOS, Android or other clients.
+ message ExternalAccessPoint {
+ // The manufacturer name, for example "ASUSTeK Computer Inc.".
+ optional string manufacturer = 1;
+
+ // The model name, for example "Wi-Fi Protected Setup Router".
+ optional string model_name = 2;
+
+ // The model number, for example "RT-N16".
+ optional string model_number = 3;
+
+ // The device name (sometime same as model_number), for example "RT-N16".
+ optional string device_name = 4;
+ }
+ optional ExternalAccessPoint external_access_point = 15;
+
+ // Number of users currently signed into a multiprofile session.
+ // A zero value indicates that the user count changed while the log is open.
+ // Logged only on ChromeOS.
+ optional uint32 multi_profile_user_count = 17;
+
+ // Information about extensions that are installed, masked to provide better
+ // privacy. Only extensions from a single profile are reported; this will
+ // generally be the profile used when the browser is started. The profile
+ // reported on will remain consistent at least until the browser is
+ // relaunched (or the profile is deleted by the user).
+ //
+ // Each client first picks a value for client_key derived from its UMA
+ // client_id:
+ // client_key = client_id % 4096
+ // Then, each installed extension is mapped into a hash bucket according to
+ // bucket = CityHash64(StringPrintf("%d:%s",
+ // client_key, extension_id)) % 1024
+ // The client reports the set of hash buckets occupied by all installed
+ // extensions. If multiple extensions map to the same bucket, that bucket is
+ // still only reported once.
+ repeated int32 occupied_extension_bucket = 18;
+
+ // The state of loaded extensions for this system. The system can have either
+ // no applicable extensions, extensions only from the webstore and verified by
+ // the webstore, extensions only from the webstore but not verified, or
+ // extensions not from the store. If there is a single off-store extension,
+ // then HAS_OFFSTORE is reported. This should be kept in sync with the
+ // corresponding enum in chrome/browser/metrics/extensions_metrics_provider.cc
+ enum ExtensionsState {
+ NO_EXTENSIONS = 0;
+ NO_OFFSTORE_VERIFIED = 1;
+ NO_OFFSTORE_UNVERIFIED = 2;
+ HAS_OFFSTORE = 3;
+ }
+ optional ExtensionsState offstore_extensions_state = 19;
+
+ // The nature of the choice the user was given concerning metrics recording.
+ // Specifically, whether the enable metrics/crash reporting checkbox that was
+ // shown on first run was checked or unchecked by default.
+ // This state is recorded on first run, and uploaded in every UMA log.
+ // Consequently this should only be defined for clients that were installed
+ // after the recording code was implemented.
+ enum UmaDefaultState {
+ // The enable checkbox was unchecked by default.
+ OPT_IN = 0;
+ // The enable checkbox was checked by default.
+ OPT_OUT = 1;
+ // Policy mandated that UMA be enaled, the user had no choice.
+ POLICY_FORCED_ENABLED = 2;
+ }
+ optional UmaDefaultState uma_default_state = 22;
+}
diff --git a/chromium/components/metrics/proto/user_action_event.proto b/chromium/components/metrics/proto/user_action_event.proto
new file mode 100644
index 00000000000..30a93180d07
--- /dev/null
+++ b/chromium/components/metrics/proto/user_action_event.proto
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an event that occurs in response to a user action,
+// e.g. an interaction with a browser UI element.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "UserActionEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 3
+message UserActionEventProto {
+ // The name of the action, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The timestamp for the event, in seconds since the epoch.
+ optional int64 time = 2;
+}
diff --git a/chromium/components/metrics/serialization/metric_sample.cc b/chromium/components/metrics/serialization/metric_sample.cc
new file mode 100644
index 00000000000..a0124744bb0
--- /dev/null
+++ b/chromium/components/metrics/serialization/metric_sample.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/metric_sample.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+MetricSample::MetricSample(MetricSample::SampleType sample_type,
+ const std::string& metric_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count)
+ : type_(sample_type),
+ name_(metric_name),
+ sample_(sample),
+ min_(min),
+ max_(max),
+ bucket_count_(bucket_count) {
+}
+
+MetricSample::~MetricSample() {
+}
+
+bool MetricSample::IsValid() const {
+ return name().find(' ') == std::string::npos &&
+ name().find('\0') == std::string::npos && !name().empty();
+}
+
+std::string MetricSample::ToString() const {
+ if (type_ == CRASH) {
+ return base::StringPrintf("crash%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ } else if (type_ == SPARSE_HISTOGRAM) {
+ return base::StringPrintf("sparsehistogram%c%s %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ '\0');
+ } else if (type_ == LINEAR_HISTOGRAM) {
+ return base::StringPrintf("linearhistogram%c%s %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ max_,
+ '\0');
+ } else if (type_ == HISTOGRAM) {
+ return base::StringPrintf("histogram%c%s %d %d %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ min_,
+ max_,
+ bucket_count_,
+ '\0');
+ } else {
+ // The type can only be USER_ACTION.
+ CHECK_EQ(type_, USER_ACTION);
+ return base::StringPrintf("useraction%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ }
+}
+
+int MetricSample::sample() const {
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, CRASH);
+ return sample_;
+}
+
+int MetricSample::min() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return min_;
+}
+
+int MetricSample::max() const {
+ CHECK_NE(type_, CRASH);
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, SPARSE_HISTOGRAM);
+ return max_;
+}
+
+int MetricSample::bucket_count() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return bucket_count_;
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::CrashSample(
+ const std::string& crash_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count) {
+ return scoped_ptr<MetricSample>(new MetricSample(
+ HISTOGRAM, histogram_name, sample, min, max, bucket_count));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ if (parts.size() != 5)
+ return scoped_ptr<MetricSample>();
+ int sample, min, max, bucket_count;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &min) ||
+ !base::StringToInt(parts[3], &max) ||
+ !base::StringToInt(parts[4], &bucket_count)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return HistogramSample(parts[0].as_string(), sample, min, max, bucket_count);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseSparseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (parts.size() != 2)
+ return scoped_ptr<MetricSample>();
+ int sample;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample))
+ return scoped_ptr<MetricSample>();
+
+ return SparseHistogramSample(parts[0].as_string(), sample);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseLinearHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<base::StringPiece> parts = base::SplitStringPiece(
+ serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ int sample, max;
+ if (parts.size() != 3)
+ return scoped_ptr<MetricSample>();
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &max)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return LinearHistogramSample(parts[0].as_string(), sample, max);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::UserActionSample(
+ const std::string& action_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0));
+}
+
+bool MetricSample::IsEqual(const MetricSample& metric) {
+ return type_ == metric.type_ && name_ == metric.name_ &&
+ sample_ == metric.sample_ && min_ == metric.min_ &&
+ max_ == metric.max_ && bucket_count_ == metric.bucket_count_;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/serialization/metric_sample.h b/chromium/components/metrics/serialization/metric_sample.h
new file mode 100644
index 00000000000..247d2e99653
--- /dev/null
+++ b/chromium/components/metrics/serialization/metric_sample.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+#define COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace metrics {
+
+// This class is used by libmetrics (ChromeOS) to serialize
+// and deserialize measurements to send them to a metrics sending service.
+// It is meant to be a simple container with serialization functions.
+class MetricSample {
+ public:
+ // Types of metric sample used.
+ enum SampleType {
+ CRASH,
+ HISTOGRAM,
+ LINEAR_HISTOGRAM,
+ SPARSE_HISTOGRAM,
+ USER_ACTION
+ };
+
+ ~MetricSample();
+
+ // Returns true if the sample is valid (can be serialized without ambiguity).
+ //
+ // This function should be used to filter bad samples before serializing them.
+ bool IsValid() const;
+
+ // Getters for type and name. All types of metrics have these so we do not
+ // need to check the type.
+ SampleType type() const { return type_; }
+ const std::string& name() const { return name_; }
+
+ // Getters for sample, min, max, bucket_count.
+ // Check the metric type to make sure the request make sense. (ex: a crash
+ // sample does not have a bucket_count so we crash if we call bucket_count()
+ // on it.)
+ int sample() const;
+ int min() const;
+ int max() const;
+ int bucket_count() const;
+
+ // Returns a serialized version of the sample.
+ //
+ // The serialized message for each type is:
+ // crash: crash\0|name_|\0
+ // user action: useraction\0|name_|\0
+ // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0
+ // sparsehistogram: sparsehistogram\0|name_| |sample_|\0
+ // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0
+ std::string ToString() const;
+
+ // Builds a crash sample.
+ static scoped_ptr<MetricSample> CrashSample(const std::string& crash_name);
+
+ // Builds a histogram sample.
+ static scoped_ptr<MetricSample> HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count);
+ // Deserializes a histogram sample.
+ static scoped_ptr<MetricSample> ParseHistogram(const std::string& serialized);
+
+ // Builds a sparse histogram sample.
+ static scoped_ptr<MetricSample> SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample);
+ // Deserializes a sparse histogram sample.
+ static scoped_ptr<MetricSample> ParseSparseHistogram(
+ const std::string& serialized);
+
+ // Builds a linear histogram sample.
+ static scoped_ptr<MetricSample> LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max);
+ // Deserializes a linear histogram sample.
+ static scoped_ptr<MetricSample> ParseLinearHistogram(
+ const std::string& serialized);
+
+ // Builds a user action sample.
+ static scoped_ptr<MetricSample> UserActionSample(
+ const std::string& action_name);
+
+ // Returns true if sample and this object represent the same sample (type,
+ // name, sample, min, max, bucket_count match).
+ bool IsEqual(const MetricSample& sample);
+
+ private:
+ MetricSample(SampleType sample_type,
+ const std::string& metric_name,
+ const int sample,
+ const int min,
+ const int max,
+ const int bucket_count);
+
+ const SampleType type_;
+ const std::string name_;
+ const int sample_;
+ const int min_;
+ const int max_;
+ const int bucket_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricSample);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
diff --git a/chromium/components/metrics/serialization/serialization_utils.cc b/chromium/components/metrics/serialization/serialization_utils.cc
new file mode 100644
index 00000000000..0038b4b2006
--- /dev/null
+++ b/chromium/components/metrics/serialization/serialization_utils.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/serialization_utils.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/file.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/metrics/serialization/metric_sample.h"
+
+#define READ_WRITE_ALL_FILE_FLAGS \
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+namespace metrics {
+namespace {
+
+// Reads the next message from |file_descriptor| into |message|.
+//
+// |message| will be set to the empty string if no message could be read (EOF)
+// or the message was badly constructed.
+//
+// Returns false if no message can be read from this file anymore (EOF or
+// unrecoverable error).
+bool ReadMessage(int fd, std::string* message) {
+ CHECK(message);
+
+ int result;
+ int32_t message_size;
+ const int32_t message_header_size = sizeof(message_size);
+ // The file containing the metrics does not leave the device so the writer and
+ // the reader will always have the same endianness.
+ result = HANDLE_EINTR(read(fd, &message_size, message_header_size));
+ if (result < 0) {
+ DPLOG(ERROR) << "reading metrics message header";
+ return false;
+ }
+ if (result == 0) {
+ // This indicates a normal EOF.
+ return false;
+ }
+ if (result < message_header_size) {
+ DLOG(ERROR) << "bad read size " << result << ", expecting "
+ << sizeof(message_size);
+ return false;
+ }
+
+ // kMessageMaxLength applies to the entire message: the 4-byte
+ // length field and the content.
+ if (message_size > SerializationUtils::kMessageMaxLength) {
+ DLOG(ERROR) << "message too long : " << message_size;
+ if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+ DLOG(ERROR) << "error while skipping message. abort";
+ return false;
+ }
+ // Badly formatted message was skipped. Treat the badly formatted sample as
+ // an empty sample.
+ message->clear();
+ return true;
+ }
+
+ if (message_size < message_header_size) {
+ DLOG(ERROR) << "message too short : " << message_size;
+ return false;
+ }
+
+ message_size -= message_header_size; // The message size includes itself.
+ char buffer[SerializationUtils::kMessageMaxLength];
+ if (!base::ReadFromFD(fd, buffer, message_size)) {
+ DPLOG(ERROR) << "reading metrics message body";
+ return false;
+ }
+ *message = std::string(buffer, message_size);
+ return true;
+}
+
+} // namespace
+
+scoped_ptr<MetricSample> SerializationUtils::ParseSample(
+ const std::string& sample) {
+ if (sample.empty())
+ return scoped_ptr<MetricSample>();
+
+ std::vector<std::string> parts = base::SplitString(
+ sample, std::string(1, '\0'),
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ // We should have two null terminated strings so split should produce
+ // three chunks.
+ if (parts.size() != 3) {
+ DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
+ << " parts (expected 3)";
+ return scoped_ptr<MetricSample>();
+ }
+ const std::string& name = parts[0];
+ const std::string& value = parts[1];
+
+ if (base::LowerCaseEqualsASCII(name, "crash")) {
+ return MetricSample::CrashSample(value);
+ } else if (base::LowerCaseEqualsASCII(name, "histogram")) {
+ return MetricSample::ParseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "linearhistogram")) {
+ return MetricSample::ParseLinearHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "sparsehistogram")) {
+ return MetricSample::ParseSparseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "useraction")) {
+ return MetricSample::UserActionSample(value);
+ } else {
+ DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
+ }
+ return scoped_ptr<MetricSample>();
+}
+
+void SerializationUtils::ReadAndTruncateMetricsFromFile(
+ const std::string& filename,
+ ScopedVector<MetricSample>* metrics) {
+ struct stat stat_buf;
+ int result;
+
+ result = stat(filename.c_str(), &stat_buf);
+ if (result < 0) {
+ if (errno != ENOENT)
+ DPLOG(ERROR) << "bad metrics file stat: " << filename;
+
+ // Nothing to collect---try later.
+ return;
+ }
+ if (stat_buf.st_size == 0) {
+ // Also nothing to collect.
+ return;
+ }
+ base::ScopedFD fd(open(filename.c_str(), O_RDWR));
+ if (fd.get() < 0) {
+ DPLOG(ERROR) << "cannot open: " << filename;
+ return;
+ }
+ result = flock(fd.get(), LOCK_EX);
+ if (result < 0) {
+ DPLOG(ERROR) << "cannot lock: " << filename;
+ return;
+ }
+
+ // This processes all messages in the log. When all messages are
+ // read and processed, or an error occurs, truncate the file to zero size.
+ for (;;) {
+ std::string message;
+
+ if (!ReadMessage(fd.get(), &message))
+ break;
+
+ scoped_ptr<MetricSample> sample = ParseSample(message);
+ if (sample)
+ metrics->push_back(std::move(sample));
+ }
+
+ result = ftruncate(fd.get(), 0);
+ if (result < 0)
+ DPLOG(ERROR) << "truncate metrics log: " << filename;
+
+ result = flock(fd.get(), LOCK_UN);
+ if (result < 0)
+ DPLOG(ERROR) << "unlock metrics log: " << filename;
+}
+
+bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
+ const std::string& filename) {
+ if (!sample.IsValid())
+ return false;
+
+ base::ScopedFD file_descriptor(open(filename.c_str(),
+ O_WRONLY | O_APPEND | O_CREAT,
+ READ_WRITE_ALL_FILE_FLAGS));
+
+ if (file_descriptor.get() < 0) {
+ DPLOG(ERROR) << "error openning the file: " << filename;
+ return false;
+ }
+
+ fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
+ // Grab a lock to avoid chrome truncating the file
+ // underneath us. Keep the file locked as briefly as possible.
+ // Freeing file_descriptor will close the file and and remove the lock.
+ if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
+ DPLOG(ERROR) << "error locking: " << filename;
+ return false;
+ }
+
+ std::string msg = sample.ToString();
+ int32_t size = msg.length() + sizeof(int32_t);
+ if (size > kMessageMaxLength) {
+ DPLOG(ERROR) << "cannot write message: too long: " << filename;
+ return false;
+ }
+
+ // The file containing the metrics samples will only be read by programs on
+ // the same device so we do not check endianness.
+ if (!base::WriteFileDescriptor(file_descriptor.get(),
+ reinterpret_cast<char*>(&size),
+ sizeof(size))) {
+ DPLOG(ERROR) << "error writing message length: " << filename;
+ return false;
+ }
+
+ if (!base::WriteFileDescriptor(
+ file_descriptor.get(), msg.c_str(), msg.size())) {
+ DPLOG(ERROR) << "error writing message: " << filename;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/serialization/serialization_utils.h b/chromium/components/metrics/serialization/serialization_utils.h
new file mode 100644
index 00000000000..17e5e4cebc5
--- /dev/null
+++ b/chromium/components/metrics/serialization/serialization_utils.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+#define COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+namespace metrics {
+
+class MetricSample;
+
+// Metrics helpers to serialize and deserialize metrics collected by
+// ChromeOS.
+namespace SerializationUtils {
+
+// Deserializes a sample passed as a string and return a sample.
+// The return value will either be a scoped_ptr to a Metric sample (if the
+// deserialization was successful) or a NULL scoped_ptr.
+scoped_ptr<MetricSample> ParseSample(const std::string& sample);
+
+// Reads all samples from a file and truncate the file when done.
+void ReadAndTruncateMetricsFromFile(const std::string& filename,
+ ScopedVector<MetricSample>* metrics);
+
+// Serializes a sample and write it to filename.
+// The format for the message is:
+// message_size, serialized_message
+// where
+// * message_size is the total length of the message (message_size +
+// serialized_message) on 4 bytes
+// * serialized_message is the serialized version of sample (using ToString)
+//
+// NB: the file will never leave the device so message_size will be written
+// with the architecture's endianness.
+bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
+
+// Maximum length of a serialized message
+static const int kMessageMaxLength = 1024;
+
+} // namespace SerializationUtils
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
diff --git a/chromium/components/metrics/serialization/serialization_utils_unittest.cc b/chromium/components/metrics/serialization/serialization_utils_unittest.cc
new file mode 100644
index 00000000000..fe8f2b2d081
--- /dev/null
+++ b/chromium/components/metrics/serialization/serialization_utils_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/serialization_utils.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/serialization/metric_sample.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace {
+
+class SerializationUtilsTest : public testing::Test {
+ protected:
+ SerializationUtilsTest() {
+ bool success = temporary_dir.CreateUniqueTempDir();
+ if (success) {
+ base::FilePath dir_path = temporary_dir.path();
+ filename = dir_path.value() + "chromeossampletest";
+ filepath = base::FilePath(filename);
+ }
+ }
+
+ void SetUp() override { base::DeleteFile(filepath, false); }
+
+ void TestSerialization(MetricSample* sample) {
+ std::string serialized(sample->ToString());
+ ASSERT_EQ('\0', serialized.back());
+ scoped_ptr<MetricSample> deserialized =
+ SerializationUtils::ParseSample(serialized);
+ ASSERT_TRUE(deserialized);
+ EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
+ }
+
+ std::string filename;
+ base::ScopedTempDir temporary_dir;
+ base::FilePath filepath;
+};
+
+TEST_F(SerializationUtilsTest, CrashSerializeTest) {
+ TestSerialization(MetricSample::CrashSample("test").get());
+}
+
+TEST_F(SerializationUtilsTest, HistogramSerializeTest) {
+ TestSerialization(
+ MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get());
+}
+
+TEST_F(SerializationUtilsTest, LinearSerializeTest) {
+ TestSerialization(
+ MetricSample::LinearHistogramSample("linearhist", 12, 30).get());
+}
+
+TEST_F(SerializationUtilsTest, SparseSerializeTest) {
+ TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get());
+}
+
+TEST_F(SerializationUtilsTest, UserActionSerializeTest) {
+ TestSerialization(MetricSample::UserActionSample("myaction").get());
+}
+
+TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) {
+ scoped_ptr<MetricSample> sample1 =
+ MetricSample::SparseHistogramSample("no space", 10);
+ scoped_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
+ base::StringPrintf("here%cbhe", '\0'), 1, 3);
+
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+ int64_t size = 0;
+
+ ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) {
+ std::string input(
+ base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0'));
+ EXPECT_EQ(NULL, MetricSample::ParseSparseHistogram(input).get());
+}
+
+TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ int64_t size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ // 4 bytes for the size
+ // 5 bytes for crash
+ // 7 bytes for mycrash
+ // 2 bytes for the \0
+ // -> total of 18
+ EXPECT_EQ(size, 18);
+}
+
+TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) {
+ // Creates a message that is bigger than the maximum allowed size.
+ // As we are adding extra character (crash, \0s, etc), if the name is
+ // kMessageMaxLength long, it will be too long.
+ std::string name(SerializationUtils::kMessageMaxLength, 'c');
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample(name);
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+ int64_t size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
+ base::File test_file(filepath,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+ std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
+
+ int32_t message_size = message.length() + sizeof(int32_t);
+ test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size),
+ sizeof(message_size));
+ test_file.WriteAtCurrentPos(message.c_str(), message.length());
+ test_file.Close();
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("test");
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+
+ ScopedVector<MetricSample> samples;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+ ASSERT_EQ(size_t(1), samples.size());
+ ASSERT_TRUE(samples[0] != NULL);
+ EXPECT_TRUE(crash->IsEqual(*samples[0]));
+}
+
+TEST_F(SerializationUtilsTest, WriteReadTest) {
+ scoped_ptr<MetricSample> hist =
+ MetricSample::HistogramSample("myhist", 1, 2, 3, 4);
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+ scoped_ptr<MetricSample> lhist =
+ MetricSample::LinearHistogramSample("linear", 1, 10);
+ scoped_ptr<MetricSample> shist =
+ MetricSample::SparseHistogramSample("mysparse", 30);
+ scoped_ptr<MetricSample> action = MetricSample::UserActionSample("myaction");
+
+ SerializationUtils::WriteMetricToFile(*hist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*shist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*action.get(), filename);
+ ScopedVector<MetricSample> vect;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+ ASSERT_EQ(vect.size(), size_t(5));
+ for (int i = 0; i < 5; i++) {
+ ASSERT_TRUE(vect[0] != NULL);
+ }
+ EXPECT_TRUE(hist->IsEqual(*vect[0]));
+ EXPECT_TRUE(crash->IsEqual(*vect[1]));
+ EXPECT_TRUE(lhist->IsEqual(*vect[2]));
+ EXPECT_TRUE(shist->IsEqual(*vect[3]));
+ EXPECT_TRUE(action->IsEqual(*vect[4]));
+
+ int64_t size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ ASSERT_EQ(0, size);
+}
+
+} // namespace
+} // namespace metrics
diff --git a/chromium/components/metrics/stability_metrics_helper.cc b/chromium/components/metrics/stability_metrics_helper.cc
new file mode 100644
index 00000000000..52b91f0f127
--- /dev/null
+++ b/chromium/components/metrics/stability_metrics_helper.cc
@@ -0,0 +1,221 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_helper.h"
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+#if defined(OS_WIN)
+#include <windows.h> // Needed for STATUS_* codes
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "components/metrics/system_memory_stats_recorder.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+enum RendererType {
+ RENDERER_TYPE_RENDERER = 1,
+ RENDERER_TYPE_EXTENSION,
+ // NOTE: Add new action types only immediately above this line. Also,
+ // make sure the enum list in tools/metrics/histograms/histograms.xml is
+ // updated with any change in here.
+ RENDERER_TYPE_COUNT
+};
+
+// Converts an exit code into something that can be inserted into our
+// histograms (which expect non-negative numbers less than MAX_INT).
+int MapCrashExitCodeForHistogram(int exit_code) {
+#if defined(OS_WIN)
+ // Since |abs(STATUS_GUARD_PAGE_VIOLATION) == MAX_INT| it causes problems in
+ // histograms.cc. Solve this by remapping it to a smaller value, which
+ // hopefully doesn't conflict with other codes.
+ if (static_cast<DWORD>(exit_code) == STATUS_GUARD_PAGE_VIOLATION)
+ return 0x1FCF7EC3; // Randomly picked number.
+#endif
+
+ return std::abs(exit_code);
+}
+
+void RecordChildKills(int histogram_type) {
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills",
+ histogram_type, RENDERER_TYPE_COUNT);
+}
+
+} // namespace
+
+StabilityMetricsHelper::StabilityMetricsHelper(PrefService* local_state)
+ : local_state_(local_state) {
+ DCHECK(local_state_);
+}
+
+StabilityMetricsHelper::~StabilityMetricsHelper() {}
+
+void StabilityMetricsHelper::ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+ SystemProfileProto_Stability* stability_proto =
+ system_profile_proto->mutable_stability();
+
+ int count = local_state_->GetInteger(prefs::kStabilityPageLoadCount);
+ if (count) {
+ stability_proto->set_page_load_count(count);
+ local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityChildProcessCrashCount);
+ if (count) {
+ stability_proto->set_child_process_crash_count(count);
+ local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityRendererCrashCount);
+ if (count) {
+ stability_proto->set_renderer_crash_count(count);
+ local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityRendererFailedLaunchCount);
+ if (count) {
+ stability_proto->set_renderer_failed_launch_count(count);
+ local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
+ }
+
+ count =
+ local_state_->GetInteger(prefs::kStabilityExtensionRendererCrashCount);
+ if (count) {
+ stability_proto->set_extension_renderer_crash_count(count);
+ local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
+ }
+
+ count = local_state_->GetInteger(
+ prefs::kStabilityExtensionRendererFailedLaunchCount);
+ if (count) {
+ stability_proto->set_extension_renderer_failed_launch_count(count);
+ local_state_->SetInteger(
+ prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
+ }
+
+ count = local_state_->GetInteger(prefs::kStabilityRendererHangCount);
+ if (count) {
+ stability_proto->set_renderer_hang_count(count);
+ local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
+ }
+}
+
+void StabilityMetricsHelper::ClearSavedStabilityMetrics() {
+ // Clear all the prefs used in this class in UMA reports (which doesn't
+ // include |kUninstallMetricsPageLoadCount| as it's not sent up by UMA).
+ local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityExtensionRendererFailedLaunchCount,
+ 0);
+ local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
+ local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
+ local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
+ local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
+}
+
+// static
+void StabilityMetricsHelper::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterIntegerPref(prefs::kStabilityChildProcessCrashCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererCrashCount,
+ 0);
+ registry->RegisterIntegerPref(
+ prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityRendererFailedLaunchCount, 0);
+ registry->RegisterIntegerPref(prefs::kStabilityRendererHangCount, 0);
+
+ registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0);
+}
+
+void StabilityMetricsHelper::BrowserChildProcessCrashed() {
+ IncrementPrefValue(prefs::kStabilityChildProcessCrashCount);
+}
+
+void StabilityMetricsHelper::LogLoadStarted() {
+ base::RecordAction(base::UserMetricsAction("PageLoad"));
+ // TODO(asvitkine): Check if this is used for anything and if not, remove.
+ LOCAL_HISTOGRAM_BOOLEAN("Chrome.UmaPageloadCounter", true);
+ IncrementPrefValue(prefs::kStabilityPageLoadCount);
+ IncrementLongPrefsValue(prefs::kUninstallMetricsPageLoadCount);
+ // We need to save the prefs, as page load count is a critical stat, and it
+ // might be lost due to a crash :-(.
+}
+
+void StabilityMetricsHelper::LogRendererCrash(bool was_extension_process,
+ base::TerminationStatus status,
+ int exit_code) {
+ int histogram_type =
+ was_extension_process ? RENDERER_TYPE_EXTENSION : RENDERER_TYPE_RENDERER;
+ if (status == base::TERMINATION_STATUS_PROCESS_CRASHED ||
+ status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION) {
+ if (was_extension_process) {
+ IncrementPrefValue(prefs::kStabilityExtensionRendererCrashCount);
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("CrashExitCodes.Extension",
+ MapCrashExitCodeForHistogram(exit_code));
+ } else {
+ IncrementPrefValue(prefs::kStabilityRendererCrashCount);
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("CrashExitCodes.Renderer",
+ MapCrashExitCodeForHistogram(exit_code));
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildCrashes",
+ histogram_type, RENDERER_TYPE_COUNT);
+ } else if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED) {
+ RecordChildKills(histogram_type);
+#if defined(OS_CHROMEOS)
+ } else if (status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM) {
+ RecordChildKills(histogram_type);
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills.OOM",
+ was_extension_process ? 2 : 1, 3);
+ RecordMemoryStats(was_extension_process
+ ? RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED
+ : RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED);
+#endif
+ } else if (status == base::TERMINATION_STATUS_STILL_RUNNING) {
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.DisconnectedAlive",
+ histogram_type, RENDERER_TYPE_COUNT);
+ } else if (status == base::TERMINATION_STATUS_LAUNCH_FAILED) {
+ UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildLaunchFailures",
+ histogram_type, RENDERER_TYPE_COUNT);
+ if (was_extension_process)
+ IncrementPrefValue(prefs::kStabilityExtensionRendererFailedLaunchCount);
+ else
+ IncrementPrefValue(prefs::kStabilityRendererFailedLaunchCount);
+ }
+}
+
+void StabilityMetricsHelper::IncrementPrefValue(const char* path) {
+ int value = local_state_->GetInteger(path);
+ local_state_->SetInteger(path, value + 1);
+}
+
+void StabilityMetricsHelper::IncrementLongPrefsValue(const char* path) {
+ int64_t value = local_state_->GetInt64(path);
+ local_state_->SetInt64(path, value + 1);
+}
+
+void StabilityMetricsHelper::LogRendererHang() {
+ IncrementPrefValue(prefs::kStabilityRendererHangCount);
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/stability_metrics_helper.h b/chromium/components/metrics/stability_metrics_helper.h
new file mode 100644
index 00000000000..42a9ca20217
--- /dev/null
+++ b/chromium/components/metrics/stability_metrics_helper.h
@@ -0,0 +1,63 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
+#define COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
+
+#include "base/macros.h"
+#include "base/metrics/user_metrics.h"
+#include "base/process/kill.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+class SystemProfileProto;
+
+// StabilityMetricsHelper is a class that providers functionality common to
+// different embedders' stability metrics providers.
+class StabilityMetricsHelper {
+ public:
+ explicit StabilityMetricsHelper(PrefService* local_state);
+ ~StabilityMetricsHelper();
+
+ // Provides stability metrics.
+ void ProvideStabilityMetrics(SystemProfileProto* system_profile_proto);
+
+ // Clears the gathered stability metrics.
+ void ClearSavedStabilityMetrics();
+
+ // Records a browser child process crash.
+ void BrowserChildProcessCrashed();
+
+ // Logs the initiation of a page load.
+ void LogLoadStarted();
+
+ // Records a renderer process crash.
+ void LogRendererCrash(bool was_exception_process,
+ base::TerminationStatus status,
+ int exit_code);
+
+ // Records a renderer process hang.
+ void LogRendererHang();
+
+ // Registers local state prefs used by this class.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ // Increment an Integer pref value specified by |path|.
+ void IncrementPrefValue(const char* path);
+
+ // Increment a 64-bit Integer pref value specified by |path|.
+ void IncrementLongPrefsValue(const char* path);
+
+ PrefService* local_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelper);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
diff --git a/chromium/components/metrics/stability_metrics_helper_unittest.cc b/chromium/components/metrics/stability_metrics_helper_unittest.cc
new file mode 100644
index 00000000000..6ca84df0f28
--- /dev/null
+++ b/chromium/components/metrics/stability_metrics_helper_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_helper.h"
+
+#include "base/macros.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+class StabilityMetricsHelperTest : public testing::Test {
+ protected:
+ StabilityMetricsHelperTest() : prefs_(new TestingPrefServiceSimple) {
+ StabilityMetricsHelper::RegisterPrefs(prefs()->registry());
+ }
+
+ TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+
+ private:
+ scoped_ptr<TestingPrefServiceSimple> prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelperTest);
+};
+
+} // namespace
+
+TEST_F(StabilityMetricsHelperTest, BrowserChildProcessCrashed) {
+ StabilityMetricsHelper helper(prefs());
+
+ helper.BrowserChildProcessCrashed();
+ helper.BrowserChildProcessCrashed();
+
+ // Call ProvideStabilityMetrics to check that it will force pending tasks to
+ // be executed immediately.
+ metrics::SystemProfileProto system_profile;
+
+ helper.ProvideStabilityMetrics(&system_profile);
+
+ // Check current number of instances created.
+ const metrics::SystemProfileProto_Stability& stability =
+ system_profile.stability();
+
+ EXPECT_EQ(2, stability.child_process_crash_count());
+}
+
+TEST_F(StabilityMetricsHelperTest, LogRendererCrash) {
+ StabilityMetricsHelper helper(prefs());
+
+ // Crash and abnormal termination should increment renderer crash count.
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_CRASHED, 1);
+
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_ABNORMAL_TERMINATION,
+ 1);
+
+ // Kill does not increment renderer crash count.
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_WAS_KILLED,
+ 1);
+
+ // Failed launch increments failed launch count.
+ helper.LogRendererCrash(false, base::TERMINATION_STATUS_LAUNCH_FAILED, 1);
+
+ metrics::SystemProfileProto system_profile;
+
+ // Call ProvideStabilityMetrics to check that it will force pending tasks to
+ // be executed immediately.
+ helper.ProvideStabilityMetrics(&system_profile);
+
+ EXPECT_EQ(2, system_profile.stability().renderer_crash_count());
+ EXPECT_EQ(1, system_profile.stability().renderer_failed_launch_count());
+ EXPECT_EQ(0, system_profile.stability().extension_renderer_crash_count());
+
+ helper.ClearSavedStabilityMetrics();
+
+ // Crash and abnormal termination should increment extension crash count.
+ helper.LogRendererCrash(true, base::TERMINATION_STATUS_PROCESS_CRASHED, 1);
+
+ // Failed launch increments extension failed launch count.
+ helper.LogRendererCrash(true, base::TERMINATION_STATUS_LAUNCH_FAILED, 1);
+
+ system_profile.Clear();
+ helper.ProvideStabilityMetrics(&system_profile);
+
+ EXPECT_EQ(0, system_profile.stability().renderer_crash_count());
+ EXPECT_EQ(1, system_profile.stability().extension_renderer_crash_count());
+ EXPECT_EQ(
+ 1, system_profile.stability().extension_renderer_failed_launch_count());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/system_memory_stats_recorder.h b/chromium/components/metrics/system_memory_stats_recorder.h
new file mode 100644
index 00000000000..bdd30f00f42
--- /dev/null
+++ b/chromium/components/metrics/system_memory_stats_recorder.h
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
+#define COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
+
+namespace metrics {
+
+// Record a memory size in megabytes, over a potential interval up to 32 GB.
+#define UMA_HISTOGRAM_LARGE_MEMORY_MB(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50)
+
+// The type of memory UMA stats to be recorded in RecordMemoryStats.
+enum RecordMemoryStatsType {
+ // When a tab was discarded.
+ RECORD_MEMORY_STATS_TAB_DISCARDED,
+
+ // Right after the renderer for contents was killed.
+ RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED,
+
+ // Right after the renderer for extensions was killed.
+ RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED,
+};
+
+void RecordMemoryStats(RecordMemoryStatsType type);
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
diff --git a/chromium/components/metrics/system_memory_stats_recorder_linux.cc b/chromium/components/metrics/system_memory_stats_recorder_linux.cc
new file mode 100644
index 00000000000..c69dbaa0460
--- /dev/null
+++ b/chromium/components/metrics/system_memory_stats_recorder_linux.cc
@@ -0,0 +1,98 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_memory_stats_recorder.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
+#include "build/build_config.h"
+
+namespace metrics {
+
+// Record a size in megabytes, a potential interval from 250MB up to 32 GB.
+#define UMA_HISTOGRAM_ALLOCATED_MEGABYTES(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 250, 32768, 50)
+
+// Records a statistics |sample| for UMA histogram |name|
+// using a linear distribution of buckets.
+#define UMA_HISTOGRAM_LINEAR(name, sample, max, buckets) \
+ STATIC_HISTOGRAM_POINTER_BLOCK( \
+ name, Add(sample), \
+ base::LinearHistogram::FactoryGet( \
+ name, \
+ 1, /* Minimum. The 0 bin for underflow is automatically added. */ \
+ max + 1, /* Ensure bucket size of |maximum| / |bucket_count|. */ \
+ buckets + 2, /* Account for the underflow and overflow bins. */ \
+ base::Histogram::kUmaTargetedHistogramFlag))
+
+#define UMA_HISTOGRAM_MEGABYTES_LINEAR(name, sample) \
+ UMA_HISTOGRAM_LINEAR(name, sample, 2500, 50)
+
+void RecordMemoryStats(RecordMemoryStatsType type) {
+ base::SystemMemoryInfoKB memory;
+ if (!base::GetSystemMemoryInfo(&memory))
+ return;
+#if defined(OS_CHROMEOS)
+ // Record graphics GEM object size in a histogram with 50 MB buckets.
+ int mem_graphics_gem_mb = 0;
+ if (memory.gem_size != -1)
+ mem_graphics_gem_mb = memory.gem_size / 1024 / 1024;
+
+ // Record shared memory (used by renderer/GPU buffers).
+ int mem_shmem_mb = memory.shmem / 1024;
+#endif
+
+ // On Intel, graphics objects are in anonymous pages, but on ARM they are
+ // not. For a total "allocated count" add in graphics pages on ARM.
+ int mem_allocated_mb = (memory.active_anon + memory.inactive_anon) / 1024;
+#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+ mem_allocated_mb += mem_graphics_gem_mb;
+#endif
+
+ int mem_available_mb =
+ (memory.active_file + memory.inactive_file + memory.free) / 1024;
+
+ switch (type) {
+ case RECORD_MEMORY_STATS_TAB_DISCARDED: {
+#if defined(OS_CHROMEOS)
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemGraphicsMB",
+ mem_graphics_gem_mb);
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemShmemMB", mem_shmem_mb);
+#endif
+ UMA_HISTOGRAM_ALLOCATED_MEGABYTES("Tabs.Discard.MemAllocatedMB",
+ mem_allocated_mb);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Tabs.Discard.MemAvailableMB",
+ mem_available_mb);
+ break;
+ }
+ case RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED: {
+#if defined(OS_CHROMEOS)
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemGraphicsMB",
+ mem_graphics_gem_mb);
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemShmemMB",
+ mem_shmem_mb);
+#endif
+ UMA_HISTOGRAM_ALLOCATED_MEGABYTES(
+ "Memory.OOMKill.Contents.MemAllocatedMB", mem_allocated_mb);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Contents.MemAvailableMB",
+ mem_available_mb);
+ break;
+ }
+ case RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED: {
+#if defined(OS_CHROMEOS)
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemGraphicsMB",
+ mem_graphics_gem_mb);
+ UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemShmemMB",
+ mem_shmem_mb);
+#endif
+ UMA_HISTOGRAM_ALLOCATED_MEGABYTES(
+ "Memory.OOMKill.Extensions.MemAllocatedMB", mem_allocated_mb);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Extensions.MemAvailableMB",
+ mem_available_mb);
+ break;
+ }
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/system_memory_stats_recorder_win.cc b/chromium/components/metrics/system_memory_stats_recorder_win.cc
new file mode 100644
index 00000000000..cdd314989f3
--- /dev/null
+++ b/chromium/components/metrics/system_memory_stats_recorder_win.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_memory_stats_recorder.h"
+
+#include <windows.h>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
+
+namespace metrics {
+namespace {
+enum { kMBytes = 1024 * 1024 };
+}
+
+void RecordMemoryStats(RecordMemoryStatsType type) {
+ MEMORYSTATUSEX mem_status;
+ mem_status.dwLength = sizeof(mem_status);
+ if (!::GlobalMemoryStatusEx(&mem_status))
+ return;
+
+ switch (type) {
+ case RECORD_MEMORY_STATS_TAB_DISCARDED: {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Stats.Win.MemoryLoad",
+ mem_status.dwMemoryLoad, 0, 100, 101);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPhys2",
+ mem_status.ullTotalPhys / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPhys2",
+ mem_status.ullAvailPhys / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPageFile2",
+ mem_status.ullTotalPageFile / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPageFile2",
+ mem_status.ullAvailPageFile / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalVirtual2",
+ mem_status.ullTotalVirtual / kMBytes);
+ UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailVirtual2",
+ mem_status.ullAvailVirtual / kMBytes);
+ break;
+ }
+ default:
+ NOTREACHED() << L"Received unexpected notification";
+ break;
+ }
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/test_metrics_provider.cc b/chromium/components/metrics/test_metrics_provider.cc
new file mode 100644
index 00000000000..409d8d129d6
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_provider.cc
@@ -0,0 +1,35 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_provider.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace metrics {
+
+void TestMetricsProvider::Init() {
+ init_called_ = true;
+}
+
+void TestMetricsProvider::OnRecordingDisabled() {
+ on_recording_disabled_called_ = true;
+}
+
+bool TestMetricsProvider::HasInitialStabilityMetrics() {
+ return has_initial_stability_metrics_;
+}
+
+void TestMetricsProvider::ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+ UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Initial", 1, 2);
+ provide_initial_stability_metrics_called_ = true;
+}
+
+void TestMetricsProvider::ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) {
+ UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Regular", 1, 2);
+ provide_stability_metrics_called_ = true;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/test_metrics_provider.h b/chromium/components/metrics/test_metrics_provider.h
new file mode 100644
index 00000000000..6536ea23198
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_provider.h
@@ -0,0 +1,57 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// A simple implementation of MetricsProvider that checks that its providing
+// functions are called, for use in tests.
+class TestMetricsProvider : public MetricsProvider {
+ public:
+ TestMetricsProvider()
+ : init_called_(false),
+ on_recording_disabled_called_(false),
+ has_initial_stability_metrics_(false),
+ provide_initial_stability_metrics_called_(false),
+ provide_stability_metrics_called_(false) {}
+
+ // MetricsProvider:
+ void Init() override;
+ void OnRecordingDisabled() override;
+ bool HasInitialStabilityMetrics() override;
+ void ProvideInitialStabilityMetrics(
+ SystemProfileProto* system_profile_proto) override;
+ void ProvideStabilityMetrics(
+ SystemProfileProto* system_profile_proto) override;
+
+ bool init_called() { return init_called_; }
+ bool on_recording_disabled_called() { return on_recording_disabled_called_; }
+ void set_has_initial_stability_metrics(bool has_initial_stability_metrics) {
+ has_initial_stability_metrics_ = has_initial_stability_metrics;
+ }
+ bool provide_initial_stability_metrics_called() const {
+ return provide_initial_stability_metrics_called_;
+ }
+ bool provide_stability_metrics_called() const {
+ return provide_stability_metrics_called_;
+ }
+
+ private:
+ bool init_called_;
+ bool on_recording_disabled_called_;
+ bool has_initial_stability_metrics_;
+ bool provide_initial_stability_metrics_called_;
+ bool provide_stability_metrics_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/test_metrics_service_client.cc b/chromium/components/metrics/test_metrics_service_client.cc
new file mode 100644
index 00000000000..7aeaddf9c69
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_service_client.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_service_client.h"
+
+#include "base/callback.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+// static
+const char TestMetricsServiceClient::kBrandForTesting[] = "brand_for_testing";
+
+TestMetricsServiceClient::TestMetricsServiceClient()
+ : version_string_("5.0.322.0-64-devel"),
+ product_(ChromeUserMetricsExtension::CHROME),
+ reporting_is_managed_(false),
+ enable_default_(MetricsServiceClient::DEFAULT_UNKNOWN) {}
+
+TestMetricsServiceClient::~TestMetricsServiceClient() {
+}
+
+metrics::MetricsService* TestMetricsServiceClient::GetMetricsService() {
+ return nullptr;
+}
+
+void TestMetricsServiceClient::SetMetricsClientId(
+ const std::string& client_id) {
+ client_id_ = client_id;
+}
+
+void TestMetricsServiceClient::OnRecordingDisabled() {
+}
+
+bool TestMetricsServiceClient::IsOffTheRecordSessionActive() {
+ return false;
+}
+
+int32_t TestMetricsServiceClient::GetProduct() {
+ return product_;
+}
+
+std::string TestMetricsServiceClient::GetApplicationLocale() {
+ return "en-US";
+}
+
+bool TestMetricsServiceClient::GetBrand(std::string* brand_code) {
+ *brand_code = kBrandForTesting;
+ return true;
+}
+
+SystemProfileProto::Channel TestMetricsServiceClient::GetChannel() {
+ return SystemProfileProto::CHANNEL_BETA;
+}
+
+std::string TestMetricsServiceClient::GetVersionString() {
+ return version_string_;
+}
+
+void TestMetricsServiceClient::OnLogUploadComplete() {
+}
+
+void TestMetricsServiceClient::InitializeSystemProfileMetrics(
+ const base::Closure& done_callback) {
+ done_callback.Run();
+}
+
+void TestMetricsServiceClient::CollectFinalMetricsForLog(
+ const base::Closure& done_callback) {
+ done_callback.Run();
+}
+
+scoped_ptr<MetricsLogUploader> TestMetricsServiceClient::CreateUploader(
+ const base::Callback<void(int)>& on_upload_complete) {
+ return scoped_ptr<MetricsLogUploader>();
+}
+
+base::TimeDelta TestMetricsServiceClient::GetStandardUploadInterval() {
+ return base::TimeDelta::FromMinutes(5);
+}
+
+bool TestMetricsServiceClient::IsReportingPolicyManaged() {
+ return reporting_is_managed_;
+}
+
+MetricsServiceClient::EnableMetricsDefault
+TestMetricsServiceClient::GetDefaultOptIn() {
+ return enable_default_;
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/test_metrics_service_client.h b/chromium/components/metrics/test_metrics_service_client.h
new file mode 100644
index 00000000000..8710355901c
--- /dev/null
+++ b/chromium/components/metrics/test_metrics_service_client.h
@@ -0,0 +1,69 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
+#define COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/metrics/metrics_service_client.h"
+
+namespace metrics {
+
+// A simple concrete implementation of the MetricsServiceClient interface, for
+// use in tests.
+class TestMetricsServiceClient : public MetricsServiceClient {
+ public:
+ static const char kBrandForTesting[];
+
+ TestMetricsServiceClient();
+ ~TestMetricsServiceClient() override;
+
+ // MetricsServiceClient:
+ metrics::MetricsService* GetMetricsService() override;
+ void SetMetricsClientId(const std::string& client_id) override;
+ void OnRecordingDisabled() override;
+ bool IsOffTheRecordSessionActive() override;
+ int32_t GetProduct() override;
+ std::string GetApplicationLocale() override;
+ bool GetBrand(std::string* brand_code) override;
+ SystemProfileProto::Channel GetChannel() override;
+ std::string GetVersionString() override;
+ void OnLogUploadComplete() override;
+ void InitializeSystemProfileMetrics(
+ const base::Closure& done_callback) override;
+ void CollectFinalMetricsForLog(const base::Closure& done_callback) override;
+ scoped_ptr<MetricsLogUploader> CreateUploader(
+ const base::Callback<void(int)>& on_upload_complete) override;
+ base::TimeDelta GetStandardUploadInterval() override;
+ bool IsReportingPolicyManaged() override;
+ MetricsServiceClient::EnableMetricsDefault GetDefaultOptIn() override;
+
+ const std::string& get_client_id() const { return client_id_; }
+ void set_version_string(const std::string& str) { version_string_ = str; }
+ void set_product(int32_t product) { product_ = product; }
+ void set_reporting_is_managed(bool managed) {
+ reporting_is_managed_ = managed;
+ }
+ void set_enable_default(
+ MetricsServiceClient::EnableMetricsDefault enable_default) {
+ enable_default_ = enable_default;
+ }
+
+ private:
+ std::string client_id_;
+ std::string version_string_;
+ int32_t product_;
+ bool reporting_is_managed_;
+ MetricsServiceClient::EnableMetricsDefault enable_default_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsServiceClient);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
diff --git a/chromium/components/metrics/ui/DEPS b/chromium/components/metrics/ui/DEPS
new file mode 100644
index 00000000000..b273ae3319c
--- /dev/null
+++ b/chromium/components/metrics/ui/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/gfx",
+]
diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider.cc b/chromium/components/metrics/ui/screen_info_metrics_provider.cc
new file mode 100644
index 00000000000..3d31e988db9
--- /dev/null
+++ b/chromium/components/metrics/ui/screen_info_metrics_provider.cc
@@ -0,0 +1,92 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/ui/screen_info_metrics_provider.h"
+
+#include "build/build_config.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "ui/gfx/screen.h"
+
+namespace metrics {
+
+#if defined(OS_WIN)
+
+#include <windows.h>
+
+namespace {
+
+struct ScreenDPIInformation {
+ double max_dpi_x;
+ double max_dpi_y;
+};
+
+// Called once for each connected monitor.
+BOOL CALLBACK GetMonitorDPICallback(HMONITOR, HDC hdc, LPRECT, LPARAM dwData) {
+ const double kMillimetersPerInch = 25.4;
+ ScreenDPIInformation* screen_info =
+ reinterpret_cast<ScreenDPIInformation*>(dwData);
+ // Size of screen, in mm.
+ DWORD size_x = GetDeviceCaps(hdc, HORZSIZE);
+ DWORD size_y = GetDeviceCaps(hdc, VERTSIZE);
+ double dpi_x = (size_x > 0) ?
+ GetDeviceCaps(hdc, HORZRES) / (size_x / kMillimetersPerInch) : 0;
+ double dpi_y = (size_y > 0) ?
+ GetDeviceCaps(hdc, VERTRES) / (size_y / kMillimetersPerInch) : 0;
+ screen_info->max_dpi_x = std::max(dpi_x, screen_info->max_dpi_x);
+ screen_info->max_dpi_y = std::max(dpi_y, screen_info->max_dpi_y);
+ return TRUE;
+}
+
+void WriteScreenDPIInformationProto(SystemProfileProto::Hardware* hardware) {
+ HDC desktop_dc = GetDC(NULL);
+ if (desktop_dc) {
+ ScreenDPIInformation si = {0, 0};
+ if (EnumDisplayMonitors(desktop_dc, NULL, GetMonitorDPICallback,
+ reinterpret_cast<LPARAM>(&si))) {
+ hardware->set_max_dpi_x(si.max_dpi_x);
+ hardware->set_max_dpi_y(si.max_dpi_y);
+ }
+ ReleaseDC(GetDesktopWindow(), desktop_dc);
+ }
+}
+
+} // namespace
+
+#endif // defined(OS_WIN)
+
+ScreenInfoMetricsProvider::ScreenInfoMetricsProvider() {
+}
+
+ScreenInfoMetricsProvider::~ScreenInfoMetricsProvider() {
+}
+
+void ScreenInfoMetricsProvider::ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) {
+ SystemProfileProto::Hardware* hardware =
+ system_profile_proto->mutable_hardware();
+
+ const gfx::Size display_size = GetScreenSize();
+ hardware->set_primary_screen_width(display_size.width());
+ hardware->set_primary_screen_height(display_size.height());
+ hardware->set_primary_screen_scale_factor(GetScreenDeviceScaleFactor());
+ hardware->set_screen_count(GetScreenCount());
+
+#if defined(OS_WIN)
+ WriteScreenDPIInformationProto(hardware);
+#endif
+}
+
+gfx::Size ScreenInfoMetricsProvider::GetScreenSize() const {
+ return gfx::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
+}
+
+float ScreenInfoMetricsProvider::GetScreenDeviceScaleFactor() const {
+ return gfx::Screen::GetScreen()->GetPrimaryDisplay().device_scale_factor();
+}
+
+int ScreenInfoMetricsProvider::GetScreenCount() const {
+ return gfx::Screen::GetScreen()->GetNumDisplays();
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider.h b/chromium/components/metrics/ui/screen_info_metrics_provider.h
new file mode 100644
index 00000000000..51ef2b77ab1
--- /dev/null
+++ b/chromium/components/metrics/ui/screen_info_metrics_provider.h
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace metrics {
+
+// ScreenInfoMetricsProvider provides metrics related to screen info.
+class ScreenInfoMetricsProvider : public MetricsProvider {
+ public:
+ ScreenInfoMetricsProvider();
+ ~ScreenInfoMetricsProvider() override;
+
+ // MetricsProvider:
+ void ProvideSystemProfileMetrics(
+ SystemProfileProto* system_profile_proto) override;
+
+ protected:
+ // Exposed for the sake of mocking in test code.
+
+ // Returns the screen size for the primary monitor.
+ virtual gfx::Size GetScreenSize() const;
+
+ // Returns the device scale factor for the primary monitor.
+ virtual float GetScreenDeviceScaleFactor() const;
+
+ // Returns the number of monitors the user is using.
+ virtual int GetScreenCount() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProvider);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
diff --git a/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc b/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc
new file mode 100644
index 00000000000..9e0046bd60e
--- /dev/null
+++ b/chromium/components/metrics/ui/screen_info_metrics_provider_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/ui/screen_info_metrics_provider.h"
+
+#include "base/macros.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace metrics {
+
+namespace {
+
+const int kScreenWidth = 1024;
+const int kScreenHeight = 768;
+const int kScreenCount = 3;
+const float kScreenScaleFactor = 2;
+
+class TestScreenInfoMetricsProvider : public ScreenInfoMetricsProvider {
+ public:
+ TestScreenInfoMetricsProvider() {}
+ ~TestScreenInfoMetricsProvider() override {}
+
+ private:
+ gfx::Size GetScreenSize() const override {
+ return gfx::Size(kScreenWidth, kScreenHeight);
+ }
+
+ float GetScreenDeviceScaleFactor() const override {
+ return kScreenScaleFactor;
+ }
+
+ int GetScreenCount() const override { return kScreenCount; }
+
+ DISALLOW_COPY_AND_ASSIGN(TestScreenInfoMetricsProvider);
+};
+
+} // namespace
+
+class ScreenInfoMetricsProviderTest : public testing::Test {
+ public:
+ ScreenInfoMetricsProviderTest() {}
+ ~ScreenInfoMetricsProviderTest() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProviderTest);
+};
+
+TEST_F(ScreenInfoMetricsProviderTest, ProvideSystemProfileMetrics) {
+ TestScreenInfoMetricsProvider provider;
+ ChromeUserMetricsExtension uma_proto;
+
+ provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile());
+
+ // Check that the system profile has the correct values set.
+ const SystemProfileProto::Hardware& hardware =
+ uma_proto.system_profile().hardware();
+ EXPECT_EQ(kScreenWidth, hardware.primary_screen_width());
+ EXPECT_EQ(kScreenHeight, hardware.primary_screen_height());
+ EXPECT_EQ(kScreenScaleFactor, hardware.primary_screen_scale_factor());
+ EXPECT_EQ(kScreenCount, hardware.screen_count());
+}
+
+} // namespace metrics
diff --git a/chromium/components/metrics/url_constants.cc b/chromium/components/metrics/url_constants.cc
new file mode 100644
index 00000000000..55d9e13d0e2
--- /dev/null
+++ b/chromium/components/metrics/url_constants.cc
@@ -0,0 +1,13 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/url_constants.h"
+
+namespace metrics {
+
+const char kDefaultMetricsServerUrl[] = "https://clients4.google.com/uma/v2";
+const char kDefaultMetricsMimeType[] = "application/vnd.chrome.uma";
+
+} // namespace metrics
+
diff --git a/chromium/components/metrics/url_constants.h b/chromium/components/metrics/url_constants.h
new file mode 100644
index 00000000000..b52ddef2ed9
--- /dev/null
+++ b/chromium/components/metrics/url_constants.h
@@ -0,0 +1,18 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_URL_CONSTANTS_H_
+#define COMPONENTS_METRICS_URL_CONSTANTS_H_
+
+namespace metrics {
+
+// The default metrics server's URL.
+extern const char kDefaultMetricsServerUrl[];
+
+// The default MIME type for the uploaded metrics data.
+extern const char kDefaultMetricsMimeType[];
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_URL_CONSTANTS_H_