aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2016-06-24 11:25:51 -0700
committerXin Li <delphij@google.com>2016-06-24 11:30:51 -0700
commitcaacd6905920128af1de8d7f93463eee88be1646 (patch)
tree1ad1513b968f01cb50fc929d5a972ed257d7ecb3
parent3832b79f0895909cd4c655081e6ef70c8c334d5b (diff)
parent80b69d460f7c9f7a7c7434e29035739f325c33a2 (diff)
downloadmetricsd-caacd6905920128af1de8d7f93463eee88be1646.tar.gz
Merge branch 'rewrite-metricsd' into merge-metricsd
Initial import of metricsd from platform/system/core. BUG: 29548040
l---------.clang-format1
-rw-r--r--Android.mk232
-rw-r--r--OWNERS3
-rw-r--r--README.md124
-rw-r--r--WATCHLISTS16
-rw-r--r--aidl/android/brillo/metrics/IMetricsCollectorService.aidl21
-rw-r--r--aidl/android/brillo/metrics/IMetricsd.aidl26
-rw-r--r--c_metrics_library.cc82
-rw-r--r--collectors/averaged_statistics_collector.cc217
-rw-r--r--collectors/averaged_statistics_collector.h79
-rw-r--r--collectors/averaged_statistics_collector_test.cc100
-rw-r--r--collectors/cpu_usage_collector.cc126
-rw-r--r--collectors/cpu_usage_collector.h59
-rw-r--r--collectors/cpu_usage_collector_test.cc50
-rw-r--r--collectors/disk_usage_collector.cc75
-rw-r--r--collectors/disk_usage_collector.h42
-rw-r--r--constants.h42
-rw-r--r--etc/weaved/traits/metrics.json20
-rw-r--r--include/metrics/c_metrics_library.h57
-rw-r--r--include/metrics/metrics_collector_service_client.h44
-rw-r--r--include/metrics/metrics_library.h175
-rw-r--r--include/metrics/metrics_library_mock.h41
-rw-r--r--include/metrics/timer.h170
-rw-r--r--include/metrics/timer_mock.h59
-rw-r--r--libmetrics-369476.gyp8
-rw-r--r--libmetrics.gypi33
-rw-r--r--libmetrics.pc.in7
-rw-r--r--metrics.gyp184
-rw-r--r--metrics_client.cc214
-rw-r--r--metrics_collector.cc756
-rw-r--r--metrics_collector.h284
-rw-r--r--metrics_collector.rc4
-rw-r--r--metrics_collector_main.cc98
-rw-r--r--metrics_collector_service_client.cc50
-rw-r--r--metrics_collector_service_impl.cc35
-rw-r--r--metrics_collector_service_impl.h48
-rw-r--r--metrics_collector_test.cc170
-rw-r--r--metrics_library.cc226
-rw-r--r--metrics_library_test.cc107
-rw-r--r--metricsd.rc9
-rw-r--r--metricsd_main.cc83
-rw-r--r--persistent_integer.cc96
-rw-r--r--persistent_integer.h75
-rw-r--r--persistent_integer_mock.h38
-rw-r--r--persistent_integer_test.cc65
-rw-r--r--timer.cc118
-rw-r--r--timer_test.cc464
-rw-r--r--uploader/bn_metricsd_impl.cc98
-rw-r--r--uploader/bn_metricsd_impl.h54
-rw-r--r--uploader/crash_counters.cc44
-rw-r--r--uploader/crash_counters.h45
-rw-r--r--uploader/metrics_hashes.cc51
-rw-r--r--uploader/metrics_hashes.h30
-rw-r--r--uploader/metrics_hashes_unittest.cc44
-rw-r--r--uploader/metrics_log.cc90
-rw-r--r--uploader/metrics_log.h67
-rw-r--r--uploader/metrics_log_base.cc154
-rw-r--r--uploader/metrics_log_base.h122
-rw-r--r--uploader/metrics_log_base_unittest.cc137
-rw-r--r--uploader/metricsd_service_runner.cc63
-rw-r--r--uploader/metricsd_service_runner.h49
-rw-r--r--uploader/mock/mock_system_profile_setter.h34
-rw-r--r--uploader/mock/sender_mock.cc36
-rw-r--r--uploader/mock/sender_mock.h60
-rw-r--r--uploader/proto/README37
-rw-r--r--uploader/proto/chrome_user_metrics_extension.proto72
-rw-r--r--uploader/proto/histogram_event.proto59
-rw-r--r--uploader/proto/system_profile.proto759
-rw-r--r--uploader/proto/user_action_event.proto35
-rw-r--r--uploader/sender.h30
-rw-r--r--uploader/sender_http.cc50
-rw-r--r--uploader/sender_http.h41
-rw-r--r--uploader/system_profile_cache.cc198
-rw-r--r--uploader/system_profile_cache.h87
-rw-r--r--uploader/system_profile_setter.h33
-rw-r--r--uploader/upload_service.cc256
-rw-r--r--uploader/upload_service.h183
-rw-r--r--uploader/upload_service_test.cc339
78 files changed, 8390 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 120000
index 0000000..f9066d4
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1 @@
+../../../build/tools/brillo-clang-format \ No newline at end of file
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..bb262b4
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,232 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+metrics_cpp_extension := .cc
+libmetrics_sources := \
+ c_metrics_library.cc \
+ metrics_library.cc \
+ timer.cc
+
+metrics_client_sources := \
+ metrics_client.cc
+
+metrics_collector_common := \
+ collectors/averaged_statistics_collector.cc \
+ collectors/cpu_usage_collector.cc \
+ collectors/disk_usage_collector.cc \
+ metrics_collector.cc \
+ metrics_collector_service_impl.cc \
+ persistent_integer.cc
+
+metricsd_common := \
+ persistent_integer.cc \
+ uploader/bn_metricsd_impl.cc \
+ uploader/crash_counters.cc \
+ uploader/metrics_hashes.cc \
+ uploader/metrics_log_base.cc \
+ uploader/metrics_log.cc \
+ uploader/metricsd_service_runner.cc \
+ uploader/sender_http.cc \
+ uploader/system_profile_cache.cc \
+ uploader/upload_service.cc
+
+metrics_collector_tests_sources := \
+ collectors/averaged_statistics_collector_test.cc \
+ collectors/cpu_usage_collector_test.cc \
+ metrics_collector_test.cc \
+ metrics_library_test.cc \
+ persistent_integer_test.cc \
+ timer_test.cc
+
+metricsd_tests_sources := \
+ uploader/metrics_hashes_unittest.cc \
+ uploader/metrics_log_base_unittest.cc \
+ uploader/mock/sender_mock.cc \
+ uploader/upload_service_test.cc
+
+metrics_CFLAGS := -Wall \
+ -Wno-char-subscripts \
+ -Wno-missing-field-initializers \
+ -Wno-unused-parameter \
+ -Werror \
+ -fvisibility=default
+metrics_CPPFLAGS := -Wno-non-virtual-dtor \
+ -Wno-sign-promo \
+ -Wno-strict-aliasing \
+ -fvisibility=default
+metrics_includes := external/gtest/include \
+ $(LOCAL_PATH)/include
+libmetrics_shared_libraries := libchrome libbinder libbrillo libutils
+metrics_collector_shared_libraries := $(libmetrics_shared_libraries) \
+ libbrillo-binder \
+ libbrillo-http \
+ libmetrics \
+ librootdev \
+ libweaved
+
+metrics_collector_static_libraries := libmetricscollectorservice
+
+metricsd_shared_libraries := \
+ libbinder \
+ libbrillo \
+ libbrillo-binder \
+ libbrillo-http \
+ libchrome \
+ libprotobuf-cpp-lite \
+ libupdate_engine_client \
+ libutils
+
+# Static proxy library for the metricsd binder interface.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd_binder_proxy
+LOCAL_SHARED_LIBRARIES := libbinder libutils
+LOCAL_SRC_FILES := aidl/android/brillo/metrics/IMetricsd.aidl
+include $(BUILD_STATIC_LIBRARY)
+
+# Static library for the metrics_collector binder interface.
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmetricscollectorservice
+LOCAL_CLANG := true
+LOCAL_SHARED_LIBRARIES := libbinder libbrillo-binder libchrome libutils
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SRC_FILES := \
+ aidl/android/brillo/metrics/IMetricsCollectorService.aidl \
+ metrics_collector_service_client.cc
+include $(BUILD_STATIC_LIBRARY)
+
+# Shared library for metrics.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmetrics
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries)
+LOCAL_SRC_FILES := $(libmetrics_sources)
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
+include $(BUILD_SHARED_LIBRARY)
+
+# CLI client for metrics.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_client
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries) \
+ libmetrics
+LOCAL_SRC_FILES := $(metrics_client_sources)
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
+include $(BUILD_EXECUTABLE)
+
+# Protobuf library for metricsd.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd_protos
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_EXPORT_C_INCLUDE_DIRS += \
+ $(generated_sources_dir)/proto/system/core/metricsd
+LOCAL_SRC_FILES := $(call all-proto-files-under,uploader/proto)
+include $(BUILD_STATIC_LIBRARY)
+
+# metrics_collector daemon.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_collector
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_INIT_RC := metrics_collector.rc
+LOCAL_REQUIRED_MODULES := metrics.json
+LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
+LOCAL_SRC_FILES := $(metrics_collector_common) \
+ metrics_collector_main.cc
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy \
+ $(metrics_collector_static_libraries)
+include $(BUILD_EXECUTABLE)
+
+# metricsd daemon.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_INIT_RC := metricsd.rc
+LOCAL_REQUIRED_MODULES := \
+ metrics_collector
+LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
+LOCAL_STATIC_LIBRARIES := metricsd_protos metricsd_binder_proxy
+LOCAL_SRC_FILES := $(metricsd_common) \
+ metricsd_main.cc
+include $(BUILD_EXECUTABLE)
+
+# Unit tests for metricsd.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd_tests
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare
+LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
+LOCAL_SRC_FILES := $(metricsd_tests_sources) $(metricsd_common)
+LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_protos metricsd_binder_proxy
+ifdef BRILLO
+LOCAL_MODULE_TAGS := eng
+endif
+include $(BUILD_NATIVE_TEST)
+
+# Unit tests for metrics_collector.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_collector_tests
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare
+LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
+LOCAL_SRC_FILES := $(metrics_collector_tests_sources) \
+ $(metrics_collector_common)
+LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy \
+ $(metrics_collector_static_libraries)
+ifdef BRILLO
+LOCAL_MODULE_TAGS := eng
+endif
+include $(BUILD_NATIVE_TEST)
+
+# Weave schema files
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics.json
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits
+LOCAL_SRC_FILES := etc/weaved/traits/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..7f5e50d
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+semenzato@chromium.org
+derat@chromium.org
+bsimonnet@chromium.org
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8d4828c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,124 @@
+Metricsd
+========
+
+The metricsd daemon is used to gather metrics from the platform and application,
+aggregate them and upload them periodically to a server.
+The metrics will then be available in their aggregated form to the developer
+for analysis.
+
+Three components are provided to interact with `metricsd`: `libmetrics`,
+`metrics_collector` and `metrics_client`.
+
+The Metrics Library: libmetrics
+-------------------------------
+
+`libmetrics` is a small library that implements the basic C++ API for
+metrics collection. All metrics collection is funneled through this library. The
+easiest and recommended way for a client-side module to collect user metrics is
+to link `libmetrics` and use its APIs to send metrics to `metricsd` for transport to
+UMA. In order to use the library in a module, you need to do the following:
+
+- Add a dependency on the shared library in your Android.mk file:
+ `LOCAL_SHARED_LIBRARIES += libmetrics`
+
+- To access the metrics library API in the module, include the
+ <metrics/metrics_library.h> header file.
+
+- The API is documented in `metrics_library.h`. Before using the API methods, a
+ MetricsLibrary object needs to be constructed and initialized through its
+ Init method.
+
+- Samples are uploaded only if the `/data/misc/metrics/enabled` file exists.
+
+
+Server Side
+-----------
+
+You will be able to see all uploaded metrics on the metrics dashboard,
+accessible via the developer console.
+
+*** note
+It usually takes a day for metrics to be available on the dashboard.
+***
+
+
+The Metrics Client: metrics_client
+----------------------------------
+
+`metrics_client` is a simple shell command-line utility for sending histogram
+samples and querying `metricsd`. It's installed under `/system/bin` on the target
+platform and uses `libmetrics`.
+
+For usage information and command-line options, run `metrics_client` on the
+target platform or look for "Usage:" in `metrics_client.cc`.
+
+
+The Metrics Daemon: metricsd
+----------------------------
+
+`metricsd` is the daemon that listens for metrics logging calls (via Binder),
+aggregates the metrics and uploads them periodically. This daemon should start as
+early as possible so that depending daemons can log at any time.
+
+`metricsd` is made of two threads that work as follows:
+
+* The binder thread listens for one-way Binder calls, aggregates the metrics in
+ memory (via `base::StatisticsRecorder`) and increments the crash counters when a
+ crash is reported. This thread is kept as simple as possible to ensure the
+ maximum throughput possible.
+* The uploader thread takes care of backing up the metrics to disk periodically
+ (to avoid losing metrics on crashes), collecting metadata about the client
+ (version number, channel, etc..) and uploading the metrics periodically to the
+ server.
+
+
+The Metrics Collector: metrics_collector
+----------------------------------------
+
+metrics_collector is a daemon that runs in the background on the target platform,
+gathers health information about the system and maintains long running counters
+(ex: number of crashes per week).
+
+The recommended way to generate metrics data from a module is to link and use
+libmetrics directly. However, we may not want to add a dependency on libmetrics
+to some modules (ex: kernel). In this case, we can add a collector to
+metrics_collector that will, for example, take measurements and report them
+periodically to metricsd (this is the case for the disk utilization histogram).
+
+
+FAQ
+---
+
+### What should my histogram's |min| and |max| values be set at?
+
+You should set the values to a range that covers the vast majority of samples
+that would appear in the field. Note that samples below the |min| will still
+be collected in the underflow bucket and samples above the |max| will end up
+in the overflow bucket. Also, the reported mean of the data will be correct
+regardless of the range.
+
+### How many buckets should I use in my histogram?
+
+You should allocate as many buckets as necessary to perform proper analysis
+on the collected data. Note, however, that the memory allocated in metricsd
+for each histogram is proportional to the number of buckets. Therefore, it is
+strongly recommended to keep this number low (e.g., 50 is normal, while 100
+is probably high).
+
+### When should I use an enumeration (linear) histogram vs. a regular (exponential) histogram?
+
+Enumeration histograms should really be used only for sampling enumerated
+events and, in some cases, percentages. Normally, you should use a regular
+histogram with exponential bucket layout that provides higher resolution at
+the low end of the range and lower resolution at the high end. Regular
+histograms are generally used for collecting performance data (e.g., timing,
+memory usage, power) as well as aggregated event counts.
+
+### How can I test that my histogram was reported correctly?
+
+* Make sure no error messages appear in logcat when you log a sample.
+* Run `metrics_client -d` to dump the currently aggregated metrics. Your
+ histogram should appear in the list.
+* Make sure that the aggregated metrics were uploaded to the server successfully
+ (check for an OK message from `metricsd` in logcat).
+* After a day, your histogram should be available on the dashboard.
diff --git a/WATCHLISTS b/WATCHLISTS
new file mode 100644
index 0000000..a051f35
--- /dev/null
+++ b/WATCHLISTS
@@ -0,0 +1,16 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+ 'WATCHLIST_DEFINITIONS': {
+ 'all': {
+ 'filepath': '.',
+ },
+ },
+ 'WATCHLISTS': {
+ 'all': ['petkov@chromium.org',
+ 'semenzato@chromium.org',
+ 'sosa@chromium.org']
+ },
+}
diff --git a/aidl/android/brillo/metrics/IMetricsCollectorService.aidl b/aidl/android/brillo/metrics/IMetricsCollectorService.aidl
new file mode 100644
index 0000000..49f484f
--- /dev/null
+++ b/aidl/android/brillo/metrics/IMetricsCollectorService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.brillo.metrics;
+
+interface IMetricsCollectorService {
+ oneway void notifyUserCrash();
+}
diff --git a/aidl/android/brillo/metrics/IMetricsd.aidl b/aidl/android/brillo/metrics/IMetricsd.aidl
new file mode 100644
index 0000000..aa3cb34
--- /dev/null
+++ b/aidl/android/brillo/metrics/IMetricsd.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.brillo.metrics;
+
+interface IMetricsd {
+ oneway void recordHistogram(String name, int sample, int min, int max,
+ int nbuckets);
+ oneway void recordLinearHistogram(String name, int sample, int max);
+ oneway void recordSparseHistogram(String name, int sample);
+ oneway void recordCrash(String type);
+ String getHistogramsDump();
+}
diff --git a/c_metrics_library.cc b/c_metrics_library.cc
new file mode 100644
index 0000000..47a543e
--- /dev/null
+++ b/c_metrics_library.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// C wrapper to libmetrics
+//
+
+#include "metrics/c_metrics_library.h"
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+extern "C" CMetricsLibrary CMetricsLibraryNew(void) {
+ MetricsLibrary* lib = new MetricsLibrary;
+ return reinterpret_cast<CMetricsLibrary>(lib);
+}
+
+extern "C" void CMetricsLibraryDelete(CMetricsLibrary handle) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ delete lib;
+}
+
+extern "C" void CMetricsLibraryInit(CMetricsLibrary handle) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib != NULL)
+ lib->Init();
+}
+
+extern "C" int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
+ const char* name, int sample,
+ int min, int max, int nbuckets) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendToUMA(std::string(name), sample, min, max, nbuckets);
+}
+
+extern "C" int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
+ const char* name, int sample,
+ int max) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendEnumToUMA(std::string(name), sample, max);
+}
+
+extern "C" int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
+ const char* name, int sample) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendSparseToUMA(std::string(name), sample);
+}
+
+extern "C" int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
+ const char* crash_kind) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendCrashToUMA(crash_kind);
+}
+
+extern "C" int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->AreMetricsEnabled();
+}
diff --git a/collectors/averaged_statistics_collector.cc b/collectors/averaged_statistics_collector.cc
new file mode 100644
index 0000000..a3aaa98
--- /dev/null
+++ b/collectors/averaged_statistics_collector.cc
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "averaged_statistics_collector.h"
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/files/file_path.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+
+#include "metrics_collector.h"
+
+namespace {
+
+// disk stats metrics
+
+// The {Read,Write}Sectors numbers are in sectors/second.
+// A sector is usually 512 bytes.
+const char kReadSectorsHistogramName[] = "Platform.ReadSectors";
+const char kWriteSectorsHistogramName[] = "Platform.WriteSectors";
+const int kDiskMetricsStatItemCount = 11;
+
+// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
+// sectors.
+const int kSectorsIOMax = 500000; // sectors/second
+const int kSectorsBuckets = 50; // buckets
+
+// Page size is 4k, sector size is 0.5k. We're not interested in page fault
+// rates that the disk cannot sustain.
+const int kPageFaultsMax = kSectorsIOMax / 8; // Page faults/second
+const int kPageFaultsBuckets = 50;
+
+// Major page faults, i.e. the ones that require data to be read from disk.
+const char kPageFaultsHistogramName[] = "Platform.PageFaults";
+
+// Swap in and Swap out
+const char kSwapInHistogramName[] = "Platform.SwapIn";
+const char kSwapOutHistogramName[] = "Platform.SwapOut";
+
+const int kIntervalBetweenCollection = 60; // seconds
+const int kCollectionDuration = 1; // seconds
+
+} // namespace
+
+AveragedStatisticsCollector::AveragedStatisticsCollector(
+ MetricsLibraryInterface* metrics_library,
+ const std::string& diskstats_path,
+ const std::string& vmstats_path) :
+ metrics_lib_(metrics_library),
+ diskstats_path_(diskstats_path),
+ vmstats_path_(vmstats_path) {
+}
+
+void AveragedStatisticsCollector::ScheduleWait() {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&AveragedStatisticsCollector::WaitCallback,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(
+ kIntervalBetweenCollection - kCollectionDuration));
+}
+
+void AveragedStatisticsCollector::ScheduleCollect() {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&AveragedStatisticsCollector::CollectCallback,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(kCollectionDuration));
+}
+
+void AveragedStatisticsCollector::WaitCallback() {
+ ReadInitialValues();
+ ScheduleCollect();
+}
+
+void AveragedStatisticsCollector::CollectCallback() {
+ Collect();
+ ScheduleWait();
+}
+
+void AveragedStatisticsCollector::ReadInitialValues() {
+ stats_start_time_ = MetricsCollector::GetActiveTime();
+ DiskStatsReadStats(&read_sectors_, &write_sectors_);
+ VmStatsReadStats(&vmstats_);
+}
+
+bool AveragedStatisticsCollector::DiskStatsReadStats(
+ uint64_t* read_sectors, uint64_t* write_sectors) {
+ CHECK(read_sectors);
+ CHECK(write_sectors);
+ std::string line;
+ if (diskstats_path_.empty()) {
+ return false;
+ }
+
+ if (!base::ReadFileToString(base::FilePath(diskstats_path_), &line)) {
+ PLOG(WARNING) << "Could not read disk stats from "
+ << diskstats_path_.value();
+ return false;
+ }
+
+ std::vector<std::string> parts = base::SplitString(
+ line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+ if (parts.size() != kDiskMetricsStatItemCount) {
+ LOG(ERROR) << "Could not parse disk stat correctly. Expected "
+ << kDiskMetricsStatItemCount << " elements but got "
+ << parts.size();
+ return false;
+ }
+ if (!base::StringToUint64(parts[2], read_sectors)) {
+ LOG(ERROR) << "Couldn't convert read sectors " << parts[2] << " to uint64";
+ return false;
+ }
+ if (!base::StringToUint64(parts[6], write_sectors)) {
+ LOG(ERROR) << "Couldn't convert write sectors " << parts[6] << " to uint64";
+ return false;
+ }
+
+ return true;
+}
+
+bool AveragedStatisticsCollector::VmStatsParseStats(
+ const char* stats, struct VmstatRecord* record) {
+ CHECK(stats);
+ CHECK(record);
+ base::StringPairs pairs;
+ base::SplitStringIntoKeyValuePairs(stats, ' ', '\n', &pairs);
+
+ for (base::StringPairs::iterator it = pairs.begin();
+ it != pairs.end(); ++it) {
+ if (it->first == "pgmajfault" &&
+ !base::StringToUint64(it->second, &record->page_faults)) {
+ return false;
+ }
+ if (it->first == "pswpin" &&
+ !base::StringToUint64(it->second, &record->swap_in)) {
+ return false;
+ }
+ if (it->first == "pswpout" &&
+ !base::StringToUint64(it->second, &record->swap_out)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool AveragedStatisticsCollector::VmStatsReadStats(struct VmstatRecord* stats) {
+ CHECK(stats);
+ std::string value_string;
+ if (!base::ReadFileToString(vmstats_path_, &value_string)) {
+ LOG(WARNING) << "cannot read " << vmstats_path_.value();
+ return false;
+ }
+ return VmStatsParseStats(value_string.c_str(), stats);
+}
+
+void AveragedStatisticsCollector::Collect() {
+ uint64_t read_sectors_now, write_sectors_now;
+ struct VmstatRecord vmstats_now;
+ double time_now = MetricsCollector::GetActiveTime();
+ double delta_time = time_now - stats_start_time_;
+ bool diskstats_success = DiskStatsReadStats(&read_sectors_now,
+ &write_sectors_now);
+
+ int delta_read = read_sectors_now - read_sectors_;
+ int delta_write = write_sectors_now - write_sectors_;
+ int read_sectors_per_second = delta_read / delta_time;
+ int write_sectors_per_second = delta_write / delta_time;
+ bool vmstats_success = VmStatsReadStats(&vmstats_now);
+ uint64_t delta_faults = vmstats_now.page_faults - vmstats_.page_faults;
+ uint64_t delta_swap_in = vmstats_now.swap_in - vmstats_.swap_in;
+ uint64_t delta_swap_out = vmstats_now.swap_out - vmstats_.swap_out;
+ uint64_t page_faults_per_second = delta_faults / delta_time;
+ uint64_t swap_in_per_second = delta_swap_in / delta_time;
+ uint64_t swap_out_per_second = delta_swap_out / delta_time;
+ if (diskstats_success) {
+ metrics_lib_->SendToUMA(kReadSectorsHistogramName,
+ read_sectors_per_second,
+ 1,
+ kSectorsIOMax,
+ kSectorsBuckets);
+ metrics_lib_->SendToUMA(kWriteSectorsHistogramName,
+ write_sectors_per_second,
+ 1,
+ kSectorsIOMax,
+ kSectorsBuckets);
+ }
+ if (vmstats_success) {
+ metrics_lib_->SendToUMA(kPageFaultsHistogramName,
+ page_faults_per_second,
+ 1,
+ kPageFaultsMax,
+ kPageFaultsBuckets);
+ metrics_lib_->SendToUMA(kSwapInHistogramName,
+ swap_in_per_second,
+ 1,
+ kPageFaultsMax,
+ kPageFaultsBuckets);
+ metrics_lib_->SendToUMA(kSwapOutHistogramName,
+ swap_out_per_second,
+ 1,
+ kPageFaultsMax,
+ kPageFaultsBuckets);
+ }
+}
diff --git a/collectors/averaged_statistics_collector.h b/collectors/averaged_statistics_collector.h
new file mode 100644
index 0000000..753f70c
--- /dev/null
+++ b/collectors/averaged_statistics_collector.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
+#define METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
+
+#include "metrics/metrics_library.h"
+
+class AveragedStatisticsCollector {
+ public:
+ AveragedStatisticsCollector(MetricsLibraryInterface* metrics_library,
+ const std::string& diskstats_path,
+ const std::string& vmstat_path);
+
+ // Schedule a wait period.
+ void ScheduleWait();
+
+ // Schedule a collection period.
+ void ScheduleCollect();
+
+ // Callback used by the main loop.
+ void CollectCallback();
+
+ // Callback used by the main loop.
+ void WaitCallback();
+
+ // Read and store the initial values at the beginning of a collection cycle.
+ void ReadInitialValues();
+
+ // Collect the disk usage statistics and report them.
+ void Collect();
+
+ private:
+ friend class AveragedStatisticsTest;
+ FRIEND_TEST(AveragedStatisticsTest, ParseDiskStats);
+ FRIEND_TEST(AveragedStatisticsTest, ParseVmStats);
+
+ // Record for retrieving and reporting values from /proc/vmstat
+ struct VmstatRecord {
+ uint64_t page_faults; // major faults
+ uint64_t swap_in; // pages swapped in
+ uint64_t swap_out; // pages swapped out
+ };
+
+ // Read the disk read/write statistics for the main disk.
+ bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors);
+
+ // Parse the content of the vmstats file into |record|.
+ bool VmStatsParseStats(const char* stats, struct VmstatRecord* record);
+
+ // Read the vmstats into |stats|.
+ bool VmStatsReadStats(struct VmstatRecord* stats);
+
+ MetricsLibraryInterface* metrics_lib_;
+ base::FilePath diskstats_path_;
+ base::FilePath vmstats_path_;
+
+ // Values observed at the beginning of the collection period.
+ uint64_t read_sectors_;
+ uint64_t write_sectors_;
+ struct VmstatRecord vmstats_;
+
+ double stats_start_time_;
+};
+
+#endif // METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
diff --git a/collectors/averaged_statistics_collector_test.cc b/collectors/averaged_statistics_collector_test.cc
new file mode 100644
index 0000000..68f9f2f
--- /dev/null
+++ b/collectors/averaged_statistics_collector_test.cc
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "averaged_statistics_collector.h"
+
+#include <memory>
+
+#include <inttypes.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+
+static const char kFakeDiskStatsFormat[] =
+ " 1793 1788 %" PRIu64 " 105580 "
+ " 196 175 %" PRIu64 " 30290 "
+ " 0 44060 135850\n";
+static const uint64_t kFakeReadSectors[] = {80000, 100000};
+static const uint64_t kFakeWriteSectors[] = {3000, 4000};
+
+
+class AveragedStatisticsTest : public testing::Test {
+ protected:
+ std::string kFakeDiskStats0;
+ std::string kFakeDiskStats1;
+
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ disk_stats_path_ = temp_dir_.path().Append("disk_stats");
+ collector_.reset(new AveragedStatisticsCollector(
+ &metrics_lib_, disk_stats_path_.value(), ""));
+
+ kFakeDiskStats0 = base::StringPrintf(kFakeDiskStatsFormat,
+ kFakeReadSectors[0],
+ kFakeWriteSectors[0]);
+ kFakeDiskStats1 = base::StringPrintf(kFakeDiskStatsFormat,
+ kFakeReadSectors[1],
+ kFakeWriteSectors[1]);
+
+ CreateFakeDiskStatsFile(kFakeDiskStats0);
+ }
+
+ // Creates or overwrites an input file containing fake disk stats.
+ void CreateFakeDiskStatsFile(const std::string& fake_stats) {
+ EXPECT_EQ(base::WriteFile(disk_stats_path_,
+ fake_stats.data(), fake_stats.size()),
+ fake_stats.size());
+ }
+
+ // Collector used for tests.
+ std::unique_ptr<AveragedStatisticsCollector> collector_;
+
+ // Temporary directory used for tests.
+ base::ScopedTempDir temp_dir_;
+
+ // Path for the fake files.
+ base::FilePath disk_stats_path_;
+
+ MetricsLibrary metrics_lib_;
+};
+
+TEST_F(AveragedStatisticsTest, ParseDiskStats) {
+ uint64_t read_sectors_now, write_sectors_now;
+ CreateFakeDiskStatsFile(kFakeDiskStats0);
+ ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now,
+ &write_sectors_now));
+ EXPECT_EQ(read_sectors_now, kFakeReadSectors[0]);
+ EXPECT_EQ(write_sectors_now, kFakeWriteSectors[0]);
+
+ CreateFakeDiskStatsFile(kFakeDiskStats1);
+ ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now,
+ &write_sectors_now));
+ EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]);
+ EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]);
+}
+
+TEST_F(AveragedStatisticsTest, ParseVmStats) {
+ static char kVmStats[] = "pswpin 1345\npswpout 8896\n"
+ "foo 100\nbar 200\npgmajfault 42\netcetc 300\n";
+ struct AveragedStatisticsCollector::VmstatRecord stats;
+ EXPECT_TRUE(collector_->VmStatsParseStats(kVmStats, &stats));
+ EXPECT_EQ(stats.page_faults, 42);
+ EXPECT_EQ(stats.swap_in, 1345);
+ EXPECT_EQ(stats.swap_out, 8896);
+}
diff --git a/collectors/cpu_usage_collector.cc b/collectors/cpu_usage_collector.cc
new file mode 100644
index 0000000..9b0bb34
--- /dev/null
+++ b/collectors/cpu_usage_collector.cc
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "collectors/cpu_usage_collector.h"
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/sys_info.h>
+
+#include "metrics/metrics_library.h"
+
+namespace {
+
+const char kCpuUsagePercent[] = "Platform.CpuUsage.Percent";
+const char kMetricsProcStatFileName[] = "/proc/stat";
+const int kMetricsProcStatFirstLineItemsCount = 11;
+
+// Collect every minute.
+const int kCollectionIntervalSecs = 60;
+
+} // namespace
+
+using base::TimeDelta;
+
+CpuUsageCollector::CpuUsageCollector(MetricsLibraryInterface* metrics_library) {
+ CHECK(metrics_library);
+ metrics_lib_ = metrics_library;
+ collect_interval_ = TimeDelta::FromSeconds(kCollectionIntervalSecs);
+}
+
+void CpuUsageCollector::Init() {
+ num_cpu_ = base::SysInfo::NumberOfProcessors();
+
+ // Get ticks per second (HZ) on this system.
+ // Sysconf cannot fail, so no sanity checks are needed.
+ ticks_per_second_ = sysconf(_SC_CLK_TCK);
+ CHECK_GT(ticks_per_second_, uint64_t(0))
+ << "Number of ticks per seconds should be positive.";
+
+ latest_cpu_use_ = GetCumulativeCpuUse();
+}
+
+void CpuUsageCollector::CollectCallback() {
+ Collect();
+ Schedule();
+}
+
+void CpuUsageCollector::Schedule() {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&CpuUsageCollector::CollectCallback, base::Unretained(this)),
+ collect_interval_);
+}
+
+void CpuUsageCollector::Collect() {
+ TimeDelta cpu_use = GetCumulativeCpuUse();
+ TimeDelta diff_per_cpu = (cpu_use - latest_cpu_use_) / num_cpu_;
+ latest_cpu_use_ = cpu_use;
+
+ // Report the cpu usage as a percentage of the total cpu usage possible.
+ int percent_use = diff_per_cpu.InMilliseconds() * 100 /
+ (kCollectionIntervalSecs * 1000);
+
+ metrics_lib_->SendEnumToUMA(kCpuUsagePercent, percent_use, 101);
+}
+
+TimeDelta CpuUsageCollector::GetCumulativeCpuUse() {
+ base::FilePath proc_stat_path(kMetricsProcStatFileName);
+ std::string proc_stat_string;
+ if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
+ LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
+ return TimeDelta();
+ }
+
+ uint64_t user_ticks, user_nice_ticks, system_ticks;
+ if (!ParseProcStat(proc_stat_string, &user_ticks, &user_nice_ticks,
+ &system_ticks)) {
+ return TimeDelta();
+ }
+
+ uint64_t total = user_ticks + user_nice_ticks + system_ticks;
+ return TimeDelta::FromMicroseconds(
+ total * 1000 * 1000 / ticks_per_second_);
+}
+
+bool CpuUsageCollector::ParseProcStat(const std::string& stat_content,
+ uint64_t *user_ticks,
+ uint64_t *user_nice_ticks,
+ uint64_t *system_ticks) {
+ std::vector<std::string> proc_stat_lines = base::SplitString(
+ stat_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ if (proc_stat_lines.empty()) {
+ LOG(WARNING) << "No lines found in " << kMetricsProcStatFileName;
+ return false;
+ }
+ std::vector<std::string> proc_stat_totals =
+ base::SplitString(proc_stat_lines[0], base::kWhitespaceASCII,
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+ if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount ||
+ proc_stat_totals[0] != "cpu" ||
+ !base::StringToUint64(proc_stat_totals[1], user_ticks) ||
+ !base::StringToUint64(proc_stat_totals[2], user_nice_ticks) ||
+ !base::StringToUint64(proc_stat_totals[3], system_ticks)) {
+ LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0];
+ return false;
+ }
+ return true;
+}
diff --git a/collectors/cpu_usage_collector.h b/collectors/cpu_usage_collector.h
new file mode 100644
index 0000000..f81dfcb
--- /dev/null
+++ b/collectors/cpu_usage_collector.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
+#define METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
+
+#include <base/time/time.h>
+
+#include "metrics/metrics_library.h"
+
+class CpuUsageCollector {
+ public:
+ CpuUsageCollector(MetricsLibraryInterface* metrics_library);
+
+ // Initialize this collector's state.
+ void Init();
+
+ // Schedule a collection interval.
+ void Schedule();
+
+ // Callback called at the end of the collection interval.
+ void CollectCallback();
+
+ // Measure the cpu use and report it.
+ void Collect();
+
+ // Gets the current cumulated Cpu usage.
+ base::TimeDelta GetCumulativeCpuUse();
+
+ private:
+ FRIEND_TEST(CpuUsageTest, ParseProcStat);
+ bool ParseProcStat(const std::string& stat_content,
+ uint64_t *user_ticks,
+ uint64_t *user_nice_ticks,
+ uint64_t *system_ticks);
+
+ int num_cpu_;
+ uint32_t ticks_per_second_;
+
+ base::TimeDelta collect_interval_;
+ base::TimeDelta latest_cpu_use_;
+
+ MetricsLibraryInterface* metrics_lib_;
+};
+
+#endif // METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
diff --git a/collectors/cpu_usage_collector_test.cc b/collectors/cpu_usage_collector_test.cc
new file mode 100644
index 0000000..ee5c92b
--- /dev/null
+++ b/collectors/cpu_usage_collector_test.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "collectors/cpu_usage_collector.h"
+#include "metrics/metrics_library_mock.h"
+
+
+TEST(CpuUsageTest, ParseProcStat) {
+ MetricsLibraryMock metrics_lib_mock;
+ CpuUsageCollector collector(&metrics_lib_mock);
+ std::vector<std::string> invalid_contents = {
+ "",
+ // First line does not start with cpu.
+ "spu 17191 11 36579 151118 289 0 2 0 0 0\n"
+ "cpu0 1564 2 866 48650 68 0 2 0 0 0\n"
+ "cpu1 14299 0 35116 1844 81 0 0 0 0 0\n",
+ // One of the field is not a number.
+ "cpu a17191 11 36579 151118 289 0 2 0 0 0",
+ // To many numbers in the first line.
+ "cpu 17191 11 36579 151118 289 0 2 0 0 0 102"
+ };
+
+ uint64_t user, nice, system;
+ for (int i = 0; i < invalid_contents.size(); i++) {
+ ASSERT_FALSE(collector.ParseProcStat(invalid_contents[i], &user, &nice,
+ &system));
+ }
+
+ ASSERT_TRUE(collector.ParseProcStat(
+ std::string("cpu 17191 11 36579 151118 289 0 2 0 0 0"),
+ &user, &nice, &system));
+ ASSERT_EQ(17191, user);
+ ASSERT_EQ(11, nice);
+ ASSERT_EQ(36579, system);
+}
diff --git a/collectors/disk_usage_collector.cc b/collectors/disk_usage_collector.cc
new file mode 100644
index 0000000..5ab51fb
--- /dev/null
+++ b/collectors/disk_usage_collector.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "collectors/disk_usage_collector.h"
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/message_loop/message_loop.h>
+#include <sys/statvfs.h>
+
+#include "metrics/metrics_library.h"
+
+namespace {
+
+const char kDiskUsageMB[] = "Platform.DataPartitionUsed.MB";
+const char kDiskUsagePercent[] = "Platform.DataPartitionUsed.Percent";
+const char kDataPartitionPath[] = "/data";
+
+// Collect every 15 minutes.
+const int kDiskUsageCollectorIntervalSeconds = 900;
+
+} // namespace
+
+DiskUsageCollector::DiskUsageCollector(
+ MetricsLibraryInterface* metrics_library) {
+ collect_interval_ = base::TimeDelta::FromSeconds(
+ kDiskUsageCollectorIntervalSeconds);
+ CHECK(metrics_library);
+ metrics_lib_ = metrics_library;
+}
+
+void DiskUsageCollector::Collect() {
+ struct statvfs buf;
+ int result = statvfs(kDataPartitionPath, &buf);
+ if (result != 0) {
+ PLOG(ERROR) << "Failed to check the available space in "
+ << kDataPartitionPath;
+ return;
+ }
+
+ unsigned long total_space = buf.f_blocks * buf.f_bsize;
+ unsigned long used_space = (buf.f_blocks - buf.f_bfree) * buf.f_bsize;
+ int percent_used = (used_space * 100) / total_space;
+
+ metrics_lib_->SendToUMA(kDiskUsageMB,
+ used_space / (1024 * 1024),
+ 0,
+ 1024, // up to 1 GB.
+ 100);
+ metrics_lib_->SendEnumToUMA(kDiskUsagePercent, percent_used, 101);
+}
+
+void DiskUsageCollector::CollectCallback() {
+ Collect();
+ Schedule();
+}
+
+void DiskUsageCollector::Schedule() {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&DiskUsageCollector::CollectCallback, base::Unretained(this)),
+ collect_interval_);
+}
diff --git a/collectors/disk_usage_collector.h b/collectors/disk_usage_collector.h
new file mode 100644
index 0000000..c1d4546
--- /dev/null
+++ b/collectors/disk_usage_collector.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
+#define METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
+
+#include <base/time/time.h>
+
+#include "metrics/metrics_library.h"
+
+class DiskUsageCollector {
+ public:
+ DiskUsageCollector(MetricsLibraryInterface* metrics_library);
+
+ // Schedule the next collection.
+ void Schedule();
+
+ // Callback used by the main loop.
+ void CollectCallback();
+
+ // Collect the disk usage statistics and report them.
+ void Collect();
+
+ private:
+ base::TimeDelta collect_interval_;
+ MetricsLibraryInterface* metrics_lib_;
+};
+
+#endif // METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
diff --git a/constants.h b/constants.h
new file mode 100644
index 0000000..b702737
--- /dev/null
+++ b/constants.h
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef METRICS_CONSTANTS_H_
+#define METRICS_CONSTANTS_H_
+
+namespace metrics {
+static const char kSharedMetricsDirectory[] = "/data/misc/metrics/";
+static const char kMetricsdDirectory[] = "/data/misc/metricsd/";
+static const char kMetricsCollectorDirectory[] =
+ "/data/misc/metrics_collector/";
+static const char kMetricsGUIDFileName[] = "Sysinfo.GUID";
+static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
+static const char kConsentFileName[] = "enabled";
+static const char kStagedLogName[] = "staged_log";
+static const char kSavedLogName[] = "saved_log";
+static const char kFailedUploadCountName[] = "failed_upload_count";
+static const char kDefaultVersion[] = "0.0.0.0";
+
+// Build time properties name.
+static const char kProductId[] = "product_id";
+static const char kProductVersion[] = "product_version";
+
+// Weave configuration.
+static const char kWeaveConfigurationFile[] = "/system/etc/weaved/weaved.conf";
+static const char kModelManifestId[] = "model_id";
+} // namespace metrics
+
+#endif // METRICS_CONSTANTS_H_
diff --git a/etc/weaved/traits/metrics.json b/etc/weaved/traits/metrics.json
new file mode 100644
index 0000000..7583270
--- /dev/null
+++ b/etc/weaved/traits/metrics.json
@@ -0,0 +1,20 @@
+{
+ "_metrics": {
+ "commands": {
+ "enableAnalyticsReporting": {
+ "minimalRole": "manager",
+ "parameters": {}
+ },
+ "disableAnalyticsReporting": {
+ "minimalRole": "manager",
+ "parameters": {}
+ }
+ },
+ "state": {
+ "analyticsReportingState": {
+ "type": "string",
+ "enum": [ "enabled", "disabled" ]
+ }
+ }
+ }
+}
diff --git a/include/metrics/c_metrics_library.h b/include/metrics/c_metrics_library.h
new file mode 100644
index 0000000..1e597c2
--- /dev/null
+++ b/include/metrics/c_metrics_library.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_C_METRICS_LIBRARY_H_
+#define METRICS_C_METRICS_LIBRARY_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+typedef struct CMetricsLibraryOpaque* CMetricsLibrary;
+
+// C wrapper for MetricsLibrary::MetricsLibrary.
+CMetricsLibrary CMetricsLibraryNew(void);
+
+// C wrapper for MetricsLibrary::~MetricsLibrary.
+void CMetricsLibraryDelete(CMetricsLibrary handle);
+
+// C wrapper for MetricsLibrary::Init.
+void CMetricsLibraryInit(CMetricsLibrary handle);
+
+// C wrapper for MetricsLibrary::SendToUMA.
+int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
+ const char* name, int sample,
+ int min, int max, int nbuckets);
+
+// C wrapper for MetricsLibrary::SendEnumToUMA.
+int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
+ const char* name, int sample, int max);
+
+// C wrapper for MetricsLibrary::SendSparseToUMA.
+int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
+ const char* name, int sample);
+
+// C wrapper for MetricsLibrary::SendCrashToUMA.
+int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
+ const char* crash_kind);
+
+// C wrapper for MetricsLibrary::AreMetricsEnabled.
+int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle);
+
+#if defined(__cplusplus)
+}
+#endif
+#endif // METRICS_C_METRICS_LIBRARY_H_
diff --git a/include/metrics/metrics_collector_service_client.h b/include/metrics/metrics_collector_service_client.h
new file mode 100644
index 0000000..c800eae
--- /dev/null
+++ b/include/metrics/metrics_collector_service_client.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Client interface to IMetricsCollectorService.
+
+#ifndef METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
+#define METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
+
+#include "android/brillo/metrics/IMetricsCollectorService.h"
+
+class MetricsCollectorServiceClient {
+ public:
+ MetricsCollectorServiceClient() = default;
+ ~MetricsCollectorServiceClient() = default;
+
+ // Initialize. Returns true if OK, or false if IMetricsCollectorService
+ // is not registered.
+ bool Init();
+
+ // Called by crash_reporter to report a userspace crash event. Returns
+ // true if successfully called the IMetricsCollectorService method of the
+ // same name, or false if the service was not registered at Init() time.
+ bool notifyUserCrash();
+
+ private:
+ // IMetricsCollectorService binder proxy
+ android::sp<android::brillo::metrics::IMetricsCollectorService>
+ metrics_collector_service_;
+};
+
+#endif // METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
diff --git a/include/metrics/metrics_library.h b/include/metrics/metrics_library.h
new file mode 100644
index 0000000..a1bb926
--- /dev/null
+++ b/include/metrics/metrics_library.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_METRICS_LIBRARY_H_
+#define METRICS_METRICS_LIBRARY_H_
+
+#include <sys/types.h>
+#include <string>
+#include <unistd.h>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <binder/IServiceManager.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+namespace android {
+namespace brillo {
+namespace metrics {
+class IMetricsd;
+} // namespace metrics
+} // namespace brillo
+} // namespace android
+
+class MetricsLibraryInterface {
+ public:
+ virtual void Init() = 0;
+ virtual bool AreMetricsEnabled() = 0;
+ virtual bool SendToUMA(const std::string& name, int sample,
+ int min, int max, int nbuckets) = 0;
+ virtual bool SendEnumToUMA(const std::string& name, int sample, int max) = 0;
+ virtual bool SendBoolToUMA(const std::string& name, bool sample) = 0;
+ virtual bool SendSparseToUMA(const std::string& name, int sample) = 0;
+ virtual ~MetricsLibraryInterface() {}
+};
+
+// Library used to send metrics to Chrome/UMA.
+class MetricsLibrary : public MetricsLibraryInterface {
+ public:
+ MetricsLibrary();
+ virtual ~MetricsLibrary();
+
+ // Initializes the library.
+ void Init() override;
+
+ // Initializes the library and disables the cache of whether or not the
+ // metrics collection is enabled.
+ // By disabling this, we may have to check for the metrics status more often
+ // but the result will never be stale.
+ void InitWithNoCaching();
+
+ // Returns whether or not the machine is running in guest mode.
+ bool IsGuestMode();
+
+ // Returns whether or not metrics collection is enabled.
+ bool AreMetricsEnabled() override;
+
+ // Sends histogram data to Chrome for transport to UMA and returns
+ // true on success. This method results in the equivalent of an
+ // asynchronous non-blocking RPC to UMA_HISTOGRAM_CUSTOM_COUNTS
+ // inside Chrome (see base/histogram.h).
+ //
+ // |sample| is the sample value to be recorded (|min| <= |sample| < |max|).
+ // |min| is the minimum value of the histogram samples (|min| > 0).
+ // |max| is the maximum value of the histogram samples.
+ // |nbuckets| is the number of histogram buckets.
+ // [0,min) is the implicit underflow bucket.
+ // [|max|,infinity) is the implicit overflow bucket.
+ //
+ // Note that the memory allocated in Chrome for each histogram is
+ // proportional to the number of buckets. Therefore, it is strongly
+ // recommended to keep this number low (e.g., 50 is normal, while
+ // 100 is high).
+ bool SendToUMA(const std::string& name, int sample,
+ int min, int max, int nbuckets) override;
+
+ // Sends linear histogram data to Chrome for transport to UMA and
+ // returns true on success. This method results in the equivalent of
+ // an asynchronous non-blocking RPC to UMA_HISTOGRAM_ENUMERATION
+ // inside Chrome (see base/histogram.h).
+ //
+ // |sample| is the sample value to be recorded (1 <= |sample| < |max|).
+ // |max| is the maximum value of the histogram samples.
+ // 0 is the implicit underflow bucket.
+ // [|max|,infinity) is the implicit overflow bucket.
+ //
+ // An enumeration histogram requires |max| + 1 number of
+ // buckets. Note that the memory allocated in Chrome for each
+ // histogram is proportional to the number of buckets. Therefore, it
+ // is strongly recommended to keep this number low (e.g., 50 is
+ // normal, while 100 is high).
+ bool SendEnumToUMA(const std::string& name, int sample, int max) override;
+
+ // Specialization of SendEnumToUMA for boolean values.
+ bool SendBoolToUMA(const std::string& name, bool sample) override;
+
+ // Sends sparse histogram sample to Chrome for transport to UMA. Returns
+ // true on success.
+ //
+ // |sample| is the 32-bit integer value to be recorded.
+ bool SendSparseToUMA(const std::string& name, int sample) override;
+
+ // Sends a signal to UMA that a crash of the given |crash_kind|
+ // has occurred. Used by UMA to generate stability statistics.
+ bool SendCrashToUMA(const char *crash_kind);
+
+ // Sends a "generic Chrome OS event" to UMA. This is an event name
+ // that is translated into an enumerated histogram entry. Event names
+ // are added to metrics_library.cc. Optionally, they can be added
+ // to histograms.xml---but part of the reason for this is to simplify
+ // the addition of events (at the cost of having to look them up by
+ // number in the histograms dashboard).
+ bool SendCrosEventToUMA(const std::string& event);
+
+ // Debugging only.
+ // Dumps the histograms aggregated since metricsd started into |dump|.
+ // Returns true iff the dump succeeds.
+ bool GetHistogramsDump(std::string* dump);
+
+ private:
+ friend class CMetricsLibraryTest;
+ friend class MetricsLibraryTest;
+ friend class UploadServiceTest;
+ FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled);
+ FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabledNoCaching);
+ FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
+ FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
+ FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted);
+ FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
+ FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
+
+ void InitForTest(const base::FilePath& metrics_directory);
+
+ // Sets |*result| to whether or not the |mounts_file| indicates that
+ // the |device_name| is currently mounted. Uses |buffer| of
+ // |buffer_size| to read the file. Returns false if any error.
+ bool IsDeviceMounted(const char* device_name,
+ const char* mounts_file,
+ char* buffer, int buffer_size,
+ bool* result);
+
+ // Connects to IMetricsd if the proxy does not exist or is not alive.
+ // Don't block if we fail to get the proxy for any reason.
+ bool CheckService();
+
+ // Time at which we last checked if metrics were enabled.
+ time_t cached_enabled_time_;
+
+ // Cached state of whether or not metrics were enabled.
+ bool cached_enabled_;
+
+ // True iff we should cache the enabled/disabled status.
+ bool use_caching_;
+
+ android::sp<android::IServiceManager> manager_;
+ android::sp<android::brillo::metrics::IMetricsd> metricsd_proxy_;
+ base::FilePath consent_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
+};
+
+#endif // METRICS_METRICS_LIBRARY_H_
diff --git a/include/metrics/metrics_library_mock.h b/include/metrics/metrics_library_mock.h
new file mode 100644
index 0000000..3b0b24d
--- /dev/null
+++ b/include/metrics/metrics_library_mock.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_METRICS_LIBRARY_MOCK_H_
+#define METRICS_METRICS_LIBRARY_MOCK_H_
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+#include <gmock/gmock.h>
+
+class MetricsLibraryMock : public MetricsLibraryInterface {
+ public:
+ bool metrics_enabled_ = true;
+
+ MOCK_METHOD0(Init, void());
+ MOCK_METHOD5(SendToUMA, bool(const std::string& name, int sample,
+ int min, int max, int nbuckets));
+ MOCK_METHOD3(SendEnumToUMA, bool(const std::string& name, int sample,
+ int max));
+ MOCK_METHOD2(SendBoolToUMA, bool(const std::string& name, bool sample));
+ MOCK_METHOD2(SendSparseToUMA, bool(const std::string& name, int sample));
+
+ bool AreMetricsEnabled() override {return metrics_enabled_;};
+};
+
+#endif // METRICS_METRICS_LIBRARY_MOCK_H_
diff --git a/include/metrics/timer.h b/include/metrics/timer.h
new file mode 100644
index 0000000..c1b8ede
--- /dev/null
+++ b/include/metrics/timer.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Timer - class that provides timer tracking.
+
+#ifndef METRICS_TIMER_H_
+#define METRICS_TIMER_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+class MetricsLibraryInterface;
+
+namespace chromeos_metrics {
+
+class TimerInterface {
+ public:
+ virtual ~TimerInterface() {}
+
+ virtual bool Start() = 0;
+ virtual bool Stop() = 0;
+ virtual bool Reset() = 0;
+ virtual bool HasStarted() const = 0;
+};
+
+// Wrapper for calls to the system clock.
+class ClockWrapper {
+ public:
+ ClockWrapper() {}
+ virtual ~ClockWrapper() {}
+
+ // Returns the current time from the system.
+ virtual base::TimeTicks GetCurrentTime() const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ClockWrapper);
+};
+
+// Implements a Timer.
+class Timer : public TimerInterface {
+ public:
+ Timer();
+ virtual ~Timer() {}
+
+ // Starts the timer. If a timer is already running, also resets current
+ // timer. Always returns true.
+ virtual bool Start();
+
+ // Stops the timer and calculates the total time elapsed between now and when
+ // Start() was called. Note that this method needs a prior call to Start().
+ // Otherwise, it fails (returns false).
+ virtual bool Stop();
+
+ // Pauses a timer. If the timer is stopped, this call starts the timer in
+ // the paused state. Fails (returns false) if the timer is already paused.
+ virtual bool Pause();
+
+ // Restarts a paused timer (or starts a stopped timer). This method fails
+ // (returns false) if the timer is already running; otherwise, returns true.
+ virtual bool Resume();
+
+ // Resets the timer, erasing the current duration being tracked. Always
+ // returns true.
+ virtual bool Reset();
+
+ // Returns whether the timer has started or not.
+ virtual bool HasStarted() const;
+
+ // Stores the current elapsed time in |elapsed_time|. If timer is stopped,
+ // stores the elapsed time from when Stop() was last called. Otherwise,
+ // calculates and stores the elapsed time since the last Start().
+ // Returns false if the timer was never Start()'ed or if called with a null
+ // pointer argument.
+ virtual bool GetElapsedTime(base::TimeDelta* elapsed_time) const;
+
+ private:
+ enum TimerState { kTimerStopped, kTimerRunning, kTimerPaused };
+ friend class TimerTest;
+ friend class TimerReporterTest;
+ FRIEND_TEST(TimerReporterTest, StartStopReport);
+ FRIEND_TEST(TimerTest, InvalidElapsedTime);
+ FRIEND_TEST(TimerTest, InvalidStop);
+ FRIEND_TEST(TimerTest, PauseResumeStop);
+ FRIEND_TEST(TimerTest, PauseStartStopResume);
+ FRIEND_TEST(TimerTest, PauseStop);
+ FRIEND_TEST(TimerTest, Reset);
+ FRIEND_TEST(TimerTest, ReStart);
+ FRIEND_TEST(TimerTest, ResumeStartStopPause);
+ FRIEND_TEST(TimerTest, SeparatedTimers);
+ FRIEND_TEST(TimerTest, StartPauseResumePauseResumeStop);
+ FRIEND_TEST(TimerTest, StartPauseResumePauseStop);
+ FRIEND_TEST(TimerTest, StartPauseResumeStop);
+ FRIEND_TEST(TimerTest, StartPauseStop);
+ FRIEND_TEST(TimerTest, StartResumeStop);
+ FRIEND_TEST(TimerTest, StartStop);
+
+ // Elapsed time of the last use of the timer.
+ base::TimeDelta elapsed_time_;
+
+ // Starting time value.
+ base::TimeTicks start_time_;
+
+ // Whether the timer is running, stopped, or paused.
+ TimerState timer_state_;
+
+ // Wrapper for the calls to the system clock.
+ std::unique_ptr<ClockWrapper> clock_wrapper_;
+
+ DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+// Extends the Timer class to report the elapsed time in milliseconds through
+// the UMA metrics library.
+class TimerReporter : public Timer {
+ public:
+ // Initializes the timer by providing a |histogram_name| to report to with
+ // |min|, |max| and |num_buckets| attributes for the histogram.
+ TimerReporter(const std::string& histogram_name, int min, int max,
+ int num_buckets);
+ virtual ~TimerReporter() {}
+
+ // Sets the metrics library used by all instances of this class.
+ static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+ metrics_lib_ = metrics_lib;
+ }
+
+ // Reports the current duration to UMA, in milliseconds. Returns false if
+ // there is nothing to report, e.g. a metrics library is not set.
+ virtual bool ReportMilliseconds() const;
+
+ // Accessor methods.
+ const std::string& histogram_name() const { return histogram_name_; }
+ int min() const { return min_; }
+ int max() const { return max_; }
+ int num_buckets() const { return num_buckets_; }
+
+ private:
+ friend class TimerReporterTest;
+ FRIEND_TEST(TimerReporterTest, StartStopReport);
+ FRIEND_TEST(TimerReporterTest, InvalidReport);
+
+ static MetricsLibraryInterface* metrics_lib_;
+ std::string histogram_name_;
+ int min_;
+ int max_;
+ int num_buckets_;
+
+ DISALLOW_COPY_AND_ASSIGN(TimerReporter);
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_TIMER_H_
diff --git a/include/metrics/timer_mock.h b/include/metrics/timer_mock.h
new file mode 100644
index 0000000..200ee9a
--- /dev/null
+++ b/include/metrics/timer_mock.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_TIMER_MOCK_H_
+#define METRICS_TIMER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "metrics/timer.h"
+
+namespace chromeos_metrics {
+
+class TimerMock : public Timer {
+ public:
+ MOCK_METHOD0(Start, bool());
+ MOCK_METHOD0(Stop, bool());
+ MOCK_METHOD0(Reset, bool());
+ MOCK_CONST_METHOD0(HasStarted, bool());
+ MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+};
+
+class TimerReporterMock : public TimerReporter {
+ public:
+ TimerReporterMock() : TimerReporter("", 0, 0, 0) {}
+ MOCK_METHOD0(Start, bool());
+ MOCK_METHOD0(Stop, bool());
+ MOCK_METHOD0(Reset, bool());
+ MOCK_CONST_METHOD0(HasStarted, bool());
+ MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+ MOCK_CONST_METHOD0(ReportMilliseconds, bool());
+ MOCK_CONST_METHOD0(histogram_name, std::string&());
+ MOCK_CONST_METHOD0(min, int());
+ MOCK_CONST_METHOD0(max, int());
+ MOCK_CONST_METHOD0(num_buckets, int());
+};
+
+class ClockWrapperMock : public ClockWrapper {
+ public:
+ MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks());
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_TIMER_MOCK_H_
diff --git a/libmetrics-369476.gyp b/libmetrics-369476.gyp
new file mode 100644
index 0000000..b545d35
--- /dev/null
+++ b/libmetrics-369476.gyp
@@ -0,0 +1,8 @@
+{
+ 'variables': {
+ 'libbase_ver': 369476,
+ },
+ 'includes': [
+ 'libmetrics.gypi',
+ ],
+}
diff --git a/libmetrics.gypi b/libmetrics.gypi
new file mode 100644
index 0000000..3c8fc7c
--- /dev/null
+++ b/libmetrics.gypi
@@ -0,0 +1,33 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libbrillo-<(libbase_ver)',
+ 'libchrome-<(libbase_ver)',
+ ]
+ },
+ 'cflags_cc': [
+ '-fno-exceptions',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'libmetrics-<(libbase_ver)',
+ 'type': 'shared_library',
+ 'cflags': [
+ '-fvisibility=default',
+ ],
+ 'libraries+': [
+ '-lpolicy-<(libbase_ver)',
+ ],
+ 'sources': [
+ 'c_metrics_library.cc',
+ 'metrics_library.cc',
+ 'serialization/metric_sample.cc',
+ 'serialization/serialization_utils.cc',
+ 'timer.cc',
+ ],
+ 'include_dirs': ['.'],
+ },
+ ],
+}
diff --git a/libmetrics.pc.in b/libmetrics.pc.in
new file mode 100644
index 0000000..233f318
--- /dev/null
+++ b/libmetrics.pc.in
@@ -0,0 +1,7 @@
+bslot=@BSLOT@
+
+Name: libmetrics
+Description: Chrome OS metrics library
+Version: ${bslot}
+Requires.private: libchrome-${bslot}
+Libs: -lmetrics-${bslot}
diff --git a/metrics.gyp b/metrics.gyp
new file mode 100644
index 0000000..c9c02d7
--- /dev/null
+++ b/metrics.gyp
@@ -0,0 +1,184 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'dbus-1',
+ 'libbrillo-<(libbase_ver)',
+ 'libchrome-<(libbase_ver)',
+ ]
+ },
+ 'cflags_cc': [
+ '-fno-exceptions',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'libmetrics_daemon',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ 'libupload_service',
+ 'metrics_proto',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lrootdev',
+ ],
+ },
+ 'sources': [
+ 'persistent_integer.cc',
+ 'metrics_daemon.cc',
+ 'metrics_daemon_main.cc',
+ ],
+ 'include_dirs': ['.'],
+ },
+ {
+ 'target_name': 'metrics_client',
+ 'type': 'executable',
+ 'dependencies': [
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'sources': [
+ 'metrics_client.cc',
+ ]
+ },
+ {
+ 'target_name': 'libupload_service',
+ 'type': 'static_library',
+ 'dependencies': [
+ 'metrics_proto',
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lvboot_host',
+ ],
+ },
+ 'variables': {
+ 'exported_deps': [
+ 'protobuf-lite',
+ ],
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps+': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'uploader/upload_service.cc',
+ 'uploader/metrics_hashes.cc',
+ 'uploader/metrics_log.cc',
+ 'uploader/metrics_log_base.cc',
+ 'uploader/system_profile_cache.cc',
+ 'uploader/sender_http.cc',
+ ],
+ 'include_dirs': ['.']
+ },
+ {
+ 'target_name': 'metrics_proto',
+ 'type': 'static_library',
+ 'variables': {
+ 'proto_in_dir': 'uploader/proto',
+ 'proto_out_dir': 'include/metrics/uploader/proto',
+ },
+ 'sources': [
+ '<(proto_in_dir)/chrome_user_metrics_extension.proto',
+ '<(proto_in_dir)/histogram_event.proto',
+ '<(proto_in_dir)/system_profile.proto',
+ '<(proto_in_dir)/user_action_event.proto',
+ ],
+ 'includes': [
+ '../common-mk/protoc.gypi'
+ ],
+ },
+ ],
+ 'conditions': [
+ ['USE_passive_metrics == 1', {
+ 'targets': [
+ {
+ 'target_name': 'metrics_daemon',
+ 'type': 'executable',
+ 'dependencies': ['libmetrics_daemon'],
+ },
+ ],
+ }],
+ ['USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'persistent_integer_test',
+ 'type': 'executable',
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'persistent_integer.cc',
+ 'persistent_integer_test.cc',
+ ]
+ },
+ {
+ 'target_name': 'metrics_library_test',
+ 'type': 'executable',
+ 'dependencies': [
+ '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+ ],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'metrics_library_test.cc',
+ 'serialization/serialization_utils_unittest.cc',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lpolicy-<(libbase_ver)',
+ ]
+ }
+ },
+ {
+ 'target_name': 'timer_test',
+ 'type': 'executable',
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'timer.cc',
+ 'timer_test.cc',
+ ]
+ },
+ {
+ 'target_name': 'upload_service_test',
+ 'type': 'executable',
+ 'sources': [
+ 'persistent_integer.cc',
+ 'uploader/metrics_hashes_unittest.cc',
+ 'uploader/metrics_log_base_unittest.cc',
+ 'uploader/mock/sender_mock.cc',
+ 'uploader/upload_service_test.cc',
+ ],
+ 'dependencies': [
+ 'libupload_service',
+ ],
+ 'includes':[
+ '../common-mk/common_test.gypi',
+ ],
+ 'include_dirs': ['.']
+ },
+ ],
+ }],
+ ['USE_passive_metrics == 1 and USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'metrics_daemon_test',
+ 'type': 'executable',
+ 'dependencies': [
+ 'libmetrics_daemon',
+ ],
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'metrics_daemon_test.cc',
+ ],
+ 'include_dirs': ['.'],
+ },
+ ],
+ }],
+ ]
+}
diff --git a/metrics_client.cc b/metrics_client.cc
new file mode 100644
index 0000000..c66b975
--- /dev/null
+++ b/metrics_client.cc
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "constants.h"
+#include "metrics/metrics_library.h"
+
+enum Mode {
+ kModeDumpHistograms,
+ kModeSendSample,
+ kModeSendEnumSample,
+ kModeSendSparseSample,
+ kModeSendCrosEvent,
+ kModeHasConsent,
+ kModeIsGuestMode,
+};
+
+void ShowUsage() {
+ fprintf(stderr,
+ "Usage: metrics_client [-t] name sample min max nbuckets\n"
+ " metrics_client -e name sample max\n"
+ " metrics_client -s name sample\n"
+ " metrics_client -v event\n"
+ " metrics_client [-cdg]\n"
+ "\n"
+ " default: send metric with integer values \n"
+ " |min| > 0, |min| <= sample < |max|\n"
+ " -c: return exit status 0 if user consents to stats, 1 otherwise,\n"
+ " in guest mode always return 1\n"
+ " -d: dump the histograms recorded by metricsd to stdout\n"
+ " -e: send linear/enumeration histogram data\n"
+ " -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
+ " -s: send a sparse histogram sample\n"
+ " -t: convert sample from double seconds to int milliseconds\n"
+ " -v: send a Platform.CrOSEvent enum histogram sample\n");
+ exit(1);
+}
+
+static int ParseInt(const char *arg) {
+ char *endptr;
+ int value = strtol(arg, &endptr, 0);
+ if (*endptr != '\0') {
+ fprintf(stderr, "metrics client: bad integer \"%s\"\n", arg);
+ ShowUsage();
+ }
+ return value;
+}
+
+static double ParseDouble(const char *arg) {
+ char *endptr;
+ double value = strtod(arg, &endptr);
+ if (*endptr != '\0') {
+ fprintf(stderr, "metrics client: bad double \"%s\"\n", arg);
+ ShowUsage();
+ }
+ return value;
+}
+
+static int DumpHistograms() {
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+
+ std::string dump;
+ if (!metrics_lib.GetHistogramsDump(&dump)) {
+ printf("Failed to dump the histograms.");
+ return 1;
+ }
+
+ printf("%s\n", dump.c_str());
+ return 0;
+}
+
+static int SendStats(char* argv[],
+ int name_index,
+ enum Mode mode,
+ bool secs_to_msecs) {
+ const char* name = argv[name_index];
+ int sample;
+ if (secs_to_msecs) {
+ sample = static_cast<int>(ParseDouble(argv[name_index + 1]) * 1000.0);
+ } else {
+ sample = ParseInt(argv[name_index + 1]);
+ }
+
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ if (mode == kModeSendSparseSample) {
+ metrics_lib.SendSparseToUMA(name, sample);
+ } else if (mode == kModeSendEnumSample) {
+ int max = ParseInt(argv[name_index + 2]);
+ metrics_lib.SendEnumToUMA(name, sample, max);
+ } else {
+ int min = ParseInt(argv[name_index + 2]);
+ int max = ParseInt(argv[name_index + 3]);
+ int nbuckets = ParseInt(argv[name_index + 4]);
+ metrics_lib.SendToUMA(name, sample, min, max, nbuckets);
+ }
+ return 0;
+}
+
+static int SendCrosEvent(char* argv[], int action_index) {
+ const char* event = argv[action_index];
+ bool result;
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ result = metrics_lib.SendCrosEventToUMA(event);
+ if (!result) {
+ fprintf(stderr, "metrics_client: could not send event %s\n", event);
+ return 1;
+ }
+ return 0;
+}
+
+static int HasConsent() {
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ return metrics_lib.AreMetricsEnabled() ? 0 : 1;
+}
+
+static int IsGuestMode() {
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ return metrics_lib.IsGuestMode() ? 0 : 1;
+}
+
+int main(int argc, char** argv) {
+ enum Mode mode = kModeSendSample;
+ bool secs_to_msecs = false;
+
+ // Parse arguments
+ int flag;
+ while ((flag = getopt(argc, argv, "abcdegstv")) != -1) {
+ switch (flag) {
+ case 'c':
+ mode = kModeHasConsent;
+ break;
+ case 'd':
+ mode = kModeDumpHistograms;
+ break;
+ case 'e':
+ mode = kModeSendEnumSample;
+ break;
+ case 'g':
+ mode = kModeIsGuestMode;
+ break;
+ case 's':
+ mode = kModeSendSparseSample;
+ break;
+ case 't':
+ secs_to_msecs = true;
+ break;
+ case 'v':
+ mode = kModeSendCrosEvent;
+ break;
+ default:
+ ShowUsage();
+ break;
+ }
+ }
+ int arg_index = optind;
+
+ int expected_args = 0;
+ if (mode == kModeSendSample)
+ expected_args = 5;
+ else if (mode == kModeSendEnumSample)
+ expected_args = 3;
+ else if (mode == kModeSendSparseSample)
+ expected_args = 2;
+ else if (mode == kModeSendCrosEvent)
+ expected_args = 1;
+
+ if ((arg_index + expected_args) != argc) {
+ ShowUsage();
+ }
+
+ switch (mode) {
+ case kModeDumpHistograms:
+ return DumpHistograms();
+ case kModeSendSample:
+ case kModeSendEnumSample:
+ case kModeSendSparseSample:
+ if ((mode != kModeSendSample) && secs_to_msecs) {
+ ShowUsage();
+ }
+ return SendStats(argv,
+ arg_index,
+ mode,
+ secs_to_msecs);
+ case kModeSendCrosEvent:
+ return SendCrosEvent(argv, arg_index);
+ case kModeHasConsent:
+ return HasConsent();
+ case kModeIsGuestMode:
+ return IsGuestMode();
+ default:
+ ShowUsage();
+ return 0;
+ }
+}
diff --git a/metrics_collector.cc b/metrics_collector.cc
new file mode 100644
index 0000000..45ae0a4
--- /dev/null
+++ b/metrics_collector.cc
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "metrics_collector.h"
+
+#include <sysexits.h>
+#include <time.h>
+
+#include <memory>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/hash.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/osrelease_reader.h>
+
+#include "constants.h"
+#include "metrics_collector_service_impl.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using chromeos_metrics::PersistentInteger;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace {
+
+const int kSecondsPerMinute = 60;
+const int kMinutesPerHour = 60;
+const int kHoursPerDay = 24;
+const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour;
+const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay;
+const int kDaysPerWeek = 7;
+const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek;
+
+// Interval between calls to UpdateStats().
+const uint32_t kUpdateStatsIntervalMs = 300000;
+
+const char kKernelCrashDetectedFile[] =
+ "/data/misc/crash_reporter/run/kernel-crash-detected";
+const char kUncleanShutdownDetectedFile[] =
+ "/var/run/unclean-shutdown-detected";
+
+const int kMetricMeminfoInterval = 30; // seconds
+
+const char kMeminfoFileName[] = "/proc/meminfo";
+const char kVmStatFileName[] = "/proc/vmstat";
+
+const char kWeaveComponent[] = "metrics";
+const char kWeaveTrait[] = "_metrics";
+
+} // namespace
+
+// Zram sysfs entries.
+
+const char MetricsCollector::kComprDataSizeName[] = "compr_data_size";
+const char MetricsCollector::kOrigDataSizeName[] = "orig_data_size";
+const char MetricsCollector::kZeroPagesName[] = "zero_pages";
+
+// Memory use stats collection intervals. We collect some memory use interval
+// at these intervals after boot, and we stop collecting after the last one,
+// with the assumption that in most cases the memory use won't change much
+// after that.
+static const int kMemuseIntervals[] = {
+ 1 * kSecondsPerMinute, // 1 minute mark
+ 4 * kSecondsPerMinute, // 5 minute mark
+ 25 * kSecondsPerMinute, // 0.5 hour mark
+ 120 * kSecondsPerMinute, // 2.5 hour mark
+ 600 * kSecondsPerMinute, // 12.5 hour mark
+};
+
+MetricsCollector::MetricsCollector()
+ : memuse_final_time_(0),
+ memuse_interval_index_(0) {}
+
+MetricsCollector::~MetricsCollector() {
+}
+
+// static
+double MetricsCollector::GetActiveTime() {
+ struct timespec ts;
+ int r = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (r < 0) {
+ PLOG(WARNING) << "clock_gettime(CLOCK_MONOTONIC) failed";
+ return 0;
+ } else {
+ return ts.tv_sec + static_cast<double>(ts.tv_nsec) / (1000 * 1000 * 1000);
+ }
+}
+
+int MetricsCollector::Run() {
+ if (CheckSystemCrash(kKernelCrashDetectedFile)) {
+ ProcessKernelCrash();
+ }
+
+ if (CheckSystemCrash(kUncleanShutdownDetectedFile)) {
+ ProcessUncleanShutdown();
+ }
+
+ // On OS version change, clear version stats (which are reported daily).
+ int32_t version = GetOsVersionHash();
+ if (version_cycle_->Get() != version) {
+ version_cycle_->Set(version);
+ kernel_crashes_version_count_->Set(0);
+ version_cumulative_active_use_->Set(0);
+ version_cumulative_cpu_use_->Set(0);
+ }
+
+ // Start metricscollectorservice
+ android::sp<BnMetricsCollectorServiceImpl> metrics_collector_service =
+ new BnMetricsCollectorServiceImpl(this);
+ android::status_t status = android::defaultServiceManager()->addService(
+ metrics_collector_service->getInterfaceDescriptor(),
+ metrics_collector_service);
+ CHECK(status == android::OK)
+ << "failed to register service metricscollectorservice";
+
+ // Watch Binder events in the main loop
+ brillo::BinderWatcher binder_watcher;
+ CHECK(binder_watcher.Init()) << "Binder FD watcher init failed";
+ return brillo::Daemon::Run();
+}
+
+uint32_t MetricsCollector::GetOsVersionHash() {
+ brillo::OsReleaseReader reader;
+ reader.Load();
+ string version;
+ if (!reader.GetString(metrics::kProductVersion, &version)) {
+ LOG(ERROR) << "failed to read the product version.";
+ version = metrics::kDefaultVersion;
+ }
+
+ uint32_t version_hash = base::Hash(version);
+ if (testing_) {
+ version_hash = 42; // return any plausible value for the hash
+ }
+ return version_hash;
+}
+
+void MetricsCollector::Init(bool testing, MetricsLibraryInterface* metrics_lib,
+ const string& diskstats_path,
+ const base::FilePath& private_metrics_directory,
+ const base::FilePath& shared_metrics_directory) {
+ CHECK(metrics_lib);
+ testing_ = testing;
+ shared_metrics_directory_ = shared_metrics_directory;
+ metrics_lib_ = metrics_lib;
+
+ daily_active_use_.reset(new PersistentInteger("Platform.UseTime.PerDay",
+ private_metrics_directory));
+ version_cumulative_active_use_.reset(new PersistentInteger(
+ "Platform.CumulativeUseTime", private_metrics_directory));
+ version_cumulative_cpu_use_.reset(new PersistentInteger(
+ "Platform.CumulativeCpuTime", private_metrics_directory));
+
+ kernel_crash_interval_.reset(new PersistentInteger(
+ "Platform.KernelCrashInterval", private_metrics_directory));
+ unclean_shutdown_interval_.reset(new PersistentInteger(
+ "Platform.UncleanShutdownInterval", private_metrics_directory));
+ user_crash_interval_.reset(new PersistentInteger("Platform.UserCrashInterval",
+ private_metrics_directory));
+
+ any_crashes_daily_count_.reset(new PersistentInteger(
+ "Platform.AnyCrashes.PerDay", private_metrics_directory));
+ any_crashes_weekly_count_.reset(new PersistentInteger(
+ "Platform.AnyCrashes.PerWeek", private_metrics_directory));
+ user_crashes_daily_count_.reset(new PersistentInteger(
+ "Platform.UserCrashes.PerDay", private_metrics_directory));
+ user_crashes_weekly_count_.reset(new PersistentInteger(
+ "Platform.UserCrashes.PerWeek", private_metrics_directory));
+ kernel_crashes_daily_count_.reset(new PersistentInteger(
+ "Platform.KernelCrashes.PerDay", private_metrics_directory));
+ kernel_crashes_weekly_count_.reset(new PersistentInteger(
+ "Platform.KernelCrashes.PerWeek", private_metrics_directory));
+ kernel_crashes_version_count_.reset(new PersistentInteger(
+ "Platform.KernelCrashesSinceUpdate", private_metrics_directory));
+ unclean_shutdowns_daily_count_.reset(new PersistentInteger(
+ "Platform.UncleanShutdown.PerDay", private_metrics_directory));
+ unclean_shutdowns_weekly_count_.reset(new PersistentInteger(
+ "Platform.UncleanShutdowns.PerWeek", private_metrics_directory));
+
+ daily_cycle_.reset(
+ new PersistentInteger("daily.cycle", private_metrics_directory));
+ weekly_cycle_.reset(
+ new PersistentInteger("weekly.cycle", private_metrics_directory));
+ version_cycle_.reset(
+ new PersistentInteger("version.cycle", private_metrics_directory));
+
+ disk_usage_collector_.reset(new DiskUsageCollector(metrics_lib_));
+ averaged_stats_collector_.reset(
+ new AveragedStatisticsCollector(metrics_lib_, diskstats_path,
+ kVmStatFileName));
+ cpu_usage_collector_.reset(new CpuUsageCollector(metrics_lib_));
+}
+
+int MetricsCollector::OnInit() {
+ int return_code = brillo::Daemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ StatsReporterInit();
+
+ // Start collecting meminfo stats.
+ ScheduleMeminfoCallback(kMetricMeminfoInterval);
+ memuse_final_time_ = GetActiveTime() + kMemuseIntervals[0];
+ ScheduleMemuseCallback(kMemuseIntervals[0]);
+
+ if (testing_)
+ return EX_OK;
+
+ weave_service_subscription_ = weaved::Service::Connect(
+ brillo::MessageLoop::current(),
+ base::Bind(&MetricsCollector::OnWeaveServiceConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ latest_cpu_use_microseconds_ = cpu_usage_collector_->GetCumulativeCpuUse();
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsCollector::HandleUpdateStatsTimeout,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+
+ return EX_OK;
+}
+
+void MetricsCollector::OnWeaveServiceConnected(
+ const std::weak_ptr<weaved::Service>& service) {
+ service_ = service;
+ auto weave_service = service_.lock();
+ if (!weave_service)
+ return;
+
+ weave_service->AddComponent(kWeaveComponent, {kWeaveTrait}, nullptr);
+ weave_service->AddCommandHandler(
+ kWeaveComponent, kWeaveTrait, "enableAnalyticsReporting",
+ base::Bind(&MetricsCollector::OnEnableMetrics,
+ weak_ptr_factory_.GetWeakPtr()));
+ weave_service->AddCommandHandler(
+ kWeaveComponent, kWeaveTrait, "disableAnalyticsReporting",
+ base::Bind(&MetricsCollector::OnDisableMetrics,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ UpdateWeaveState();
+}
+
+void MetricsCollector::OnEnableMetrics(
+ std::unique_ptr<weaved::Command> command) {
+ if (base::WriteFile(
+ shared_metrics_directory_.Append(metrics::kConsentFileName), "", 0) !=
+ 0) {
+ PLOG(ERROR) << "Could not create the consent file.";
+ command->Abort("metrics_error", "Could not create the consent file",
+ nullptr);
+ return;
+ }
+
+ UpdateWeaveState();
+ command->Complete({}, nullptr);
+}
+
+void MetricsCollector::OnDisableMetrics(
+ std::unique_ptr<weaved::Command> command) {
+ if (!base::DeleteFile(
+ shared_metrics_directory_.Append(metrics::kConsentFileName), false)) {
+ PLOG(ERROR) << "Could not delete the consent file.";
+ command->Abort("metrics_error", "Could not delete the consent file",
+ nullptr);
+ return;
+ }
+
+ UpdateWeaveState();
+ command->Complete({}, nullptr);
+}
+
+void MetricsCollector::UpdateWeaveState() {
+ auto weave_service = service_.lock();
+ if (!weave_service)
+ return;
+
+ std::string enabled =
+ metrics_lib_->AreMetricsEnabled() ? "enabled" : "disabled";
+
+ if (!weave_service->SetStateProperty(kWeaveComponent, kWeaveTrait,
+ "analyticsReportingState",
+ *brillo::ToValue(enabled),
+ nullptr)) {
+ LOG(ERROR) << "failed to update weave's state";
+ }
+}
+
+void MetricsCollector::ProcessUserCrash() {
+ // Counts the active time up to now.
+ UpdateStats(TimeTicks::Now(), Time::Now());
+
+ // Reports the active use time since the last crash and resets it.
+ SendAndResetCrashIntervalSample(user_crash_interval_);
+
+ any_crashes_daily_count_->Add(1);
+ any_crashes_weekly_count_->Add(1);
+ user_crashes_daily_count_->Add(1);
+ user_crashes_weekly_count_->Add(1);
+}
+
+void MetricsCollector::ProcessKernelCrash() {
+ // Counts the active time up to now.
+ UpdateStats(TimeTicks::Now(), Time::Now());
+
+ // Reports the active use time since the last crash and resets it.
+ SendAndResetCrashIntervalSample(kernel_crash_interval_);
+
+ any_crashes_daily_count_->Add(1);
+ any_crashes_weekly_count_->Add(1);
+ kernel_crashes_daily_count_->Add(1);
+ kernel_crashes_weekly_count_->Add(1);
+
+ kernel_crashes_version_count_->Add(1);
+}
+
+void MetricsCollector::ProcessUncleanShutdown() {
+ // Counts the active time up to now.
+ UpdateStats(TimeTicks::Now(), Time::Now());
+
+ // Reports the active use time since the last crash and resets it.
+ SendAndResetCrashIntervalSample(unclean_shutdown_interval_);
+
+ unclean_shutdowns_daily_count_->Add(1);
+ unclean_shutdowns_weekly_count_->Add(1);
+ any_crashes_daily_count_->Add(1);
+ any_crashes_weekly_count_->Add(1);
+}
+
+bool MetricsCollector::CheckSystemCrash(const string& crash_file) {
+ FilePath crash_detected(crash_file);
+ if (!base::PathExists(crash_detected))
+ return false;
+
+ // Deletes the crash-detected file so that the daemon doesn't report
+ // another kernel crash in case it's restarted.
+ base::DeleteFile(crash_detected, false); // not recursive
+ return true;
+}
+
+void MetricsCollector::StatsReporterInit() {
+ disk_usage_collector_->Schedule();
+
+ cpu_usage_collector_->Init();
+ cpu_usage_collector_->Schedule();
+
+ // Don't start a collection cycle during the first run to avoid delaying the
+ // boot.
+ averaged_stats_collector_->ScheduleWait();
+}
+
+void MetricsCollector::ScheduleMeminfoCallback(int wait) {
+ if (testing_) {
+ return;
+ }
+ base::TimeDelta waitDelta = base::TimeDelta::FromSeconds(wait);
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsCollector::MeminfoCallback,
+ weak_ptr_factory_.GetWeakPtr(), waitDelta),
+ waitDelta);
+}
+
+void MetricsCollector::MeminfoCallback(base::TimeDelta wait) {
+ string meminfo_raw;
+ const FilePath meminfo_path(kMeminfoFileName);
+ if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+ LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+ return;
+ }
+ // Make both calls even if the first one fails.
+ if (ProcessMeminfo(meminfo_raw)) {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsCollector::MeminfoCallback,
+ weak_ptr_factory_.GetWeakPtr(), wait),
+ wait);
+ }
+}
+
+// static
+bool MetricsCollector::ReadFileToUint64(const base::FilePath& path,
+ uint64_t* value) {
+ std::string content;
+ if (!base::ReadFileToString(path, &content)) {
+ PLOG(WARNING) << "cannot read " << path.MaybeAsASCII();
+ return false;
+ }
+ // Remove final newline.
+ base::TrimWhitespaceASCII(content, base::TRIM_TRAILING, &content);
+ if (!base::StringToUint64(content, value)) {
+ LOG(WARNING) << "invalid integer: " << content;
+ return false;
+ }
+ return true;
+}
+
+bool MetricsCollector::ReportZram(const base::FilePath& zram_dir) {
+ // Data sizes are in bytes. |zero_pages| is in number of pages.
+ uint64_t compr_data_size, orig_data_size, zero_pages;
+ const size_t page_size = 4096;
+
+ if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName),
+ &compr_data_size) ||
+ !ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) ||
+ !ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) {
+ return false;
+ }
+
+ // |orig_data_size| does not include zero-filled pages.
+ orig_data_size += zero_pages * page_size;
+
+ const int compr_data_size_mb = compr_data_size >> 20;
+ const int savings_mb = (orig_data_size - compr_data_size) >> 20;
+ const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size;
+
+ // Report compressed size in megabytes. 100 MB or less has little impact.
+ SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50);
+ SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50);
+ // The compression ratio is multiplied by 100 for better resolution. The
+ // ratios of interest are between 1 and 6 (100% and 600% as reported). We
+ // don't want samples when very little memory is being compressed.
+ if (compr_data_size_mb >= 1) {
+ SendSample("Platform.ZramCompressionRatioPercent",
+ orig_data_size * 100 / compr_data_size, 100, 600, 50);
+ }
+ // The values of interest for zero_pages are between 1MB and 1GB. The units
+ // are number of pages.
+ SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50);
+ SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50);
+
+ return true;
+}
+
+bool MetricsCollector::ProcessMeminfo(const string& meminfo_raw) {
+ static const MeminfoRecord fields_array[] = {
+ { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
+ { "MemFree", "MemFree" },
+ { "Buffers", "Buffers" },
+ { "Cached", "Cached" },
+ // { "SwapCached", "SwapCached" },
+ { "Active", "Active" },
+ { "Inactive", "Inactive" },
+ { "ActiveAnon", "Active(anon)" },
+ { "InactiveAnon", "Inactive(anon)" },
+ { "ActiveFile" , "Active(file)" },
+ { "InactiveFile", "Inactive(file)" },
+ { "Unevictable", "Unevictable", kMeminfoOp_HistLog },
+ // { "Mlocked", "Mlocked" },
+ { "SwapTotal", "SwapTotal", kMeminfoOp_SwapTotal },
+ { "SwapFree", "SwapFree", kMeminfoOp_SwapFree },
+ // { "Dirty", "Dirty" },
+ // { "Writeback", "Writeback" },
+ { "AnonPages", "AnonPages" },
+ { "Mapped", "Mapped" },
+ { "Shmem", "Shmem", kMeminfoOp_HistLog },
+ { "Slab", "Slab", kMeminfoOp_HistLog },
+ // { "SReclaimable", "SReclaimable" },
+ // { "SUnreclaim", "SUnreclaim" },
+ };
+ vector<MeminfoRecord> fields(fields_array,
+ fields_array + arraysize(fields_array));
+ if (!FillMeminfo(meminfo_raw, &fields)) {
+ return false;
+ }
+ int total_memory = fields[0].value;
+ if (total_memory == 0) {
+ // this "cannot happen"
+ LOG(WARNING) << "borked meminfo parser";
+ return false;
+ }
+ int swap_total = 0;
+ int swap_free = 0;
+ // Send all fields retrieved, except total memory.
+ for (unsigned int i = 1; i < fields.size(); i++) {
+ string metrics_name = base::StringPrintf("Platform.Meminfo%s",
+ fields[i].name);
+ int percent;
+ switch (fields[i].op) {
+ case kMeminfoOp_HistPercent:
+ // report value as percent of total memory
+ percent = fields[i].value * 100 / total_memory;
+ SendLinearSample(metrics_name, percent, 100, 101);
+ break;
+ case kMeminfoOp_HistLog:
+ // report value in kbytes, log scale, 4Gb max
+ SendSample(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100);
+ break;
+ case kMeminfoOp_SwapTotal:
+ swap_total = fields[i].value;
+ case kMeminfoOp_SwapFree:
+ swap_free = fields[i].value;
+ break;
+ }
+ }
+ if (swap_total > 0) {
+ int swap_used = swap_total - swap_free;
+ int swap_used_percent = swap_used * 100 / swap_total;
+ SendSample("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100);
+ SendLinearSample("Platform.MeminfoSwapUsed.Percent", swap_used_percent,
+ 100, 101);
+ }
+ return true;
+}
+
+bool MetricsCollector::FillMeminfo(const string& meminfo_raw,
+ vector<MeminfoRecord>* fields) {
+ vector<std::string> lines =
+ base::SplitString(meminfo_raw, "\n", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+
+ // Scan meminfo output and collect field values. Each field name has to
+ // match a meminfo entry (case insensitive) after removing non-alpha
+ // characters from the entry.
+ size_t ifield = 0;
+ for (size_t iline = 0;
+ iline < lines.size() && ifield < fields->size();
+ iline++) {
+ vector<string> tokens =
+ base::SplitString(lines[iline], ": ", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) {
+ // Name matches. Parse value and save.
+ if (!base::StringToInt(tokens[1], &(*fields)[ifield].value)) {
+ LOG(WARNING) << "Cound not convert " << tokens[1] << " to int";
+ return false;
+ }
+ ifield++;
+ }
+ }
+ if (ifield < fields->size()) {
+ // End of input reached while scanning.
+ LOG(WARNING) << "cannot find field " << (*fields)[ifield].match
+ << " and following";
+ return false;
+ }
+ return true;
+}
+
+void MetricsCollector::ScheduleMemuseCallback(double interval) {
+ if (testing_) {
+ return;
+ }
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsCollector::MemuseCallback,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(interval));
+}
+
+void MetricsCollector::MemuseCallback() {
+ // Since we only care about active time (i.e. uptime minus sleep time) but
+ // the callbacks are driven by real time (uptime), we check if we should
+ // reschedule this callback due to intervening sleep periods.
+ double now = GetActiveTime();
+ // Avoid intervals of less than one second.
+ double remaining_time = ceil(memuse_final_time_ - now);
+ if (remaining_time > 0) {
+ ScheduleMemuseCallback(remaining_time);
+ } else {
+ // Report stats and advance the measurement interval unless there are
+ // errors or we've completed the last interval.
+ if (MemuseCallbackWork() &&
+ memuse_interval_index_ < arraysize(kMemuseIntervals)) {
+ double interval = kMemuseIntervals[memuse_interval_index_++];
+ memuse_final_time_ = now + interval;
+ ScheduleMemuseCallback(interval);
+ }
+ }
+}
+
+bool MetricsCollector::MemuseCallbackWork() {
+ string meminfo_raw;
+ const FilePath meminfo_path(kMeminfoFileName);
+ if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+ LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+ return false;
+ }
+ return ProcessMemuse(meminfo_raw);
+}
+
+bool MetricsCollector::ProcessMemuse(const string& meminfo_raw) {
+ static const MeminfoRecord fields_array[] = {
+ { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
+ { "ActiveAnon", "Active(anon)" },
+ { "InactiveAnon", "Inactive(anon)" },
+ };
+ vector<MeminfoRecord> fields(fields_array,
+ fields_array + arraysize(fields_array));
+ if (!FillMeminfo(meminfo_raw, &fields)) {
+ return false;
+ }
+ int total = fields[0].value;
+ int active_anon = fields[1].value;
+ int inactive_anon = fields[2].value;
+ if (total == 0) {
+ // this "cannot happen"
+ LOG(WARNING) << "borked meminfo parser";
+ return false;
+ }
+ string metrics_name = base::StringPrintf("Platform.MemuseAnon%d",
+ memuse_interval_index_);
+ SendLinearSample(metrics_name, (active_anon + inactive_anon) * 100 / total,
+ 100, 101);
+ return true;
+}
+
+void MetricsCollector::SendSample(const string& name, int sample,
+ int min, int max, int nbuckets) {
+ metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
+}
+
+void MetricsCollector::SendKernelCrashesCumulativeCountStats() {
+ // Report the number of crashes for this OS version, but don't clear the
+ // counter. It is cleared elsewhere on version change.
+ int64_t crashes_count = kernel_crashes_version_count_->Get();
+ SendSample(kernel_crashes_version_count_->Name(),
+ crashes_count,
+ 1, // value of first bucket
+ 500, // value of last bucket
+ 100); // number of buckets
+
+
+ int64_t cpu_use_ms = version_cumulative_cpu_use_->Get();
+ SendSample(version_cumulative_cpu_use_->Name(),
+ cpu_use_ms / 1000, // stat is in seconds
+ 1, // device may be used very little...
+ 8 * 1000 * 1000, // ... or a lot (a little over 90 days)
+ 100);
+
+ // On the first run after an autoupdate, cpu_use_ms and active_use_seconds
+ // can be zero. Avoid division by zero.
+ if (cpu_use_ms > 0) {
+ // Send the crash frequency since update in number of crashes per CPU year.
+ SendSample("Logging.KernelCrashesPerCpuYear",
+ crashes_count * kSecondsPerDay * 365 * 1000 / cpu_use_ms,
+ 1,
+ 1000 * 1000, // about one crash every 30s of CPU time
+ 100);
+ }
+
+ int64_t active_use_seconds = version_cumulative_active_use_->Get();
+ if (active_use_seconds > 0) {
+ SendSample(version_cumulative_active_use_->Name(),
+ active_use_seconds,
+ 1, // device may be used very little...
+ 8 * 1000 * 1000, // ... or a lot (about 90 days)
+ 100);
+ // Same as above, but per year of active time.
+ SendSample("Logging.KernelCrashesPerActiveYear",
+ crashes_count * kSecondsPerDay * 365 / active_use_seconds,
+ 1,
+ 1000 * 1000, // about one crash every 30s of active time
+ 100);
+ }
+}
+
+void MetricsCollector::SendAndResetDailyUseSample(
+ const unique_ptr<PersistentInteger>& use) {
+ SendSample(use->Name(),
+ use->GetAndClear(),
+ 1, // value of first bucket
+ kSecondsPerDay, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsCollector::SendAndResetCrashIntervalSample(
+ const unique_ptr<PersistentInteger>& interval) {
+ SendSample(interval->Name(),
+ interval->GetAndClear(),
+ 1, // value of first bucket
+ 4 * kSecondsPerWeek, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsCollector::SendAndResetCrashFrequencySample(
+ const unique_ptr<PersistentInteger>& frequency) {
+ SendSample(frequency->Name(),
+ frequency->GetAndClear(),
+ 1, // value of first bucket
+ 100, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsCollector::SendLinearSample(const string& name, int sample,
+ int max, int nbuckets) {
+ // TODO(semenzato): add a proper linear histogram to the Chrome external
+ // metrics API.
+ LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale";
+ metrics_lib_->SendEnumToUMA(name, sample, max);
+}
+
+void MetricsCollector::UpdateStats(TimeTicks now_ticks,
+ Time now_wall_time) {
+ const int elapsed_seconds = (now_ticks - last_update_stats_time_).InSeconds();
+ daily_active_use_->Add(elapsed_seconds);
+ version_cumulative_active_use_->Add(elapsed_seconds);
+ user_crash_interval_->Add(elapsed_seconds);
+ kernel_crash_interval_->Add(elapsed_seconds);
+ TimeDelta cpu_use = cpu_usage_collector_->GetCumulativeCpuUse();
+ version_cumulative_cpu_use_->Add(
+ (cpu_use - latest_cpu_use_microseconds_).InMilliseconds());
+ latest_cpu_use_microseconds_ = cpu_use;
+ last_update_stats_time_ = now_ticks;
+
+ const TimeDelta since_epoch = now_wall_time - Time::UnixEpoch();
+ const int day = since_epoch.InDays();
+ const int week = day / 7;
+
+ if (daily_cycle_->Get() != day) {
+ daily_cycle_->Set(day);
+ SendAndResetDailyUseSample(daily_active_use_);
+ SendAndResetCrashFrequencySample(any_crashes_daily_count_);
+ SendAndResetCrashFrequencySample(user_crashes_daily_count_);
+ SendAndResetCrashFrequencySample(kernel_crashes_daily_count_);
+ SendAndResetCrashFrequencySample(unclean_shutdowns_daily_count_);
+ SendKernelCrashesCumulativeCountStats();
+ }
+
+ if (weekly_cycle_->Get() != week) {
+ weekly_cycle_->Set(week);
+ SendAndResetCrashFrequencySample(any_crashes_weekly_count_);
+ SendAndResetCrashFrequencySample(user_crashes_weekly_count_);
+ SendAndResetCrashFrequencySample(kernel_crashes_weekly_count_);
+ SendAndResetCrashFrequencySample(unclean_shutdowns_weekly_count_);
+ }
+}
+
+void MetricsCollector::HandleUpdateStatsTimeout() {
+ UpdateStats(TimeTicks::Now(), Time::Now());
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsCollector::HandleUpdateStatsTimeout,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+}
diff --git a/metrics_collector.h b/metrics_collector.h
new file mode 100644
index 0000000..30659bd
--- /dev/null
+++ b/metrics_collector.h
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_METRICS_COLLECTOR_H_
+#define METRICS_METRICS_COLLECTOR_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/daemons/daemon.h>
+#include <libweaved/command.h>
+#include <libweaved/service.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "collectors/averaged_statistics_collector.h"
+#include "collectors/cpu_usage_collector.h"
+#include "collectors/disk_usage_collector.h"
+#include "metrics/metrics_library.h"
+#include "persistent_integer.h"
+
+using chromeos_metrics::PersistentInteger;
+using std::unique_ptr;
+
+class MetricsCollector : public brillo::Daemon {
+ public:
+ MetricsCollector();
+ ~MetricsCollector();
+
+ // Initializes metrics class variables.
+ void Init(bool testing,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& diskstats_path,
+ const base::FilePath& private_metrics_directory,
+ const base::FilePath& shared_metrics_directory);
+
+ // Initializes the daemon.
+ int OnInit() override;
+
+ // Does all the work.
+ int Run() override;
+
+ // Returns the active time since boot (uptime minus sleep time) in seconds.
+ static double GetActiveTime();
+
+ // Updates the active use time and logs time between user-space
+ // process crashes. Called via MetricsCollectorServiceTrampoline.
+ void ProcessUserCrash();
+
+ protected:
+ // Used also by the unit tests.
+ static const char kComprDataSizeName[];
+ static const char kOrigDataSizeName[];
+ static const char kZeroPagesName[];
+
+ private:
+ friend class MetricsCollectorTest;
+ FRIEND_TEST(MetricsCollectorTest, CheckSystemCrash);
+ FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoCurrent);
+ FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoLast);
+ FRIEND_TEST(MetricsCollectorTest, GetHistogramPath);
+ FRIEND_TEST(MetricsCollectorTest, IsNewEpoch);
+ FRIEND_TEST(MetricsCollectorTest, MessageFilter);
+ FRIEND_TEST(MetricsCollectorTest, ProcessKernelCrash);
+ FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo);
+ FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo2);
+ FRIEND_TEST(MetricsCollectorTest, ProcessUncleanShutdown);
+ FRIEND_TEST(MetricsCollectorTest, ProcessUserCrash);
+ FRIEND_TEST(MetricsCollectorTest, ReportCrashesDailyFrequency);
+ FRIEND_TEST(MetricsCollectorTest, ReportKernelCrashInterval);
+ FRIEND_TEST(MetricsCollectorTest, ReportUncleanShutdownInterval);
+ FRIEND_TEST(MetricsCollectorTest, ReportUserCrashInterval);
+ FRIEND_TEST(MetricsCollectorTest, SendSample);
+ FRIEND_TEST(MetricsCollectorTest, SendZramMetrics);
+
+ // Type of scale to use for meminfo histograms. For most of them we use
+ // percent of total RAM, but for some we use absolute numbers, usually in
+ // megabytes, on a log scale from 0 to 4000, and 0 to 8000 for compressed
+ // swap (since it can be larger than total RAM).
+ enum MeminfoOp {
+ kMeminfoOp_HistPercent = 0,
+ kMeminfoOp_HistLog,
+ kMeminfoOp_SwapTotal,
+ kMeminfoOp_SwapFree,
+ };
+
+ // Record for retrieving and reporting values from /proc/meminfo.
+ struct MeminfoRecord {
+ const char* name; // print name
+ const char* match; // string to match in output of /proc/meminfo
+ MeminfoOp op; // histogram scale selector, or other operator
+ int value; // value from /proc/meminfo
+ };
+
+ // Enables metrics reporting.
+ void OnEnableMetrics(std::unique_ptr<weaved::Command> command);
+
+ // Disables metrics reporting.
+ void OnDisableMetrics(std::unique_ptr<weaved::Command> command);
+
+ // Updates the weave device state.
+ void UpdateWeaveState();
+
+ // Updates the active use time and logs time between kernel crashes.
+ void ProcessKernelCrash();
+
+ // Updates the active use time and logs time between unclean shutdowns.
+ void ProcessUncleanShutdown();
+
+ // Checks if a kernel crash has been detected and returns true if
+ // so. The method assumes that a kernel crash has happened if
+ // |crash_file| exists. It removes the file immediately if it
+ // exists, so it must not be called more than once.
+ bool CheckSystemCrash(const std::string& crash_file);
+
+ // Sends a regular (exponential) histogram sample to Chrome for
+ // transport to UMA. See MetricsLibrary::SendToUMA in
+ // metrics_library.h for a description of the arguments.
+ void SendSample(const std::string& name, int sample,
+ int min, int max, int nbuckets);
+
+ // Sends a linear histogram sample to Chrome for transport to UMA. See
+ // MetricsLibrary::SendToUMA in metrics_library.h for a description of the
+ // arguments.
+ void SendLinearSample(const std::string& name, int sample,
+ int max, int nbuckets);
+
+ // Sends various cumulative kernel crash-related stats, for instance the
+ // total number of kernel crashes since the last version update.
+ void SendKernelCrashesCumulativeCountStats();
+
+ // Sends a sample representing the number of seconds of active use
+ // for a 24-hour period and reset |use|.
+ void SendAndResetDailyUseSample(const unique_ptr<PersistentInteger>& use);
+
+ // Sends a sample representing a time interval between two crashes of the
+ // same type and reset |interval|.
+ void SendAndResetCrashIntervalSample(
+ const unique_ptr<PersistentInteger>& interval);
+
+ // Sends a sample representing a frequency of crashes of some type and reset
+ // |frequency|.
+ void SendAndResetCrashFrequencySample(
+ const unique_ptr<PersistentInteger>& frequency);
+
+ // Initializes vm and disk stats reporting.
+ void StatsReporterInit();
+
+ // Schedules meminfo collection callback.
+ void ScheduleMeminfoCallback(int wait);
+
+ // Reports memory statistics. Reschedules callback on success.
+ void MeminfoCallback(base::TimeDelta wait);
+
+ // Parses content of /proc/meminfo and sends fields of interest to UMA.
+ // Returns false on errors. |meminfo_raw| contains the content of
+ // /proc/meminfo.
+ bool ProcessMeminfo(const std::string& meminfo_raw);
+
+ // Parses meminfo data from |meminfo_raw|. |fields| is a vector containing
+ // the fields of interest. The order of the fields must be the same in which
+ // /proc/meminfo prints them. The result of parsing fields[i] is placed in
+ // fields[i].value.
+ bool FillMeminfo(const std::string& meminfo_raw,
+ std::vector<MeminfoRecord>* fields);
+
+ // Schedule a memory use callback in |interval| seconds.
+ void ScheduleMemuseCallback(double interval);
+
+ // Calls MemuseCallbackWork, and possibly schedules next callback, if enough
+ // active time has passed. Otherwise reschedules itself to simulate active
+ // time callbacks (i.e. wall clock time minus sleep time).
+ void MemuseCallback();
+
+ // Reads /proc/meminfo and sends total anonymous memory usage to UMA.
+ bool MemuseCallbackWork();
+
+ // Parses meminfo data and sends it to UMA.
+ bool ProcessMemuse(const std::string& meminfo_raw);
+
+ // Reads the current OS version from /etc/lsb-release and hashes it
+ // to a unsigned 32-bit int.
+ uint32_t GetOsVersionHash();
+
+ // Updates stats, additionally sending them to UMA if enough time has elapsed
+ // since the last report.
+ void UpdateStats(base::TimeTicks now_ticks, base::Time now_wall_time);
+
+ // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
+ void HandleUpdateStatsTimeout();
+
+ // Reports zram statistics.
+ bool ReportZram(const base::FilePath& zram_dir);
+
+ // Reads a string from a file and converts it to uint64_t.
+ static bool ReadFileToUint64(const base::FilePath& path, uint64_t* value);
+
+ // Callback invoked when a connection to weaved's service is established
+ // over Binder interface.
+ void OnWeaveServiceConnected(const std::weak_ptr<weaved::Service>& service);
+
+ // VARIABLES
+
+ // Test mode.
+ bool testing_;
+
+ // Publicly readable metrics directory.
+ base::FilePath shared_metrics_directory_;
+
+ // The metrics library handle.
+ MetricsLibraryInterface* metrics_lib_;
+
+ // The last time that UpdateStats() was called.
+ base::TimeTicks last_update_stats_time_;
+
+ // End time of current memuse stat collection interval.
+ double memuse_final_time_;
+
+ // Selects the wait time for the next memory use callback.
+ unsigned int memuse_interval_index_;
+
+ // Used internally by GetIncrementalCpuUse() to return the CPU utilization
+ // between calls.
+ base::TimeDelta latest_cpu_use_microseconds_;
+
+ // Persistent values and accumulators for crash statistics.
+ unique_ptr<PersistentInteger> daily_cycle_;
+ unique_ptr<PersistentInteger> weekly_cycle_;
+ unique_ptr<PersistentInteger> version_cycle_;
+
+ // Active use accumulated in a day.
+ unique_ptr<PersistentInteger> daily_active_use_;
+ // Active use accumulated since the latest version update.
+ unique_ptr<PersistentInteger> version_cumulative_active_use_;
+
+ // The CPU time accumulator. This contains the CPU time, in milliseconds,
+ // used by the system since the most recent OS version update.
+ unique_ptr<PersistentInteger> version_cumulative_cpu_use_;
+
+ unique_ptr<PersistentInteger> user_crash_interval_;
+ unique_ptr<PersistentInteger> kernel_crash_interval_;
+ unique_ptr<PersistentInteger> unclean_shutdown_interval_;
+
+ unique_ptr<PersistentInteger> any_crashes_daily_count_;
+ unique_ptr<PersistentInteger> any_crashes_weekly_count_;
+ unique_ptr<PersistentInteger> user_crashes_daily_count_;
+ unique_ptr<PersistentInteger> user_crashes_weekly_count_;
+ unique_ptr<PersistentInteger> kernel_crashes_daily_count_;
+ unique_ptr<PersistentInteger> kernel_crashes_weekly_count_;
+ unique_ptr<PersistentInteger> kernel_crashes_version_count_;
+ unique_ptr<PersistentInteger> unclean_shutdowns_daily_count_;
+ unique_ptr<PersistentInteger> unclean_shutdowns_weekly_count_;
+
+ unique_ptr<CpuUsageCollector> cpu_usage_collector_;
+ unique_ptr<DiskUsageCollector> disk_usage_collector_;
+ unique_ptr<AveragedStatisticsCollector> averaged_stats_collector_;
+
+ unique_ptr<weaved::Service::Subscription> weave_service_subscription_;
+ std::weak_ptr<weaved::Service> service_;
+
+ base::WeakPtrFactory<MetricsCollector> weak_ptr_factory_{this};
+};
+
+#endif // METRICS_METRICS_COLLECTOR_H_
diff --git a/metrics_collector.rc b/metrics_collector.rc
new file mode 100644
index 0000000..2d7667d
--- /dev/null
+++ b/metrics_collector.rc
@@ -0,0 +1,4 @@
+service metricscollector /system/bin/metrics_collector --foreground --logtosyslog
+ class late_start
+ user metrics_coll
+ group metrics_coll
diff --git a/metrics_collector_main.cc b/metrics_collector_main.cc
new file mode 100644
index 0000000..14bb935
--- /dev/null
+++ b/metrics_collector_main.cc
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <brillo/flag_helper.h>
+#include <brillo/syslog_logging.h>
+#include <rootdev.h>
+
+#include "constants.h"
+#include "metrics_collector.h"
+
+
+// Returns the path to the disk stats in the sysfs. Returns the null string if
+// it cannot find the disk stats file.
+static
+const std::string MetricsMainDiskStatsPath() {
+ char dev_path_cstr[PATH_MAX];
+ std::string dev_prefix = "/dev/block/";
+ std::string dev_path;
+
+ int ret = rootdev(dev_path_cstr, sizeof(dev_path_cstr), true, true);
+ if (ret != 0) {
+ LOG(WARNING) << "error " << ret << " determining root device";
+ return "";
+ }
+ dev_path = dev_path_cstr;
+ // Check that rootdev begins with "/dev/block/".
+ if (!base::StartsWith(dev_path, dev_prefix,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ LOG(WARNING) << "unexpected root device " << dev_path;
+ return "";
+ }
+ return "/sys/class/block/" + dev_path.substr(dev_prefix.length()) + "/stat";
+}
+
+int main(int argc, char** argv) {
+ DEFINE_bool(foreground, false, "Don't daemonize");
+
+ DEFINE_string(private_directory, metrics::kMetricsCollectorDirectory,
+ "Path to the private directory used by metrics_collector "
+ "(testing only)");
+ DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory,
+ "Path to the shared metrics directory, used by "
+ "metrics_collector, metricsd and all metrics clients "
+ "(testing only)");
+
+ DEFINE_bool(logtostderr, false, "Log to standard error");
+ DEFINE_bool(logtosyslog, false, "Log to syslog");
+
+ brillo::FlagHelper::Init(argc, argv, "Chromium OS Metrics Daemon");
+
+ int logging_location = (FLAGS_foreground ? brillo::kLogToStderr
+ : brillo::kLogToSyslog);
+ if (FLAGS_logtosyslog)
+ logging_location = brillo::kLogToSyslog;
+
+ if (FLAGS_logtostderr)
+ logging_location = brillo::kLogToStderr;
+
+ // Also log to stderr when not running as daemon.
+ brillo::InitLog(logging_location | brillo::kLogHeader);
+
+ if (FLAGS_logtostderr && FLAGS_logtosyslog) {
+ LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set";
+ return 1;
+ }
+
+ if (!FLAGS_foreground && daemon(0, 0) != 0) {
+ return errno;
+ }
+
+ MetricsLibrary metrics_lib;
+ metrics_lib.InitWithNoCaching();
+ MetricsCollector daemon;
+ daemon.Init(false,
+ &metrics_lib,
+ MetricsMainDiskStatsPath(),
+ base::FilePath(FLAGS_private_directory),
+ base::FilePath(FLAGS_shared_directory));
+
+ daemon.Run();
+}
diff --git a/metrics_collector_service_client.cc b/metrics_collector_service_client.cc
new file mode 100644
index 0000000..08aaa4a
--- /dev/null
+++ b/metrics_collector_service_client.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Client interface to IMetricsCollectorService.
+
+#include "metrics/metrics_collector_service_client.h"
+
+#include <base/logging.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+
+#include "android/brillo/metrics/IMetricsCollectorService.h"
+
+namespace {
+const char kMetricsCollectorServiceName[] =
+ "android.brillo.metrics.IMetricsCollectorService";
+}
+
+bool MetricsCollectorServiceClient::Init() {
+ const android::String16 name(kMetricsCollectorServiceName);
+ metrics_collector_service_ = android::interface_cast<
+ android::brillo::metrics::IMetricsCollectorService>(
+ android::defaultServiceManager()->checkService(name));
+
+ if (metrics_collector_service_ == nullptr)
+ LOG(ERROR) << "Unable to lookup service " << kMetricsCollectorServiceName;
+
+ return metrics_collector_service_ != nullptr;
+}
+
+bool MetricsCollectorServiceClient::notifyUserCrash() {
+ if (metrics_collector_service_ == nullptr)
+ return false;
+
+ metrics_collector_service_->notifyUserCrash();
+ return true;
+}
diff --git a/metrics_collector_service_impl.cc b/metrics_collector_service_impl.cc
new file mode 100644
index 0000000..4d9a05a
--- /dev/null
+++ b/metrics_collector_service_impl.cc
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "metrics_collector_service_impl.h"
+
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <utils/Errors.h>
+
+#include "metrics_collector.h"
+
+using namespace android;
+
+BnMetricsCollectorServiceImpl::BnMetricsCollectorServiceImpl(
+ MetricsCollector* metrics_collector)
+ : metrics_collector_(metrics_collector) {
+}
+
+android::binder::Status BnMetricsCollectorServiceImpl::notifyUserCrash() {
+ metrics_collector_->ProcessUserCrash();
+ return android::binder::Status::ok();
+}
diff --git a/metrics_collector_service_impl.h b/metrics_collector_service_impl.h
new file mode 100644
index 0000000..8db418a
--- /dev/null
+++ b/metrics_collector_service_impl.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
+#define METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
+
+// metrics_collector binder service implementation. Constructed by
+// MetricsCollector.
+
+#include "android/brillo/metrics/BnMetricsCollectorService.h"
+
+#include <binder/Status.h>
+
+class MetricsCollector;
+
+class BnMetricsCollectorServiceImpl
+ : public android::brillo::metrics::BnMetricsCollectorService {
+ public:
+ // Passed a this pointer from the MetricsCollector object that constructs us.
+ explicit BnMetricsCollectorServiceImpl(
+ MetricsCollector* metrics_collector_service);
+
+ virtual ~BnMetricsCollectorServiceImpl() = default;
+
+ // Called by crash_reporter to report a userspace crash event. We relay
+ // this to MetricsCollector.
+ android::binder::Status notifyUserCrash();
+
+ private:
+ // MetricsCollector object that constructs us, we use this to call back
+ // to it.
+ MetricsCollector* metrics_collector_;
+};
+
+#endif // METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
diff --git a/metrics_collector_test.cc b/metrics_collector_test.cc
new file mode 100644
index 0000000..8dda529
--- /dev/null
+++ b/metrics_collector_test.cc
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vector>
+
+#include <base/at_exit.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_number_conversions.h>
+#include <brillo/flag_helper.h>
+#include <gtest/gtest.h>
+
+#include "constants.h"
+#include "metrics_collector.h"
+#include "metrics/metrics_library_mock.h"
+#include "persistent_integer_mock.h"
+
+using base::FilePath;
+using base::TimeDelta;
+using std::string;
+using std::vector;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Return;
+using ::testing::StrictMock;
+using chromeos_metrics::PersistentIntegerMock;
+
+
+class MetricsCollectorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ brillo::FlagHelper::Init(0, nullptr, "");
+ EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ base::FilePath private_dir = temp_dir_.path().Append("private");
+ base::FilePath shared_dir = temp_dir_.path().Append("shared");
+
+ EXPECT_TRUE(base::CreateDirectory(private_dir));
+ EXPECT_TRUE(base::CreateDirectory(shared_dir));
+
+ daemon_.Init(true, &metrics_lib_, "", private_dir, shared_dir);
+ }
+
+ // Adds a metrics library mock expectation that the specified metric
+ // will be generated.
+ void ExpectSample(const std::string& name, int sample) {
+ EXPECT_CALL(metrics_lib_, SendToUMA(name, sample, _, _, _))
+ .Times(1)
+ .WillOnce(Return(true))
+ .RetiresOnSaturation();
+ }
+
+ // Creates or overwrites the file in |path| so that it contains the printable
+ // representation of |value|.
+ void CreateUint64ValueFile(const base::FilePath& path, uint64_t value) {
+ std::string value_string = base::Uint64ToString(value);
+ ASSERT_EQ(value_string.length(),
+ base::WriteFile(path, value_string.c_str(),
+ value_string.length()));
+ }
+
+ // The MetricsCollector under test.
+ MetricsCollector daemon_;
+
+ // Temporary directory used for tests.
+ base::ScopedTempDir temp_dir_;
+
+ // Mocks. They are strict mock so that all unexpected
+ // calls are marked as failures.
+ StrictMock<MetricsLibraryMock> metrics_lib_;
+};
+
+TEST_F(MetricsCollectorTest, SendSample) {
+ ExpectSample("Dummy.Metric", 3);
+ daemon_.SendSample("Dummy.Metric", /* sample */ 3,
+ /* min */ 1, /* max */ 100, /* buckets */ 50);
+}
+
+TEST_F(MetricsCollectorTest, ProcessMeminfo) {
+ string meminfo =
+ "MemTotal: 2000000 kB\nMemFree: 500000 kB\n"
+ "Buffers: 1000000 kB\nCached: 213652 kB\n"
+ "SwapCached: 0 kB\nActive: 133400 kB\n"
+ "Inactive: 183396 kB\nActive(anon): 92984 kB\n"
+ "Inactive(anon): 58860 kB\nActive(file): 40416 kB\n"
+ "Inactive(file): 124536 kB\nUnevictable: 0 kB\n"
+ "Mlocked: 0 kB\nSwapTotal: 0 kB\n"
+ "SwapFree: 0 kB\nDirty: 40 kB\n"
+ "Writeback: 0 kB\nAnonPages: 92652 kB\n"
+ "Mapped: 59716 kB\nShmem: 59196 kB\n"
+ "Slab: 16656 kB\nSReclaimable: 6132 kB\n"
+ "SUnreclaim: 10524 kB\nKernelStack: 1648 kB\n"
+ "PageTables: 2780 kB\nNFS_Unstable: 0 kB\n"
+ "Bounce: 0 kB\nWritebackTmp: 0 kB\n"
+ "CommitLimit: 970656 kB\nCommitted_AS: 1260528 kB\n"
+ "VmallocTotal: 122880 kB\nVmallocUsed: 12144 kB\n"
+ "VmallocChunk: 103824 kB\nDirectMap4k: 9636 kB\n"
+ "DirectMap2M: 1955840 kB\n";
+
+ // All enum calls must report percents.
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100)).Times(AtLeast(1));
+ // Check that MemFree is correctly computed at 25%.
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMemFree", 25, 100))
+ .Times(AtLeast(1));
+ // Check that we call SendToUma at least once (log histogram).
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
+ .Times(AtLeast(1));
+ // Make sure we don't report fields not in the list.
+ EXPECT_CALL(metrics_lib_, SendToUMA("Platform.MeminfoMlocked", _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMlocked", _, _))
+ .Times(0);
+ EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsCollectorTest, ProcessMeminfo2) {
+ string meminfo = "MemTotal: 2000000 kB\nMemFree: 1000000 kB\n";
+ // Not enough fields.
+ EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsCollectorTest, SendZramMetrics) {
+ EXPECT_TRUE(daemon_.testing_);
+
+ // |compr_data_size| is the size in bytes of compressed data.
+ const uint64_t compr_data_size = 50 * 1000 * 1000;
+ // The constant '3' is a realistic but random choice.
+ // |orig_data_size| does not include zero pages.
+ const uint64_t orig_data_size = compr_data_size * 3;
+ const uint64_t page_size = 4096;
+ const uint64_t zero_pages = 10 * 1000 * 1000 / page_size;
+
+ CreateUint64ValueFile(
+ temp_dir_.path().Append(MetricsCollector::kComprDataSizeName),
+ compr_data_size);
+ CreateUint64ValueFile(
+ temp_dir_.path().Append(MetricsCollector::kOrigDataSizeName),
+ orig_data_size);
+ CreateUint64ValueFile(
+ temp_dir_.path().Append(MetricsCollector::kZeroPagesName), zero_pages);
+
+ const uint64_t real_orig_size = orig_data_size + zero_pages * page_size;
+ const uint64_t zero_ratio_percent =
+ zero_pages * page_size * 100 / real_orig_size;
+ // Ratio samples are in percents.
+ const uint64_t actual_ratio_sample = real_orig_size * 100 / compr_data_size;
+
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _));
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _));
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _));
+
+ EXPECT_TRUE(daemon_.ReportZram(temp_dir_.path()));
+}
diff --git a/metrics_library.cc b/metrics_library.cc
new file mode 100644
index 0000000..d211ab4
--- /dev/null
+++ b/metrics_library.cc
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "metrics/metrics_library.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <binder/IServiceManager.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <utils/String16.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "android/brillo/metrics/IMetricsd.h"
+#include "constants.h"
+
+static const char kCrosEventHistogramName[] = "Platform.CrOSEvent";
+static const int kCrosEventHistogramMax = 100;
+static const char kMetricsServiceName[] = "android.brillo.metrics.IMetricsd";
+
+/* Add new cros events here.
+ *
+ * The index of the event is sent in the message, so please do not
+ * reorder the names.
+ */
+static const char *kCrosEventNames[] = {
+ "ModemManagerCommandSendFailure", // 0
+ "HwWatchdogReboot", // 1
+ "Cras.NoCodecsFoundAtBoot", // 2
+ "Chaps.DatabaseCorrupted", // 3
+ "Chaps.DatabaseRepairFailure", // 4
+ "Chaps.DatabaseCreateFailure", // 5
+ "Attestation.OriginSpecificExhausted", // 6
+ "SpringPowerSupply.Original.High", // 7
+ "SpringPowerSupply.Other.High", // 8
+ "SpringPowerSupply.Original.Low", // 9
+ "SpringPowerSupply.ChargerIdle", // 10
+ "TPM.NonZeroDictionaryAttackCounter", // 11
+ "TPM.EarlyResetDuringCommand", // 12
+};
+
+using android::binder::Status;
+using android::brillo::metrics::IMetricsd;
+using android::String16;
+
+MetricsLibrary::MetricsLibrary() {}
+MetricsLibrary::~MetricsLibrary() {}
+
+// We take buffer and buffer_size as parameters in order to simplify testing
+// of various alignments of the |device_name| with |buffer_size|.
+bool MetricsLibrary::IsDeviceMounted(const char* device_name,
+ const char* mounts_file,
+ char* buffer,
+ int buffer_size,
+ bool* result) {
+ if (buffer == nullptr || buffer_size < 1)
+ return false;
+ int mounts_fd = open(mounts_file, O_RDONLY);
+ if (mounts_fd < 0)
+ return false;
+ // match_offset describes:
+ // -1 -- not beginning of line
+ // 0..strlen(device_name)-1 -- this offset in device_name is next to match
+ // strlen(device_name) -- matched full name, just need a space.
+ int match_offset = 0;
+ bool match = false;
+ while (!match) {
+ int read_size = read(mounts_fd, buffer, buffer_size);
+ if (read_size <= 0) {
+ if (errno == -EINTR)
+ continue;
+ break;
+ }
+ for (int i = 0; i < read_size; ++i) {
+ if (buffer[i] == '\n') {
+ match_offset = 0;
+ continue;
+ }
+ if (match_offset < 0) {
+ continue;
+ }
+ if (device_name[match_offset] == '\0') {
+ if (buffer[i] == ' ') {
+ match = true;
+ break;
+ }
+ match_offset = -1;
+ continue;
+ }
+
+ if (buffer[i] == device_name[match_offset]) {
+ ++match_offset;
+ } else {
+ match_offset = -1;
+ }
+ }
+ }
+ close(mounts_fd);
+ *result = match;
+ return true;
+}
+
+bool MetricsLibrary::IsGuestMode() {
+ char buffer[256];
+ bool result = false;
+ if (!IsDeviceMounted("guestfs",
+ "/proc/mounts",
+ buffer,
+ sizeof(buffer),
+ &result)) {
+ return false;
+ }
+ return result && (access("/var/run/state/logged-in", F_OK) == 0);
+}
+
+bool MetricsLibrary::CheckService() {
+ if (metricsd_proxy_.get() &&
+ android::IInterface::asBinder(metricsd_proxy_)->isBinderAlive())
+ return true;
+
+ const String16 name(kMetricsServiceName);
+ metricsd_proxy_ = android::interface_cast<IMetricsd>(
+ android::defaultServiceManager()->checkService(name));
+ return metricsd_proxy_.get();
+}
+
+bool MetricsLibrary::AreMetricsEnabled() {
+ static struct stat stat_buffer;
+ time_t this_check_time = time(nullptr);
+ if (!use_caching_ || this_check_time != cached_enabled_time_) {
+ cached_enabled_time_ = this_check_time;
+ cached_enabled_ = stat(consent_file_.value().data(), &stat_buffer) >= 0;
+ }
+ return cached_enabled_;
+}
+
+void MetricsLibrary::Init() {
+ base::FilePath dir = base::FilePath(metrics::kSharedMetricsDirectory);
+ consent_file_ = dir.Append(metrics::kConsentFileName);
+ cached_enabled_ = false;
+ cached_enabled_time_ = 0;
+ use_caching_ = true;
+}
+
+void MetricsLibrary::InitWithNoCaching() {
+ Init();
+ use_caching_ = false;
+}
+
+void MetricsLibrary::InitForTest(const base::FilePath& metrics_directory) {
+ consent_file_ = metrics_directory.Append(metrics::kConsentFileName);
+ cached_enabled_ = false;
+ cached_enabled_time_ = 0;
+ use_caching_ = true;
+}
+
+bool MetricsLibrary::SendToUMA(
+ const std::string& name, int sample, int min, int max, int nbuckets) {
+ return CheckService() &&
+ metricsd_proxy_->recordHistogram(String16(name.c_str()), sample, min,
+ max, nbuckets)
+ .isOk();
+}
+
+bool MetricsLibrary::SendEnumToUMA(const std::string& name,
+ int sample,
+ int max) {
+ return CheckService() &&
+ metricsd_proxy_->recordLinearHistogram(String16(name.c_str()), sample,
+ max)
+ .isOk();
+}
+
+bool MetricsLibrary::SendBoolToUMA(const std::string& name, bool sample) {
+ return CheckService() &&
+ metricsd_proxy_->recordLinearHistogram(String16(name.c_str()),
+ sample ? 1 : 0, 2)
+ .isOk();
+}
+
+bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
+ return CheckService() &&
+ metricsd_proxy_->recordSparseHistogram(String16(name.c_str()), sample)
+ .isOk();
+}
+
+bool MetricsLibrary::SendCrashToUMA(const char* crash_kind) {
+ return CheckService() &&
+ metricsd_proxy_->recordCrash(String16(crash_kind)).isOk();
+}
+
+bool MetricsLibrary::SendCrosEventToUMA(const std::string& event) {
+ for (size_t i = 0; i < arraysize(kCrosEventNames); i++) {
+ if (strcmp(event.c_str(), kCrosEventNames[i]) == 0) {
+ return SendEnumToUMA(kCrosEventHistogramName, i, kCrosEventHistogramMax);
+ }
+ }
+ return false;
+}
+
+bool MetricsLibrary::GetHistogramsDump(std::string* dump) {
+ android::String16 temp_dump;
+ if (!CheckService() ||
+ !metricsd_proxy_->getHistogramsDump(&temp_dump).isOk()) {
+ return false;
+ }
+
+ *dump = android::String8(temp_dump).string();
+ return true;
+}
diff --git a/metrics_library_test.cc b/metrics_library_test.cc
new file mode 100644
index 0000000..52fcce3
--- /dev/null
+++ b/metrics_library_test.cc
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metrics/c_metrics_library.h"
+#include "metrics/metrics_library.h"
+
+
+class MetricsLibraryTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ lib_.InitForTest(temp_dir_.path());
+ // Defeat metrics enabled caching between tests.
+ lib_.cached_enabled_time_ = 0;
+ }
+
+ void SetMetricsConsent(bool enabled) {
+ if (enabled) {
+ ASSERT_EQ(base::WriteFile(lib_.consent_file_, "", 0), 0);
+ } else {
+ ASSERT_TRUE(base::DeleteFile(lib_.consent_file_, false));
+ }
+ }
+
+ void VerifyEnabledCacheHit(bool to_value);
+ void VerifyEnabledCacheEviction(bool to_value);
+
+ MetricsLibrary lib_;
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) {
+ SetMetricsConsent(false);
+ EXPECT_FALSE(lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledTrue) {
+ SetMetricsConsent(true);
+ EXPECT_TRUE(lib_.AreMetricsEnabled());
+}
+
+void MetricsLibraryTest::VerifyEnabledCacheHit(bool to_value) {
+ // We might step from one second to the next one time, but not 100
+ // times in a row.
+ for (int i = 0; i < 100; ++i) {
+ lib_.cached_enabled_time_ = 0;
+ SetMetricsConsent(to_value);
+ lib_.AreMetricsEnabled();
+ // If we check the metrics status twice in a row, we use the cached value
+ // the second time.
+ SetMetricsConsent(!to_value);
+ if (lib_.AreMetricsEnabled() == to_value)
+ return;
+ }
+ ADD_FAILURE() << "Did not see evidence of caching";
+}
+
+void MetricsLibraryTest::VerifyEnabledCacheEviction(bool to_value) {
+ lib_.cached_enabled_time_ = 0;
+ SetMetricsConsent(!to_value);
+ ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
+
+ SetMetricsConsent(to_value);
+ // Sleep one second (or cheat to be faster) and check that we are not using
+ // the cached value.
+ --lib_.cached_enabled_time_;
+ ASSERT_EQ(to_value, lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledCaching) {
+ VerifyEnabledCacheHit(false);
+ VerifyEnabledCacheHit(true);
+ VerifyEnabledCacheEviction(false);
+ VerifyEnabledCacheEviction(true);
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledNoCaching) {
+ // disable caching.
+ lib_.use_caching_ = false;
+
+ // Checking the consent repeatedly should return the right result.
+ for (int i=0; i<100; ++i) {
+ SetMetricsConsent(true);
+ ASSERT_EQ(true, lib_.AreMetricsEnabled());
+ SetMetricsConsent(false);
+ ASSERT_EQ(false, lib_.AreMetricsEnabled());
+ }
+}
diff --git a/metricsd.rc b/metricsd.rc
new file mode 100644
index 0000000..3d3e695
--- /dev/null
+++ b/metricsd.rc
@@ -0,0 +1,9 @@
+on post-fs-data
+ mkdir /data/misc/metrics 0750 metrics_coll system
+ mkdir /data/misc/metricsd 0700 metricsd metricsd
+ mkdir /data/misc/metrics_collector 0700 metrics_coll metrics_coll
+
+service metricsd /system/bin/metricsd --foreground --logtosyslog
+ class late_start
+ user metricsd
+ group system inet
diff --git a/metricsd_main.cc b/metricsd_main.cc
new file mode 100644
index 0000000..0178342
--- /dev/null
+++ b/metricsd_main.cc
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/command_line.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <brillo/flag_helper.h>
+#include <brillo/syslog_logging.h>
+
+#include "constants.h"
+#include "uploader/metricsd_service_runner.h"
+#include "uploader/upload_service.h"
+
+int main(int argc, char** argv) {
+ DEFINE_bool(foreground, false, "Don't daemonize");
+
+ // Upload the metrics once and exit. (used for testing)
+ DEFINE_bool(uploader_test, false, "run the uploader once and exit");
+
+ // Upload Service flags.
+ DEFINE_int32(upload_interval_secs, 1800,
+ "Interval at which metricsd uploads the metrics.");
+ DEFINE_int32(disk_persistence_interval_secs, 300,
+ "Interval at which metricsd saves the aggregated metrics to "
+ "disk to avoid losing them if metricsd stops in between "
+ "two uploads.");
+ DEFINE_string(server, metrics::kMetricsServer,
+ "Server to upload the metrics to.");
+ DEFINE_string(private_directory, metrics::kMetricsdDirectory,
+ "Path to the private directory used by metricsd "
+ "(testing only)");
+ DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory,
+ "Path to the shared metrics directory, used by "
+ "metrics_collector, metricsd and all metrics clients "
+ "(testing only)");
+
+ DEFINE_bool(logtostderr, false, "Log to standard error");
+ DEFINE_bool(logtosyslog, false, "Log to syslog");
+
+ brillo::FlagHelper::Init(argc, argv, "Brillo metrics daemon.");
+
+ int logging_location =
+ (FLAGS_foreground ? brillo::kLogToStderr : brillo::kLogToSyslog);
+ if (FLAGS_logtosyslog)
+ logging_location = brillo::kLogToSyslog;
+
+ if (FLAGS_logtostderr)
+ logging_location = brillo::kLogToStderr;
+
+ // Also log to stderr when not running as daemon.
+ brillo::InitLog(logging_location | brillo::kLogHeader);
+
+ if (FLAGS_logtostderr && FLAGS_logtosyslog) {
+ LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set";
+ return 1;
+ }
+
+ if (!FLAGS_foreground && daemon(0, 0) != 0) {
+ return errno;
+ }
+
+ UploadService upload_service(
+ FLAGS_server, base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs),
+ base::TimeDelta::FromSeconds(FLAGS_disk_persistence_interval_secs),
+ base::FilePath(FLAGS_private_directory),
+ base::FilePath(FLAGS_shared_directory));
+
+ return upload_service.Run();
+}
diff --git a/persistent_integer.cc b/persistent_integer.cc
new file mode 100644
index 0000000..7fe355e
--- /dev/null
+++ b/persistent_integer.cc
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "persistent_integer.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "constants.h"
+
+namespace chromeos_metrics {
+
+PersistentInteger::PersistentInteger(const std::string& name,
+ const base::FilePath& directory)
+ : value_(0),
+ version_(kVersion),
+ name_(name),
+ backing_file_path_(directory.Append(name_)),
+ synced_(false) {}
+
+PersistentInteger::~PersistentInteger() {}
+
+void PersistentInteger::Set(int64_t value) {
+ value_ = value;
+ Write();
+}
+
+int64_t PersistentInteger::Get() {
+ // If not synced, then read. If the read fails, it's a good idea to write.
+ if (!synced_ && !Read())
+ Write();
+ return value_;
+}
+
+int64_t PersistentInteger::GetAndClear() {
+ int64_t v = Get();
+ Set(0);
+ return v;
+}
+
+void PersistentInteger::Add(int64_t x) {
+ Set(Get() + x);
+}
+
+void PersistentInteger::Write() {
+ int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC,
+ S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH));
+ PCHECK(fd >= 0) << "cannot open " << backing_file_path_.value()
+ << " for writing";
+ PCHECK((HANDLE_EINTR(write(fd, &version_, sizeof(version_))) ==
+ sizeof(version_)) &&
+ (HANDLE_EINTR(write(fd, &value_, sizeof(value_))) ==
+ sizeof(value_)))
+ << "cannot write to " << backing_file_path_.value();
+ close(fd);
+ synced_ = true;
+}
+
+bool PersistentInteger::Read() {
+ int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(), O_RDONLY));
+ if (fd < 0) {
+ PLOG(WARNING) << "cannot open " << backing_file_path_.value()
+ << " for reading";
+ return false;
+ }
+ int32_t version;
+ int64_t value;
+ bool read_succeeded = false;
+ if (HANDLE_EINTR(read(fd, &version, sizeof(version))) == sizeof(version) &&
+ version == version_ &&
+ HANDLE_EINTR(read(fd, &value, sizeof(value))) == sizeof(value)) {
+ value_ = value;
+ read_succeeded = true;
+ synced_ = true;
+ }
+ close(fd);
+ return read_succeeded;
+}
+
+} // namespace chromeos_metrics
diff --git a/persistent_integer.h b/persistent_integer.h
new file mode 100644
index 0000000..96d9fc0
--- /dev/null
+++ b/persistent_integer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_PERSISTENT_INTEGER_H_
+#define METRICS_PERSISTENT_INTEGER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+
+namespace chromeos_metrics {
+
+// PersistentIntegers is a named 64-bit integer value backed by a file.
+// The in-memory value acts as a write-through cache of the file value.
+// If the backing file doesn't exist or has bad content, the value is 0.
+
+class PersistentInteger {
+ public:
+ PersistentInteger(const std::string& name, const base::FilePath& directory);
+
+ // Virtual only because of mock.
+ virtual ~PersistentInteger();
+
+ // Sets the value. This writes through to the backing file.
+ void Set(int64_t v);
+
+ // Gets the value. May sync from backing file first.
+ int64_t Get();
+
+ // Returns the name of the object.
+ std::string Name() { return name_; }
+
+ // Convenience function for Get() followed by Set(0).
+ int64_t GetAndClear();
+
+ // Convenience function for v = Get, Set(v + x).
+ // Virtual only because of mock.
+ virtual void Add(int64_t x);
+
+ private:
+ static const int kVersion = 1001;
+
+ // Writes |value_| to the backing file, creating it if necessary.
+ void Write();
+
+ // Reads the value from the backing file, stores it in |value_|, and returns
+ // true if the backing file is valid. Returns false otherwise, and creates
+ // a valid backing file as a side effect.
+ bool Read();
+
+ int64_t value_;
+ int32_t version_;
+ std::string name_;
+ base::FilePath backing_file_path_;
+ bool synced_;
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_PERSISTENT_INTEGER_H_
diff --git a/persistent_integer_mock.h b/persistent_integer_mock.h
new file mode 100644
index 0000000..0be54d4
--- /dev/null
+++ b/persistent_integer_mock.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_PERSISTENT_INTEGER_MOCK_H_
+#define METRICS_PERSISTENT_INTEGER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "persistent_integer.h"
+
+namespace chromeos_metrics {
+
+class PersistentIntegerMock : public PersistentInteger {
+ public:
+ explicit PersistentIntegerMock(const std::string& name,
+ const base::FilePath& directory)
+ : PersistentInteger(name, directory) {}
+ MOCK_METHOD1(Add, void(int64_t count));
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_PERSISTENT_INTEGER_MOCK_H_
diff --git a/persistent_integer_test.cc b/persistent_integer_test.cc
new file mode 100644
index 0000000..bf76261
--- /dev/null
+++ b/persistent_integer_test.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "persistent_integer.h"
+
+const char kBackingFileName[] = "1.pibakf";
+
+using chromeos_metrics::PersistentInteger;
+
+class PersistentIntegerTest : public testing::Test {
+ void SetUp() override {
+ // Set testing mode.
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ protected:
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(PersistentIntegerTest, BasicChecks) {
+ std::unique_ptr<PersistentInteger> pi(
+ new PersistentInteger(kBackingFileName, temp_dir_.path()));
+
+ // Test initialization.
+ EXPECT_EQ(0, pi->Get());
+ EXPECT_EQ(kBackingFileName, pi->Name()); // boring
+
+ // Test set and add.
+ pi->Set(2);
+ pi->Add(3);
+ EXPECT_EQ(5, pi->Get());
+
+ // Test persistence.
+ pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path()));
+ EXPECT_EQ(5, pi->Get());
+
+ // Test GetAndClear.
+ EXPECT_EQ(5, pi->GetAndClear());
+ EXPECT_EQ(pi->Get(), 0);
+
+ // Another persistence test.
+ pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path()));
+ EXPECT_EQ(0, pi->Get());
+}
diff --git a/timer.cc b/timer.cc
new file mode 100644
index 0000000..06fc336
--- /dev/null
+++ b/timer.cc
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "metrics/timer.h"
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+namespace chromeos_metrics {
+
+base::TimeTicks ClockWrapper::GetCurrentTime() const {
+ return base::TimeTicks::Now();
+}
+
+Timer::Timer()
+ : timer_state_(kTimerStopped),
+ clock_wrapper_(new ClockWrapper()) {}
+
+bool Timer::Start() {
+ elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero.
+ start_time_ = clock_wrapper_->GetCurrentTime();
+ timer_state_ = kTimerRunning;
+ return true;
+}
+
+bool Timer::Stop() {
+ if (timer_state_ == kTimerStopped)
+ return false;
+ if (timer_state_ == kTimerRunning)
+ elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
+ timer_state_ = kTimerStopped;
+ return true;
+}
+
+bool Timer::Pause() {
+ switch (timer_state_) {
+ case kTimerStopped:
+ if (!Start())
+ return false;
+ timer_state_ = kTimerPaused;
+ return true;
+ case kTimerRunning:
+ timer_state_ = kTimerPaused;
+ elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Timer::Resume() {
+ switch (timer_state_) {
+ case kTimerStopped:
+ return Start();
+ case kTimerPaused:
+ start_time_ = clock_wrapper_->GetCurrentTime();
+ timer_state_ = kTimerRunning;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Timer::Reset() {
+ elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero.
+ timer_state_ = kTimerStopped;
+ return true;
+}
+
+bool Timer::HasStarted() const {
+ return timer_state_ != kTimerStopped;
+}
+
+bool Timer::GetElapsedTime(base::TimeDelta* elapsed_time) const {
+ if (start_time_.is_null() || !elapsed_time)
+ return false;
+ *elapsed_time = elapsed_time_;
+ if (timer_state_ == kTimerRunning) {
+ *elapsed_time += clock_wrapper_->GetCurrentTime() - start_time_;
+ }
+ return true;
+}
+
+// static
+MetricsLibraryInterface* TimerReporter::metrics_lib_ = nullptr;
+
+TimerReporter::TimerReporter(const std::string& histogram_name, int min,
+ int max, int num_buckets)
+ : histogram_name_(histogram_name),
+ min_(min),
+ max_(max),
+ num_buckets_(num_buckets) {}
+
+bool TimerReporter::ReportMilliseconds() const {
+ base::TimeDelta elapsed_time;
+ if (!metrics_lib_ || !GetElapsedTime(&elapsed_time)) return false;
+ return metrics_lib_->SendToUMA(histogram_name_,
+ elapsed_time.InMilliseconds(),
+ min_,
+ max_,
+ num_buckets_);
+}
+
+} // namespace chromeos_metrics
diff --git a/timer_test.cc b/timer_test.cc
new file mode 100644
index 0000000..cfbcd8a
--- /dev/null
+++ b/timer_test.cc
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <memory>
+
+#include "metrics/metrics_library_mock.h"
+#include "metrics/timer.h"
+#include "metrics/timer_mock.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace chromeos_metrics {
+
+namespace {
+const int64_t kStime1MSec = 1400;
+const int64_t kEtime1MSec = 3000;
+const int64_t kDelta1MSec = 1600;
+
+const int64_t kStime2MSec = 4200;
+const int64_t kEtime2MSec = 5000;
+const int64_t kDelta2MSec = 800;
+
+const int64_t kStime3MSec = 6600;
+const int64_t kEtime3MSec = 6800;
+const int64_t kDelta3MSec = 200;
+} // namespace
+
+class TimerTest : public testing::Test {
+ public:
+ TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+ virtual void SetUp() {
+ EXPECT_EQ(Timer::kTimerStopped, timer_.timer_state_);
+ stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
+ etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
+ stime2 += base::TimeDelta::FromMilliseconds(kStime2MSec);
+ etime2 += base::TimeDelta::FromMilliseconds(kEtime2MSec);
+ stime3 += base::TimeDelta::FromMilliseconds(kStime3MSec);
+ etime3 += base::TimeDelta::FromMilliseconds(kEtime3MSec);
+ }
+
+ virtual void TearDown() {}
+
+ Timer timer_;
+ std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_;
+ base::TimeTicks stime, etime, stime2, etime2, stime3, etime3;
+};
+
+TEST_F(TimerTest, StartStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, ReStart) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ timer_.Start();
+ base::TimeTicks buffer = timer_.start_time_;
+ timer_.Start();
+ ASSERT_FALSE(timer_.start_time_ == buffer);
+}
+
+TEST_F(TimerTest, Reset) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ timer_.Start();
+ ASSERT_TRUE(timer_.Reset());
+ ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, SeparatedTimers) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime2);
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, InvalidStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_FALSE(timer_.Stop());
+ // Now we try it again, but after a valid start/stop.
+ timer_.Start();
+ timer_.Stop();
+ base::TimeDelta elapsed_time = timer_.elapsed_time_;
+ ASSERT_FALSE(timer_.Stop());
+ ASSERT_TRUE(elapsed_time == timer_.elapsed_time_);
+}
+
+TEST_F(TimerTest, InvalidElapsedTime) {
+ base::TimeDelta elapsed_time;
+ ASSERT_FALSE(timer_.GetElapsedTime(&elapsed_time));
+}
+
+TEST_F(TimerTest, PauseStartStopResume) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2))
+ .WillOnce(Return(stime3))
+ .WillOnce(Return(etime3));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Pause()); // Starts timer paused.
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Start()); // Restarts timer.
+ ASSERT_TRUE(timer_.start_time_ == stime2);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(kDelta3MSec, elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, ResumeStartStopPause) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2))
+ .WillOnce(Return(stime3));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime2);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(0, elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_FALSE(timer_.Resume());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, PauseStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, PauseResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumePauseStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(stime3))
+ .WillOnce(Return(etime3));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+ // Make sure GetElapsedTime works while we're running.
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(kDelta1MSec + kStime3MSec - kStime2MSec,
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ kDelta1MSec + kEtime3MSec - kStime2MSec);
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ kDelta1MSec + kEtime3MSec - kStime2MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumePauseResumeStop) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime))
+ .WillOnce(Return(stime2))
+ .WillOnce(Return(etime2))
+ .WillOnce(Return(stime3))
+ .WillOnce(Return(etime3));
+ timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ ASSERT_TRUE(timer_.Start());
+ ASSERT_TRUE(timer_.start_time_ == stime);
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+ base::TimeDelta elapsed_time;
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Pause());
+ ASSERT_TRUE(timer_.HasStarted());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+
+ ASSERT_TRUE(timer_.Resume());
+ ASSERT_TRUE(timer_.HasStarted());
+
+ ASSERT_TRUE(timer_.Stop());
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ kDelta1MSec + kDelta2MSec + kDelta3MSec);
+ ASSERT_FALSE(timer_.HasStarted());
+ ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+ ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+ elapsed_time.InMilliseconds());
+}
+
+static const char kMetricName[] = "test-timer";
+static const int kMinSample = 0;
+static const int kMaxSample = 120 * 1E6;
+static const int kNumBuckets = 50;
+
+class TimerReporterTest : public testing::Test {
+ public:
+ TimerReporterTest() : timer_reporter_(kMetricName, kMinSample, kMaxSample,
+ kNumBuckets),
+ clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+ virtual void SetUp() {
+ timer_reporter_.set_metrics_lib(&lib_);
+ EXPECT_EQ(timer_reporter_.histogram_name_, kMetricName);
+ EXPECT_EQ(timer_reporter_.min_, kMinSample);
+ EXPECT_EQ(timer_reporter_.max_, kMaxSample);
+ EXPECT_EQ(timer_reporter_.num_buckets_, kNumBuckets);
+ stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
+ etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
+ }
+
+ virtual void TearDown() {
+ timer_reporter_.set_metrics_lib(nullptr);
+ }
+
+ TimerReporter timer_reporter_;
+ MetricsLibraryMock lib_;
+ std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_;
+ base::TimeTicks stime, etime;
+};
+
+TEST_F(TimerReporterTest, StartStopReport) {
+ EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+ .WillOnce(Return(stime))
+ .WillOnce(Return(etime));
+ timer_reporter_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+ EXPECT_CALL(lib_, SendToUMA(kMetricName, kDelta1MSec, kMinSample, kMaxSample,
+ kNumBuckets)).WillOnce(Return(true));
+ ASSERT_TRUE(timer_reporter_.Start());
+ ASSERT_TRUE(timer_reporter_.Stop());
+ ASSERT_TRUE(timer_reporter_.ReportMilliseconds());
+}
+
+TEST_F(TimerReporterTest, InvalidReport) {
+ ASSERT_FALSE(timer_reporter_.ReportMilliseconds());
+}
+
+} // namespace chromeos_metrics
+
+int main(int argc, char **argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/uploader/bn_metricsd_impl.cc b/uploader/bn_metricsd_impl.cc
new file mode 100644
index 0000000..219ed60
--- /dev/null
+++ b/uploader/bn_metricsd_impl.cc
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/bn_metricsd_impl.h"
+
+#include <base/metrics/histogram.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+using android::binder::Status;
+using android::String16;
+
+static const char16_t kCrashTypeKernel[] = u"kernel";
+static const char16_t kCrashTypeUncleanShutdown[] = u"uncleanshutdown";
+static const char16_t kCrashTypeUser[] = u"user";
+
+BnMetricsdImpl::BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters)
+ : counters_(counters) {
+ CHECK(counters_) << "Invalid counters argument to constructor";
+}
+
+Status BnMetricsdImpl::recordHistogram(
+ const String16& name, int sample, int min, int max, int nbuckets) {
+ base::HistogramBase* histogram = base::Histogram::FactoryGet(
+ android::String8(name).string(), min, max, nbuckets,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ // |histogram| may be null if a client reports two contradicting histograms
+ // with the same name but different limits.
+ // FactoryGet will print a useful message if that is the case.
+ if (histogram) {
+ histogram->Add(sample);
+ }
+ return Status::ok();
+}
+
+Status BnMetricsdImpl::recordLinearHistogram(const String16& name,
+ int sample,
+ int max) {
+ base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
+ android::String8(name).string(), 1, max, max + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ // |histogram| may be null if a client reports two contradicting histograms
+ // with the same name but different limits.
+ // FactoryGet will print a useful message if that is the case.
+ if (histogram) {
+ histogram->Add(sample);
+ }
+ return Status::ok();
+}
+
+Status BnMetricsdImpl::recordSparseHistogram(const String16& name, int sample) {
+ base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
+ android::String8(name).string(),
+ base::Histogram::kUmaTargetedHistogramFlag);
+ // |histogram| may be null if a client reports two contradicting histograms
+ // with the same name but different limits.
+ // FactoryGet will print a useful message if that is the case.
+ if (histogram) {
+ histogram->Add(sample);
+ }
+ return Status::ok();
+}
+
+Status BnMetricsdImpl::recordCrash(const String16& type) {
+ if (type == kCrashTypeUser) {
+ counters_->IncrementUserCrashCount();
+ } else if (type == kCrashTypeKernel) {
+ counters_->IncrementKernelCrashCount();
+ } else if (type == kCrashTypeUncleanShutdown) {
+ counters_->IncrementUncleanShutdownCount();
+ } else {
+ LOG(ERROR) << "Unknown crash type received: " << type;
+ }
+ return Status::ok();
+}
+
+Status BnMetricsdImpl::getHistogramsDump(String16* dump) {
+ std::string str_dump;
+ base::StatisticsRecorder::WriteGraph(std::string(), &str_dump);
+ *dump = String16(str_dump.c_str());
+ return Status::ok();
+}
diff --git a/uploader/bn_metricsd_impl.h b/uploader/bn_metricsd_impl.h
new file mode 100644
index 0000000..bf47e80
--- /dev/null
+++ b/uploader/bn_metricsd_impl.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
+#define METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
+
+#include "android/brillo/metrics/BnMetricsd.h"
+#include "uploader/crash_counters.h"
+
+class BnMetricsdImpl : public android::brillo::metrics::BnMetricsd {
+ public:
+ explicit BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters);
+ virtual ~BnMetricsdImpl() = default;
+
+ // Records a histogram.
+ android::binder::Status recordHistogram(const android::String16& name,
+ int sample,
+ int min,
+ int max,
+ int nbuckets) override;
+
+ // Records a linear histogram.
+ android::binder::Status recordLinearHistogram(const android::String16& name,
+ int sample,
+ int max) override;
+
+ // Records a sparse histogram.
+ android::binder::Status recordSparseHistogram(const android::String16& name,
+ int sample) override;
+
+ // Records a crash.
+ android::binder::Status recordCrash(const android::String16& type) override;
+
+ // Returns a dump of the histograms aggregated in memory.
+ android::binder::Status getHistogramsDump(android::String16* dump) override;
+
+ private:
+ std::shared_ptr<CrashCounters> counters_;
+};
+
+#endif // METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
diff --git a/uploader/crash_counters.cc b/uploader/crash_counters.cc
new file mode 100644
index 0000000..1478b9a
--- /dev/null
+++ b/uploader/crash_counters.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/crash_counters.h"
+
+CrashCounters::CrashCounters()
+ : kernel_crashes_(0), unclean_shutdowns_(0), user_crashes_(0) {}
+
+void CrashCounters::IncrementKernelCrashCount() {
+ kernel_crashes_++;
+}
+
+unsigned int CrashCounters::GetAndResetKernelCrashCount() {
+ return kernel_crashes_.exchange(0);
+}
+
+void CrashCounters::IncrementUncleanShutdownCount() {
+ unclean_shutdowns_++;
+}
+
+unsigned int CrashCounters::GetAndResetUncleanShutdownCount() {
+ return unclean_shutdowns_.exchange(0);
+}
+
+void CrashCounters::IncrementUserCrashCount() {
+ user_crashes_++;
+}
+
+unsigned int CrashCounters::GetAndResetUserCrashCount() {
+ return user_crashes_.exchange(0);
+}
diff --git a/uploader/crash_counters.h b/uploader/crash_counters.h
new file mode 100644
index 0000000..3fdbf3f
--- /dev/null
+++ b/uploader/crash_counters.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_UPLOADER_CRASH_COUNTERS_H_
+#define METRICSD_UPLOADER_CRASH_COUNTERS_H_
+
+#include <atomic>
+
+// This class is used to keep track of the crash counters.
+// An instance of it will be used by both the binder thread (to increment the
+// counters) and the uploader thread (to gather and reset the counters).
+// As such, the internal counters are atomic uints to allow concurrent access.
+class CrashCounters {
+ public:
+ CrashCounters();
+
+ void IncrementKernelCrashCount();
+ unsigned int GetAndResetKernelCrashCount();
+
+ void IncrementUserCrashCount();
+ unsigned int GetAndResetUserCrashCount();
+
+ void IncrementUncleanShutdownCount();
+ unsigned int GetAndResetUncleanShutdownCount();
+
+ private:
+ std::atomic_uint kernel_crashes_;
+ std::atomic_uint unclean_shutdowns_;
+ std::atomic_uint user_crashes_;
+};
+
+#endif // METRICSD_UPLOADER_CRASH_COUNTERS_H_
diff --git a/uploader/metrics_hashes.cc b/uploader/metrics_hashes.cc
new file mode 100644
index 0000000..208c560
--- /dev/null
+++ b/uploader/metrics_hashes.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/metrics_hashes.h"
+
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/sys_byteorder.h"
+
+namespace metrics {
+
+namespace {
+
+// Converts the 8-byte prefix of an MD5 hash into a uint64 value.
+inline uint64_t HashToUInt64(const std::string& hash) {
+ uint64_t value;
+ DCHECK_GE(hash.size(), sizeof(value));
+ memcpy(&value, hash.data(), sizeof(value));
+ return base::HostToNet64(value);
+}
+
+} // namespace
+
+uint64_t HashMetricName(const std::string& name) {
+ // Create an MD5 hash of the given |name|, represented as a byte buffer
+ // encoded as an std::string.
+ base::MD5Context context;
+ base::MD5Init(&context);
+ base::MD5Update(&context, name);
+
+ base::MD5Digest digest;
+ base::MD5Final(&digest, &context);
+
+ std::string hash_str(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
+ return HashToUInt64(hash_str);
+}
+
+} // namespace metrics
diff --git a/uploader/metrics_hashes.h b/uploader/metrics_hashes.h
new file mode 100644
index 0000000..1082b42
--- /dev/null
+++ b/uploader/metrics_hashes.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_METRICS_HASHES_H_
+#define METRICS_UPLOADER_METRICS_HASHES_H_
+
+#include <string>
+
+namespace metrics {
+
+// Computes a uint64 hash of a given string based on its MD5 hash. Suitable for
+// metric names.
+uint64_t HashMetricName(const std::string& name);
+
+} // namespace metrics
+
+#endif // METRICS_UPLOADER_METRICS_HASHES_H_
diff --git a/uploader/metrics_hashes_unittest.cc b/uploader/metrics_hashes_unittest.cc
new file mode 100644
index 0000000..b8c2575
--- /dev/null
+++ b/uploader/metrics_hashes_unittest.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/metrics_hashes.h"
+
+#include <base/format_macros.h>
+#include <base/macros.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+namespace metrics {
+
+// Make sure our ID hashes are the same as what we see on the server side.
+TEST(MetricsUtilTest, HashMetricName) {
+ static const struct {
+ std::string input;
+ std::string output;
+ } cases[] = {
+ {"Back", "0x0557fa923dcee4d0"},
+ {"Forward", "0x67d2f6740a8eaebf"},
+ {"NewTab", "0x290eb683f96572f1"},
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ uint64_t hash = HashMetricName(cases[i].input);
+ std::string hash_hex = base::StringPrintf("0x%016" PRIx64, hash);
+ EXPECT_EQ(cases[i].output, hash_hex);
+ }
+}
+
+} // namespace metrics
diff --git a/uploader/metrics_log.cc b/uploader/metrics_log.cc
new file mode 100644
index 0000000..fcaa8c1
--- /dev/null
+++ b/uploader/metrics_log.cc
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/metrics_log.h"
+
+#include <string>
+
+#include <base/files/file_util.h>
+
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_setter.h"
+
+// We use default values for the MetricsLogBase constructor as the setter will
+// override them.
+MetricsLog::MetricsLog()
+ : MetricsLogBase("", 0, metrics::MetricsLogBase::ONGOING_LOG, "") {
+}
+
+bool MetricsLog::LoadFromFile(const base::FilePath& saved_log) {
+ std::string encoded_log;
+ if (!base::ReadFileToString(saved_log, &encoded_log)) {
+ LOG(ERROR) << "Failed to read the metrics log backup from "
+ << saved_log.value();
+ return false;
+ }
+
+ if (!uma_proto()->ParseFromString(encoded_log)) {
+ LOG(ERROR) << "Failed to parse log from " << saved_log.value()
+ << ", deleting the log";
+ base::DeleteFile(saved_log, false);
+ uma_proto()->Clear();
+ return false;
+ }
+
+ VLOG(1) << uma_proto()->histogram_event_size() << " histograms loaded from "
+ << saved_log.value();
+
+ return true;
+}
+
+bool MetricsLog::SaveToFile(const base::FilePath& path) {
+ std::string encoded_log;
+ GetEncodedLog(&encoded_log);
+
+ if (static_cast<int>(encoded_log.size()) !=
+ base::WriteFile(path, encoded_log.data(), encoded_log.size())) {
+ LOG(ERROR) << "Failed to persist the current log to " << path.value();
+ return false;
+ }
+ return true;
+}
+
+void MetricsLog::IncrementUserCrashCount(unsigned int count) {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->other_user_crash_count();
+ stability->set_other_user_crash_count(current + count);
+}
+
+void MetricsLog::IncrementKernelCrashCount(unsigned int count) {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->kernel_crash_count();
+ stability->set_kernel_crash_count(current + count);
+}
+
+void MetricsLog::IncrementUncleanShutdownCount(unsigned int count) {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->unclean_system_shutdown_count();
+ stability->set_unclean_system_shutdown_count(current + count);
+}
+
+bool MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
+ CHECK(profile_setter);
+ return profile_setter->Populate(uma_proto());
+}
diff --git a/uploader/metrics_log.h b/uploader/metrics_log.h
new file mode 100644
index 0000000..9e60b97
--- /dev/null
+++ b/uploader/metrics_log.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_UPLOADER_METRICS_LOG_H_
+#define METRICSD_UPLOADER_METRICS_LOG_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+#include "uploader/metrics_log_base.h"
+
+// 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.
+class SystemProfileSetter;
+
+// This class provides base functionality for logging metrics data.
+class MetricsLog : public metrics::MetricsLogBase {
+ public:
+ // The constructor doesn't set any metadata. The metadata is only set by a
+ // SystemProfileSetter.
+ MetricsLog();
+
+ // Increment the crash counters in the protobuf.
+ // These methods don't have to be thread safe as metrics logs are only
+ // accessed by the uploader thread.
+ void IncrementUserCrashCount(unsigned int count);
+ void IncrementKernelCrashCount(unsigned int count);
+ void IncrementUncleanShutdownCount(unsigned int count);
+
+ // Populate the system profile with system information using setter.
+ bool PopulateSystemProfile(SystemProfileSetter* setter);
+
+ // Load the log from |path|.
+ bool LoadFromFile(const base::FilePath& path);
+
+ // Save this log to |path|.
+ bool SaveToFile(const base::FilePath& path);
+
+ private:
+ friend class UploadServiceTest;
+ FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
+ FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+ FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts);
+ FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+ FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+ FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+#endif // METRICSD_UPLOADER_METRICS_LOG_H_
diff --git a/uploader/metrics_log_base.cc b/uploader/metrics_log_base.cc
new file mode 100644
index 0000000..f23bd63
--- /dev/null
+++ b/uploader/metrics_log_base.cc
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/metrics_log_base.h"
+
+#include <memory>
+
+#include "base/build_time.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "uploader/metrics_hashes.h"
+#include "uploader/proto/histogram_event.pb.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/proto/user_action_event.pb.h"
+
+using base::Histogram;
+using base::HistogramBase;
+using base::HistogramSamples;
+using base::SampleCountIterator;
+using base::Time;
+using base::TimeDelta;
+using metrics::HistogramEventProto;
+using metrics::SystemProfileProto;
+using metrics::UserActionEventProto;
+
+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;
+}
+
+} // namespace
+
+MetricsLogBase::MetricsLogBase(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ const std::string& version_string)
+ : num_events_(0),
+ locked_(false),
+ log_type_(log_type) {
+ DCHECK_NE(NO_LOG, log_type);
+ 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);
+ uma_proto_.mutable_system_profile()->set_build_timestamp(GetBuildTime());
+ uma_proto_.mutable_system_profile()->set_app_version(version_string);
+}
+
+MetricsLogBase::~MetricsLogBase() {}
+
+// static
+uint64_t MetricsLogBase::Hash(const std::string& value) {
+ uint64_t hash = metrics::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.
+ VLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
+
+ return hash;
+}
+
+// static
+int64_t MetricsLogBase::GetBuildTime() {
+ static int64_t integral_build_time = 0;
+ if (!integral_build_time) {
+ Time time = base::GetBuildTime();
+ integral_build_time = static_cast<int64_t>(time.ToTimeT());
+ }
+ return integral_build_time;
+}
+
+// static
+int64_t MetricsLogBase::GetCurrentTime() {
+ return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLogBase::CloseLog() {
+ DCHECK(!locked_);
+ locked_ = true;
+}
+
+void MetricsLogBase::GetEncodedLog(std::string* encoded_log) {
+ DCHECK(locked_);
+ uma_proto_.SerializeToString(encoded_log);
+}
+
+void MetricsLogBase::RecordUserAction(const std::string& key) {
+ DCHECK(!locked_);
+
+ UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+ user_action->set_name_hash(Hash(key));
+ user_action->set_time(GetCurrentTime());
+
+ ++num_events_;
+}
+
+void MetricsLogBase::RecordHistogramDelta(const std::string& histogram_name,
+ const HistogramSamples& snapshot) {
+ DCHECK(!locked_);
+ DCHECK_NE(0, snapshot.TotalCount());
+
+ // 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(Hash(histogram_name));
+ histogram_proto->set_sum(snapshot.sum());
+
+ for (std::unique_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done();
+ it->Next()) {
+ HistogramBase::Sample min;
+ HistogramBase::Sample max;
+ HistogramBase::Count count;
+ it->Get(&min, &max, &count);
+ HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+ bucket->set_min(min);
+ bucket->set_max(max);
+ 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/uploader/metrics_log_base.h b/uploader/metrics_log_base.h
new file mode 100644
index 0000000..f4e1995
--- /dev/null
+++ b/uploader/metrics_log_base.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// 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 METRICS_UPLOADER_METRICS_LOG_BASE_H_
+#define METRICS_UPLOADER_METRICS_LOG_BASE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class HistogramSamples;
+} // namespace base
+
+namespace metrics {
+
+// This class provides base functionality for logging metrics data.
+class MetricsLogBase {
+ public:
+ // TODO(asvitkine): Remove the NO_LOG value.
+ enum LogType {
+ INITIAL_STABILITY_LOG, // The initial log containing stability stats.
+ ONGOING_LOG, // Subsequent logs in a session.
+ NO_LOG, // Placeholder value for when there is no log.
+ };
+
+ // 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
+ MetricsLogBase(const std::string& client_id,
+ int session_id,
+ LogType log_type,
+ const std::string& version_string);
+ virtual ~MetricsLogBase();
+
+ // 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);
+
+ // 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);
+
+ int num_events() { return num_events_; }
+
+ void set_hardware_class(const std::string& hardware_class) {
+ uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+ hardware_class);
+ }
+
+ LogType log_type() const { return log_type_; }
+
+ protected:
+ bool locked() const { return locked_; }
+
+ metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+ const metrics::ChromeUserMetricsExtension* uma_proto() const {
+ return &uma_proto_;
+ }
+
+ // TODO(isherman): Remove this once the XML pipeline is outta here.
+ int num_events_; // the number of events recorded in this log
+
+ private:
+ // locked_ is true when record has been packed up for sending, and should
+ // no longer be written to. It is only used for sanity checking and is
+ // not a real lock.
+ bool locked_;
+
+ // The type of the log, i.e. initial or ongoing.
+ const LogType log_type_;
+
+ // Stores the protocol buffer representation for this log.
+ metrics::ChromeUserMetricsExtension uma_proto_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLogBase);
+};
+
+} // namespace metrics
+
+#endif // METRICS_UPLOADER_METRICS_LOG_BASE_H_
diff --git a/uploader/metrics_log_base_unittest.cc b/uploader/metrics_log_base_unittest.cc
new file mode 100644
index 0000000..980afd5
--- /dev/null
+++ b/uploader/metrics_log_base_unittest.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/metrics_log_base.h"
+
+#include <string>
+
+#include <base/metrics/bucket_ranges.h>
+#include <base/metrics/sample_vector.h>
+#include <gtest/gtest.h>
+
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+namespace {
+
+class TestMetricsLogBase : public MetricsLogBase {
+ public:
+ TestMetricsLogBase()
+ : MetricsLogBase("client_id", 1, MetricsLogBase::ONGOING_LOG, "1.2.3.4") {
+ }
+ virtual ~TestMetricsLogBase() {}
+
+ using MetricsLogBase::uma_proto;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestMetricsLogBase);
+};
+
+} // namespace
+
+TEST(MetricsLogBaseTest, LogType) {
+ MetricsLogBase log1("id", 0, MetricsLogBase::ONGOING_LOG, "1.2.3");
+ EXPECT_EQ(MetricsLogBase::ONGOING_LOG, log1.log_type());
+
+ MetricsLogBase log2("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "1.2.3");
+ EXPECT_EQ(MetricsLogBase::INITIAL_STABILITY_LOG, log2.log_type());
+}
+
+TEST(MetricsLogBaseTest, EmptyRecord) {
+ MetricsLogBase log("totally bogus client ID", 137,
+ MetricsLogBase::ONGOING_LOG, "bogus version");
+ log.set_hardware_class("sample-class");
+ 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.
+ metrics::ChromeUserMetricsExtension parsed;
+ ASSERT_TRUE(parsed.ParseFromString(encoded));
+
+ metrics::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()->mutable_hardware()->set_hardware_class(
+ "sample-class");
+
+ EXPECT_EQ(expected.SerializeAsString(), encoded);
+}
+
+TEST(MetricsLogBaseTest, 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(&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.
+
+ TestMetricsLogBase log;
+ log.RecordHistogramDelta("Test", samples);
+
+ const metrics::ChromeUserMetricsExtension* uma_proto = log.uma_proto();
+ const metrics::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/uploader/metricsd_service_runner.cc b/uploader/metricsd_service_runner.cc
new file mode 100644
index 0000000..4361cac
--- /dev/null
+++ b/uploader/metricsd_service_runner.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/metricsd_service_runner.h"
+
+#include <thread>
+
+#include <binder/IServiceManager.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <utils/Errors.h>
+
+#include "uploader/bn_metricsd_impl.h"
+
+MetricsdServiceRunner::MetricsdServiceRunner(
+ std::shared_ptr<CrashCounters> counters)
+ : counters_(counters) {}
+
+void MetricsdServiceRunner::Start() {
+ thread_.reset(new std::thread(&MetricsdServiceRunner::Run, this));
+}
+
+void MetricsdServiceRunner::Run() {
+ android::sp<BnMetricsdImpl> metrics_service(new BnMetricsdImpl(counters_));
+
+ android::status_t status = android::defaultServiceManager()->addService(
+ metrics_service->getInterfaceDescriptor(), metrics_service);
+ CHECK(status == android::OK) << "Metricsd service registration failed";
+
+ message_loop_for_io_.reset(new base::MessageLoopForIO);
+ message_loop_.reset(new brillo::BaseMessageLoop(message_loop_for_io_.get()));
+
+ brillo::BinderWatcher watcher(message_loop_.get());
+ CHECK(watcher.Init()) << "failed to initialize the binder file descriptor "
+ << "watcher";
+
+ message_loop_->Run();
+
+ // Delete the message loop here as it needs to be deconstructed in the thread
+ // it is attached to.
+ message_loop_.reset();
+ message_loop_for_io_.reset();
+}
+
+void MetricsdServiceRunner::Stop() {
+ message_loop_for_io_->PostTask(FROM_HERE,
+ message_loop_for_io_->QuitWhenIdleClosure());
+
+ thread_->join();
+}
diff --git a/uploader/metricsd_service_runner.h b/uploader/metricsd_service_runner.h
new file mode 100644
index 0000000..f5dad21
--- /dev/null
+++ b/uploader/metricsd_service_runner.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
+#define METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
+
+#include <memory>
+#include <thread>
+
+#include <base/message_loop/message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "uploader/crash_counters.h"
+
+class MetricsdServiceRunner {
+ public:
+ MetricsdServiceRunner(std::shared_ptr<CrashCounters> counters);
+
+ // Start the Metricsd Binder service in a new thread.
+ void Start();
+
+ // Stop the Metricsd service and wait for its thread to exit.
+ void Stop();
+
+ private:
+ // Creates and run the main loop for metricsd's Binder service.
+ void Run();
+
+ std::unique_ptr<base::MessageLoopForIO> message_loop_for_io_;
+ std::unique_ptr<brillo::MessageLoop> message_loop_;
+
+ std::unique_ptr<std::thread> thread_;
+ std::shared_ptr<CrashCounters> counters_;
+};
+
+#endif // METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
diff --git a/uploader/mock/mock_system_profile_setter.h b/uploader/mock/mock_system_profile_setter.h
new file mode 100644
index 0000000..9b20291
--- /dev/null
+++ b/uploader/mock/mock_system_profile_setter.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
+#define METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
+
+#include "uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+// Mock profile setter used for testing.
+class MockSystemProfileSetter : public SystemProfileSetter {
+ public:
+ bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) override {
+ return true;
+ }
+};
+
+#endif // METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
diff --git a/uploader/mock/sender_mock.cc b/uploader/mock/sender_mock.cc
new file mode 100644
index 0000000..bb4dc7d
--- /dev/null
+++ b/uploader/mock/sender_mock.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/mock/sender_mock.h"
+
+SenderMock::SenderMock() {
+ Reset();
+}
+
+bool SenderMock::Send(const std::string& content, const std::string& hash) {
+ send_call_count_ += 1;
+ last_message_ = content;
+ is_good_proto_ = last_message_proto_.ParseFromString(content);
+ return should_succeed_;
+}
+
+void SenderMock::Reset() {
+ send_call_count_ = 0;
+ last_message_ = "";
+ should_succeed_ = true;
+ last_message_proto_.Clear();
+ is_good_proto_ = false;
+}
diff --git a/uploader/mock/sender_mock.h b/uploader/mock/sender_mock.h
new file mode 100644
index 0000000..e79233f
--- /dev/null
+++ b/uploader/mock/sender_mock.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+#define METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/sender.h"
+
+class SenderMock : public Sender {
+ public:
+ SenderMock();
+
+ bool Send(const std::string& content, const std::string& hash) override;
+ void Reset();
+
+ bool is_good_proto() { return is_good_proto_; }
+ int send_call_count() { return send_call_count_; }
+ const std::string last_message() { return last_message_; }
+ metrics::ChromeUserMetricsExtension last_message_proto() {
+ return last_message_proto_;
+ }
+ void set_should_succeed(bool succeed) { should_succeed_ = succeed; }
+
+ private:
+ // Is set to true if the proto was parsed successfully.
+ bool is_good_proto_;
+
+ // If set to true, the Send method will return true to simulate a successful
+ // send.
+ bool should_succeed_;
+
+ // Count of how many times Send was called since the last reset.
+ int send_call_count_;
+
+ // Last message received by Send.
+ std::string last_message_;
+
+ // If is_good_proto is true, last_message_proto is the deserialized
+ // representation of last_message.
+ metrics::ChromeUserMetricsExtension last_message_proto_;
+};
+
+#endif // METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
diff --git a/uploader/proto/README b/uploader/proto/README
new file mode 100644
index 0000000..4292a40
--- /dev/null
+++ b/uploader/proto/README
@@ -0,0 +1,37 @@
+Copyright (C) 2015 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+
+
+This directory contains the protocol buffers used by the standalone metrics
+uploader. Those protobuffers are copied from the chromium protobuffers from
+https://chromium.googlesource.com/chromium/src/+/master/components/metrics/proto/
+at 3bfe5f2b4c03d2cac718d137ed14cd2c6354bfed.
+
+Any change to this protobuf must first be made to the backend's protobuf and be
+compatible with the chromium protobuffers.
+
+
+Q: Why fork the chromium protobuffers ?
+A: The standalone metrics uploader needs chromium os fields that are not defined
+by the chromium protobufs. Instead of pushing chromium os specific changes to
+chromium, we can add them only to chromium os (and to the backend of course).
+
+
+Q: What's the difference between those protobuffers and chromium's protobuffers?
+A: When the protobuffers were copied, some chromium specific protobuffers were
+not imported:
+* omnibox related protobuffers.
+* performance profiling protobuffers (not used in chromium os).
diff --git a/uploader/proto/chrome_user_metrics_extension.proto b/uploader/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 0000000..a07830f
--- /dev/null
+++ b/uploader/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//
+// Protocol buffer for Chrome UMA (User Metrics Analysis).
+//
+// Note: this protobuf must be compatible with the one in chromium.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ChromeUserMetricsExtensionProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "system/core/metricsd/uploader/proto/histogram_event.proto";
+import "system/core/metricsd/uploader/proto/system_profile.proto";
+import "system/core/metricsd/uploader/proto/user_action_event.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;
+ }
+ // 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 HistogramEventProto histogram_event = 6;
+
+}
diff --git a/uploader/proto/histogram_event.proto b/uploader/proto/histogram_event.proto
new file mode 100644
index 0000000..3825063
--- /dev/null
+++ b/uploader/proto/histogram_event.proto
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//
+// 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.
+ 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.
+ optional int64 count = 4;
+ }
+ repeated Bucket bucket = 3;
+}
diff --git a/uploader/proto/system_profile.proto b/uploader/proto/system_profile.proto
new file mode 100644
index 0000000..bac828b
--- /dev/null
+++ b/uploader/proto/system_profile.proto
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//
+// 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: 21
+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;
+
+ message BrilloDeviceData {
+ optional string product_id = 1;
+ }
+ optional BrilloDeviceData brillo = 21;
+
+ // 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 Chrome OS 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;
+
+ // See net::NetworkChangeNotifier::ConnectionType.
+ enum ConnectionType {
+ CONNECTION_UNKNOWN = 0;
+ CONNECTION_ETHERNET = 1;
+ CONNECTION_WIFI = 2;
+ CONNECTION_2G = 3;
+ CONNECTION_3G = 4;
+ CONNECTION_4G = 5;
+ CONNECTION_BLUETOOTH = 6;
+ }
+ // 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: 24
+ 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 and hangs, 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;
+
+ // Number of renderer crashes that were for extensions. These crashes are
+ // not counted in renderer_crash_count.
+ optional int32 extension_renderer_crash_count = 5;
+
+ // 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
+ 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;
+ }
+ 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;
+}
diff --git a/uploader/proto/user_action_event.proto b/uploader/proto/user_action_event.proto
new file mode 100644
index 0000000..464f3c8
--- /dev/null
+++ b/uploader/proto/user_action_event.proto
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//
+// 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/uploader/sender.h b/uploader/sender.h
new file mode 100644
index 0000000..369c9c2
--- /dev/null
+++ b/uploader/sender.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_SENDER_H_
+#define METRICS_UPLOADER_SENDER_H_
+
+#include <string>
+
+// Abstract class for a Sender that uploads a metrics message.
+class Sender {
+ public:
+ virtual ~Sender() {}
+ // Sends a message |content| with its sha1 hash |hash|
+ virtual bool Send(const std::string& content, const std::string& hash) = 0;
+};
+
+#endif // METRICS_UPLOADER_SENDER_H_
diff --git a/uploader/sender_http.cc b/uploader/sender_http.cc
new file mode 100644
index 0000000..4b572a6
--- /dev/null
+++ b/uploader/sender_http.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/sender_http.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <brillo/http/http_utils.h>
+#include <brillo/mime_utils.h>
+
+HttpSender::HttpSender(const std::string server_url)
+ : server_url_(server_url) {}
+
+bool HttpSender::Send(const std::string& content,
+ const std::string& content_hash) {
+ const std::string hash =
+ base::HexEncode(content_hash.data(), content_hash.size());
+
+ brillo::http::HeaderList headers = {{"X-Chrome-UMA-Log-SHA1", hash}};
+ brillo::ErrorPtr error;
+ auto response = brillo::http::PostTextAndBlock(
+ server_url_,
+ content,
+ brillo::mime::application::kWwwFormUrlEncoded,
+ headers,
+ brillo::http::Transport::CreateDefault(),
+ &error);
+ if (!response || response->ExtractDataAsString() != "OK") {
+ if (error) {
+ DLOG(ERROR) << "Failed to send data: " << error->GetMessage();
+ }
+ return false;
+ }
+ return true;
+}
diff --git a/uploader/sender_http.h b/uploader/sender_http.h
new file mode 100644
index 0000000..4f1c08f
--- /dev/null
+++ b/uploader/sender_http.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_SENDER_HTTP_H_
+#define METRICS_UPLOADER_SENDER_HTTP_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "uploader/sender.h"
+
+// Sender implemented using http_utils from libbrillo
+class HttpSender : public Sender {
+ public:
+ explicit HttpSender(std::string server_url);
+ ~HttpSender() override = default;
+ // Sends |content| whose SHA1 hash is |hash| to server_url with a synchronous
+ // POST request to server_url.
+ bool Send(const std::string& content, const std::string& hash) override;
+
+ private:
+ const std::string server_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpSender);
+};
+
+#endif // METRICS_UPLOADER_SENDER_HTTP_H_
diff --git a/uploader/system_profile_cache.cc b/uploader/system_profile_cache.cc
new file mode 100644
index 0000000..e6f6617
--- /dev/null
+++ b/uploader/system_profile_cache.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/system_profile_cache.h"
+
+#include <base/files/file_util.h>
+#include <base/guid.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <brillo/osrelease_reader.h>
+#include <string>
+#include <update_engine/client.h>
+#include <vector>
+
+#include "constants.h"
+#include "persistent_integer.h"
+#include "uploader/metrics_log_base.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace {
+
+const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId";
+
+} // namespace
+
+std::string ChannelToString(
+ const metrics::SystemProfileProto_Channel& channel) {
+ switch (channel) {
+ case metrics::SystemProfileProto::CHANNEL_STABLE:
+ return "STABLE";
+ case metrics::SystemProfileProto::CHANNEL_DEV:
+ return "DEV";
+ case metrics::SystemProfileProto::CHANNEL_BETA:
+ return "BETA";
+ case metrics::SystemProfileProto::CHANNEL_CANARY:
+ return "CANARY";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+SystemProfileCache::SystemProfileCache()
+ : initialized_(false),
+ testing_(false),
+ metrics_directory_(metrics::kMetricsdDirectory),
+ session_id_(new chromeos_metrics::PersistentInteger(
+ kPersistentSessionIdFilename, metrics_directory_)) {}
+
+SystemProfileCache::SystemProfileCache(bool testing,
+ const base::FilePath& metrics_directory)
+ : initialized_(false),
+ testing_(testing),
+ metrics_directory_(metrics_directory),
+ session_id_(new chromeos_metrics::PersistentInteger(
+ kPersistentSessionIdFilename, metrics_directory)) {}
+
+bool SystemProfileCache::Initialize() {
+ CHECK(!initialized_)
+ << "this should be called only once in the metrics_daemon lifetime.";
+
+ brillo::OsReleaseReader reader;
+ std::string channel;
+ if (testing_) {
+ reader.LoadTestingOnly(metrics_directory_);
+ channel = "unknown";
+ } else {
+ reader.Load();
+ auto client = update_engine::UpdateEngineClient::CreateInstance();
+ if (!client) {
+ LOG(ERROR) << "failed to create the update engine client";
+ return false;
+ }
+ if (!client->GetChannel(&channel)) {
+ LOG(ERROR) << "failed to read the current channel from update engine.";
+ return false;
+ }
+ }
+
+ if (!reader.GetString(metrics::kProductId, &profile_.product_id)
+ || profile_.product_id.empty()) {
+ LOG(ERROR) << "product_id is not set.";
+ return false;
+ }
+
+ if (!reader.GetString(metrics::kProductVersion, &profile_.version)) {
+ LOG(ERROR) << "failed to read the product version";
+ }
+
+ if (channel.empty() || profile_.version.empty()) {
+ // If the channel or version is missing, the image is not official.
+ // In this case, set the channel to unknown and the version to 0.0.0.0 to
+ // avoid polluting the production data.
+ channel = "";
+ profile_.version = metrics::kDefaultVersion;
+ }
+ std::string guid_path = metrics_directory_.Append(
+ metrics::kMetricsGUIDFileName).value();
+ profile_.client_id = testing_ ?
+ "client_id_test" :
+ GetPersistentGUID(guid_path);
+ profile_.model_manifest_id = "unknown";
+ if (!testing_) {
+ brillo::KeyValueStore weave_config;
+ if (!weave_config.Load(base::FilePath(metrics::kWeaveConfigurationFile))) {
+ LOG(ERROR) << "Failed to load the weave configuration file.";
+ } else if (!weave_config.GetString(metrics::kModelManifestId,
+ &profile_.model_manifest_id)) {
+ LOG(ERROR) << "The model manifest id (model_id) is undefined in "
+ << metrics::kWeaveConfigurationFile;
+ }
+ }
+
+ profile_.channel = ProtoChannelFromString(channel);
+
+ // Increment the session_id everytime we initialize this. If metrics_daemon
+ // does not crash, this should correspond to the number of reboots of the
+ // system.
+ session_id_->Add(1);
+ profile_.session_id = static_cast<int32_t>(session_id_->Get());
+
+ initialized_ = true;
+ return initialized_;
+}
+
+bool SystemProfileCache::InitializeOrCheck() {
+ return initialized_ || Initialize();
+}
+
+bool SystemProfileCache::Populate(
+ metrics::ChromeUserMetricsExtension* metrics_proto) {
+ CHECK(metrics_proto);
+ if (not InitializeOrCheck()) {
+ return false;
+ }
+
+ // The client id is hashed before being sent.
+ metrics_proto->set_client_id(
+ metrics::MetricsLogBase::Hash(profile_.client_id));
+ metrics_proto->set_session_id(profile_.session_id);
+
+ // Sets the product id.
+ metrics_proto->set_product(9);
+
+ metrics::SystemProfileProto* profile_proto =
+ metrics_proto->mutable_system_profile();
+ profile_proto->mutable_hardware()->set_hardware_class(
+ profile_.model_manifest_id);
+ profile_proto->set_app_version(profile_.version);
+ profile_proto->set_channel(profile_.channel);
+ metrics::SystemProfileProto_BrilloDeviceData* device_data =
+ profile_proto->mutable_brillo();
+ device_data->set_product_id(profile_.product_id);
+
+ return true;
+}
+
+std::string SystemProfileCache::GetPersistentGUID(
+ const std::string& filename) {
+ std::string guid;
+ base::FilePath filepath(filename);
+ if (!base::ReadFileToString(filepath, &guid)) {
+ guid = base::GenerateGUID();
+ // If we can't read or write the file, the guid will not be preserved during
+ // the next reboot. Crash.
+ CHECK(base::WriteFile(filepath, guid.c_str(), guid.size()));
+ }
+ return guid;
+}
+
+metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString(
+ const std::string& channel) {
+ if (channel == "stable-channel") {
+ return metrics::SystemProfileProto::CHANNEL_STABLE;
+ } else if (channel == "dev-channel") {
+ return metrics::SystemProfileProto::CHANNEL_DEV;
+ } else if (channel == "beta-channel") {
+ return metrics::SystemProfileProto::CHANNEL_BETA;
+ } else if (channel == "canary-channel") {
+ return metrics::SystemProfileProto::CHANNEL_CANARY;
+ }
+
+ DLOG(INFO) << "unknown channel: " << channel;
+ return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
+}
diff --git a/uploader/system_profile_cache.h b/uploader/system_profile_cache.h
new file mode 100644
index 0000000..f9c484c
--- /dev/null
+++ b/uploader/system_profile_cache.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "persistent_integer.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+struct SystemProfile {
+ std::string version;
+ std::string model_manifest_id;
+ std::string client_id;
+ int session_id;
+ metrics::SystemProfileProto::Channel channel;
+ std::string product_id;
+};
+
+// Retrieves general system informations needed by the protobuf for context and
+// remembers them to avoid expensive calls.
+//
+// The cache is populated lazily. The only method needed is Populate.
+class SystemProfileCache : public SystemProfileSetter {
+ public:
+ SystemProfileCache();
+
+ SystemProfileCache(bool testing, const base::FilePath& metrics_directory);
+
+ // Populates the ProfileSystem protobuf with system information.
+ bool Populate(metrics::ChromeUserMetricsExtension* metrics_proto) override;
+
+ // Converts a string representation of the channel to a
+ // SystemProfileProto_Channel
+ static metrics::SystemProfileProto_Channel ProtoChannelFromString(
+ const std::string& channel);
+
+ // Gets the persistent GUID and create it if it has not been created yet.
+ static std::string GetPersistentGUID(const std::string& filename);
+
+ private:
+ friend class UploadServiceTest;
+ FRIEND_TEST(UploadServiceTest, ExtractChannelFromDescription);
+ FRIEND_TEST(UploadServiceTest, ReadKeyValueFromFile);
+ FRIEND_TEST(UploadServiceTest, SessionIdIncrementedAtInitialization);
+ FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+ FRIEND_TEST(UploadServiceTest, ProductIdMandatory);
+
+ // Fetches all informations and populates |profile_|
+ bool Initialize();
+
+ // Initializes |profile_| only if it has not been yet initialized.
+ bool InitializeOrCheck();
+
+ bool initialized_;
+ bool testing_;
+ base::FilePath metrics_directory_;
+ std::unique_ptr<chromeos_metrics::PersistentInteger> session_id_;
+ SystemProfile profile_;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
diff --git a/uploader/system_profile_setter.h b/uploader/system_profile_setter.h
new file mode 100644
index 0000000..bd3ff42
--- /dev/null
+++ b/uploader/system_profile_setter.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+// Abstract class used to delegate populating SystemProfileProto with system
+// information to simplify testing.
+class SystemProfileSetter {
+ public:
+ virtual ~SystemProfileSetter() {}
+ // Populates the protobuf with system informations.
+ virtual bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
diff --git a/uploader/upload_service.cc b/uploader/upload_service.cc
new file mode 100644
index 0000000..333a7e6
--- /dev/null
+++ b/uploader/upload_service.cc
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "uploader/upload_service.h"
+
+#include <sysexits.h>
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/memory/scoped_vector.h>
+#include <base/message_loop/message_loop.h>
+#include <base/metrics/histogram.h>
+#include <base/metrics/histogram_base.h>
+#include <base/metrics/histogram_snapshot_manager.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <base/sha1.h>
+
+#include "constants.h"
+#include "uploader/metrics_log.h"
+#include "uploader/sender_http.h"
+#include "uploader/system_profile_setter.h"
+
+const int UploadService::kMaxFailedUpload = 10;
+
+UploadService::UploadService(const std::string& server,
+ const base::TimeDelta& upload_interval,
+ const base::TimeDelta& disk_persistence_interval,
+ const base::FilePath& private_metrics_directory,
+ const base::FilePath& shared_metrics_directory)
+ : brillo::Daemon(),
+ histogram_snapshot_manager_(this),
+ sender_(new HttpSender(server)),
+ failed_upload_count_(metrics::kFailedUploadCountName,
+ private_metrics_directory),
+ counters_(new CrashCounters),
+ upload_interval_(upload_interval),
+ disk_persistence_interval_(disk_persistence_interval),
+ metricsd_service_runner_(counters_) {
+ staged_log_path_ = private_metrics_directory.Append(metrics::kStagedLogName);
+ saved_log_path_ = private_metrics_directory.Append(metrics::kSavedLogName);
+ consent_file_ = shared_metrics_directory.Append(metrics::kConsentFileName);
+}
+
+void UploadService::LoadSavedLog() {
+ if (base::PathExists(saved_log_path_)) {
+ GetOrCreateCurrentLog()->LoadFromFile(saved_log_path_);
+ }
+}
+
+int UploadService::OnInit() {
+ brillo::Daemon::OnInit();
+
+ base::StatisticsRecorder::Initialize();
+ metricsd_service_runner_.Start();
+
+ system_profile_setter_.reset(new SystemProfileCache());
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)),
+ upload_interval_);
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)),
+ disk_persistence_interval_);
+
+ LoadSavedLog();
+
+ return EX_OK;
+}
+
+void UploadService::OnShutdown(int* exit_code) {
+ metricsd_service_runner_.Stop();
+ PersistToDisk();
+}
+
+void UploadService::InitForTest(SystemProfileSetter* setter) {
+ LoadSavedLog();
+ system_profile_setter_.reset(setter);
+}
+
+void UploadService::StartNewLog() {
+ current_log_.reset(new MetricsLog());
+}
+
+void UploadService::UploadEventCallback() {
+ UploadEvent();
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)),
+ upload_interval_);
+}
+
+void UploadService::PersistEventCallback() {
+ PersistToDisk();
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)),
+ disk_persistence_interval_);
+}
+
+void UploadService::PersistToDisk() {
+ GatherHistograms();
+ if (current_log_) {
+ current_log_->SaveToFile(saved_log_path_);
+ }
+}
+
+void UploadService::UploadEvent() {
+ // If the system shutdown or crashed while uploading a report, we may not have
+ // deleted an old log.
+ RemoveFailedLog();
+
+ if (HasStagedLog()) {
+ // Previous upload failed, retry sending the logs.
+ SendStagedLog();
+ return;
+ }
+
+ // Previous upload successful, stage another log.
+ GatherHistograms();
+ StageCurrentLog();
+
+ // If a log is available for upload, upload it.
+ if (HasStagedLog()) {
+ SendStagedLog();
+ }
+}
+
+void UploadService::SendStagedLog() {
+ // If metrics are not enabled, discard the log and exit.
+ if (!AreMetricsEnabled()) {
+ LOG(INFO) << "Metrics disabled. Don't upload metrics samples.";
+ base::DeleteFile(staged_log_path_, false);
+ return;
+ }
+
+ std::string staged_log;
+ CHECK(base::ReadFileToString(staged_log_path_, &staged_log));
+
+ // Increase the failed count in case the daemon crashes while sending the log.
+ failed_upload_count_.Add(1);
+
+ if (!sender_->Send(staged_log, base::SHA1HashString(staged_log))) {
+ LOG(WARNING) << "log failed to upload";
+ } else {
+ VLOG(1) << "uploaded " << staged_log.length() << " bytes";
+ base::DeleteFile(staged_log_path_, false);
+ }
+
+ RemoveFailedLog();
+}
+
+void UploadService::Reset() {
+ base::DeleteFile(staged_log_path_, false);
+ current_log_.reset();
+ failed_upload_count_.Set(0);
+}
+
+void UploadService::GatherHistograms() {
+ base::StatisticsRecorder::Histograms histograms;
+ base::StatisticsRecorder::GetHistograms(&histograms);
+
+ histogram_snapshot_manager_.PrepareDeltas(
+ histograms.begin(), histograms.end(),
+ base::Histogram::kNoFlags, base::Histogram::kUmaTargetedHistogramFlag);
+
+ // Gather and reset the crash counters, shared with the binder threads.
+ unsigned int kernel_crashes = counters_->GetAndResetKernelCrashCount();
+ unsigned int unclean_shutdowns = counters_->GetAndResetUncleanShutdownCount();
+ unsigned int user_crashes = counters_->GetAndResetUserCrashCount();
+
+ // Only create a log if the counters have changed.
+ if (kernel_crashes > 0 || unclean_shutdowns > 0 || user_crashes > 0) {
+ GetOrCreateCurrentLog()->IncrementKernelCrashCount(kernel_crashes);
+ GetOrCreateCurrentLog()->IncrementUncleanShutdownCount(unclean_shutdowns);
+ GetOrCreateCurrentLog()->IncrementUserCrashCount(user_crashes);
+ }
+}
+
+void UploadService::RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) {
+ GetOrCreateCurrentLog()->RecordHistogramDelta(histogram.histogram_name(),
+ snapshot);
+}
+
+void UploadService::StageCurrentLog() {
+ // If we haven't logged anything since the last upload, don't upload an empty
+ // report.
+ if (!current_log_)
+ return;
+
+ std::unique_ptr<MetricsLog> staged_log;
+ staged_log.swap(current_log_);
+ staged_log->CloseLog();
+ if (!staged_log->PopulateSystemProfile(system_profile_setter_.get())) {
+ LOG(WARNING) << "Error while adding metadata to the log. Discarding the "
+ << "log.";
+ return;
+ }
+
+ if (!base::DeleteFile(saved_log_path_, false)) {
+ // There is a chance that we will upload the same metrics twice but, if we
+ // are lucky, the backup should be overridden before that. In doubt, try not
+ // to lose any metrics.
+ LOG(ERROR) << "failed to delete the last backup of the current log.";
+ }
+
+ failed_upload_count_.Set(0);
+ staged_log->SaveToFile(staged_log_path_);
+}
+
+MetricsLog* UploadService::GetOrCreateCurrentLog() {
+ if (!current_log_) {
+ StartNewLog();
+ }
+ return current_log_.get();
+}
+
+bool UploadService::HasStagedLog() {
+ return base::PathExists(staged_log_path_);
+}
+
+void UploadService::RemoveFailedLog() {
+ if (failed_upload_count_.Get() > kMaxFailedUpload) {
+ LOG(INFO) << "log failed more than " << kMaxFailedUpload << " times.";
+ CHECK(base::DeleteFile(staged_log_path_, false))
+ << "failed to delete staged log at " << staged_log_path_.value();
+ failed_upload_count_.Set(0);
+ }
+}
+
+bool UploadService::AreMetricsEnabled() {
+ return base::PathExists(consent_file_);
+}
diff --git a/uploader/upload_service.h b/uploader/upload_service.h
new file mode 100644
index 0000000..a1d9d3b
--- /dev/null
+++ b/uploader/upload_service.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICS_UPLOADER_UPLOAD_SERVICE_H_
+#define METRICS_UPLOADER_UPLOAD_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/metrics/histogram_base.h>
+#include <base/metrics/histogram_flattener.h>
+#include <base/metrics/histogram_snapshot_manager.h>
+#include <brillo/daemons/daemon.h>
+
+#include "persistent_integer.h"
+#include "uploader/crash_counters.h"
+#include "uploader/metrics_log.h"
+#include "uploader/metricsd_service_runner.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/sender.h"
+#include "uploader/system_profile_cache.h"
+
+class SystemProfileSetter;
+
+// Service responsible for backing up the currently aggregated metrics to disk
+// and uploading them periodically to the server.
+//
+// A given metrics sample can be in one of three locations.
+// * in-memory metrics: in memory aggregated metrics, waiting to be staged for
+// upload.
+// * saved log: protobuf message, written to disk periodically and on shutdown
+// to make a backup of metrics data for uploading later.
+// * staged log: protobuf message waiting to be uploaded.
+//
+// The service works as follows:
+// On startup, we create the in-memory metrics from the saved log if it exists.
+//
+// Periodically (every |disk_persistence_interval_| seconds), we take a snapshot
+// of the in-memory metrics and save them to disk.
+//
+// Periodically (every |upload_interval| seconds), we:
+// * take a snapshot of the in-memory metrics and create the staged log
+// * save the staged log to disk to avoid losing it if metricsd or the system
+// crashes between two uploads.
+// * delete the last saved log: all the metrics contained in it are also in the
+// newly created staged log.
+//
+// On shutdown (SIGINT or SIGTERM), we save the in-memory metrics to disk.
+//
+// Note: the in-memory metrics can be stored in |current_log_| or
+// base::StatisticsRecorder.
+class UploadService : public base::HistogramFlattener, public brillo::Daemon {
+ public:
+ UploadService(const std::string& server,
+ const base::TimeDelta& upload_interval,
+ const base::TimeDelta& disk_persistence_interval,
+ const base::FilePath& private_metrics_directory,
+ const base::FilePath& shared_metrics_directory);
+
+ // Initializes the upload service.
+ int OnInit() override;
+
+ // Cleans up the internal state before exiting.
+ void OnShutdown(int* exit_code) override;
+
+ // Starts a new log. The log needs to be regenerated after each successful
+ // launch as it is destroyed when staging the log.
+ void StartNewLog();
+
+ // Saves the current metrics to a file.
+ void PersistToDisk();
+
+ // Triggers an upload event.
+ void UploadEvent();
+
+ // Sends the staged log.
+ void SendStagedLog();
+
+ // Implements inconsistency detection to match HistogramFlattener's
+ // interface.
+ void InconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override {}
+ void UniqueInconsistencyDetected(
+ base::HistogramBase::Inconsistency problem) override {}
+ void InconsistencyDetectedInLoggedCount(int amount) override {}
+
+ private:
+ friend class UploadServiceTest;
+
+ FRIEND_TEST(UploadServiceTest, CanSendMultipleTimes);
+ FRIEND_TEST(UploadServiceTest, CorruptedSavedLog);
+ FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
+ FRIEND_TEST(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload);
+ FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent);
+ FRIEND_TEST(UploadServiceTest, FailedSendAreRetried);
+ FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+ FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts);
+ FRIEND_TEST(UploadServiceTest, LogEmptyAfterUpload);
+ FRIEND_TEST(UploadServiceTest, LogEmptyByDefault);
+ FRIEND_TEST(UploadServiceTest, LogFromTheMetricsLibrary);
+ FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+ FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+ FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, PersistEmptyLog);
+ FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+ FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+
+ // Initializes the upload service for testing.
+ void InitForTest(SystemProfileSetter* setter);
+
+ // If a staged log fails to upload more than kMaxFailedUpload times, it
+ // will be discarded.
+ static const int kMaxFailedUpload;
+
+ // Loads the log saved to disk if it exists.
+ void LoadSavedLog();
+
+ // Resets the internal state.
+ void Reset();
+
+ // Returns true iff metrics reporting is enabled.
+ bool AreMetricsEnabled();
+
+ // Event callback for handling Upload events.
+ void UploadEventCallback();
+
+ // Event callback for handling Persist events.
+ void PersistEventCallback();
+
+ // Aggregates all histogram available in memory and store them in the current
+ // log.
+ void GatherHistograms();
+
+ // Callback for HistogramSnapshotManager to store the histograms.
+ void RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) override;
+
+ // Compiles all the samples received into a single protobuf and adds all
+ // system information.
+ void StageCurrentLog();
+
+ // Returns true iff a log is staged.
+ bool HasStagedLog();
+
+ // Remove the staged log iff the upload failed more than |kMaxFailedUpload|.
+ void RemoveFailedLog();
+
+ // Returns the current log. If there is no current log, creates it first.
+ MetricsLog* GetOrCreateCurrentLog();
+
+ std::unique_ptr<SystemProfileSetter> system_profile_setter_;
+ base::HistogramSnapshotManager histogram_snapshot_manager_;
+ std::unique_ptr<Sender> sender_;
+ chromeos_metrics::PersistentInteger failed_upload_count_;
+ std::unique_ptr<MetricsLog> current_log_;
+ std::shared_ptr<CrashCounters> counters_;
+
+ base::TimeDelta upload_interval_;
+ base::TimeDelta disk_persistence_interval_;
+
+ MetricsdServiceRunner metricsd_service_runner_;
+
+ base::FilePath consent_file_;
+ base::FilePath staged_log_path_;
+ base::FilePath saved_log_path_;
+
+ bool testing_;
+};
+
+#endif // METRICS_UPLOADER_UPLOAD_SERVICE_H_
diff --git a/uploader/upload_service_test.cc b/uploader/upload_service_test.cc
new file mode 100644
index 0000000..0f77fe4
--- /dev/null
+++ b/uploader/upload_service_test.cc
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <base/at_exit.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <base/sys_info.h>
+#include <gtest/gtest.h>
+
+#include "constants.h"
+#include "persistent_integer.h"
+#include "uploader/metrics_log.h"
+#include "uploader/mock/mock_system_profile_setter.h"
+#include "uploader/mock/sender_mock.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/proto/histogram_event.pb.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_cache.h"
+#include "uploader/upload_service.h"
+
+class UploadServiceTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ CHECK(dir_.CreateUniqueTempDir());
+ // Make sure the statistics recorder is inactive (contains no metrics) then
+ // initialize it.
+ ASSERT_FALSE(base::StatisticsRecorder::IsActive());
+ base::StatisticsRecorder::Initialize();
+
+ private_dir_ = dir_.path().Append("private");
+ shared_dir_ = dir_.path().Append("shared");
+
+ EXPECT_TRUE(base::CreateDirectory(private_dir_));
+ EXPECT_TRUE(base::CreateDirectory(shared_dir_));
+
+ ASSERT_EQ(0, base::WriteFile(shared_dir_.Append(metrics::kConsentFileName),
+ "", 0));
+
+ upload_service_.reset(new UploadService(
+ "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+ counters_ = upload_service_->counters_;
+
+ upload_service_->sender_.reset(new SenderMock);
+ upload_service_->InitForTest(new MockSystemProfileSetter);
+ upload_service_->GatherHistograms();
+ upload_service_->Reset();
+ }
+
+ void SendSparseHistogram(const std::string& name, int sample) {
+ base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
+ name, base::Histogram::kUmaTargetedHistogramFlag);
+ histogram->Add(sample);
+ }
+
+ void SendHistogram(
+ const std::string& name, int sample, int min, int max, int nbuckets) {
+ base::HistogramBase* histogram = base::Histogram::FactoryGet(
+ name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
+ histogram->Add(sample);
+ }
+
+ void SetTestingProperty(const std::string& name, const std::string& value) {
+ base::FilePath filepath =
+ dir_.path().Append("etc/os-release.d").Append(name);
+ ASSERT_TRUE(base::CreateDirectory(filepath.DirName()));
+ ASSERT_EQ(value.size(),
+ base::WriteFile(filepath, value.data(), value.size()));
+ }
+
+ const metrics::SystemProfileProto_Stability GetCurrentStability() {
+ EXPECT_TRUE(upload_service_->current_log_.get());
+
+ return upload_service_->current_log_->uma_proto()
+ ->system_profile()
+ .stability();
+ }
+
+ base::ScopedTempDir dir_;
+ std::unique_ptr<UploadService> upload_service_;
+
+ std::unique_ptr<base::AtExitManager> exit_manager_;
+ std::shared_ptr<CrashCounters> counters_;
+ base::FilePath private_dir_;
+ base::FilePath shared_dir_;
+};
+
+TEST_F(UploadServiceTest, FailedSendAreRetried) {
+ SenderMock* sender = new SenderMock();
+ upload_service_->sender_.reset(sender);
+
+ sender->set_should_succeed(false);
+
+ SendSparseHistogram("hello", 1);
+ upload_service_->UploadEvent();
+ EXPECT_EQ(1, sender->send_call_count());
+ std::string sent_string = sender->last_message();
+
+ upload_service_->UploadEvent();
+ EXPECT_EQ(2, sender->send_call_count());
+ EXPECT_EQ(sent_string, sender->last_message());
+}
+
+TEST_F(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload) {
+ SenderMock* sender = new SenderMock();
+ upload_service_->sender_.reset(sender);
+
+ sender->set_should_succeed(false);
+
+ SendSparseHistogram("hello", 1);
+
+ for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+ upload_service_->UploadEvent();
+ }
+
+ EXPECT_TRUE(upload_service_->HasStagedLog());
+ upload_service_->UploadEvent();
+ EXPECT_FALSE(upload_service_->HasStagedLog());
+
+ // Log a new sample. The failed upload counter should be reset.
+ SendSparseHistogram("hello", 1);
+ for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+ upload_service_->UploadEvent();
+ }
+ // The log is not discarded after multiple failed uploads.
+ EXPECT_TRUE(upload_service_->HasStagedLog());
+}
+
+TEST_F(UploadServiceTest, EmptyLogsAreNotSent) {
+ SenderMock* sender = new SenderMock();
+ upload_service_->sender_.reset(sender);
+ upload_service_->UploadEvent();
+ EXPECT_FALSE(upload_service_->current_log_);
+ EXPECT_EQ(0, sender->send_call_count());
+}
+
+TEST_F(UploadServiceTest, LogEmptyByDefault) {
+ // current_log_ should be initialized later as it needs AtExitManager to exist
+ // in order to gather system information from SysInfo.
+ EXPECT_FALSE(upload_service_->current_log_);
+}
+
+TEST_F(UploadServiceTest, CanSendMultipleTimes) {
+ SenderMock* sender = new SenderMock();
+ upload_service_->sender_.reset(sender);
+
+ SendSparseHistogram("hello", 1);
+
+ upload_service_->UploadEvent();
+
+ std::string first_message = sender->last_message();
+ SendSparseHistogram("hello", 2);
+
+ upload_service_->UploadEvent();
+
+ EXPECT_NE(first_message, sender->last_message());
+}
+
+TEST_F(UploadServiceTest, LogEmptyAfterUpload) {
+ SendSparseHistogram("hello", 2);
+
+ upload_service_->UploadEvent();
+ EXPECT_FALSE(upload_service_->current_log_);
+}
+
+TEST_F(UploadServiceTest, LogContainsAggregatedValues) {
+ SendHistogram("foo", 11, 0, 42, 10);
+ SendHistogram("foo", 12, 0, 42, 10);
+
+ upload_service_->GatherHistograms();
+ metrics::ChromeUserMetricsExtension* proto =
+ upload_service_->current_log_->uma_proto();
+ EXPECT_EQ(1, proto->histogram_event().size());
+}
+
+TEST_F(UploadServiceTest, LogContainsCrashCounts) {
+ // By default, there is no current log.
+ upload_service_->GatherHistograms();
+ EXPECT_FALSE(upload_service_->current_log_);
+
+ // If the user crash counter is incremented, we add the count to the current
+ // log.
+ counters_->IncrementUserCrashCount();
+ upload_service_->GatherHistograms();
+ EXPECT_EQ(1, GetCurrentStability().other_user_crash_count());
+
+ // If the kernel crash counter is incremented, we add the count to the current
+ // log.
+ counters_->IncrementKernelCrashCount();
+ upload_service_->GatherHistograms();
+ EXPECT_EQ(1, GetCurrentStability().kernel_crash_count());
+
+ // If the kernel crash counter is incremented, we add the count to the current
+ // log.
+ counters_->IncrementUncleanShutdownCount();
+ counters_->IncrementUncleanShutdownCount();
+ upload_service_->GatherHistograms();
+ EXPECT_EQ(2, GetCurrentStability().unclean_system_shutdown_count());
+
+ // If no counter is incremented, the reported numbers don't change.
+ upload_service_->GatherHistograms();
+ EXPECT_EQ(1, GetCurrentStability().other_user_crash_count());
+ EXPECT_EQ(1, GetCurrentStability().kernel_crash_count());
+ EXPECT_EQ(2, GetCurrentStability().unclean_system_shutdown_count());
+}
+
+TEST_F(UploadServiceTest, ExtractChannelFromString) {
+ EXPECT_EQ(SystemProfileCache::ProtoChannelFromString("developer-build"),
+ metrics::SystemProfileProto::CHANNEL_UNKNOWN);
+
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_DEV,
+ SystemProfileCache::ProtoChannelFromString("dev-channel"));
+
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_STABLE,
+ SystemProfileCache::ProtoChannelFromString("stable-channel"));
+
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_UNKNOWN,
+ SystemProfileCache::ProtoChannelFromString("this is a test"));
+}
+
+TEST_F(UploadServiceTest, ValuesInConfigFileAreSent) {
+ SenderMock* sender = new SenderMock();
+ upload_service_->sender_.reset(sender);
+
+ SetTestingProperty(metrics::kProductId, "hello");
+ SetTestingProperty(metrics::kProductVersion, "1.2.3.4");
+
+ SendSparseHistogram("hello", 1);
+
+ // Reset to create the new log with the profile setter.
+ upload_service_->system_profile_setter_.reset(
+ new SystemProfileCache(true, dir_.path()));
+ upload_service_->Reset();
+ upload_service_->UploadEvent();
+
+ EXPECT_EQ(1, sender->send_call_count());
+ EXPECT_TRUE(sender->is_good_proto());
+ EXPECT_EQ(1, sender->last_message_proto().histogram_event().size());
+
+ EXPECT_NE(0, sender->last_message_proto().client_id());
+ EXPECT_NE(0, sender->last_message_proto().system_profile().build_timestamp());
+ EXPECT_NE(0, sender->last_message_proto().session_id());
+}
+
+TEST_F(UploadServiceTest, PersistentGUID) {
+ std::string tmp_file = dir_.path().Append("tmpfile").value();
+
+ std::string first_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+ std::string second_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+
+ // The GUID are cached.
+ EXPECT_EQ(first_guid, second_guid);
+
+ base::DeleteFile(base::FilePath(tmp_file), false);
+
+ first_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+ base::DeleteFile(base::FilePath(tmp_file), false);
+ second_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+
+ // Random GUIDs are generated (not all the same).
+ EXPECT_NE(first_guid, second_guid);
+}
+
+TEST_F(UploadServiceTest, SessionIdIncrementedAtInitialization) {
+ SetTestingProperty(metrics::kProductId, "hello");
+ SystemProfileCache cache(true, dir_.path());
+ cache.Initialize();
+ int session_id = cache.profile_.session_id;
+ cache.initialized_ = false;
+ cache.Initialize();
+ EXPECT_EQ(cache.profile_.session_id, session_id + 1);
+}
+
+// The product id must be set for metrics to be uploaded.
+// If it is not set, the system profile cache should fail to initialize.
+TEST_F(UploadServiceTest, ProductIdMandatory) {
+ SystemProfileCache cache(true, dir_.path());
+ ASSERT_FALSE(cache.Initialize());
+ SetTestingProperty(metrics::kProductId, "");
+ ASSERT_FALSE(cache.Initialize());
+ SetTestingProperty(metrics::kProductId, "hello");
+ ASSERT_TRUE(cache.Initialize());
+}
+
+TEST_F(UploadServiceTest, CurrentLogSavedAndResumed) {
+ SendHistogram("hello", 10, 0, 100, 10);
+ upload_service_->PersistToDisk();
+ EXPECT_EQ(
+ 1, upload_service_->current_log_->uma_proto()->histogram_event().size());
+ // Destroy the old service before creating a new one.
+ upload_service_.reset();
+ upload_service_.reset(new UploadService(
+ "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+ upload_service_->InitForTest(nullptr);
+
+ SendHistogram("hello", 10, 0, 100, 10);
+ upload_service_->GatherHistograms();
+ EXPECT_EQ(2, upload_service_->GetOrCreateCurrentLog()
+ ->uma_proto()
+ ->histogram_event()
+ .size());
+}
+
+TEST_F(UploadServiceTest, PersistEmptyLog) {
+ upload_service_->PersistToDisk();
+ EXPECT_FALSE(base::PathExists(upload_service_->saved_log_path_));
+}
+
+TEST_F(UploadServiceTest, CorruptedSavedLog) {
+ // Write a bogus saved log.
+ EXPECT_EQ(5, base::WriteFile(upload_service_->saved_log_path_, "hello", 5));
+
+ // Destroy the old service before creating a new one.
+ upload_service_.reset();
+ upload_service_.reset(new UploadService(
+ "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+
+ upload_service_->InitForTest(nullptr);
+ // If the log is unreadable, we drop it and continue execution.
+ ASSERT_NE(nullptr, upload_service_->GetOrCreateCurrentLog());
+ ASSERT_FALSE(base::PathExists(upload_service_->saved_log_path_));
+}