summaryrefslogtreecommitdiffstats
path: root/metricsd
diff options
context:
space:
mode:
authorBertrand SIMONNET <bsimonnet@google.com>2015-08-10 11:27:31 -0700
committerBertrand SIMONNET <bsimonnet@google.com>2015-08-10 15:26:01 -0700
commitda21ac0751a3abf3502ff74c5de7ad95db52ba30 (patch)
treec41ed39dda40a2e1a04404edac00f5d71fe9bba4 /metricsd
parentfec4d2cc3f5c1d98b616d6d9a6bc309bebb3783c (diff)
downloadcore-da21ac0751a3abf3502ff74c5de7ad95db52ba30.tar.gz
core-da21ac0751a3abf3502ff74c5de7ad95db52ba30.tar.bz2
core-da21ac0751a3abf3502ff74c5de7ad95db52ba30.zip
metricsd: Rename metrics to metricsd.
This makes the import path less confusing: * metrics/metrics_library.h is imported from the exported headers. * metricsd/* for includes by the metrics daemon itself. BUG: 22879597 Change-Id: I9f44ea3a548cae39d4546fcd724e8007f6dd4bd0
Diffstat (limited to 'metricsd')
-rw-r--r--metricsd/MODULE_LICENSE_BSD0
-rw-r--r--metricsd/NOTICE27
-rw-r--r--metricsd/OWNERS3
-rw-r--r--metricsd/README138
-rw-r--r--metricsd/WATCHLISTS16
-rw-r--r--metricsd/c_metrics_library.cc78
-rw-r--r--metricsd/constants.h29
-rw-r--r--metricsd/include/metrics/c_metrics_library.h49
-rw-r--r--metricsd/include/metrics/metrics_library.h140
-rw-r--r--metricsd/init/metrics_daemon.conf18
-rw-r--r--metricsd/init/metrics_library.conf25
-rw-r--r--metricsd/libmetrics-334380.gyp8
-rw-r--r--metricsd/libmetrics.gypi33
-rw-r--r--metricsd/libmetrics.pc.in7
-rwxr-xr-xmetricsd/make_tests.sh12
-rw-r--r--metricsd/metrics.gyp184
-rw-r--r--metricsd/metrics_client.cc198
-rw-r--r--metricsd/metrics_daemon.cc1105
-rw-r--r--metricsd/metrics_daemon.h371
-rw-r--r--metricsd/metrics_daemon_main.cc80
-rw-r--r--metricsd/metrics_daemon_test.cc388
-rw-r--r--metricsd/metrics_library.cc172
-rw-r--r--metricsd/metrics_library_mock.h29
-rw-r--r--metricsd/metrics_library_test.cc214
-rw-r--r--metricsd/persistent_integer.cc96
-rw-r--r--metricsd/persistent_integer.h67
-rw-r--r--metricsd/persistent_integer_mock.h25
-rw-r--r--metricsd/persistent_integer_test.cc66
-rwxr-xr-xmetricsd/platform2_preinstall.sh13
-rw-r--r--metricsd/serialization/metric_sample.cc197
-rw-r--r--metricsd/serialization/metric_sample.h119
-rw-r--r--metricsd/serialization/serialization_utils.cc221
-rw-r--r--metricsd/serialization/serialization_utils.h48
-rw-r--r--metricsd/serialization/serialization_utils_unittest.cc169
-rwxr-xr-xmetricsd/syslog_parser.sh69
-rw-r--r--metricsd/timer.cc108
-rw-r--r--metricsd/timer.h158
-rw-r--r--metricsd/timer_mock.h47
-rw-r--r--metricsd/timer_test.cc452
-rw-r--r--metricsd/uploader/metrics_hashes.cc39
-rw-r--r--metricsd/uploader/metrics_hashes.h18
-rw-r--r--metricsd/uploader/metrics_hashes_unittest.cc32
-rw-r--r--metricsd/uploader/metrics_log.cc41
-rw-r--r--metricsd/uploader/metrics_log.h42
-rw-r--r--metricsd/uploader/metrics_log_base.cc142
-rw-r--r--metricsd/uploader/metrics_log_base.h110
-rw-r--r--metricsd/uploader/metrics_log_base_unittest.cc125
-rw-r--r--metricsd/uploader/mock/mock_system_profile_setter.h20
-rw-r--r--metricsd/uploader/mock/sender_mock.cc24
-rw-r--r--metricsd/uploader/mock/sender_mock.h48
-rw-r--r--metricsd/uploader/proto/README25
-rw-r--r--metricsd/uploader/proto/chrome_user_metrics_extension.proto60
-rw-r--r--metricsd/uploader/proto/histogram_event.proto47
-rw-r--r--metricsd/uploader/proto/system_profile.proto747
-rw-r--r--metricsd/uploader/proto/user_action_event.proto23
-rw-r--r--metricsd/uploader/sender.h18
-rw-r--r--metricsd/uploader/sender_http.cc38
-rw-r--r--metricsd/uploader/sender_http.h29
-rw-r--r--metricsd/uploader/system_profile_cache.cc152
-rw-r--r--metricsd/uploader/system_profile_cache.h73
-rw-r--r--metricsd/uploader/system_profile_setter.h21
-rw-r--r--metricsd/uploader/upload_service.cc224
-rw-r--r--metricsd/uploader/upload_service.h153
-rw-r--r--metricsd/uploader/upload_service_test.cc254
64 files changed, 7684 insertions, 0 deletions
diff --git a/metricsd/MODULE_LICENSE_BSD b/metricsd/MODULE_LICENSE_BSD
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/metricsd/MODULE_LICENSE_BSD
diff --git a/metricsd/NOTICE b/metricsd/NOTICE
new file mode 100644
index 000000000..b9e779f92
--- /dev/null
+++ b/metricsd/NOTICE
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/metricsd/OWNERS b/metricsd/OWNERS
new file mode 100644
index 000000000..7f5e50dab
--- /dev/null
+++ b/metricsd/OWNERS
@@ -0,0 +1,3 @@
+semenzato@chromium.org
+derat@chromium.org
+bsimonnet@chromium.org
diff --git a/metricsd/README b/metricsd/README
new file mode 100644
index 000000000..4b92af35f
--- /dev/null
+++ b/metricsd/README
@@ -0,0 +1,138 @@
+Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+The Chrome OS "metrics" package contains utilities for client-side user metric
+collection.
+When Chrome is installed, Chrome will take care of aggregating and uploading the
+metrics to the UMA server.
+When Chrome is not installed (embedded build) and the metrics_uploader USE flag
+is set, metrics_daemon will aggregate and upload the metrics itself.
+
+
+================================================================================
+The Metrics Library: libmetrics
+================================================================================
+
+libmetrics is a small library that implements the basic C and 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 Chrome for transport to
+UMA. In order to use the library in a module, you need to do the following:
+
+- Add a dependence (DEPEND and RDEPEND) on chromeos-base/metrics to the module's
+ ebuild.
+
+- Link the module with libmetrics (for example, by passing -lmetrics to the
+ module's link command). Both libmetrics.so and libmetrics.a are built and
+ installed under $SYSROOT/usr/lib/. Note that by default -lmetrics will link
+ against libmetrics.so, which is preferred.
+
+- To access the metrics library API in the module, include the
+ <metrics/metrics_library.h> header file. The file is installed in
+ $SYSROOT/usr/include/ when the metrics library is built and installed.
+
+- The API is documented in metrics_library.h under src/platform/metrics/. Before
+ using the API methods, a MetricsLibrary object needs to be constructed and
+ initialized through its Init method.
+
+ For more information on the C API see c_metrics_library.h.
+
+- Samples are sent to Chrome only if the "/home/chronos/Consent To Send Stats"
+ file exists or the metrics are declared enabled in the policy file (see the
+ AreMetricsEnabled API method).
+
+- On the target platform, shortly after the sample is sent, it should be visible
+ in Chrome through "about:histograms".
+
+
+================================================================================
+Histogram Naming Convention
+================================================================================
+
+Use TrackerArea.MetricName. For example:
+
+Platform.DailyUseTime
+Network.TimeToDrop
+
+
+================================================================================
+Server Side
+================================================================================
+
+If the histogram data is visible in about:histograms, it will be sent by an
+official Chrome build to UMA, assuming the user has opted into metrics
+collection. To make the histogram visible on "chromedashboard", the histogram
+description XML file needs to be updated (steps 2 and 3 after following the
+"Details on how to add your own histograms" link under the Histograms tab).
+Include the string "Chrome OS" in the histogram description so that it's easier
+to distinguish Chrome OS specific metrics from general Chrome histograms.
+
+The UMA server logs and keeps the collected field data even if the metric's name
+is not added to the histogram XML. However, the dashboard histogram for that
+metric will show field data as of the histogram XML update date; it will not
+include data for older dates. If past data needs to be displayed, manual
+server-side intervention is required. In other words, one should assume that
+field data collection starts only after the histogram XML has been updated.
+
+
+================================================================================
+The Metrics Client: metrics_client
+================================================================================
+
+metrics_client is a simple shell command-line utility for sending histogram
+samples and user actions. It's installed under /usr/bin on the target platform
+and uses libmetrics to send the data to Chrome. The utility is useful for
+generating metrics from shell scripts.
+
+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: metrics_daemon
+================================================================================
+
+metrics_daemon is a daemon that runs in the background on the target platform
+and is intended for passive or ongoing metrics collection, or metrics collection
+requiring feedback from multiple modules. For example, it listens to D-Bus
+signals related to the user session and screen saver states to determine if the
+user is actively using the device or not and generates the corresponding
+data. The metrics daemon uses libmetrics to send the data to Chrome.
+
+The recommended way to generate metrics data from a module is to link and use
+libmetrics directly. However, the module could instead send signals to or
+communicate in some alternative way with the metrics daemon. Then the metrics
+daemon needs to monitor for the relevant events and take appropriate action --
+for example, aggregate data and send the histogram samples.
+
+
+================================================================================
+FAQ
+================================================================================
+
+Q. What should my histogram's |min| and |max| values be set at?
+
+A. 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.
+
+Q. How many buckets should I use in my histogram?
+
+A. You should allocate as many buckets as necessary to perform proper analysis
+ on the collected data. Note, however, 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 probably high).
+
+Q. When should I use an enumeration (linear) histogram vs. a regular
+ (exponential) histogram?
+
+A. 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.
diff --git a/metricsd/WATCHLISTS b/metricsd/WATCHLISTS
new file mode 100644
index 000000000..a051f350f
--- /dev/null
+++ b/metricsd/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/metricsd/c_metrics_library.cc b/metricsd/c_metrics_library.cc
new file mode 100644
index 000000000..90a2d59c9
--- /dev/null
+++ b/metricsd/c_metrics_library.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//
+// 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 CMetricsLibrarySendUserActionToUMA(CMetricsLibrary handle,
+ const char* action) {
+ MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+ if (lib == NULL)
+ return 0;
+ return lib->SendUserActionToUMA(std::string(action));
+}
+
+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/metricsd/constants.h b/metricsd/constants.h
new file mode 100644
index 000000000..d65e0e0cc
--- /dev/null
+++ b/metricsd/constants.h
@@ -0,0 +1,29 @@
+//
+// 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 kMetricsDirectory[] = "/data/misc/metrics/";
+static const char kMetricsEventsFilePath[] = "/data/misc/metrics/uma-events";
+static const char kMetricsGUIDFilePath[] = "/data/misc/metrics/Sysinfo.GUID";
+static const char kMetricsServer[] = "http://clients4.google.com/uma/v2";
+static const char kConsentFilePath[] = "/data/misc/metrics/enabled";
+static const char kDefaultVersion[] = "0.0.0.0";
+} // namespace metrics
+
+#endif // METRICS_CONSTANTS_H_
diff --git a/metricsd/include/metrics/c_metrics_library.h b/metricsd/include/metrics/c_metrics_library.h
new file mode 100644
index 000000000..7f78e43a7
--- /dev/null
+++ b/metricsd/include/metrics/c_metrics_library.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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::SendUserActionToUMA.
+int CMetricsLibrarySendUserActionToUMA(CMetricsLibrary handle,
+ const char* action);
+
+// 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/metricsd/include/metrics/metrics_library.h b/metricsd/include/metrics/metrics_library.h
new file mode 100644
index 000000000..1c124d2ae
--- /dev/null
+++ b/metricsd/include/metrics/metrics_library.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/macros.h>
+#include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+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 SendSparseToUMA(const std::string& name, int sample) = 0;
+ virtual bool SendUserActionToUMA(const std::string& action) = 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;
+
+ // 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;
+
+ // 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 user action to Chrome for transport to UMA and returns true on
+ // success. This method results in the equivalent of an asynchronous
+ // non-blocking RPC to UserMetrics::RecordAction. The new metric must be
+ // added to chrome/tools/extract_actions.py in the Chromium repository, which
+ // should then be run to generate a hash for the new action.
+ //
+ // Until http://crosbug.com/11125 is fixed, the metric must also be added to
+ // chrome/browser/chromeos/external_metrics.cc.
+ //
+ // |action| is the user-generated event (e.g., "MuteKeyPressed").
+ bool SendUserActionToUMA(const std::string& action) 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);
+
+ private:
+ friend class CMetricsLibraryTest;
+ friend class MetricsLibraryTest;
+ FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled);
+ FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
+ FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
+ FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted);
+ FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
+ FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
+
+ // 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);
+
+ // Time at which we last checked if metrics were enabled.
+ static time_t cached_enabled_time_;
+
+ // Cached state of whether or not metrics were enabled.
+ static bool cached_enabled_;
+
+ std::string uma_events_file_;
+ std::string consent_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
+};
+
+#endif // METRICS_METRICS_LIBRARY_H_
diff --git a/metricsd/init/metrics_daemon.conf b/metricsd/init/metrics_daemon.conf
new file mode 100644
index 000000000..e6932cf99
--- /dev/null
+++ b/metricsd/init/metrics_daemon.conf
@@ -0,0 +1,18 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Metrics collection daemon"
+author "chromium-os-dev@chromium.org"
+
+# The metrics daemon is responsible for receiving and forwarding to
+# chrome UMA statistics not produced by chrome.
+start on starting system-services
+stop on stopping system-services
+respawn
+
+# metrics will update the next line to add -uploader for embedded builds.
+env DAEMON_FLAGS=""
+
+expect fork
+exec metrics_daemon ${DAEMON_FLAGS}
diff --git a/metricsd/init/metrics_library.conf b/metricsd/init/metrics_library.conf
new file mode 100644
index 000000000..03016d129
--- /dev/null
+++ b/metricsd/init/metrics_library.conf
@@ -0,0 +1,25 @@
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+description "Metrics Library upstart file"
+author "chromium-os-dev@chromium.org"
+
+# The metrics library is used by several programs (daemons and others)
+# to send UMA stats.
+start on starting boot-services
+
+pre-start script
+ # Create the file used as communication endpoint for metrics.
+ METRICS_DIR=/var/lib/metrics
+ EVENTS_FILE=${METRICS_DIR}/uma-events
+ mkdir -p ${METRICS_DIR}
+ touch ${EVENTS_FILE}
+ chown chronos.chronos ${EVENTS_FILE}
+ chmod 666 ${EVENTS_FILE}
+ # TRANSITION ONLY.
+ # TODO(semenzato) Remove after Chrome change, see issue 447256.
+ # Let Chrome read the metrics file from the old location.
+ mkdir -p /var/run/metrics
+ ln -sf ${EVENTS_FILE} /var/run/metrics
+end script
diff --git a/metricsd/libmetrics-334380.gyp b/metricsd/libmetrics-334380.gyp
new file mode 100644
index 000000000..9771821fb
--- /dev/null
+++ b/metricsd/libmetrics-334380.gyp
@@ -0,0 +1,8 @@
+{
+ 'variables': {
+ 'libbase_ver': 334380,
+ },
+ 'includes': [
+ 'libmetrics.gypi',
+ ],
+}
diff --git a/metricsd/libmetrics.gypi b/metricsd/libmetrics.gypi
new file mode 100644
index 000000000..5b90a550c
--- /dev/null
+++ b/metricsd/libmetrics.gypi
@@ -0,0 +1,33 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(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/metricsd/libmetrics.pc.in b/metricsd/libmetrics.pc.in
new file mode 100644
index 000000000..233f3181a
--- /dev/null
+++ b/metricsd/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/metricsd/make_tests.sh b/metricsd/make_tests.sh
new file mode 100755
index 000000000..9dcc80475
--- /dev/null
+++ b/metricsd/make_tests.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Builds tests.
+
+set -e
+make tests
+mkdir -p "${OUT_DIR}"
+cp *_test "${OUT_DIR}"
diff --git a/metricsd/metrics.gyp b/metricsd/metrics.gyp
new file mode 100644
index 000000000..276ec7876
--- /dev/null
+++ b/metricsd/metrics.gyp
@@ -0,0 +1,184 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'dbus-1',
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(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/metricsd/metrics_client.cc b/metricsd/metrics_client.cc
new file mode 100644
index 000000000..b587e3a24
--- /dev/null
+++ b/metricsd/metrics_client.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "metrics/metrics_library.h"
+
+enum Mode {
+ kModeSendSample,
+ kModeSendEnumSample,
+ kModeSendSparseSample,
+ kModeSendUserAction,
+ 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 -u action\n"
+ " metrics_client [-cg]\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"
+ " -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"
+ " -u: send a user action to Chrome\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 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 SendUserAction(char* argv[], int action_index) {
+ const char* action = argv[action_index];
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ metrics_lib.SendUserActionToUMA(action);
+ 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, "abcegstuv")) != -1) {
+ switch (flag) {
+ case 'c':
+ mode = kModeHasConsent;
+ break;
+ case 'e':
+ mode = kModeSendEnumSample;
+ break;
+ case 'g':
+ mode = kModeIsGuestMode;
+ break;
+ case 's':
+ mode = kModeSendSparseSample;
+ break;
+ case 't':
+ secs_to_msecs = true;
+ break;
+ case 'u':
+ mode = kModeSendUserAction;
+ 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 == kModeSendUserAction)
+ expected_args = 1;
+ else if (mode == kModeSendCrosEvent)
+ expected_args = 1;
+
+ if ((arg_index + expected_args) != argc) {
+ ShowUsage();
+ }
+
+ switch (mode) {
+ case kModeSendSample:
+ case kModeSendEnumSample:
+ case kModeSendSparseSample:
+ if ((mode != kModeSendSample) && secs_to_msecs) {
+ ShowUsage();
+ }
+ return SendStats(argv,
+ arg_index,
+ mode,
+ secs_to_msecs);
+ case kModeSendUserAction:
+ return SendUserAction(argv, arg_index);
+ case kModeSendCrosEvent:
+ return SendCrosEvent(argv, arg_index);
+ case kModeHasConsent:
+ return HasConsent();
+ case kModeIsGuestMode:
+ return IsGuestMode();
+ default:
+ ShowUsage();
+ return 0;
+ }
+}
diff --git a/metricsd/metrics_daemon.cc b/metricsd/metrics_daemon.cc
new file mode 100644
index 000000000..f9061d563
--- /dev/null
+++ b/metricsd/metrics_daemon.cc
@@ -0,0 +1,1105 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics_daemon.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+
+#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 <base/sys_info.h>
+#include <dbus/dbus.h>
+#include <dbus/message.h>
+
+#include "constants.h"
+#include "uploader/upload_service.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 {
+
+#define SAFE_MESSAGE(e) (e.message ? e.message : "unknown error")
+
+const char kCrashReporterInterface[] = "org.chromium.CrashReporter";
+const char kCrashReporterUserCrashSignal[] = "UserCrash";
+const char kCrashReporterMatchRule[] =
+ "type='signal',interface='%s',path='/',member='%s'";
+
+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[] = "/var/run/kernel-crash-detected";
+const char kUncleanShutdownDetectedFile[] =
+ "/var/run/unclean-shutdown-detected";
+
+} // namespace
+
+// disk stats metrics
+
+// The {Read,Write}Sectors numbers are in sectors/second.
+// A sector is usually 512 bytes.
+
+const char MetricsDaemon::kMetricReadSectorsLongName[] =
+ "Platform.ReadSectorsLong";
+const char MetricsDaemon::kMetricWriteSectorsLongName[] =
+ "Platform.WriteSectorsLong";
+const char MetricsDaemon::kMetricReadSectorsShortName[] =
+ "Platform.ReadSectorsShort";
+const char MetricsDaemon::kMetricWriteSectorsShortName[] =
+ "Platform.WriteSectorsShort";
+
+const int MetricsDaemon::kMetricStatsShortInterval = 1; // seconds
+const int MetricsDaemon::kMetricStatsLongInterval = 30; // seconds
+
+const int MetricsDaemon::kMetricMeminfoInterval = 30; // seconds
+
+// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
+// sectors.
+const int MetricsDaemon::kMetricSectorsIOMax = 500000; // sectors/second
+const int MetricsDaemon::kMetricSectorsBuckets = 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 MetricsDaemon::kMetricPageFaultsMax = kMetricSectorsIOMax / 8;
+const int MetricsDaemon::kMetricPageFaultsBuckets = 50;
+
+// Major page faults, i.e. the ones that require data to be read from disk.
+
+const char MetricsDaemon::kMetricPageFaultsLongName[] =
+ "Platform.PageFaultsLong";
+const char MetricsDaemon::kMetricPageFaultsShortName[] =
+ "Platform.PageFaultsShort";
+
+// Swap in and Swap out
+
+const char MetricsDaemon::kMetricSwapInLongName[] =
+ "Platform.SwapInLong";
+const char MetricsDaemon::kMetricSwapInShortName[] =
+ "Platform.SwapInShort";
+
+const char MetricsDaemon::kMetricSwapOutLongName[] =
+ "Platform.SwapOutLong";
+const char MetricsDaemon::kMetricSwapOutShortName[] =
+ "Platform.SwapOutShort";
+
+const char MetricsDaemon::kMetricsProcStatFileName[] = "/proc/stat";
+const int MetricsDaemon::kMetricsProcStatFirstLineItemsCount = 11;
+
+// Thermal CPU throttling.
+
+const char MetricsDaemon::kMetricScaledCpuFrequencyName[] =
+ "Platform.CpuFrequencyThermalScaling";
+
+// Zram sysfs entries.
+
+const char MetricsDaemon::kComprDataSizeName[] = "compr_data_size";
+const char MetricsDaemon::kOrigDataSizeName[] = "orig_data_size";
+const char MetricsDaemon::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
+};
+
+MetricsDaemon::MetricsDaemon()
+ : memuse_final_time_(0),
+ memuse_interval_index_(0),
+ read_sectors_(0),
+ write_sectors_(0),
+ vmstats_(),
+ stats_state_(kStatsShort),
+ stats_initial_time_(0),
+ ticks_per_second_(0),
+ latest_cpu_use_ticks_(0) {}
+
+MetricsDaemon::~MetricsDaemon() {
+}
+
+double MetricsDaemon::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 MetricsDaemon::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);
+ }
+
+ return chromeos::DBusDaemon::Run();
+}
+
+void MetricsDaemon::RunUploaderTest() {
+ upload_service_.reset(new UploadService(new SystemProfileCache(true,
+ config_root_),
+ metrics_lib_,
+ server_));
+ upload_service_->Init(upload_interval_, metrics_file_);
+ upload_service_->UploadEvent();
+}
+
+uint32_t MetricsDaemon::GetOsVersionHash() {
+ static uint32_t cached_version_hash = 0;
+ static bool version_hash_is_cached = false;
+ if (version_hash_is_cached)
+ return cached_version_hash;
+ version_hash_is_cached = true;
+ std::string version = metrics::kDefaultVersion;
+ // The version might not be set for development devices. In this case, use the
+ // zero version.
+ base::SysInfo::GetLsbReleaseValue("BRILLO_VERSION", &version);
+ cached_version_hash = base::Hash(version);
+ if (testing_) {
+ cached_version_hash = 42; // return any plausible value for the hash
+ }
+ return cached_version_hash;
+}
+
+void MetricsDaemon::Init(bool testing,
+ bool uploader_active,
+ bool dbus_enabled,
+ MetricsLibraryInterface* metrics_lib,
+ const string& vmstats_path,
+ const string& scaling_max_freq_path,
+ const string& cpuinfo_max_freq_path,
+ const base::TimeDelta& upload_interval,
+ const string& server,
+ const string& metrics_file,
+ const string& config_root) {
+ testing_ = testing;
+ uploader_active_ = uploader_active;
+ dbus_enabled_ = dbus_enabled;
+ config_root_ = config_root;
+ DCHECK(metrics_lib != nullptr);
+ metrics_lib_ = metrics_lib;
+
+ upload_interval_ = upload_interval;
+ server_ = server;
+ metrics_file_ = metrics_file;
+
+ // Get ticks per second (HZ) on this system.
+ // Sysconf cannot fail, so no sanity checks are needed.
+ ticks_per_second_ = sysconf(_SC_CLK_TCK);
+
+ daily_active_use_.reset(
+ new PersistentInteger("Platform.DailyUseTime"));
+ version_cumulative_active_use_.reset(
+ new PersistentInteger("Platform.CumulativeDailyUseTime"));
+ version_cumulative_cpu_use_.reset(
+ new PersistentInteger("Platform.CumulativeCpuTime"));
+
+ kernel_crash_interval_.reset(
+ new PersistentInteger("Platform.KernelCrashInterval"));
+ unclean_shutdown_interval_.reset(
+ new PersistentInteger("Platform.UncleanShutdownInterval"));
+ user_crash_interval_.reset(
+ new PersistentInteger("Platform.UserCrashInterval"));
+
+ any_crashes_daily_count_.reset(
+ new PersistentInteger("Platform.AnyCrashesDaily"));
+ any_crashes_weekly_count_.reset(
+ new PersistentInteger("Platform.AnyCrashesWeekly"));
+ user_crashes_daily_count_.reset(
+ new PersistentInteger("Platform.UserCrashesDaily"));
+ user_crashes_weekly_count_.reset(
+ new PersistentInteger("Platform.UserCrashesWeekly"));
+ kernel_crashes_daily_count_.reset(
+ new PersistentInteger("Platform.KernelCrashesDaily"));
+ kernel_crashes_weekly_count_.reset(
+ new PersistentInteger("Platform.KernelCrashesWeekly"));
+ kernel_crashes_version_count_.reset(
+ new PersistentInteger("Platform.KernelCrashesSinceUpdate"));
+ unclean_shutdowns_daily_count_.reset(
+ new PersistentInteger("Platform.UncleanShutdownsDaily"));
+ unclean_shutdowns_weekly_count_.reset(
+ new PersistentInteger("Platform.UncleanShutdownsWeekly"));
+
+ daily_cycle_.reset(new PersistentInteger("daily.cycle"));
+ weekly_cycle_.reset(new PersistentInteger("weekly.cycle"));
+ version_cycle_.reset(new PersistentInteger("version.cycle"));
+
+ vmstats_path_ = vmstats_path;
+ scaling_max_freq_path_ = scaling_max_freq_path;
+ cpuinfo_max_freq_path_ = cpuinfo_max_freq_path;
+}
+
+int MetricsDaemon::OnInit() {
+ int return_code = dbus_enabled_ ? chromeos::DBusDaemon::OnInit() :
+ chromeos::Daemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ if (testing_)
+ return EX_OK;
+
+ if (dbus_enabled_) {
+ bus_->AssertOnDBusThread();
+ CHECK(bus_->SetUpAsyncOperations());
+
+ if (bus_->is_connected()) {
+ const std::string match_rule =
+ base::StringPrintf(kCrashReporterMatchRule,
+ kCrashReporterInterface,
+ kCrashReporterUserCrashSignal);
+
+ bus_->AddFilterFunction(&MetricsDaemon::MessageFilter, this);
+
+ DBusError error;
+ dbus_error_init(&error);
+ bus_->AddMatch(match_rule, &error);
+
+ if (dbus_error_is_set(&error)) {
+ LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got "
+ << error.name << ": " << error.message;
+ return EX_SOFTWARE;
+ }
+ } else {
+ LOG(ERROR) << "DBus isn't connected.";
+ return EX_UNAVAILABLE;
+ }
+ }
+
+ if (uploader_active_) {
+ upload_service_.reset(
+ new UploadService(new SystemProfileCache(), metrics_lib_, server_));
+ upload_service_->Init(upload_interval_, metrics_file_);
+ }
+
+ return EX_OK;
+}
+
+void MetricsDaemon::OnShutdown(int* return_code) {
+ if (!testing_ && dbus_enabled_ && bus_->is_connected()) {
+ const std::string match_rule =
+ base::StringPrintf(kCrashReporterMatchRule,
+ kCrashReporterInterface,
+ kCrashReporterUserCrashSignal);
+
+ bus_->RemoveFilterFunction(&MetricsDaemon::MessageFilter, this);
+
+ DBusError error;
+ dbus_error_init(&error);
+ bus_->RemoveMatch(match_rule, &error);
+
+ if (dbus_error_is_set(&error)) {
+ LOG(ERROR) << "Failed to remove match rule \"" << match_rule << "\". Got "
+ << error.name << ": " << error.message;
+ }
+ }
+ chromeos::DBusDaemon::OnShutdown(return_code);
+}
+
+// static
+DBusHandlerResult MetricsDaemon::MessageFilter(DBusConnection* connection,
+ DBusMessage* message,
+ void* user_data) {
+ int message_type = dbus_message_get_type(message);
+ if (message_type != DBUS_MESSAGE_TYPE_SIGNAL) {
+ DLOG(WARNING) << "unexpected message type " << message_type;
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ // Signal messages always have interfaces.
+ const std::string interface(dbus_message_get_interface(message));
+ const std::string member(dbus_message_get_member(message));
+ DLOG(INFO) << "Got " << interface << "." << member << " D-Bus signal";
+
+ MetricsDaemon* daemon = static_cast<MetricsDaemon*>(user_data);
+
+ DBusMessageIter iter;
+ dbus_message_iter_init(message, &iter);
+ if (interface == kCrashReporterInterface) {
+ CHECK_EQ(member, kCrashReporterUserCrashSignal);
+ daemon->ProcessUserCrash();
+ } else {
+ // Ignore messages from the bus itself.
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+// One might argue that parts of this should go into
+// chromium/src/base/sys_info_chromeos.c instead, but put it here for now.
+
+TimeDelta MetricsDaemon::GetIncrementalCpuUse() {
+ FilePath proc_stat_path = FilePath(kMetricsProcStatFileName);
+ std::string proc_stat_string;
+ if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
+ LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
+ return TimeDelta();
+ }
+
+ std::vector<std::string> proc_stat_lines;
+ base::SplitString(proc_stat_string, '\n', &proc_stat_lines);
+ if (proc_stat_lines.empty()) {
+ LOG(WARNING) << "cannot parse " << kMetricsProcStatFileName
+ << ": " << proc_stat_string;
+ return TimeDelta();
+ }
+ std::vector<std::string> proc_stat_totals;
+ base::SplitStringAlongWhitespace(proc_stat_lines[0], &proc_stat_totals);
+
+ uint64_t user_ticks, user_nice_ticks, system_ticks;
+ 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 TimeDelta(base::TimeDelta::FromSeconds(0));
+ }
+
+ uint64_t total_cpu_use_ticks = user_ticks + user_nice_ticks + system_ticks;
+
+ // Sanity check.
+ if (total_cpu_use_ticks < latest_cpu_use_ticks_) {
+ LOG(WARNING) << "CPU time decreasing from " << latest_cpu_use_ticks_
+ << " to " << total_cpu_use_ticks;
+ return TimeDelta();
+ }
+
+ uint64_t diff = total_cpu_use_ticks - latest_cpu_use_ticks_;
+ latest_cpu_use_ticks_ = total_cpu_use_ticks;
+ // Use microseconds to avoid significant truncations.
+ return base::TimeDelta::FromMicroseconds(
+ diff * 1000 * 1000 / ticks_per_second_);
+}
+
+void MetricsDaemon::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.
+ SendCrashIntervalSample(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 MetricsDaemon::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.
+ SendCrashIntervalSample(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 MetricsDaemon::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.
+ SendCrashIntervalSample(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 MetricsDaemon::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 MetricsDaemon::StatsReporterInit() {
+ DiskStatsReadStats(&read_sectors_, &write_sectors_);
+ VmStatsReadStats(&vmstats_);
+ // The first time around just run the long stat, so we don't delay boot.
+ stats_state_ = kStatsLong;
+ stats_initial_time_ = GetActiveTime();
+ if (stats_initial_time_ < 0) {
+ LOG(WARNING) << "not collecting disk stats";
+ } else {
+ ScheduleStatsCallback(kMetricStatsLongInterval);
+ }
+}
+
+void MetricsDaemon::ScheduleStatsCallback(int wait) {
+ if (testing_) {
+ return;
+ }
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::StatsCallback, base::Unretained(this)),
+ base::TimeDelta::FromSeconds(wait));
+}
+
+bool MetricsDaemon::VmStatsParseStats(const char* stats,
+ struct VmstatRecord* record) {
+ // a mapping of string name to field in VmstatRecord and whether we found it
+ struct mapping {
+ const string name;
+ uint64_t* value_p;
+ bool found;
+ } map[] =
+ { { .name = "pgmajfault",
+ .value_p = &record->page_faults_,
+ .found = false },
+ { .name = "pswpin",
+ .value_p = &record->swap_in_,
+ .found = false },
+ { .name = "pswpout",
+ .value_p = &record->swap_out_,
+ .found = false }, };
+
+ // Each line in the file has the form
+ // <ID> <VALUE>
+ // for instance:
+ // nr_free_pages 213427
+ vector<string> lines;
+ Tokenize(stats, "\n", &lines);
+ for (vector<string>::iterator it = lines.begin();
+ it != lines.end(); ++it) {
+ vector<string> tokens;
+ base::SplitString(*it, ' ', &tokens);
+ if (tokens.size() == 2) {
+ for (unsigned int i = 0; i < sizeof(map)/sizeof(struct mapping); i++) {
+ if (!tokens[0].compare(map[i].name)) {
+ if (!base::StringToUint64(tokens[1], map[i].value_p))
+ return false;
+ map[i].found = true;
+ }
+ }
+ } else {
+ LOG(WARNING) << "unexpected vmstat format";
+ }
+ }
+ // make sure we got all the stats
+ for (unsigned i = 0; i < sizeof(map)/sizeof(struct mapping); i++) {
+ if (map[i].found == false) {
+ LOG(WARNING) << "vmstat missing " << map[i].name;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MetricsDaemon::VmStatsReadStats(struct VmstatRecord* stats) {
+ string value_string;
+ FilePath* path = new FilePath(vmstats_path_);
+ if (!base::ReadFileToString(*path, &value_string)) {
+ delete path;
+ LOG(WARNING) << "cannot read " << vmstats_path_;
+ return false;
+ }
+ delete path;
+ return VmStatsParseStats(value_string.c_str(), stats);
+}
+
+bool MetricsDaemon::ReadFreqToInt(const string& sysfs_file_name, int* value) {
+ const FilePath sysfs_path(sysfs_file_name);
+ string value_string;
+ if (!base::ReadFileToString(sysfs_path, &value_string)) {
+ LOG(WARNING) << "cannot read " << sysfs_path.value().c_str();
+ return false;
+ }
+ if (!base::RemoveChars(value_string, "\n", &value_string)) {
+ LOG(WARNING) << "no newline in " << value_string;
+ // Continue even though the lack of newline is suspicious.
+ }
+ if (!base::StringToInt(value_string, value)) {
+ LOG(WARNING) << "cannot convert " << value_string << " to int";
+ return false;
+ }
+ return true;
+}
+
+void MetricsDaemon::SendCpuThrottleMetrics() {
+ // |max_freq| is 0 only the first time through.
+ static int max_freq = 0;
+ if (max_freq == -1)
+ // Give up, as sysfs did not report max_freq correctly.
+ return;
+ if (max_freq == 0 || testing_) {
+ // One-time initialization of max_freq. (Every time when testing.)
+ if (!ReadFreqToInt(cpuinfo_max_freq_path_, &max_freq)) {
+ max_freq = -1;
+ return;
+ }
+ if (max_freq == 0) {
+ LOG(WARNING) << "sysfs reports 0 max CPU frequency\n";
+ max_freq = -1;
+ return;
+ }
+ if (max_freq % 10000 == 1000) {
+ // Special case: system has turbo mode, and max non-turbo frequency is
+ // max_freq - 1000. This relies on "normal" (non-turbo) frequencies
+ // being multiples of (at least) 10 MHz. Although there is no guarantee
+ // of this, it seems a fairly reasonable assumption. Otherwise we should
+ // read scaling_available_frequencies, sort the frequencies, compare the
+ // two highest ones, and check if they differ by 1000 (kHz) (and that's a
+ // hack too, no telling when it will change).
+ max_freq -= 1000;
+ }
+ }
+ int scaled_freq = 0;
+ if (!ReadFreqToInt(scaling_max_freq_path_, &scaled_freq))
+ return;
+ // Frequencies are in kHz. If scaled_freq > max_freq, turbo is on, but
+ // scaled_freq is not the actual turbo frequency. We indicate this situation
+ // with a 101% value.
+ int percent = scaled_freq > max_freq ? 101 : scaled_freq / (max_freq / 100);
+ SendLinearSample(kMetricScaledCpuFrequencyName, percent, 101, 102);
+}
+
+// Collects disk and vm stats alternating over a short and a long interval.
+
+void MetricsDaemon::StatsCallback() {
+ uint64_t read_sectors_now, write_sectors_now;
+ struct VmstatRecord vmstats_now;
+ double time_now = GetActiveTime();
+ double delta_time = time_now - stats_initial_time_;
+ if (testing_) {
+ // Fake the time when testing.
+ delta_time = stats_state_ == kStatsShort ?
+ kMetricStatsShortInterval : kMetricStatsLongInterval;
+ }
+ 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;
+
+ switch (stats_state_) {
+ case kStatsShort:
+ if (diskstats_success) {
+ SendSample(kMetricReadSectorsShortName,
+ read_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ SendSample(kMetricWriteSectorsShortName,
+ write_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ }
+ if (vmstats_success) {
+ SendSample(kMetricPageFaultsShortName,
+ page_faults_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapInShortName,
+ swap_in_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapOutShortName,
+ swap_out_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ }
+ // Schedule long callback.
+ stats_state_ = kStatsLong;
+ ScheduleStatsCallback(kMetricStatsLongInterval -
+ kMetricStatsShortInterval);
+ break;
+ case kStatsLong:
+ if (diskstats_success) {
+ SendSample(kMetricReadSectorsLongName,
+ read_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ SendSample(kMetricWriteSectorsLongName,
+ write_sectors_per_second,
+ 1,
+ kMetricSectorsIOMax,
+ kMetricSectorsBuckets);
+ // Reset sector counters.
+ read_sectors_ = read_sectors_now;
+ write_sectors_ = write_sectors_now;
+ }
+ if (vmstats_success) {
+ SendSample(kMetricPageFaultsLongName,
+ page_faults_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapInLongName,
+ swap_in_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+ SendSample(kMetricSwapOutLongName,
+ swap_out_per_second,
+ 1,
+ kMetricPageFaultsMax,
+ kMetricPageFaultsBuckets);
+
+ vmstats_ = vmstats_now;
+ }
+ SendCpuThrottleMetrics();
+ // Set start time for new cycle.
+ stats_initial_time_ = time_now;
+ // Schedule short callback.
+ stats_state_ = kStatsShort;
+ ScheduleStatsCallback(kMetricStatsShortInterval);
+ break;
+ default:
+ LOG(FATAL) << "Invalid stats state";
+ }
+}
+
+void MetricsDaemon::ScheduleMeminfoCallback(int wait) {
+ if (testing_) {
+ return;
+ }
+ base::TimeDelta waitDelta = base::TimeDelta::FromSeconds(wait);
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::MeminfoCallback, base::Unretained(this),
+ waitDelta),
+ waitDelta);
+}
+
+void MetricsDaemon::MeminfoCallback(base::TimeDelta wait) {
+ string meminfo_raw;
+ const FilePath meminfo_path("/proc/meminfo");
+ 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.
+ bool success = ProcessMeminfo(meminfo_raw);
+ bool reschedule =
+ ReportZram(base::FilePath(FILE_PATH_LITERAL("/sys/block/zram0"))) &&
+ success;
+ if (reschedule) {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::MeminfoCallback, base::Unretained(this),
+ wait),
+ wait);
+ }
+}
+
+// static
+bool MetricsDaemon::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 MetricsDaemon::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 MetricsDaemon::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.MeminfoSwapUsedPercent", swap_used_percent,
+ 100, 101);
+ }
+ return true;
+}
+
+bool MetricsDaemon::FillMeminfo(const string& meminfo_raw,
+ vector<MeminfoRecord>* fields) {
+ vector<string> lines;
+ unsigned int nlines = Tokenize(meminfo_raw, "\n", &lines);
+
+ // 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.
+ unsigned int ifield = 0;
+ for (unsigned int iline = 0;
+ iline < nlines && ifield < fields->size();
+ iline++) {
+ vector<string> tokens;
+ Tokenize(lines[iline], ": ", &tokens);
+ if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) {
+ // Name matches. Parse value and save.
+ char* rest;
+ (*fields)[ifield].value =
+ static_cast<int>(strtol(tokens[1].c_str(), &rest, 10));
+ if (*rest != '\0') {
+ LOG(WARNING) << "missing meminfo value";
+ 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 MetricsDaemon::ScheduleMemuseCallback(double interval) {
+ if (testing_) {
+ return;
+ }
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::MemuseCallback, base::Unretained(this)),
+ base::TimeDelta::FromSeconds(interval));
+}
+
+void MetricsDaemon::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 MetricsDaemon::MemuseCallbackWork() {
+ string meminfo_raw;
+ const FilePath meminfo_path("/proc/meminfo");
+ if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+ LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+ return false;
+ }
+ return ProcessMemuse(meminfo_raw);
+}
+
+bool MetricsDaemon::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 MetricsDaemon::SendSample(const string& name, int sample,
+ int min, int max, int nbuckets) {
+ metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
+}
+
+void MetricsDaemon::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 / 1000, // stat is in 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 MetricsDaemon::SendDailyUseSample(
+ const scoped_ptr<PersistentInteger>& use) {
+ SendSample(use->Name(),
+ use->GetAndClear(),
+ 1, // value of first bucket
+ kSecondsPerDay, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsDaemon::SendCrashIntervalSample(
+ const scoped_ptr<PersistentInteger>& interval) {
+ SendSample(interval->Name(),
+ interval->GetAndClear(),
+ 1, // value of first bucket
+ 4 * kSecondsPerWeek, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsDaemon::SendCrashFrequencySample(
+ const scoped_ptr<PersistentInteger>& frequency) {
+ SendSample(frequency->Name(),
+ frequency->GetAndClear(),
+ 1, // value of first bucket
+ 100, // value of last bucket
+ 50); // number of buckets
+}
+
+void MetricsDaemon::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 MetricsDaemon::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);
+ version_cumulative_cpu_use_->Add(GetIncrementalCpuUse().InMilliseconds());
+ 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);
+ SendDailyUseSample(daily_active_use_);
+ SendDailyUseSample(version_cumulative_active_use_);
+ SendCrashFrequencySample(any_crashes_daily_count_);
+ SendCrashFrequencySample(user_crashes_daily_count_);
+ SendCrashFrequencySample(kernel_crashes_daily_count_);
+ SendCrashFrequencySample(unclean_shutdowns_daily_count_);
+ SendKernelCrashesCumulativeCountStats();
+ }
+
+ if (weekly_cycle_->Get() != week) {
+ weekly_cycle_->Set(week);
+ SendCrashFrequencySample(any_crashes_weekly_count_);
+ SendCrashFrequencySample(user_crashes_weekly_count_);
+ SendCrashFrequencySample(kernel_crashes_weekly_count_);
+ SendCrashFrequencySample(unclean_shutdowns_weekly_count_);
+ }
+}
+
+void MetricsDaemon::HandleUpdateStatsTimeout() {
+ UpdateStats(TimeTicks::Now(), Time::Now());
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&MetricsDaemon::HandleUpdateStatsTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+}
diff --git a/metricsd/metrics_daemon.h b/metricsd/metrics_daemon.h
new file mode 100644
index 000000000..ccac52a19
--- /dev/null
+++ b/metricsd/metrics_daemon.h
@@ -0,0 +1,371 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_METRICS_DAEMON_H_
+#define METRICS_METRICS_DAEMON_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/time/time.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "metrics/metrics_library.h"
+#include "persistent_integer.h"
+#include "uploader/upload_service.h"
+
+using chromeos_metrics::PersistentInteger;
+
+class MetricsDaemon : public chromeos::DBusDaemon {
+ public:
+ MetricsDaemon();
+ ~MetricsDaemon();
+
+ // Initializes metrics class variables.
+ void Init(bool testing,
+ bool uploader_active,
+ bool dbus_enabled,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& vmstats_path,
+ const std::string& cpuinfo_max_freq_path,
+ const std::string& scaling_max_freq_path,
+ const base::TimeDelta& upload_interval,
+ const std::string& server,
+ const std::string& metrics_file,
+ const std::string& config_root);
+
+ // Initializes DBus and MessageLoop variables before running the MessageLoop.
+ int OnInit() override;
+
+ // Clean up data set up in OnInit before shutting down message loop.
+ void OnShutdown(int* return_code) override;
+
+ // Does all the work.
+ int Run() override;
+
+ // Triggers an upload event and exit. (Used to test UploadService)
+ void RunUploaderTest();
+
+ protected:
+ // Used also by the unit tests.
+ static const char kComprDataSizeName[];
+ static const char kOrigDataSizeName[];
+ static const char kZeroPagesName[];
+
+ private:
+ friend class MetricsDaemonTest;
+ FRIEND_TEST(MetricsDaemonTest, CheckSystemCrash);
+ FRIEND_TEST(MetricsDaemonTest, ComputeEpochNoCurrent);
+ FRIEND_TEST(MetricsDaemonTest, ComputeEpochNoLast);
+ FRIEND_TEST(MetricsDaemonTest, GetHistogramPath);
+ FRIEND_TEST(MetricsDaemonTest, IsNewEpoch);
+ FRIEND_TEST(MetricsDaemonTest, MessageFilter);
+ FRIEND_TEST(MetricsDaemonTest, ParseVmStats);
+ FRIEND_TEST(MetricsDaemonTest, ProcessKernelCrash);
+ FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo);
+ FRIEND_TEST(MetricsDaemonTest, ProcessMeminfo2);
+ FRIEND_TEST(MetricsDaemonTest, ProcessUncleanShutdown);
+ FRIEND_TEST(MetricsDaemonTest, ProcessUserCrash);
+ FRIEND_TEST(MetricsDaemonTest, ReportCrashesDailyFrequency);
+ FRIEND_TEST(MetricsDaemonTest, ReadFreqToInt);
+ FRIEND_TEST(MetricsDaemonTest, ReportDiskStats);
+ FRIEND_TEST(MetricsDaemonTest, ReportKernelCrashInterval);
+ FRIEND_TEST(MetricsDaemonTest, ReportUncleanShutdownInterval);
+ FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval);
+ FRIEND_TEST(MetricsDaemonTest, SendSample);
+ FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics);
+ FRIEND_TEST(MetricsDaemonTest, SendZramMetrics);
+
+ // State for disk stats collector callback.
+ enum StatsState {
+ kStatsShort, // short wait before short interval collection
+ kStatsLong, // final wait before new collection
+ };
+
+ // Data record for aggregating daily usage.
+ class UseRecord {
+ public:
+ UseRecord() : day_(0), seconds_(0) {}
+ int day_;
+ int seconds_;
+ };
+
+ // 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
+ };
+
+ // 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
+ };
+
+ // Metric parameters.
+ static const char kMetricReadSectorsLongName[];
+ static const char kMetricReadSectorsShortName[];
+ static const char kMetricWriteSectorsLongName[];
+ static const char kMetricWriteSectorsShortName[];
+ static const char kMetricPageFaultsShortName[];
+ static const char kMetricPageFaultsLongName[];
+ static const char kMetricSwapInLongName[];
+ static const char kMetricSwapInShortName[];
+ static const char kMetricSwapOutLongName[];
+ static const char kMetricSwapOutShortName[];
+ static const char kMetricScaledCpuFrequencyName[];
+ static const int kMetricStatsShortInterval;
+ static const int kMetricStatsLongInterval;
+ static const int kMetricMeminfoInterval;
+ static const int kMetricSectorsIOMax;
+ static const int kMetricSectorsBuckets;
+ static const int kMetricPageFaultsMax;
+ static const int kMetricPageFaultsBuckets;
+ static const char kMetricsDiskStatsPath[];
+ static const char kMetricsVmStatsPath[];
+ static const char kMetricsProcStatFileName[];
+ static const int kMetricsProcStatFirstLineItemsCount;
+
+ // Returns the active time since boot (uptime minus sleep time) in seconds.
+ double GetActiveTime();
+
+ // D-Bus filter callback.
+ static DBusHandlerResult MessageFilter(DBusConnection* connection,
+ DBusMessage* message,
+ void* user_data);
+
+ // Updates the daily usage file, if necessary, by adding |seconds|
+ // of active use to the |day| since Epoch. If there's usage data for
+ // day in the past in the usage file, that data is sent to UMA and
+ // removed from the file. If there's already usage data for |day| in
+ // the usage file, the |seconds| are accumulated.
+ void LogDailyUseRecord(int day, int seconds);
+
+ // Updates the active use time and logs time between user-space
+ // process crashes.
+ void ProcessUserCrash();
+
+ // 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();
+
+ // Returns the total (system-wide) CPU usage between the time of the most
+ // recent call to this function and now.
+ base::TimeDelta GetIncrementalCpuUse();
+
+ // Sends a sample representing the number of seconds of active use
+ // for a 24-hour period.
+ void SendDailyUseSample(const scoped_ptr<PersistentInteger>& use);
+
+ // Sends a sample representing a time interval between two crashes of the
+ // same type.
+ void SendCrashIntervalSample(const scoped_ptr<PersistentInteger>& interval);
+
+ // Sends a sample representing a frequency of crashes of some type.
+ void SendCrashFrequencySample(const scoped_ptr<PersistentInteger>& frequency);
+
+ // Initializes vm and disk stats reporting.
+ void StatsReporterInit();
+
+ // Schedules a callback for the next vm and disk stats collection.
+ void ScheduleStatsCallback(int wait);
+
+ // Reads cumulative disk statistics from sysfs. Returns true for success.
+ bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors);
+
+ // Reads cumulative vm statistics from procfs. Returns true for success.
+ bool VmStatsReadStats(struct VmstatRecord* stats);
+
+ // Parse cumulative vm statistics from a C string. Returns true for success.
+ bool VmStatsParseStats(const char* stats, struct VmstatRecord* record);
+
+ // Reports disk and vm statistics.
+ void StatsCallback();
+
+ // 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);
+
+ // Sends stats for thermal CPU throttling.
+ void SendCpuThrottleMetrics();
+
+ // Reads an integer CPU frequency value from sysfs.
+ bool ReadFreqToInt(const std::string& sysfs_file_name, int* value);
+
+ // 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);
+
+ // VARIABLES
+
+ // Test mode.
+ bool testing_;
+
+ // Whether the uploader is enabled or disabled.
+ bool uploader_active_;
+
+ // Whether or not dbus should be used.
+ // If disabled, we will not collect the frequency of crashes.
+ bool dbus_enabled_;
+
+ // Root of the configuration files to use.
+ std::string config_root_;
+
+ // The metrics library handle.
+ MetricsLibraryInterface* metrics_lib_;
+
+ // Timestamps last network state update. This timestamp is used to
+ // sample the time from the network going online to going offline so
+ // TimeTicks ensures a monotonically increasing TimeDelta.
+ base::TimeTicks network_state_last_;
+
+ // 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_;
+
+ // Contain the most recent disk and vm cumulative stats.
+ uint64_t read_sectors_;
+ uint64_t write_sectors_;
+ struct VmstatRecord vmstats_;
+
+ StatsState stats_state_;
+ double stats_initial_time_;
+
+ // The system "HZ", or frequency of ticks. Some system data uses ticks as a
+ // unit, and this is used to convert to standard time units.
+ uint32_t ticks_per_second_;
+ // Used internally by GetIncrementalCpuUse() to return the CPU utilization
+ // between calls.
+ uint64_t latest_cpu_use_ticks_;
+
+ // Persistent values and accumulators for crash statistics.
+ scoped_ptr<PersistentInteger> daily_cycle_;
+ scoped_ptr<PersistentInteger> weekly_cycle_;
+ scoped_ptr<PersistentInteger> version_cycle_;
+
+ // Active use accumulated in a day.
+ scoped_ptr<PersistentInteger> daily_active_use_;
+ // Active use accumulated since the latest version update.
+ scoped_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.
+ scoped_ptr<PersistentInteger> version_cumulative_cpu_use_;
+
+ scoped_ptr<PersistentInteger> user_crash_interval_;
+ scoped_ptr<PersistentInteger> kernel_crash_interval_;
+ scoped_ptr<PersistentInteger> unclean_shutdown_interval_;
+
+ scoped_ptr<PersistentInteger> any_crashes_daily_count_;
+ scoped_ptr<PersistentInteger> any_crashes_weekly_count_;
+ scoped_ptr<PersistentInteger> user_crashes_daily_count_;
+ scoped_ptr<PersistentInteger> user_crashes_weekly_count_;
+ scoped_ptr<PersistentInteger> kernel_crashes_daily_count_;
+ scoped_ptr<PersistentInteger> kernel_crashes_weekly_count_;
+ scoped_ptr<PersistentInteger> kernel_crashes_version_count_;
+ scoped_ptr<PersistentInteger> unclean_shutdowns_daily_count_;
+ scoped_ptr<PersistentInteger> unclean_shutdowns_weekly_count_;
+
+ std::string vmstats_path_;
+ std::string scaling_max_freq_path_;
+ std::string cpuinfo_max_freq_path_;
+
+ base::TimeDelta upload_interval_;
+ std::string server_;
+ std::string metrics_file_;
+
+ scoped_ptr<UploadService> upload_service_;
+};
+
+#endif // METRICS_METRICS_DAEMON_H_
diff --git a/metricsd/metrics_daemon_main.cc b/metricsd/metrics_daemon_main.cc
new file mode 100644
index 000000000..6c580ba25
--- /dev/null
+++ b/metricsd/metrics_daemon_main.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/syslog_logging.h>
+
+#include "constants.h"
+#include "metrics_daemon.h"
+
+const char kScalingMaxFreqPath[] =
+ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq";
+const char kCpuinfoMaxFreqPath[] =
+ "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
+
+int main(int argc, char** argv) {
+ DEFINE_bool(daemon, true, "run as daemon (use -nodaemon for debugging)");
+
+ // The uploader is disabled by default on ChromeOS as Chrome is responsible
+ // for sending the metrics.
+ DEFINE_bool(uploader, false, "activate the uploader");
+
+ // Upload the metrics once and exit. (used for testing)
+ DEFINE_bool(uploader_test,
+ false,
+ "run the uploader once and exit");
+
+ // Enable dbus.
+ DEFINE_bool(withdbus, true, "Enable dbus");
+
+ // Upload Service flags.
+ DEFINE_int32(upload_interval_secs,
+ 1800,
+ "Interval at which metrics_daemon sends the metrics. (needs "
+ "-uploader)");
+ DEFINE_string(server,
+ metrics::kMetricsServer,
+ "Server to upload the metrics to. (needs -uploader)");
+ DEFINE_string(metrics_file,
+ metrics::kMetricsEventsFilePath,
+ "File to use as a proxy for uploading the metrics");
+ DEFINE_string(config_root,
+ "/", "Root of the configuration files (testing only)");
+
+ chromeos::FlagHelper::Init(argc, argv, "Chromium OS Metrics Daemon");
+
+ // Also log to stderr when not running as daemon.
+ chromeos::InitLog(chromeos::kLogToSyslog | chromeos::kLogHeader |
+ (FLAGS_daemon ? 0 : chromeos::kLogToStderr));
+
+ if (FLAGS_daemon && daemon(0, 0) != 0) {
+ return errno;
+ }
+
+ MetricsLibrary metrics_lib;
+ metrics_lib.Init();
+ MetricsDaemon daemon;
+ daemon.Init(FLAGS_uploader_test,
+ FLAGS_uploader | FLAGS_uploader_test,
+ FLAGS_withdbus,
+ &metrics_lib,
+ "/proc/vmstat",
+ kScalingMaxFreqPath,
+ kCpuinfoMaxFreqPath,
+ base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs),
+ FLAGS_server,
+ FLAGS_metrics_file,
+ FLAGS_config_root);
+
+ if (FLAGS_uploader_test) {
+ daemon.RunUploaderTest();
+ return 0;
+ }
+
+ daemon.Run();
+}
diff --git a/metricsd/metrics_daemon_test.cc b/metricsd/metrics_daemon_test.cc
new file mode 100644
index 000000000..5aa7ab88e
--- /dev/null
+++ b/metricsd/metrics_daemon_test.cc
@@ -0,0 +1,388 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <inttypes.h>
+#include <utime.h>
+
+#include <string>
+#include <vector>
+
+#include <base/at_exit.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest.h>
+
+#include "metrics_daemon.h"
+#include "metrics_library_mock.h"
+#include "persistent_integer_mock.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using std::string;
+using std::vector;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Return;
+using ::testing::StrictMock;
+using chromeos_metrics::PersistentIntegerMock;
+
+static const char kFakeDiskStatsName[] = "fake-disk-stats";
+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};
+
+static const char kFakeVmStatsName[] = "fake-vm-stats";
+static const char kFakeScalingMaxFreqPath[] = "fake-scaling-max-freq";
+static const char kFakeCpuinfoMaxFreqPath[] = "fake-cpuinfo-max-freq";
+
+class MetricsDaemonTest : public testing::Test {
+ protected:
+ std::string kFakeDiskStats0;
+ std::string kFakeDiskStats1;
+
+ virtual void SetUp() {
+ kFakeDiskStats0 = base::StringPrintf(kFakeDiskStatsFormat,
+ kFakeReadSectors[0],
+ kFakeWriteSectors[0]);
+ kFakeDiskStats1 = base::StringPrintf(kFakeDiskStatsFormat,
+ kFakeReadSectors[1],
+ kFakeWriteSectors[1]);
+ CreateFakeDiskStatsFile(kFakeDiskStats0.c_str());
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 10000000);
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 10000000);
+
+ chromeos_metrics::PersistentInteger::SetTestingMode(true);
+ daemon_.Init(true,
+ false,
+ &metrics_lib_,
+ kFakeDiskStatsName,
+ kFakeVmStatsName,
+ kFakeScalingMaxFreqPath,
+ kFakeCpuinfoMaxFreqPath,
+ base::TimeDelta::FromMinutes(30),
+ kMetricsServer,
+ kMetricsFilePath,
+ "/");
+
+ // Replace original persistent values with mock ones.
+ daily_active_use_mock_ =
+ new StrictMock<PersistentIntegerMock>("1.mock");
+ daemon_.daily_active_use_.reset(daily_active_use_mock_);
+
+ kernel_crash_interval_mock_ =
+ new StrictMock<PersistentIntegerMock>("2.mock");
+ daemon_.kernel_crash_interval_.reset(kernel_crash_interval_mock_);
+
+ user_crash_interval_mock_ =
+ new StrictMock<PersistentIntegerMock>("3.mock");
+ daemon_.user_crash_interval_.reset(user_crash_interval_mock_);
+
+ unclean_shutdown_interval_mock_ =
+ new StrictMock<PersistentIntegerMock>("4.mock");
+ daemon_.unclean_shutdown_interval_.reset(unclean_shutdown_interval_mock_);
+ }
+
+ virtual void TearDown() {
+ EXPECT_EQ(0, unlink(kFakeDiskStatsName));
+ EXPECT_EQ(0, unlink(kFakeScalingMaxFreqPath));
+ EXPECT_EQ(0, unlink(kFakeCpuinfoMaxFreqPath));
+ }
+
+ // Adds active use aggregation counters update expectations that the
+ // specified count will be added.
+ void ExpectActiveUseUpdate(int count) {
+ EXPECT_CALL(*daily_active_use_mock_, Add(count))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*kernel_crash_interval_mock_, Add(count))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*user_crash_interval_mock_, Add(count))
+ .Times(1)
+ .RetiresOnSaturation();
+ }
+
+ // As above, but ignore values of counter updates.
+ void IgnoreActiveUseUpdate() {
+ EXPECT_CALL(*daily_active_use_mock_, Add(_))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*kernel_crash_interval_mock_, Add(_))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*user_crash_interval_mock_, Add(_))
+ .Times(1)
+ .RetiresOnSaturation();
+ }
+
+ // 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 a new DBus signal message with zero or more string arguments.
+ // The message can be deallocated through DeleteDBusMessage.
+ //
+ // |path| is the object emitting the signal.
+ // |interface| is the interface the signal is emitted from.
+ // |name| is the name of the signal.
+ // |arg_values| contains the values of the string arguments.
+ DBusMessage* NewDBusSignalString(const string& path,
+ const string& interface,
+ const string& name,
+ const vector<string>& arg_values) {
+ DBusMessage* msg = dbus_message_new_signal(path.c_str(),
+ interface.c_str(),
+ name.c_str());
+ DBusMessageIter iter;
+ dbus_message_iter_init_append(msg, &iter);
+ for (vector<string>::const_iterator it = arg_values.begin();
+ it != arg_values.end(); ++it) {
+ const char* str_value = it->c_str();
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &str_value);
+ }
+ return msg;
+ }
+
+ // Deallocates the DBus message |msg| previously allocated through
+ // dbus_message_new*.
+ void DeleteDBusMessage(DBusMessage* msg) {
+ dbus_message_unref(msg);
+ }
+
+ // Creates or overwrites an input file containing fake disk stats.
+ void CreateFakeDiskStatsFile(const char* fake_stats) {
+ if (unlink(kFakeDiskStatsName) < 0) {
+ EXPECT_EQ(errno, ENOENT);
+ }
+ FILE* f = fopen(kFakeDiskStatsName, "w");
+ EXPECT_EQ(1, fwrite(fake_stats, strlen(fake_stats), 1, f));
+ EXPECT_EQ(0, fclose(f));
+ }
+
+ // 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) {
+ base::DeleteFile(path, false);
+ std::string value_string = base::Uint64ToString(value);
+ ASSERT_EQ(value_string.length(),
+ base::WriteFile(path, value_string.c_str(),
+ value_string.length()));
+ }
+
+ // The MetricsDaemon under test.
+ MetricsDaemon daemon_;
+
+ // Mocks. They are strict mock so that all unexpected
+ // calls are marked as failures.
+ StrictMock<MetricsLibraryMock> metrics_lib_;
+ StrictMock<PersistentIntegerMock>* daily_active_use_mock_;
+ StrictMock<PersistentIntegerMock>* kernel_crash_interval_mock_;
+ StrictMock<PersistentIntegerMock>* user_crash_interval_mock_;
+ StrictMock<PersistentIntegerMock>* unclean_shutdown_interval_mock_;
+};
+
+TEST_F(MetricsDaemonTest, CheckSystemCrash) {
+ static const char kKernelCrashDetected[] = "test-kernel-crash-detected";
+ EXPECT_FALSE(daemon_.CheckSystemCrash(kKernelCrashDetected));
+
+ base::FilePath crash_detected(kKernelCrashDetected);
+ base::WriteFile(crash_detected, "", 0);
+ EXPECT_TRUE(base::PathExists(crash_detected));
+ EXPECT_TRUE(daemon_.CheckSystemCrash(kKernelCrashDetected));
+ EXPECT_FALSE(base::PathExists(crash_detected));
+ EXPECT_FALSE(daemon_.CheckSystemCrash(kKernelCrashDetected));
+ EXPECT_FALSE(base::PathExists(crash_detected));
+ base::DeleteFile(crash_detected, false);
+}
+
+TEST_F(MetricsDaemonTest, MessageFilter) {
+ // Ignore calls to SendToUMA.
+ EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AnyNumber());
+
+ DBusMessage* msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+ DBusHandlerResult res =
+ MetricsDaemon::MessageFilter(/* connection */ nullptr, msg, &daemon_);
+ EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
+ DeleteDBusMessage(msg);
+
+ IgnoreActiveUseUpdate();
+ vector<string> signal_args;
+ msg = NewDBusSignalString("/",
+ "org.chromium.CrashReporter",
+ "UserCrash",
+ signal_args);
+ res = MetricsDaemon::MessageFilter(/* connection */ nullptr, msg, &daemon_);
+ EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
+ DeleteDBusMessage(msg);
+
+ signal_args.clear();
+ signal_args.push_back("randomstate");
+ signal_args.push_back("bob"); // arbitrary username
+ msg = NewDBusSignalString("/",
+ "org.chromium.UnknownService.Manager",
+ "StateChanged",
+ signal_args);
+ res = MetricsDaemon::MessageFilter(/* connection */ nullptr, msg, &daemon_);
+ EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
+ DeleteDBusMessage(msg);
+}
+
+TEST_F(MetricsDaemonTest, SendSample) {
+ ExpectSample("Dummy.Metric", 3);
+ daemon_.SendSample("Dummy.Metric", /* sample */ 3,
+ /* min */ 1, /* max */ 100, /* buckets */ 50);
+}
+
+TEST_F(MetricsDaemonTest, ReportDiskStats) {
+ uint64_t read_sectors_now, write_sectors_now;
+ CreateFakeDiskStatsFile(kFakeDiskStats1.c_str());
+ daemon_.DiskStatsReadStats(&read_sectors_now, &write_sectors_now);
+ EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]);
+ EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]);
+
+ MetricsDaemon::StatsState s_state = daemon_.stats_state_;
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (kFakeReadSectors[1] - kFakeReadSectors[0]) / 30,
+ _, _, _));
+ EXPECT_CALL(metrics_lib_,
+ SendToUMA(_, (kFakeWriteSectors[1] - kFakeWriteSectors[0]) / 30,
+ _, _, _));
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, _)); // SendCpuThrottleMetrics
+ daemon_.StatsCallback();
+ EXPECT_TRUE(s_state != daemon_.stats_state_);
+}
+
+TEST_F(MetricsDaemonTest, 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(MetricsDaemonTest, ProcessMeminfo2) {
+ string meminfo = "MemTotal: 2000000 kB\nMemFree: 1000000 kB\n";
+ // Not enough fields.
+ EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsDaemonTest, ParseVmStats) {
+ static char kVmStats[] = "pswpin 1345\npswpout 8896\n"
+ "foo 100\nbar 200\npgmajfault 42\netcetc 300\n";
+ struct MetricsDaemon::VmstatRecord stats;
+ EXPECT_TRUE(daemon_.VmStatsParseStats(kVmStats, &stats));
+ EXPECT_EQ(stats.page_faults_, 42);
+ EXPECT_EQ(stats.swap_in_, 1345);
+ EXPECT_EQ(stats.swap_out_, 8896);
+}
+
+TEST_F(MetricsDaemonTest, ReadFreqToInt) {
+ const int fake_scaled_freq = 1666999;
+ const int fake_max_freq = 2000000;
+ int scaled_freq = 0;
+ int max_freq = 0;
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath),
+ fake_scaled_freq);
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), fake_max_freq);
+ EXPECT_TRUE(daemon_.testing_);
+ EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeScalingMaxFreqPath, &scaled_freq));
+ EXPECT_TRUE(daemon_.ReadFreqToInt(kFakeCpuinfoMaxFreqPath, &max_freq));
+ EXPECT_EQ(fake_scaled_freq, scaled_freq);
+ EXPECT_EQ(fake_max_freq, max_freq);
+}
+
+TEST_F(MetricsDaemonTest, SendCpuThrottleMetrics) {
+ CreateUint64ValueFile(base::FilePath(kFakeCpuinfoMaxFreqPath), 2001000);
+ // Test the 101% and 100% cases.
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2001000);
+ EXPECT_TRUE(daemon_.testing_);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 101, 101));
+ daemon_.SendCpuThrottleMetrics();
+ CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 2000000);
+ EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, 100, 101));
+ daemon_.SendCpuThrottleMetrics();
+}
+
+TEST_F(MetricsDaemonTest, 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(base::FilePath(MetricsDaemon::kComprDataSizeName),
+ compr_data_size);
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::kOrigDataSizeName),
+ orig_data_size);
+ CreateUint64ValueFile(base::FilePath(MetricsDaemon::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(base::FilePath(".")));
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/metricsd/metrics_library.cc b/metricsd/metrics_library.cc
new file mode 100644
index 000000000..f777f2823
--- /dev/null
+++ b/metricsd/metrics_library.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "metrics/metrics_library.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "constants.h"
+#include "serialization/metric_sample.h"
+#include "serialization/serialization_utils.h"
+
+static const char kCrosEventHistogramName[] = "Platform.CrOSEvent";
+static const int kCrosEventHistogramMax = 100;
+
+/* 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
+};
+
+time_t MetricsLibrary::cached_enabled_time_ = 0;
+bool MetricsLibrary::cached_enabled_ = false;
+
+MetricsLibrary::MetricsLibrary() : consent_file_(metrics::kConsentFilePath) {}
+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::AreMetricsEnabled() {
+ static struct stat stat_buffer;
+ time_t this_check_time = time(nullptr);
+ if (this_check_time != cached_enabled_time_) {
+ cached_enabled_time_ = this_check_time;
+ cached_enabled_ = stat(consent_file_.c_str(), &stat_buffer) >= 0;
+ }
+ return cached_enabled_;
+}
+
+void MetricsLibrary::Init() {
+ uma_events_file_ = metrics::kMetricsEventsFilePath;
+}
+
+bool MetricsLibrary::SendToUMA(const std::string& name,
+ int sample,
+ int min,
+ int max,
+ int nbuckets) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::HistogramSample(name, sample, min, max, nbuckets)
+ .get(),
+ metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendEnumToUMA(const std::string& name, int sample,
+ int max) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::LinearHistogramSample(name, sample, max).get(),
+ metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::SparseHistogramSample(name, sample).get(),
+ metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendUserActionToUMA(const std::string& action) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::UserActionSample(action).get(), metrics::kMetricsEventsFilePath);
+}
+
+bool MetricsLibrary::SendCrashToUMA(const char *crash_kind) {
+ return metrics::SerializationUtils::WriteMetricToFile(
+ *metrics::MetricSample::CrashSample(crash_kind).get(), metrics::kMetricsEventsFilePath);
+}
+
+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;
+}
diff --git a/metricsd/metrics_library_mock.h b/metricsd/metrics_library_mock.h
new file mode 100644
index 000000000..99892bfa0
--- /dev/null
+++ b/metricsd/metrics_library_mock.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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(SendSparseToUMA, bool(const std::string& name, int sample));
+ MOCK_METHOD1(SendUserActionToUMA, bool(const std::string& action));
+
+ bool AreMetricsEnabled() override {return metrics_enabled_;};
+};
+
+#endif // METRICS_METRICS_LIBRARY_MOCK_H_
diff --git a/metricsd/metrics_library_test.cc b/metricsd/metrics_library_test.cc
new file mode 100644
index 000000000..7ede303c8
--- /dev/null
+++ b/metricsd/metrics_library_test.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstring>
+
+#include <base/files/file_util.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "metrics/c_metrics_library.h"
+#include "metrics/metrics_library.h"
+
+using base::FilePath;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::AnyNumber;
+
+static const FilePath kTestUMAEventsFile("test-uma-events");
+static const char kTestMounts[] = "test-mounts";
+
+ACTION_P(SetMetricsPolicy, enabled) {
+ *arg0 = enabled;
+ return true;
+}
+
+class MetricsLibraryTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ EXPECT_TRUE(lib_.uma_events_file_.empty());
+ lib_.Init();
+ EXPECT_FALSE(lib_.uma_events_file_.empty());
+ lib_.uma_events_file_ = kTestUMAEventsFile.value();
+ EXPECT_EQ(0, WriteFile(kTestUMAEventsFile, "", 0));
+ device_policy_ = new policy::MockDevicePolicy();
+ EXPECT_CALL(*device_policy_, LoadPolicy())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(SetMetricsPolicy(true));
+ provider_ = new policy::PolicyProvider(device_policy_);
+ lib_.SetPolicyProvider(provider_);
+ // Defeat metrics enabled caching between tests.
+ lib_.cached_enabled_time_ = 0;
+ }
+
+ virtual void TearDown() {
+ base::DeleteFile(FilePath(kTestMounts), false);
+ base::DeleteFile(kTestUMAEventsFile, false);
+ }
+
+ void VerifyEnabledCacheHit(bool to_value);
+ void VerifyEnabledCacheEviction(bool to_value);
+
+ MetricsLibrary lib_;
+ policy::MockDevicePolicy* device_policy_;
+ policy::PolicyProvider* provider_;
+};
+
+TEST_F(MetricsLibraryTest, IsDeviceMounted) {
+ static const char kTestContents[] =
+ "0123456789abcde 0123456789abcde\nguestfs foo bar\n";
+ char buffer[1024];
+ int block_sizes[] = { 1, 2, 3, 4, 5, 6, 8, 12, 14, 16, 32, 1024 };
+ bool result;
+ EXPECT_FALSE(lib_.IsDeviceMounted("guestfs",
+ "nonexistent",
+ buffer,
+ 1,
+ &result));
+ ASSERT_TRUE(base::WriteFile(base::FilePath(kTestMounts),
+ kTestContents,
+ strlen(kTestContents)));
+ EXPECT_FALSE(lib_.IsDeviceMounted("guestfs",
+ kTestMounts,
+ buffer,
+ 0,
+ &result));
+ for (size_t i = 0; i < arraysize(block_sizes); ++i) {
+ EXPECT_TRUE(lib_.IsDeviceMounted("0123456789abcde",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("guestfs",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("0123456",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("9abcde",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("foo",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ EXPECT_TRUE(lib_.IsDeviceMounted("bar",
+ kTestMounts,
+ buffer,
+ block_sizes[i],
+ &result));
+ EXPECT_FALSE(result);
+ }
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) {
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(false));
+ EXPECT_FALSE(lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledTrue) {
+ 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;
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(!to_value));
+ ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
+ ON_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillByDefault(SetMetricsPolicy(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;
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(!to_value));
+ ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(to_value));
+ ASSERT_LT(abs(static_cast<int>(time(nullptr) - lib_.cached_enabled_time_)),
+ 5);
+ // Sleep one second (or cheat to be faster).
+ --lib_.cached_enabled_time_;
+ ASSERT_EQ(to_value, lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledCaching) {
+ VerifyEnabledCacheHit(false);
+ VerifyEnabledCacheHit(true);
+ VerifyEnabledCacheEviction(false);
+ VerifyEnabledCacheEviction(true);
+}
+
+class CMetricsLibraryTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ lib_ = CMetricsLibraryNew();
+ MetricsLibrary& ml = *reinterpret_cast<MetricsLibrary*>(lib_);
+ EXPECT_TRUE(ml.uma_events_file_.empty());
+ CMetricsLibraryInit(lib_);
+ EXPECT_FALSE(ml.uma_events_file_.empty());
+ ml.uma_events_file_ = kTestUMAEventsFile.value();
+ EXPECT_EQ(0, WriteFile(kTestUMAEventsFile, "", 0));
+ device_policy_ = new policy::MockDevicePolicy();
+ EXPECT_CALL(*device_policy_, LoadPolicy())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(SetMetricsPolicy(true));
+ provider_ = new policy::PolicyProvider(device_policy_);
+ ml.SetPolicyProvider(provider_);
+ reinterpret_cast<MetricsLibrary*>(lib_)->cached_enabled_time_ = 0;
+ }
+
+ virtual void TearDown() {
+ CMetricsLibraryDelete(lib_);
+ base::DeleteFile(kTestUMAEventsFile, false);
+ }
+
+ CMetricsLibrary lib_;
+ policy::MockDevicePolicy* device_policy_;
+ policy::PolicyProvider* provider_;
+};
+
+TEST_F(CMetricsLibraryTest, AreMetricsEnabledFalse) {
+ EXPECT_CALL(*device_policy_, GetMetricsEnabled(_))
+ .WillOnce(SetMetricsPolicy(false));
+ EXPECT_FALSE(CMetricsLibraryAreMetricsEnabled(lib_));
+}
+
+TEST_F(CMetricsLibraryTest, AreMetricsEnabledTrue) {
+ EXPECT_TRUE(CMetricsLibraryAreMetricsEnabled(lib_));
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/metricsd/persistent_integer.cc b/metricsd/persistent_integer.cc
new file mode 100644
index 000000000..0dcd52af1
--- /dev/null
+++ b/metricsd/persistent_integer.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "persistent_integer.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "constants.h"
+#include "metrics/metrics_library.h"
+
+
+namespace chromeos_metrics {
+
+// Static class member instantiation.
+bool PersistentInteger::testing_ = false;
+
+PersistentInteger::PersistentInteger(const std::string& name) :
+ value_(0),
+ version_(kVersion),
+ name_(name),
+ synced_(false) {
+ if (testing_) {
+ backing_file_name_ = name_;
+ } else {
+ backing_file_name_ = metrics::kMetricsDirectory + name_;
+ }
+}
+
+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_name_.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC,
+ S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH));
+ PCHECK(fd >= 0) << "cannot open " << backing_file_name_ << " 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_name_;
+ close(fd);
+ synced_ = true;
+}
+
+bool PersistentInteger::Read() {
+ int fd = HANDLE_EINTR(open(backing_file_name_.c_str(), O_RDONLY));
+ if (fd < 0) {
+ PLOG(WARNING) << "cannot open " << backing_file_name_ << " 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;
+}
+
+void PersistentInteger::SetTestingMode(bool testing) {
+ testing_ = testing;
+}
+
+
+} // namespace chromeos_metrics
diff --git a/metricsd/persistent_integer.h b/metricsd/persistent_integer.h
new file mode 100644
index 000000000..b1cfcf4ef
--- /dev/null
+++ b/metricsd/persistent_integer.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_PERSISTENT_INTEGER_H_
+#define METRICS_PERSISTENT_INTEGER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+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:
+ explicit PersistentInteger(const std::string& name);
+
+ // 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);
+
+ // After calling with |testing| = true, changes some behavior for the purpose
+ // of testing. For instance: instances created while testing use the current
+ // directory for the backing files.
+ static void SetTestingMode(bool testing);
+
+ 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_;
+ std::string backing_file_name_;
+ bool synced_;
+ static bool testing_;
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_PERSISTENT_INTEGER_H_
diff --git a/metricsd/persistent_integer_mock.h b/metricsd/persistent_integer_mock.h
new file mode 100644
index 000000000..31bfc3515
--- /dev/null
+++ b/metricsd/persistent_integer_mock.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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)
+ : PersistentInteger(name) {}
+ MOCK_METHOD1(Add, void(int64_t count));
+};
+
+} // namespace chromeos_metrics
+
+#endif // METRICS_PERSISTENT_INTEGER_MOCK_H_
diff --git a/metricsd/persistent_integer_test.cc b/metricsd/persistent_integer_test.cc
new file mode 100644
index 000000000..4fccb7286
--- /dev/null
+++ b/metricsd/persistent_integer_test.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+
+#include "persistent_integer.h"
+
+const char kBackingFileName[] = "1.pibakf";
+const char kBackingFilePattern[] = "*.pibakf";
+
+using chromeos_metrics::PersistentInteger;
+
+class PersistentIntegerTest : public testing::Test {
+ void SetUp() override {
+ // Set testing mode.
+ chromeos_metrics::PersistentInteger::SetTestingMode(true);
+ }
+
+ void TearDown() override {
+ // Remove backing files. The convention is that they all end in ".pibakf".
+ base::FileEnumerator f_enum(base::FilePath("."),
+ false,
+ base::FileEnumerator::FILES,
+ FILE_PATH_LITERAL(kBackingFilePattern));
+ for (base::FilePath name = f_enum.Next();
+ !name.empty();
+ name = f_enum.Next()) {
+ base::DeleteFile(name, false);
+ }
+ }
+};
+
+TEST_F(PersistentIntegerTest, BasicChecks) {
+ scoped_ptr<PersistentInteger> pi(new PersistentInteger(kBackingFileName));
+
+ // 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));
+ 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));
+ EXPECT_EQ(0, pi->Get());
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/metricsd/platform2_preinstall.sh b/metricsd/platform2_preinstall.sh
new file mode 100755
index 000000000..ccf353ff4
--- /dev/null
+++ b/metricsd/platform2_preinstall.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+
+OUT=$1
+shift
+for v; do
+ sed -e "s/@BSLOT@/${v}/g" libmetrics.pc.in > "${OUT}/lib/libmetrics-${v}.pc"
+done
diff --git a/metricsd/serialization/metric_sample.cc b/metricsd/serialization/metric_sample.cc
new file mode 100644
index 000000000..bc6583d7b
--- /dev/null
+++ b/metricsd/serialization/metric_sample.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "serialization/metric_sample.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+MetricSample::MetricSample(MetricSample::SampleType sample_type,
+ const std::string& metric_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count)
+ : type_(sample_type),
+ name_(metric_name),
+ sample_(sample),
+ min_(min),
+ max_(max),
+ bucket_count_(bucket_count) {
+}
+
+MetricSample::~MetricSample() {
+}
+
+bool MetricSample::IsValid() const {
+ return name().find(' ') == std::string::npos &&
+ name().find('\0') == std::string::npos && !name().empty();
+}
+
+std::string MetricSample::ToString() const {
+ if (type_ == CRASH) {
+ return base::StringPrintf("crash%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ } else if (type_ == SPARSE_HISTOGRAM) {
+ return base::StringPrintf("sparsehistogram%c%s %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ '\0');
+ } else if (type_ == LINEAR_HISTOGRAM) {
+ return base::StringPrintf("linearhistogram%c%s %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ max_,
+ '\0');
+ } else if (type_ == HISTOGRAM) {
+ return base::StringPrintf("histogram%c%s %d %d %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ min_,
+ max_,
+ bucket_count_,
+ '\0');
+ } else {
+ // The type can only be USER_ACTION.
+ CHECK_EQ(type_, USER_ACTION);
+ return base::StringPrintf("useraction%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ }
+}
+
+int MetricSample::sample() const {
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, CRASH);
+ return sample_;
+}
+
+int MetricSample::min() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return min_;
+}
+
+int MetricSample::max() const {
+ CHECK_NE(type_, CRASH);
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, SPARSE_HISTOGRAM);
+ return max_;
+}
+
+int MetricSample::bucket_count() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return bucket_count_;
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::CrashSample(
+ const std::string& crash_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count) {
+ return scoped_ptr<MetricSample>(new MetricSample(
+ HISTOGRAM, histogram_name, sample, min, max, bucket_count));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ base::SplitString(serialized_histogram, ' ', &parts);
+
+ if (parts.size() != 5)
+ return scoped_ptr<MetricSample>();
+ int sample, min, max, bucket_count;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &min) ||
+ !base::StringToInt(parts[3], &max) ||
+ !base::StringToInt(parts[4], &bucket_count)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return HistogramSample(parts[0], sample, min, max, bucket_count);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseSparseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ base::SplitString(serialized_histogram, ' ', &parts);
+ if (parts.size() != 2)
+ return scoped_ptr<MetricSample>();
+ int sample;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample))
+ return scoped_ptr<MetricSample>();
+
+ return SparseHistogramSample(parts[0], sample);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseLinearHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ int sample, max;
+ base::SplitString(serialized_histogram, ' ', &parts);
+ if (parts.size() != 3)
+ return scoped_ptr<MetricSample>();
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &max)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return LinearHistogramSample(parts[0], sample, max);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::UserActionSample(
+ const std::string& action_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0));
+}
+
+bool MetricSample::IsEqual(const MetricSample& metric) {
+ return type_ == metric.type_ && name_ == metric.name_ &&
+ sample_ == metric.sample_ && min_ == metric.min_ &&
+ max_ == metric.max_ && bucket_count_ == metric.bucket_count_;
+}
+
+} // namespace metrics
diff --git a/metricsd/serialization/metric_sample.h b/metricsd/serialization/metric_sample.h
new file mode 100644
index 000000000..877114d0a
--- /dev/null
+++ b/metricsd/serialization/metric_sample.h
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+#define METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace metrics {
+
+// This class is used by libmetrics (ChromeOS) to serialize
+// and deserialize measurements to send them to a metrics sending service.
+// It is meant to be a simple container with serialization functions.
+class MetricSample {
+ public:
+ // Types of metric sample used.
+ enum SampleType {
+ CRASH,
+ HISTOGRAM,
+ LINEAR_HISTOGRAM,
+ SPARSE_HISTOGRAM,
+ USER_ACTION
+ };
+
+ ~MetricSample();
+
+ // Returns true if the sample is valid (can be serialized without ambiguity).
+ //
+ // This function should be used to filter bad samples before serializing them.
+ bool IsValid() const;
+
+ // Getters for type and name. All types of metrics have these so we do not
+ // need to check the type.
+ SampleType type() const { return type_; }
+ const std::string& name() const { return name_; }
+
+ // Getters for sample, min, max, bucket_count.
+ // Check the metric type to make sure the request make sense. (ex: a crash
+ // sample does not have a bucket_count so we crash if we call bucket_count()
+ // on it.)
+ int sample() const;
+ int min() const;
+ int max() const;
+ int bucket_count() const;
+
+ // Returns a serialized version of the sample.
+ //
+ // The serialized message for each type is:
+ // crash: crash\0|name_|\0
+ // user action: useraction\0|name_|\0
+ // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0
+ // sparsehistogram: sparsehistogram\0|name_| |sample_|\0
+ // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0
+ std::string ToString() const;
+
+ // Builds a crash sample.
+ static scoped_ptr<MetricSample> CrashSample(const std::string& crash_name);
+
+ // Builds a histogram sample.
+ static scoped_ptr<MetricSample> HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count);
+ // Deserializes a histogram sample.
+ static scoped_ptr<MetricSample> ParseHistogram(const std::string& serialized);
+
+ // Builds a sparse histogram sample.
+ static scoped_ptr<MetricSample> SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample);
+ // Deserializes a sparse histogram sample.
+ static scoped_ptr<MetricSample> ParseSparseHistogram(
+ const std::string& serialized);
+
+ // Builds a linear histogram sample.
+ static scoped_ptr<MetricSample> LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max);
+ // Deserializes a linear histogram sample.
+ static scoped_ptr<MetricSample> ParseLinearHistogram(
+ const std::string& serialized);
+
+ // Builds a user action sample.
+ static scoped_ptr<MetricSample> UserActionSample(
+ const std::string& action_name);
+
+ // Returns true if sample and this object represent the same sample (type,
+ // name, sample, min, max, bucket_count match).
+ bool IsEqual(const MetricSample& sample);
+
+ private:
+ MetricSample(SampleType sample_type,
+ const std::string& metric_name,
+ const int sample,
+ const int min,
+ const int max,
+ const int bucket_count);
+
+ const SampleType type_;
+ const std::string name_;
+ const int sample_;
+ const int min_;
+ const int max_;
+ const int bucket_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricSample);
+};
+
+} // namespace metrics
+
+#endif // METRICS_SERIALIZATION_METRIC_SAMPLE_H_
diff --git a/metricsd/serialization/serialization_utils.cc b/metricsd/serialization/serialization_utils.cc
new file mode 100644
index 000000000..d18dcd754
--- /dev/null
+++ b/metricsd/serialization/serialization_utils.cc
@@ -0,0 +1,221 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "serialization/serialization_utils.h"
+
+#include <sys/file.h>
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "serialization/metric_sample.h"
+
+#define READ_WRITE_ALL_FILE_FLAGS \
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+namespace metrics {
+namespace {
+
+// Reads the next message from |file_descriptor| into |message|.
+//
+// |message| will be set to the empty string if no message could be read (EOF)
+// or the message was badly constructed.
+//
+// Returns false if no message can be read from this file anymore (EOF or
+// unrecoverable error).
+bool ReadMessage(int fd, std::string* message) {
+ CHECK(message);
+
+ int result;
+ int32_t message_size;
+ const int32_t message_hdr_size = sizeof(message_size);
+ // The file containing the metrics do not leave the device so the writer and
+ // the reader will always have the same endianness.
+ result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
+ if (result < 0) {
+ DPLOG(ERROR) << "reading metrics message header";
+ return false;
+ }
+ if (result == 0) {
+ // This indicates a normal EOF.
+ return false;
+ }
+ if (result < message_hdr_size) {
+ DLOG(ERROR) << "bad read size " << result << ", expecting "
+ << sizeof(message_size);
+ return false;
+ }
+
+ // kMessageMaxLength applies to the entire message: the 4-byte
+ // length field and the content.
+ if (message_size > SerializationUtils::kMessageMaxLength) {
+ DLOG(ERROR) << "message too long : " << message_size;
+ if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+ DLOG(ERROR) << "error while skipping message. abort";
+ return false;
+ }
+ // Badly formatted message was skipped. Treat the badly formatted sample as
+ // an empty sample.
+ message->clear();
+ return true;
+ }
+
+ if (message_size < message_hdr_size) {
+ DLOG(ERROR) << "message too short : " << message_size;
+ return false;
+ }
+
+ message_size -= message_hdr_size; // The message size includes itself.
+ char buffer[SerializationUtils::kMessageMaxLength];
+ if (!base::ReadFromFD(fd, buffer, message_size)) {
+ DPLOG(ERROR) << "reading metrics message body";
+ return false;
+ }
+ *message = std::string(buffer, message_size);
+ return true;
+}
+
+} // namespace
+
+scoped_ptr<MetricSample> SerializationUtils::ParseSample(
+ const std::string& sample) {
+ if (sample.empty())
+ return scoped_ptr<MetricSample>();
+
+ std::vector<std::string> parts;
+ base::SplitString(sample, '\0', &parts);
+ // We should have two null terminated strings so split should produce
+ // three chunks.
+ if (parts.size() != 3) {
+ DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
+ << " parts (expected 3)";
+ return scoped_ptr<MetricSample>();
+ }
+ const std::string& name = parts[0];
+ const std::string& value = parts[1];
+
+ if (base::LowerCaseEqualsASCII(name, "crash")) {
+ return MetricSample::CrashSample(value);
+ } else if (base::LowerCaseEqualsASCII(name, "histogram")) {
+ return MetricSample::ParseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "linearhistogram")) {
+ return MetricSample::ParseLinearHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "sparsehistogram")) {
+ return MetricSample::ParseSparseHistogram(value);
+ } else if (base::LowerCaseEqualsASCII(name, "useraction")) {
+ return MetricSample::UserActionSample(value);
+ } else {
+ DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
+ }
+ return scoped_ptr<MetricSample>();
+}
+
+void SerializationUtils::ReadAndTruncateMetricsFromFile(
+ const std::string& filename,
+ ScopedVector<MetricSample>* metrics) {
+ struct stat stat_buf;
+ int result;
+
+ result = stat(filename.c_str(), &stat_buf);
+ if (result < 0) {
+ if (errno != ENOENT)
+ DPLOG(ERROR) << filename << ": bad metrics file stat";
+
+ // Nothing to collect---try later.
+ return;
+ }
+ if (stat_buf.st_size == 0) {
+ // Also nothing to collect.
+ return;
+ }
+ base::ScopedFD fd(open(filename.c_str(), O_RDWR));
+ if (fd.get() < 0) {
+ DPLOG(ERROR) << filename << ": cannot open";
+ return;
+ }
+ result = flock(fd.get(), LOCK_EX);
+ if (result < 0) {
+ DPLOG(ERROR) << filename << ": cannot lock";
+ return;
+ }
+
+ // This processes all messages in the log. When all messages are
+ // read and processed, or an error occurs, truncate the file to zero size.
+ for (;;) {
+ std::string message;
+
+ if (!ReadMessage(fd.get(), &message))
+ break;
+
+ scoped_ptr<MetricSample> sample = ParseSample(message);
+ if (sample)
+ metrics->push_back(sample.release());
+ }
+
+ result = ftruncate(fd.get(), 0);
+ if (result < 0)
+ DPLOG(ERROR) << "truncate metrics log";
+
+ result = flock(fd.get(), LOCK_UN);
+ if (result < 0)
+ DPLOG(ERROR) << "unlock metrics log";
+}
+
+bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
+ const std::string& filename) {
+ if (!sample.IsValid())
+ return false;
+
+ base::ScopedFD file_descriptor(open(filename.c_str(),
+ O_WRONLY | O_APPEND | O_CREAT,
+ READ_WRITE_ALL_FILE_FLAGS));
+
+ if (file_descriptor.get() < 0) {
+ DPLOG(ERROR) << filename << ": cannot open";
+ return false;
+ }
+
+ fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
+ // Grab a lock to avoid chrome truncating the file
+ // underneath us. Keep the file locked as briefly as possible.
+ // Freeing file_descriptor will close the file and and remove the lock.
+ if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
+ DPLOG(ERROR) << filename << ": cannot lock";
+ return false;
+ }
+
+ std::string msg = sample.ToString();
+ int32 size = msg.length() + sizeof(int32);
+ if (size > kMessageMaxLength) {
+ DLOG(ERROR) << "cannot write message: too long";
+ return false;
+ }
+
+ // The file containing the metrics samples will only be read by programs on
+ // the same device so we do not check endianness.
+ if (!base::WriteFileDescriptor(file_descriptor.get(),
+ reinterpret_cast<char*>(&size),
+ sizeof(size))) {
+ DPLOG(ERROR) << "error writing message length";
+ return false;
+ }
+
+ if (!base::WriteFileDescriptor(
+ file_descriptor.get(), msg.c_str(), msg.size())) {
+ DPLOG(ERROR) << "error writing message";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace metrics
diff --git a/metricsd/serialization/serialization_utils.h b/metricsd/serialization/serialization_utils.h
new file mode 100644
index 000000000..5af61660f
--- /dev/null
+++ b/metricsd/serialization/serialization_utils.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+#define METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+namespace metrics {
+
+class MetricSample;
+
+// Metrics helpers to serialize and deserialize metrics collected by
+// ChromeOS.
+namespace SerializationUtils {
+
+// Deserializes a sample passed as a string and return a sample.
+// The return value will either be a scoped_ptr to a Metric sample (if the
+// deserialization was successful) or a NULL scoped_ptr.
+scoped_ptr<MetricSample> ParseSample(const std::string& sample);
+
+// Reads all samples from a file and truncate the file when done.
+void ReadAndTruncateMetricsFromFile(const std::string& filename,
+ ScopedVector<MetricSample>* metrics);
+
+// Serializes a sample and write it to filename.
+// The format for the message is:
+// message_size, serialized_message
+// where
+// * message_size is the total length of the message (message_size +
+// serialized_message) on 4 bytes
+// * serialized_message is the serialized version of sample (using ToString)
+//
+// NB: the file will never leave the device so message_size will be written
+// with the architecture's endianness.
+bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
+
+// Maximum length of a serialized message
+static const int kMessageMaxLength = 1024;
+
+} // namespace SerializationUtils
+} // namespace metrics
+
+#endif // METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
diff --git a/metricsd/serialization/serialization_utils_unittest.cc b/metricsd/serialization/serialization_utils_unittest.cc
new file mode 100644
index 000000000..fb802bcc9
--- /dev/null
+++ b/metricsd/serialization/serialization_utils_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "serialization/serialization_utils.h"
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "serialization/metric_sample.h"
+
+namespace metrics {
+namespace {
+
+class SerializationUtilsTest : public testing::Test {
+ protected:
+ SerializationUtilsTest() {
+ bool success = temporary_dir.CreateUniqueTempDir();
+ if (success) {
+ base::FilePath dir_path = temporary_dir.path();
+ filename = dir_path.value() + "chromeossampletest";
+ filepath = base::FilePath(filename);
+ }
+ }
+
+ void SetUp() override { base::DeleteFile(filepath, false); }
+
+ void TestSerialization(MetricSample* sample) {
+ std::string serialized(sample->ToString());
+ ASSERT_EQ('\0', serialized[serialized.length() - 1]);
+ scoped_ptr<MetricSample> deserialized =
+ SerializationUtils::ParseSample(serialized);
+ ASSERT_TRUE(deserialized);
+ EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
+ }
+
+ std::string filename;
+ base::ScopedTempDir temporary_dir;
+ base::FilePath filepath;
+};
+
+TEST_F(SerializationUtilsTest, CrashSerializeTest) {
+ TestSerialization(MetricSample::CrashSample("test").get());
+}
+
+TEST_F(SerializationUtilsTest, HistogramSerializeTest) {
+ TestSerialization(
+ MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get());
+}
+
+TEST_F(SerializationUtilsTest, LinearSerializeTest) {
+ TestSerialization(
+ MetricSample::LinearHistogramSample("linearhist", 12, 30).get());
+}
+
+TEST_F(SerializationUtilsTest, SparseSerializeTest) {
+ TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get());
+}
+
+TEST_F(SerializationUtilsTest, UserActionSerializeTest) {
+ TestSerialization(MetricSample::UserActionSample("myaction").get());
+}
+
+TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) {
+ scoped_ptr<MetricSample> sample1 =
+ MetricSample::SparseHistogramSample("no space", 10);
+ scoped_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
+ base::StringPrintf("here%cbhe", '\0'), 1, 3);
+
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+ int64 size = 0;
+
+ ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) {
+ std::string input(
+ base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0'));
+ EXPECT_EQ(NULL, MetricSample::ParseSparseHistogram(input).get());
+}
+
+TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ // 4 bytes for the size
+ // 5 bytes for crash
+ // 7 bytes for mycrash
+ // 2 bytes for the \0
+ // -> total of 18
+ EXPECT_EQ(size, 18);
+}
+
+TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) {
+ // Creates a message that is bigger than the maximum allowed size.
+ // As we are adding extra character (crash, \0s, etc), if the name is
+ // kMessageMaxLength long, it will be too long.
+ std::string name(SerializationUtils::kMessageMaxLength, 'c');
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample(name);
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
+ base::File test_file(filepath,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+ std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
+
+ int32 message_size = message.length() + sizeof(int32);
+ test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size),
+ sizeof(message_size));
+ test_file.WriteAtCurrentPos(message.c_str(), message.length());
+ test_file.Close();
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("test");
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+
+ ScopedVector<MetricSample> samples;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+ ASSERT_EQ(size_t(1), samples.size());
+ ASSERT_TRUE(samples[0] != NULL);
+ EXPECT_TRUE(crash->IsEqual(*samples[0]));
+}
+
+TEST_F(SerializationUtilsTest, WriteReadTest) {
+ scoped_ptr<MetricSample> hist =
+ MetricSample::HistogramSample("myhist", 1, 2, 3, 4);
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+ scoped_ptr<MetricSample> lhist =
+ MetricSample::LinearHistogramSample("linear", 1, 10);
+ scoped_ptr<MetricSample> shist =
+ MetricSample::SparseHistogramSample("mysparse", 30);
+ scoped_ptr<MetricSample> action = MetricSample::UserActionSample("myaction");
+
+ SerializationUtils::WriteMetricToFile(*hist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*shist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*action.get(), filename);
+ ScopedVector<MetricSample> vect;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+ ASSERT_EQ(vect.size(), size_t(5));
+ for (int i = 0; i < 5; i++) {
+ ASSERT_TRUE(vect[0] != NULL);
+ }
+ EXPECT_TRUE(hist->IsEqual(*vect[0]));
+ EXPECT_TRUE(crash->IsEqual(*vect[1]));
+ EXPECT_TRUE(lhist->IsEqual(*vect[2]));
+ EXPECT_TRUE(shist->IsEqual(*vect[3]));
+ EXPECT_TRUE(action->IsEqual(*vect[4]));
+
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ ASSERT_EQ(0, size);
+}
+
+} // namespace
+} // namespace metrics
diff --git a/metricsd/syslog_parser.sh b/metricsd/syslog_parser.sh
new file mode 100755
index 000000000..7d064be96
--- /dev/null
+++ b/metricsd/syslog_parser.sh
@@ -0,0 +1,69 @@
+#! /bin/sh
+
+# This script parses /var/log/syslog for messages from programs that log
+# uptime and disk stats (number of sectors read). It then outputs
+# these stats in a format usable by the metrics collector, which forwards
+# them to autotest and UMA.
+
+# To add a new metric add a line below, as PROGRAM_NAME METRIC_NAME.
+# PROGRAM_NAME is the name of the job whose start time we
+# are interested in. METRIC_NAME is the prefix we want to use for
+# reporting to UMA and autotest. The script prepends "Time" and
+# "Sectors" to METRIC_NAME for the two available measurements, uptime
+# and number of sectors read thus far.
+
+# You will need to emit messages similar to the following in order to add a
+# a metric using this process. You will need to emit both a start and stop
+# time and the metric reported will be the difference in values
+
+# Nov 15 08:05 localhost PROGRAM_NAME[822]: start METRIC_NAME time 12 sectors 56
+# Nov 15 08:05 localhost PROGRAM_NAME[822]: stop METRIC_NAME time 24 sectors 68
+
+# If you add metrics without a start, it is assumed you are requesting the
+# time differece from system start
+
+# Metrics we are interested in measuring
+METRICS="
+upstart start_x
+"
+
+first=1
+program=""
+
+# Get the metrics for all things
+for m in $METRICS
+do
+ if [ $first -eq 1 ]
+ then
+ first=0
+ program_name=$m
+ else
+ first=1
+ metrics_name=$m
+
+ # Example of line from /var/log/messages:
+ # Nov 15 08:05:42 localhost connmand[822]: start metric time 12 sectors 56
+ # "upstart:" is $5, 1234 is $9, etc.
+ program="${program}/$program_name([[0-9]+]:|:) start $metrics_name/\
+ {
+ metrics_start[\"${metrics_name}Time\"] = \$9;
+ metrics_start[\"${metrics_name}Sectors\"] = \$11;
+ }"
+ program="${program}/$program_name([[0-9]+]:|:) stop $metrics_name/\
+ {
+ metrics_stop[\"${metrics_name}Time\"] = \$9;
+ metrics_stop[\"${metrics_name}Sectors\"] = \$11;
+ }"
+ fi
+done
+
+# Do all the differencing here
+program="${program}\
+END{
+ for (i in metrics_stop) {
+ value_time = metrics_stop[i] - metrics_start[i];
+ print i \"=\" value_time;
+ }
+}"
+
+exec awk "$program" /var/log/syslog
diff --git a/metricsd/timer.cc b/metricsd/timer.cc
new file mode 100644
index 000000000..ce4bf67a3
--- /dev/null
+++ b/metricsd/timer.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "timer.h"
+
+#include <string>
+
+#include <base/memory/scoped_ptr.h>
+
+#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/metricsd/timer.h b/metricsd/timer.h
new file mode 100644
index 000000000..52cc57892
--- /dev/null
+++ b/metricsd/timer.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Timer - class that provides timer tracking.
+
+#ifndef METRICS_TIMER_H_
+#define METRICS_TIMER_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/memory/scoped_ptr.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.
+ scoped_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/metricsd/timer_mock.h b/metricsd/timer_mock.h
new file mode 100644
index 000000000..ed76f12cd
--- /dev/null
+++ b/metricsd/timer_mock.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_TIMER_MOCK_H_
+#define METRICS_TIMER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "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/metricsd/timer_test.cc b/metricsd/timer_test.cc
new file mode 100644
index 000000000..b1689bf8c
--- /dev/null
+++ b/metricsd/timer_test.cc
@@ -0,0 +1,452 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include <base/memory/scoped_ptr.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metrics_library_mock.h"
+#include "timer.h"
+#include "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_;
+ scoped_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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_.reset(clock_wrapper_mock_.release());
+ 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_;
+ scoped_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_.reset(clock_wrapper_mock_.release());
+ 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/metricsd/uploader/metrics_hashes.cc b/metricsd/uploader/metrics_hashes.cc
new file mode 100644
index 000000000..f9d0cfeaa
--- /dev/null
+++ b/metricsd/uploader/metrics_hashes.cc
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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/metricsd/uploader/metrics_hashes.h b/metricsd/uploader/metrics_hashes.h
new file mode 100644
index 000000000..8679077e1
--- /dev/null
+++ b/metricsd/uploader/metrics_hashes.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/metricsd/uploader/metrics_hashes_unittest.cc b/metricsd/uploader/metrics_hashes_unittest.cc
new file mode 100644
index 000000000..8cdc7a92d
--- /dev/null
+++ b/metricsd/uploader/metrics_hashes_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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/metricsd/uploader/metrics_log.cc b/metricsd/uploader/metrics_log.cc
new file mode 100644
index 000000000..6f11f8aac
--- /dev/null
+++ b/metricsd/uploader/metrics_log.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/metrics_log.h"
+
+#include <string>
+
+#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, "") {
+}
+
+void MetricsLog::IncrementUserCrashCount() {
+ 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 + 1);
+}
+
+void MetricsLog::IncrementKernelCrashCount() {
+ metrics::SystemProfileProto::Stability* stability(
+ uma_proto()->mutable_system_profile()->mutable_stability());
+ int current = stability->kernel_crash_count();
+ stability->set_kernel_crash_count(current + 1);
+}
+
+void MetricsLog::IncrementUncleanShutdownCount() {
+ 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 + 1);
+}
+
+void MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
+ profile_setter->Populate(uma_proto());
+}
diff --git a/metricsd/uploader/metrics_log.h b/metricsd/uploader/metrics_log.h
new file mode 100644
index 000000000..a62798fb3
--- /dev/null
+++ b/metricsd/uploader/metrics_log.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_METRICS_LOG_H_
+#define METRICS_UPLOADER_METRICS_LOG_H_
+
+#include <string>
+
+#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();
+
+ void IncrementUserCrashCount();
+ void IncrementKernelCrashCount();
+ void IncrementUncleanShutdownCount();
+
+ // Populate the system profile with system information using setter.
+ void PopulateSystemProfile(SystemProfileSetter* setter);
+
+ private:
+ FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+ FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+ FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+ FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+
+ DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+#endif // METRICS_UPLOADER_METRICS_LOG_H_
diff --git a/metricsd/uploader/metrics_log_base.cc b/metricsd/uploader/metrics_log_base.cc
new file mode 100644
index 000000000..3ae01e85a
--- /dev/null
+++ b/metricsd/uploader/metrics_log_base.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/metrics_log_base.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.
+ DVLOG(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;
+ const char* kDateTime = __DATE__ " " __TIME__ " GMT";
+ bool result = Time::FromString(kDateTime, &time);
+ DCHECK(result);
+ 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 (scoped_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/metricsd/uploader/metrics_log_base.h b/metricsd/uploader/metrics_log_base.h
new file mode 100644
index 000000000..4173335fb
--- /dev/null
+++ b/metricsd/uploader/metrics_log_base.h
@@ -0,0 +1,110 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService. This is the unit of data that is sent to the server.
+
+#ifndef 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/metricsd/uploader/metrics_log_base_unittest.cc b/metricsd/uploader/metrics_log_base_unittest.cc
new file mode 100644
index 000000000..dc03f0032
--- /dev/null
+++ b/metricsd/uploader/metrics_log_base_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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/metricsd/uploader/mock/mock_system_profile_setter.h b/metricsd/uploader/mock/mock_system_profile_setter.h
new file mode 100644
index 000000000..c6e8f0d1e
--- /dev/null
+++ b/metricsd/uploader/mock/mock_system_profile_setter.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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:
+ void Populate(metrics::ChromeUserMetricsExtension* profile_proto) override {}
+};
+
+#endif // METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
diff --git a/metricsd/uploader/mock/sender_mock.cc b/metricsd/uploader/mock/sender_mock.cc
new file mode 100644
index 000000000..064ec6d01
--- /dev/null
+++ b/metricsd/uploader/mock/sender_mock.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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/metricsd/uploader/mock/sender_mock.h b/metricsd/uploader/mock/sender_mock.h
new file mode 100644
index 000000000..0a15d6163
--- /dev/null
+++ b/metricsd/uploader/mock/sender_mock.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/metricsd/uploader/proto/README b/metricsd/uploader/proto/README
new file mode 100644
index 000000000..9bd3249b0
--- /dev/null
+++ b/metricsd/uploader/proto/README
@@ -0,0 +1,25 @@
+Copyright 2015 The Chromium OS Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+
+
+This 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/metricsd/uploader/proto/chrome_user_metrics_extension.proto b/metricsd/uploader/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 000000000..d4d4f240b
--- /dev/null
+++ b/metricsd/uploader/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// 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/metricsd/uploader/proto/histogram_event.proto b/metricsd/uploader/proto/histogram_event.proto
new file mode 100644
index 000000000..4b68094ff
--- /dev/null
+++ b/metricsd/uploader/proto/histogram_event.proto
@@ -0,0 +1,47 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Histogram-collected metrics.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "HistogramEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 4
+message HistogramEventProto {
+ // The name of the histogram, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The sum of all the sample values.
+ // Together with the total count of the sample values, this allows us to
+ // compute the average value. The count of all sample values is just the sum
+ // of the counts of all the buckets.
+ 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/metricsd/uploader/proto/system_profile.proto b/metricsd/uploader/proto/system_profile.proto
new file mode 100644
index 000000000..d33ff6097
--- /dev/null
+++ b/metricsd/uploader/proto/system_profile.proto
@@ -0,0 +1,747 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about the user's brower and system configuration.
+// The system configuration fields are recorded once per client session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SystemProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 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 build_target_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/metricsd/uploader/proto/user_action_event.proto b/metricsd/uploader/proto/user_action_event.proto
new file mode 100644
index 000000000..30a93180d
--- /dev/null
+++ b/metricsd/uploader/proto/user_action_event.proto
@@ -0,0 +1,23 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Stores information about an event that occurs in response to a user action,
+// e.g. an interaction with a browser UI element.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "UserActionEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 3
+message UserActionEventProto {
+ // The name of the action, hashed.
+ optional fixed64 name_hash = 1;
+
+ // The timestamp for the event, in seconds since the epoch.
+ optional int64 time = 2;
+}
diff --git a/metricsd/uploader/sender.h b/metricsd/uploader/sender.h
new file mode 100644
index 000000000..521183407
--- /dev/null
+++ b/metricsd/uploader/sender.h
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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/metricsd/uploader/sender_http.cc b/metricsd/uploader/sender_http.cc
new file mode 100644
index 000000000..a740310e8
--- /dev/null
+++ b/metricsd/uploader/sender_http.cc
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/sender_http.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <chromeos/http/http_utils.h>
+#include <chromeos/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());
+
+ chromeos::http::HeaderList headers = {{"X-Chrome-UMA-Log-SHA1", hash}};
+ chromeos::ErrorPtr error;
+ auto response = chromeos::http::PostTextAndBlock(
+ server_url_,
+ content,
+ chromeos::mime::application::kWwwFormUrlEncoded,
+ headers,
+ chromeos::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/metricsd/uploader/sender_http.h b/metricsd/uploader/sender_http.h
new file mode 100644
index 000000000..380cad867
--- /dev/null
+++ b/metricsd/uploader/sender_http.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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 libchromeos
+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/metricsd/uploader/system_profile_cache.cc b/metricsd/uploader/system_profile_cache.cc
new file mode 100644
index 000000000..adbe0ae0a
--- /dev/null
+++ b/metricsd/uploader/system_profile_cache.cc
@@ -0,0 +1,152 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "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 <base/sys_info.h>
+#include <string>
+#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),
+ config_root_("/"),
+ session_id_(new chromeos_metrics::PersistentInteger(
+ kPersistentSessionIdFilename)) {
+}
+
+SystemProfileCache::SystemProfileCache(bool testing,
+ const std::string& config_root)
+ : initialized_(false),
+ testing_(testing),
+ config_root_(config_root),
+ session_id_(new chromeos_metrics::PersistentInteger(
+ kPersistentSessionIdFilename)) {
+}
+
+bool SystemProfileCache::Initialize() {
+ CHECK(!initialized_)
+ << "this should be called only once in the metrics_daemon lifetime.";
+
+ if (!base::SysInfo::GetLsbReleaseValue("BRILLO_BUILD_TARGET_ID",
+ &profile_.build_target_id)) {
+ LOG(ERROR) << "Could not initialize system profile.";
+ return false;
+ }
+
+ std::string channel;
+ if (!base::SysInfo::GetLsbReleaseValue("BRILLO_CHANNEL", &channel) ||
+ !base::SysInfo::GetLsbReleaseValue("BRILLO_VERSION", &profile_.version)) {
+ // 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;
+
+ }
+ profile_.client_id =
+ testing_ ? "client_id_test" :
+ GetPersistentGUID(metrics::kMetricsGUIDFilePath);
+ profile_.hardware_class = "unknown";
+ 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();
+}
+
+void SystemProfileCache::Populate(
+ metrics::ChromeUserMetricsExtension* metrics_proto) {
+ CHECK(metrics_proto);
+ CHECK(InitializeOrCheck())
+ << "failed to initialize system information.";
+
+ // 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_.hardware_class);
+ 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_build_target_id(profile_.build_target_id);
+}
+
+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") {
+ return metrics::SystemProfileProto::CHANNEL_STABLE;
+ } else if (channel == "dev") {
+ return metrics::SystemProfileProto::CHANNEL_DEV;
+ } else if (channel == "beta") {
+ return metrics::SystemProfileProto::CHANNEL_BETA;
+ } else if (channel == "canary") {
+ return metrics::SystemProfileProto::CHANNEL_CANARY;
+ }
+
+ DLOG(INFO) << "unknown channel: " << channel;
+ return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
+}
diff --git a/metricsd/uploader/system_profile_cache.h b/metricsd/uploader/system_profile_cache.h
new file mode 100644
index 000000000..b6ff3377c
--- /dev/null
+++ b/metricsd/uploader/system_profile_cache.h
@@ -0,0 +1,73 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.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 hardware_class;
+ std::string client_id;
+ int session_id;
+ metrics::SystemProfileProto::Channel channel;
+ std::string build_target_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 std::string& config_root);
+
+ // Populates the ProfileSystem protobuf with system information.
+ void 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);
+
+ // Fetches all informations and populates |profile_|
+ bool Initialize();
+
+ // Initializes |profile_| only if it has not been yet initialized.
+ bool InitializeOrCheck();
+
+ bool initialized_;
+ bool testing_;
+ std::string config_root_;
+ scoped_ptr<chromeos_metrics::PersistentInteger> session_id_;
+ SystemProfile profile_;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
diff --git a/metricsd/uploader/system_profile_setter.h b/metricsd/uploader/system_profile_setter.h
new file mode 100644
index 000000000..c535664a0
--- /dev/null
+++ b/metricsd/uploader/system_profile_setter.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef 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 void Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0;
+};
+
+#endif // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
diff --git a/metricsd/uploader/upload_service.cc b/metricsd/uploader/upload_service.cc
new file mode 100644
index 000000000..34110045a
--- /dev/null
+++ b/metricsd/uploader/upload_service.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/upload_service.h"
+
+#include <string>
+
+#include <base/bind.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 "serialization/metric_sample.h"
+#include "serialization/serialization_utils.h"
+#include "uploader/metrics_log.h"
+#include "uploader/sender_http.h"
+#include "uploader/system_profile_setter.h"
+
+const int UploadService::kMaxFailedUpload = 10;
+
+UploadService::UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server)
+ : system_profile_setter_(setter),
+ metrics_lib_(metrics_lib),
+ histogram_snapshot_manager_(this),
+ sender_(new HttpSender(server)),
+ testing_(false) {
+}
+
+UploadService::UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server,
+ bool testing)
+ : UploadService(setter, metrics_lib, server) {
+ testing_ = testing;
+}
+
+void UploadService::Init(const base::TimeDelta& upload_interval,
+ const std::string& metrics_file) {
+ base::StatisticsRecorder::Initialize();
+ metrics_file_ = metrics_file;
+
+ if (!testing_) {
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback,
+ base::Unretained(this),
+ upload_interval),
+ upload_interval);
+ }
+}
+
+void UploadService::StartNewLog() {
+ CHECK(!staged_log_) << "the staged log should be discarded before starting "
+ "a new metrics log";
+ MetricsLog* log = new MetricsLog();
+ log->PopulateSystemProfile(system_profile_setter_.get());
+ current_log_.reset(log);
+}
+
+void UploadService::UploadEventCallback(const base::TimeDelta& interval) {
+ UploadEvent();
+
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&UploadService::UploadEventCallback,
+ base::Unretained(this),
+ interval),
+ interval);
+}
+
+void UploadService::UploadEvent() {
+ if (staged_log_) {
+ // Previous upload failed, retry sending the logs.
+ SendStagedLog();
+ return;
+ }
+
+ // Previous upload successful, reading metrics sample from the file.
+ ReadMetrics();
+ GatherHistograms();
+
+ // No samples found. Exit to avoid sending an empty log.
+ if (!current_log_)
+ return;
+
+ StageCurrentLog();
+ SendStagedLog();
+}
+
+void UploadService::SendStagedLog() {
+ CHECK(staged_log_) << "staged_log_ must exist to be sent";
+
+ // If metrics are not enabled, discard the log and exit.
+ if (!metrics_lib_->AreMetricsEnabled()) {
+ LOG(INFO) << "Metrics disabled. Don't upload metrics samples.";
+ staged_log_.reset();
+ return;
+ }
+
+ std::string log_text;
+ staged_log_->GetEncodedLog(&log_text);
+ if (!sender_->Send(log_text, base::SHA1HashString(log_text))) {
+ ++failed_upload_count_;
+ if (failed_upload_count_ <= kMaxFailedUpload) {
+ LOG(WARNING) << "log upload failed " << failed_upload_count_
+ << " times. It will be retried later.";
+ return;
+ }
+ LOG(WARNING) << "log failed more than " << kMaxFailedUpload << " times.";
+ } else {
+ LOG(INFO) << "uploaded " << log_text.length() << " bytes";
+ }
+ // Discard staged log.
+ staged_log_.reset();
+}
+
+void UploadService::Reset() {
+ staged_log_.reset();
+ current_log_.reset();
+ failed_upload_count_ = 0;
+}
+
+void UploadService::ReadMetrics() {
+ CHECK(!staged_log_)
+ << "cannot read metrics until the old logs have been discarded";
+
+ ScopedVector<metrics::MetricSample> vector;
+ metrics::SerializationUtils::ReadAndTruncateMetricsFromFile(
+ metrics_file_, &vector);
+
+ int i = 0;
+ for (ScopedVector<metrics::MetricSample>::iterator it = vector.begin();
+ it != vector.end(); it++) {
+ metrics::MetricSample* sample = *it;
+ AddSample(*sample);
+ i++;
+ }
+ DLOG(INFO) << i << " samples read";
+}
+
+void UploadService::AddSample(const metrics::MetricSample& sample) {
+ base::HistogramBase* counter;
+ switch (sample.type()) {
+ case metrics::MetricSample::CRASH:
+ AddCrash(sample.name());
+ break;
+ case metrics::MetricSample::HISTOGRAM:
+ counter = base::Histogram::FactoryGet(
+ sample.name(), sample.min(), sample.max(), sample.bucket_count(),
+ base::Histogram::kUmaTargetedHistogramFlag);
+ counter->Add(sample.sample());
+ break;
+ case metrics::MetricSample::SPARSE_HISTOGRAM:
+ counter = base::SparseHistogram::FactoryGet(
+ sample.name(), base::HistogramBase::kUmaTargetedHistogramFlag);
+ counter->Add(sample.sample());
+ break;
+ case metrics::MetricSample::LINEAR_HISTOGRAM:
+ counter = base::LinearHistogram::FactoryGet(
+ sample.name(),
+ 1,
+ sample.max(),
+ sample.max() + 1,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ counter->Add(sample.sample());
+ break;
+ case metrics::MetricSample::USER_ACTION:
+ GetOrCreateCurrentLog()->RecordUserAction(sample.name());
+ break;
+ default:
+ break;
+ }
+}
+
+void UploadService::AddCrash(const std::string& crash_name) {
+ if (crash_name == "user") {
+ GetOrCreateCurrentLog()->IncrementUserCrashCount();
+ } else if (crash_name == "kernel") {
+ GetOrCreateCurrentLog()->IncrementKernelCrashCount();
+ } else if (crash_name == "uncleanshutdown") {
+ GetOrCreateCurrentLog()->IncrementUncleanShutdownCount();
+ } else {
+ DLOG(ERROR) << "crash name unknown" << crash_name;
+ }
+}
+
+void UploadService::GatherHistograms() {
+ base::StatisticsRecorder::Histograms histograms;
+ base::StatisticsRecorder::GetHistograms(&histograms);
+
+ histogram_snapshot_manager_.PrepareDeltas(
+ base::Histogram::kNoFlags, base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+void UploadService::RecordDelta(const base::HistogramBase& histogram,
+ const base::HistogramSamples& snapshot) {
+ GetOrCreateCurrentLog()->RecordHistogramDelta(histogram.histogram_name(),
+ snapshot);
+}
+
+void UploadService::StageCurrentLog() {
+ CHECK(!staged_log_)
+ << "staged logs must be discarded before another log can be staged";
+
+ if (!current_log_) return;
+
+ staged_log_.swap(current_log_);
+ staged_log_->CloseLog();
+ failed_upload_count_ = 0;
+}
+
+MetricsLog* UploadService::GetOrCreateCurrentLog() {
+ if (!current_log_) {
+ StartNewLog();
+ }
+ return current_log_.get();
+}
diff --git a/metricsd/uploader/upload_service.h b/metricsd/uploader/upload_service.h
new file mode 100644
index 000000000..c08fc1ac8
--- /dev/null
+++ b/metricsd/uploader/upload_service.h
@@ -0,0 +1,153 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_UPLOAD_SERVICE_H_
+#define METRICS_UPLOADER_UPLOAD_SERVICE_H_
+
+#include <string>
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+
+#include "metrics/metrics_library.h"
+#include "uploader/metrics_log.h"
+#include "uploader/sender.h"
+#include "uploader/system_profile_cache.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+class CrashSample;
+class HistogramSample;
+class LinearHistogramSample;
+class MetricSample;
+class SparseHistogramSample;
+class UserActionSample;
+}
+
+class SystemProfileSetter;
+
+// Service responsible for uploading the metrics periodically to the server.
+// This service works as a simple 2-state state-machine.
+//
+// The two states are the presence or not of a staged log.
+// A staged log is a compressed protobuffer containing both the aggregated
+// metrics and event and information about the client. (product, hardware id,
+// etc...).
+//
+// At regular intervals, the upload event will be triggered and the following
+// will happen:
+// * if a staged log is present:
+// The previous upload may have failed for various reason. We then retry to
+// upload the same log.
+// - if the upload is successful, we discard the log (therefore
+// transitioning back to no staged log)
+// - if the upload fails, we keep the log to try again later.
+// We do not try to read the metrics that are stored on
+// the disk as we want to avoid storing the metrics in memory.
+//
+// * if no staged logs are present:
+// Read all metrics from the disk, aggregate them and try to send them.
+// - if the upload succeeds, we discard the staged log (transitioning back
+// to the no staged log state)
+// - if the upload fails, we keep the staged log in memory to retry
+// uploading later.
+//
+class UploadService : public base::HistogramFlattener {
+ public:
+ explicit UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server);
+
+ void Init(const base::TimeDelta& upload_interval,
+ const std::string& metrics_file);
+
+ // Starts a new log. The log needs to be regenerated after each successful
+ // launch as it is destroyed when staging the log.
+ void StartNewLog();
+
+ // Event callback for handling MessageLoop events.
+ void UploadEventCallback(const base::TimeDelta& interval);
+
+ // 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, DiscardLogsAfterTooManyFailedUpload);
+ FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent);
+ FRIEND_TEST(UploadServiceTest, FailedSendAreRetried);
+ FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+ FRIEND_TEST(UploadServiceTest, LogEmptyAfterUpload);
+ FRIEND_TEST(UploadServiceTest, LogEmptyByDefault);
+ FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+ FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+ FRIEND_TEST(UploadServiceTest, LogUserCrash);
+ FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+ FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+
+ // Private constructor for use in unit testing.
+ UploadService(SystemProfileSetter* setter,
+ MetricsLibraryInterface* metrics_lib,
+ const std::string& server,
+ bool testing);
+
+ // If a staged log fails to upload more than kMaxFailedUpload times, it
+ // will be discarded.
+ static const int kMaxFailedUpload;
+
+ // Resets the internal state.
+ void Reset();
+
+ // Reads all the metrics from the disk.
+ void ReadMetrics();
+
+ // Adds a generic sample to the current log.
+ void AddSample(const metrics::MetricSample& sample);
+
+ // Adds a crash to the current log.
+ void AddCrash(const std::string& crash_name);
+
+ // 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 the current log. If there is no current log, creates it first.
+ MetricsLog* GetOrCreateCurrentLog();
+
+ scoped_ptr<SystemProfileSetter> system_profile_setter_;
+ MetricsLibraryInterface* metrics_lib_;
+ base::HistogramSnapshotManager histogram_snapshot_manager_;
+ scoped_ptr<Sender> sender_;
+ int failed_upload_count_;
+ scoped_ptr<MetricsLog> current_log_;
+ scoped_ptr<MetricsLog> staged_log_;
+
+ std::string metrics_file_;
+
+ bool testing_;
+};
+
+#endif // METRICS_UPLOADER_UPLOAD_SERVICE_H_
diff --git a/metricsd/uploader/upload_service_test.cc b/metricsd/uploader/upload_service_test.cc
new file mode 100644
index 000000000..efd0a56fa
--- /dev/null
+++ b/metricsd/uploader/upload_service_test.cc
@@ -0,0 +1,254 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "base/at_exit.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "metrics_library_mock.h"
+#include "serialization/metric_sample.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:
+ UploadServiceTest()
+ : cache_(true, "/"),
+ upload_service_(new MockSystemProfileSetter(), &metrics_lib_,
+ kMetricsServer, true),
+ exit_manager_(new base::AtExitManager()) {
+ sender_ = new SenderMock;
+ upload_service_.sender_.reset(sender_);
+ upload_service_.Init(base::TimeDelta::FromMinutes(30), kMetricsFilePath);
+ }
+
+ virtual void SetUp() {
+ CHECK(dir_.CreateUniqueTempDir());
+ upload_service_.GatherHistograms();
+ upload_service_.Reset();
+ sender_->Reset();
+
+ chromeos_metrics::PersistentInteger::SetTestingMode(true);
+ cache_.session_id_.reset(new chromeos_metrics::PersistentInteger(
+ dir_.path().Append("session_id").value()));
+ }
+
+ scoped_ptr<metrics::MetricSample> Crash(const std::string& name) {
+ return metrics::MetricSample::CrashSample(name);
+ }
+
+ base::ScopedTempDir dir_;
+ SenderMock* sender_;
+ SystemProfileCache cache_;
+ UploadService upload_service_;
+ MetricsLibraryMock metrics_lib_;
+
+ scoped_ptr<base::AtExitManager> exit_manager_;
+};
+
+// Tests that the right crash increments a values.
+TEST_F(UploadServiceTest, LogUserCrash) {
+ upload_service_.AddSample(*Crash("user").get());
+
+ MetricsLog* log = upload_service_.current_log_.get();
+ metrics::ChromeUserMetricsExtension* proto = log->uma_proto();
+
+ EXPECT_EQ(1, proto->system_profile().stability().other_user_crash_count());
+}
+
+TEST_F(UploadServiceTest, LogUncleanShutdown) {
+ upload_service_.AddSample(*Crash("uncleanshutdown"));
+
+ EXPECT_EQ(1, upload_service_.current_log_
+ ->uma_proto()
+ ->system_profile()
+ .stability()
+ .unclean_system_shutdown_count());
+}
+
+TEST_F(UploadServiceTest, LogKernelCrash) {
+ upload_service_.AddSample(*Crash("kernel"));
+
+ EXPECT_EQ(1, upload_service_.current_log_
+ ->uma_proto()
+ ->system_profile()
+ .stability()
+ .kernel_crash_count());
+}
+
+TEST_F(UploadServiceTest, UnknownCrashIgnored) {
+ upload_service_.AddSample(*Crash("foo"));
+
+ // The log should be empty.
+ EXPECT_FALSE(upload_service_.current_log_);
+}
+
+TEST_F(UploadServiceTest, FailedSendAreRetried) {
+ sender_->set_should_succeed(false);
+
+ upload_service_.AddSample(*Crash("user"));
+ 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) {
+ sender_->set_should_succeed(false);
+ upload_service_.AddSample(*Crash("user"));
+
+ for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+ upload_service_.UploadEvent();
+ }
+
+ EXPECT_TRUE(upload_service_.staged_log_);
+ upload_service_.UploadEvent();
+ EXPECT_FALSE(upload_service_.staged_log_);
+}
+
+TEST_F(UploadServiceTest, EmptyLogsAreNotSent) {
+ upload_service_.UploadEvent();
+ EXPECT_FALSE(upload_service_.current_log_);
+ EXPECT_EQ(0, sender_->send_call_count());
+}
+
+TEST_F(UploadServiceTest, LogEmptyByDefault) {
+ UploadService upload_service(new MockSystemProfileSetter(), &metrics_lib_,
+ kMetricsServer);
+
+ // current_log_ should be initialized later as it needs AtExitManager to exit
+ // in order to gather system information from SysInfo.
+ EXPECT_FALSE(upload_service.current_log_);
+}
+
+TEST_F(UploadServiceTest, CanSendMultipleTimes) {
+ upload_service_.AddSample(*Crash("user"));
+ upload_service_.UploadEvent();
+
+ std::string first_message = sender_->last_message();
+
+ upload_service_.AddSample(*Crash("kernel"));
+ upload_service_.UploadEvent();
+
+ EXPECT_NE(first_message, sender_->last_message());
+}
+
+TEST_F(UploadServiceTest, LogEmptyAfterUpload) {
+ upload_service_.AddSample(*Crash("user"));
+
+ EXPECT_TRUE(upload_service_.current_log_);
+
+ upload_service_.UploadEvent();
+ EXPECT_FALSE(upload_service_.current_log_);
+}
+
+TEST_F(UploadServiceTest, LogContainsAggregatedValues) {
+ scoped_ptr<metrics::MetricSample> histogram =
+ metrics::MetricSample::HistogramSample("foo", 10, 0, 42, 10);
+ upload_service_.AddSample(*histogram.get());
+
+
+ scoped_ptr<metrics::MetricSample> histogram2 =
+ metrics::MetricSample::HistogramSample("foo", 11, 0, 42, 10);
+ upload_service_.AddSample(*histogram2.get());
+
+ upload_service_.GatherHistograms();
+ metrics::ChromeUserMetricsExtension* proto =
+ upload_service_.current_log_->uma_proto();
+ EXPECT_EQ(1, proto->histogram_event().size());
+}
+
+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_UNKNOWN,
+ SystemProfileCache::ProtoChannelFromString("dev-channel test"));
+}
+
+TEST_F(UploadServiceTest, ValuesInConfigFileAreSent) {
+ std::string name("os name");
+ std::string content(
+ "CHROMEOS_RELEASE_NAME=" + name +
+ "\nCHROMEOS_RELEASE_VERSION=version\n"
+ "CHROMEOS_RELEASE_DESCRIPTION=description beta-channel test\n"
+ "CHROMEOS_RELEASE_TRACK=beta-channel\n"
+ "CHROMEOS_RELEASE_BUILD_TYPE=developer build\n"
+ "CHROMEOS_RELEASE_BOARD=myboard");
+
+ base::SysInfo::SetChromeOSVersionInfoForTest(content, base::Time());
+ scoped_ptr<metrics::MetricSample> histogram =
+ metrics::MetricSample::SparseHistogramSample("myhistogram", 1);
+ SystemProfileCache* local_cache_ = new SystemProfileCache(true, "/");
+ local_cache_->session_id_.reset(new chromeos_metrics::PersistentInteger(
+ dir_.path().Append("session_id").value()));
+
+ upload_service_.system_profile_setter_.reset(local_cache_);
+ // Reset to create the new log with the profile setter.
+ upload_service_.Reset();
+ upload_service_.AddSample(*histogram.get());
+ 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_EQ(name, sender_->last_message_proto().system_profile().os().name());
+ EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_BETA,
+ sender_->last_message_proto().system_profile().channel());
+ 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) {
+ cache_.Initialize();
+ int session_id = cache_.profile_.session_id;
+ cache_.initialized_ = false;
+ cache_.Initialize();
+ EXPECT_EQ(cache_.profile_.session_id, session_id + 1);
+}
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}