Merge branch 'rewrite-metrics' into merge-metrics

BUG:22773266
This commit is contained in:
Bill Yi 2015-07-28 09:35:20 -07:00
commit 15b8a77876
61 changed files with 7901 additions and 0 deletions

3
metrics/OWNERS Normal file
View File

@ -0,0 +1,3 @@
semenzato@chromium.org
derat@chromium.org
bsimonnet@chromium.org

138
metrics/README Normal file
View File

@ -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.

16
metrics/WATCHLISTS Normal file
View File

@ -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']
},
}

View File

@ -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();
}

View File

@ -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_

View File

@ -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}

View File

@ -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

View File

@ -0,0 +1,8 @@
{
'variables': {
'libbase_ver': 334380,
},
'includes': [
'libmetrics.gypi',
],
}

33
metrics/libmetrics.gypi Normal file
View File

@ -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': ['.'],
},
],
}

7
metrics/libmetrics.pc.in Normal file
View File

@ -0,0 +1,7 @@
bslot=@BSLOT@
Name: libmetrics
Description: Chrome OS metrics library
Version: ${bslot}
Requires.private: libchrome-${bslot}
Libs: -lmetrics-${bslot}

12
metrics/make_tests.sh Executable file
View File

@ -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}"

184
metrics/metrics.gyp Normal file
View File

@ -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': ['.'],
},
],
}],
]
}

221
metrics/metrics_client.cc Normal file
View File

@ -0,0 +1,221 @@
// 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 [-ab] [-t] name sample min max nbuckets\n"
" metrics_client [-ab] -e name sample max\n"
" metrics_client [-ab] -s name sample\n"
" metrics_client [-ab] -v event\n"
" metrics_client -u action\n"
" metrics_client [-cg]\n"
"\n"
" default: send metric with integer values to Chrome only\n"
" |min| > 0, |min| <= sample < |max|\n"
" -a: send metric (name/sample) to Autotest only\n"
" -b: send metric to both Chrome and Autotest\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,
bool send_to_autotest,
bool send_to_chrome) {
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]);
}
// Send metrics
if (send_to_autotest) {
MetricsLibrary::SendToAutotest(name, sample);
}
if (send_to_chrome) {
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 send_to_autotest = false;
bool send_to_chrome = true;
bool secs_to_msecs = false;
// Parse arguments
int flag;
while ((flag = getopt(argc, argv, "abcegstuv")) != -1) {
switch (flag) {
case 'a':
send_to_autotest = true;
send_to_chrome = false;
break;
case 'b':
send_to_chrome = true;
send_to_autotest = true;
break;
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,
send_to_autotest,
send_to_chrome);
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;
}
}

1167
metrics/metrics_daemon.cc Normal file

File diff suppressed because it is too large Load Diff

371
metrics/metrics_daemon.h Normal file
View File

@ -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 "metrics/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,
MetricsLibraryInterface* metrics_lib,
const std::string& diskstats_path,
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();
// Returns true if the system is using an official build.
bool IsOnOfficialBuild() const;
// 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_;
// 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 diskstats_path_;
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_

View File

@ -0,0 +1,102 @@
// 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 <rootdev/rootdev.h>
#include "metrics/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";
// Returns the path to the disk stats in the sysfs. Returns the null string if
// it cannot find the disk stats file.
static
const std::string MetricsMainDiskStatsPath() {
char dev_path_cstr[PATH_MAX];
std::string dev_prefix = "/dev/";
std::string dev_path;
std::string dev_name;
int ret = rootdev(dev_path_cstr, sizeof(dev_path_cstr), true, true);
if (ret != 0) {
LOG(WARNING) << "error " << ret << " determining root device";
return "";
}
dev_path = dev_path_cstr;
// Check that rootdev begins with "/dev/".
if (!base::StartsWithASCII(dev_path, dev_prefix, false)) {
LOG(WARNING) << "unexpected root device " << dev_path;
return "";
}
// Get the device name, e.g. "sda" from "/dev/sda".
dev_name = dev_path.substr(dev_prefix.length());
return "/sys/class/block/" + dev_name + "/stat";
}
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");
// Upload Service flags.
DEFINE_int32(upload_interval_secs,
1800,
"Interval at which metrics_daemon sends the metrics. (needs "
"-uploader)");
DEFINE_string(server,
"https://clients4.google.com/uma/v2",
"Server to upload the metrics to. (needs -uploader)");
DEFINE_string(metrics_file,
"/var/lib/metrics/uma-events",
"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,
&metrics_lib,
MetricsMainDiskStatsPath(),
"/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();
}

View File

@ -0,0 +1,390 @@
// 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/metrics_daemon.h"
#include "metrics/metrics_library_mock.h"
#include "metrics/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";
static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
static const char kMetricsFilePath[] = "/var/lib/metrics/uma-events";
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();
}

214
metrics/metrics_library.cc Normal file
View File

@ -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 "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 "metrics/serialization/metric_sample.h"
#include "metrics/serialization/serialization_utils.h"
#include "policy/device_policy.h"
static const char kAutotestPath[] = "/var/log/metrics/autotest-events";
static const char kUMAEventsPath[] = "/var/lib/metrics/uma-events";
static const char kConsentFile[] = "/home/chronos/Consent To Send Stats";
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_(kConsentFile) {}
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;
if (!policy_provider_.get())
policy_provider_.reset(new policy::PolicyProvider());
policy_provider_->Reload();
// We initialize with the default value which is false and will be preserved
// if the policy is not set.
bool enabled = false;
bool has_policy = false;
if (policy_provider_->device_policy_is_loaded()) {
has_policy =
policy_provider_->GetDevicePolicy().GetMetricsEnabled(&enabled);
}
// If policy couldn't be loaded or the metrics policy is not set we should
// still respect the consent file if it is present for migration purposes.
// TODO(pastarmovj)
if (!has_policy) {
enabled = stat(consent_file_.c_str(), &stat_buffer) >= 0;
}
if (enabled && !IsGuestMode())
cached_enabled_ = true;
else
cached_enabled_ = false;
}
return cached_enabled_;
}
void MetricsLibrary::Init() {
uma_events_file_ = kUMAEventsPath;
}
bool MetricsLibrary::SendToAutotest(const std::string& name, int value) {
FILE* autotest_file = fopen(kAutotestPath, "a+");
if (autotest_file == nullptr) {
PLOG(ERROR) << kAutotestPath << ": fopen";
return false;
}
fprintf(autotest_file, "%s=%d\n", name.c_str(), value);
fclose(autotest_file);
return true;
}
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(),
kUMAEventsPath);
}
bool MetricsLibrary::SendEnumToUMA(const std::string& name, int sample,
int max) {
return metrics::SerializationUtils::WriteMetricToFile(
*metrics::MetricSample::LinearHistogramSample(name, sample, max).get(),
kUMAEventsPath);
}
bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
return metrics::SerializationUtils::WriteMetricToFile(
*metrics::MetricSample::SparseHistogramSample(name, sample).get(),
kUMAEventsPath);
}
bool MetricsLibrary::SendUserActionToUMA(const std::string& action) {
return metrics::SerializationUtils::WriteMetricToFile(
*metrics::MetricSample::UserActionSample(action).get(), kUMAEventsPath);
}
bool MetricsLibrary::SendCrashToUMA(const char *crash_kind) {
return metrics::SerializationUtils::WriteMetricToFile(
*metrics::MetricSample::CrashSample(crash_kind).get(), kUMAEventsPath);
}
void MetricsLibrary::SetPolicyProvider(policy::PolicyProvider* provider) {
policy_provider_.reset(provider);
}
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;
}

150
metrics/metrics_library.h Normal file
View File

@ -0,0 +1,150 @@
// 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
#include "policy/libpolicy.h"
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 both Autotest and 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);
// Sends to Autotest and returns true on success.
static bool SendToAutotest(const std::string& name, int value);
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);
// This function is used by tests only to mock the device policies.
void SetPolicyProvider(policy::PolicyProvider* provider);
// 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_;
scoped_ptr<policy::PolicyProvider> policy_provider_;
DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
};
#endif // METRICS_METRICS_LIBRARY_H_

View File

@ -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_

View File

@ -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();
}

View File

@ -0,0 +1,101 @@
// 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 "metrics/persistent_integer.h"
#include <fcntl.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include "metrics/metrics_library.h"
namespace {
// The directory for the persistent storage.
const char kBackingFilesDirectory[] = "/var/lib/metrics/";
}
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_ = kBackingFilesDirectory + 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

View File

@ -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_

View File

@ -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 "metrics/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_

View File

@ -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 "metrics/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();
}

13
metrics/platform2_preinstall.sh Executable file
View File

@ -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

View File

@ -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 "metrics/serialization/metric_sample.h"
#include <string>
#include <vector>
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
namespace metrics {
MetricSample::MetricSample(MetricSample::SampleType sample_type,
const std::string& metric_name,
int sample,
int min,
int max,
int bucket_count)
: type_(sample_type),
name_(metric_name),
sample_(sample),
min_(min),
max_(max),
bucket_count_(bucket_count) {
}
MetricSample::~MetricSample() {
}
bool MetricSample::IsValid() const {
return name().find(' ') == std::string::npos &&
name().find('\0') == std::string::npos && !name().empty();
}
std::string MetricSample::ToString() const {
if (type_ == CRASH) {
return base::StringPrintf("crash%c%s%c",
'\0',
name().c_str(),
'\0');
} else if (type_ == SPARSE_HISTOGRAM) {
return base::StringPrintf("sparsehistogram%c%s %d%c",
'\0',
name().c_str(),
sample_,
'\0');
} else if (type_ == LINEAR_HISTOGRAM) {
return base::StringPrintf("linearhistogram%c%s %d %d%c",
'\0',
name().c_str(),
sample_,
max_,
'\0');
} else if (type_ == HISTOGRAM) {
return base::StringPrintf("histogram%c%s %d %d %d %d%c",
'\0',
name().c_str(),
sample_,
min_,
max_,
bucket_count_,
'\0');
} else {
// The type can only be USER_ACTION.
CHECK_EQ(type_, USER_ACTION);
return base::StringPrintf("useraction%c%s%c",
'\0',
name().c_str(),
'\0');
}
}
int MetricSample::sample() const {
CHECK_NE(type_, USER_ACTION);
CHECK_NE(type_, CRASH);
return sample_;
}
int MetricSample::min() const {
CHECK_EQ(type_, HISTOGRAM);
return min_;
}
int MetricSample::max() const {
CHECK_NE(type_, CRASH);
CHECK_NE(type_, USER_ACTION);
CHECK_NE(type_, SPARSE_HISTOGRAM);
return max_;
}
int MetricSample::bucket_count() const {
CHECK_EQ(type_, HISTOGRAM);
return bucket_count_;
}
// static
scoped_ptr<MetricSample> MetricSample::CrashSample(
const std::string& crash_name) {
return scoped_ptr<MetricSample>(
new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
}
// static
scoped_ptr<MetricSample> MetricSample::HistogramSample(
const std::string& histogram_name,
int sample,
int min,
int max,
int bucket_count) {
return scoped_ptr<MetricSample>(new MetricSample(
HISTOGRAM, histogram_name, sample, min, max, bucket_count));
}
// static
scoped_ptr<MetricSample> MetricSample::ParseHistogram(
const std::string& serialized_histogram) {
std::vector<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

View File

@ -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_

View File

@ -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 "metrics/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 "metrics/serialization/metric_sample.h"
#define READ_WRITE_ALL_FILE_FLAGS \
(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
namespace metrics {
namespace {
// Reads the next message from |file_descriptor| into |message|.
//
// |message| will be set to the empty string if no message could be read (EOF)
// or the message was badly constructed.
//
// Returns false if no message can be read from this file anymore (EOF or
// unrecoverable error).
bool ReadMessage(int fd, std::string* message) {
CHECK(message);
int result;
int32_t message_size;
const int32_t message_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

View File

@ -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_

View File

@ -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 "metrics/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 "metrics/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

69
metrics/syslog_parser.sh Executable file
View File

@ -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

108
metrics/timer.cc Normal file
View File

@ -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 "metrics/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

158
metrics/timer.h Normal file
View File

@ -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_

47
metrics/timer_mock.h Normal file
View File

@ -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 "metrics/timer.h"
namespace chromeos_metrics {
class TimerMock : public Timer {
public:
MOCK_METHOD0(Start, bool());
MOCK_METHOD0(Stop, bool());
MOCK_METHOD0(Reset, bool());
MOCK_CONST_METHOD0(HasStarted, bool());
MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
};
class TimerReporterMock : public TimerReporter {
public:
TimerReporterMock() : TimerReporter("", 0, 0, 0) {}
MOCK_METHOD0(Start, bool());
MOCK_METHOD0(Stop, bool());
MOCK_METHOD0(Reset, bool());
MOCK_CONST_METHOD0(HasStarted, bool());
MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
MOCK_CONST_METHOD0(ReportMilliseconds, bool());
MOCK_CONST_METHOD0(histogram_name, std::string&());
MOCK_CONST_METHOD0(min, int());
MOCK_CONST_METHOD0(max, int());
MOCK_CONST_METHOD0(num_buckets, int());
};
class ClockWrapperMock : public ClockWrapper {
public:
MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks());
};
} // namespace chromeos_metrics
#endif // METRICS_TIMER_MOCK_H_

452
metrics/timer_test.cc Normal file
View File

@ -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/metrics_library_mock.h"
#include "metrics/timer.h"
#include "metrics/timer_mock.h"
using ::testing::_;
using ::testing::Return;
namespace chromeos_metrics {
namespace {
const int64_t kStime1MSec = 1400;
const int64_t kEtime1MSec = 3000;
const int64_t kDelta1MSec = 1600;
const int64_t kStime2MSec = 4200;
const int64_t kEtime2MSec = 5000;
const int64_t kDelta2MSec = 800;
const int64_t kStime3MSec = 6600;
const int64_t kEtime3MSec = 6800;
const int64_t kDelta3MSec = 200;
} // namespace
class TimerTest : public testing::Test {
public:
TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {}
protected:
virtual void SetUp() {
EXPECT_EQ(Timer::kTimerStopped, timer_.timer_state_);
stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
stime2 += base::TimeDelta::FromMilliseconds(kStime2MSec);
etime2 += base::TimeDelta::FromMilliseconds(kEtime2MSec);
stime3 += base::TimeDelta::FromMilliseconds(kStime3MSec);
etime3 += base::TimeDelta::FromMilliseconds(kEtime3MSec);
}
virtual void TearDown() {}
Timer timer_;
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();
}

View File

@ -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 "metrics/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

View File

@ -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_

View File

@ -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 "metrics/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

View File

@ -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 "metrics/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());
}

View File

@ -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 "metrics/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_

View File

@ -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 "metrics/uploader/metrics_log_base.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "metrics/uploader/metrics_hashes.h"
#include "metrics/uploader/proto/histogram_event.pb.h"
#include "metrics/uploader/proto/system_profile.pb.h"
#include "metrics/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

View File

@ -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 "metrics/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_

View File

@ -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 "metrics/uploader/metrics_log_base.h"
#include <string>
#include <base/metrics/bucket_ranges.h>
#include <base/metrics/sample_vector.h>
#include <gtest/gtest.h>
#include "metrics/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

View File

@ -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_

View File

@ -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;
}

View File

@ -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 "metrics/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_

View File

@ -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).

View File

@ -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 "histogram_event.proto";
import "system_profile.proto";
import "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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

18
metrics/uploader/sender.h Normal file
View File

@ -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_

View File

@ -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 "metrics/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;
}

View File

@ -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 "metrics/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_

View File

@ -0,0 +1,238 @@
// 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 "metrics/uploader/system_profile_cache.h"
#include <string>
#include <vector>
#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 "metrics/persistent_integer.h"
#include "metrics/uploader/metrics_log_base.h"
#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
#include "vboot/crossystem.h"
namespace {
const char kPersistentGUIDFile[] = "/var/lib/metrics/Sysinfo.GUID";
const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId";
const char kProductIdFieldName[] = "GOOGLE_METRICS_PRODUCT_ID";
} // 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.";
std::string chromeos_version;
std::string board;
std::string build_type;
if (!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_NAME",
&profile_.os_name) ||
!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION",
&profile_.os_version) ||
!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BOARD", &board) ||
!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BUILD_TYPE",
&build_type) ||
!GetChromeOSVersion(&chromeos_version) ||
!GetHardwareId(&profile_.hardware_class)) {
DLOG(ERROR) << "failing to initialize profile cache";
return false;
}
std::string channel_string;
base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_TRACK", &channel_string);
profile_.channel = ProtoChannelFromString(channel_string);
profile_.app_version = chromeos_version + " (" + build_type + ")" +
ChannelToString(profile_.channel) + " " + board;
// If the product id is not defined, use the default one from the protobuf.
profile_.product_id = metrics::ChromeUserMetricsExtension::CHROME;
if (GetProductId(&profile_.product_id)) {
DLOG(INFO) << "Set the product id to " << profile_.product_id;
}
profile_.client_id =
testing_ ? "client_id_test" : GetPersistentGUID(kPersistentGUIDFile);
// 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.
// TODO(bsimonnet): Change this to map to the number of time system-services
// is started.
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(profile_.product_id);
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_.app_version);
profile_proto->set_channel(profile_.channel);
metrics::SystemProfileProto_OS* os = profile_proto->mutable_os();
os->set_name(profile_.os_name);
os->set_version(profile_.os_version);
}
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;
}
bool SystemProfileCache::GetChromeOSVersion(std::string* version) {
if (testing_) {
*version = "0.0.0.0";
return true;
}
std::string milestone, build, branch, patch;
unsigned tmp;
if (base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_CHROME_MILESTONE",
&milestone) &&
base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BUILD_NUMBER",
&build) &&
base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_BRANCH_NUMBER",
&branch) &&
base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_PATCH_NUMBER",
&patch)) {
// Convert to uint to ensure those fields are positive numbers.
if (base::StringToUint(milestone, &tmp) &&
base::StringToUint(build, &tmp) &&
base::StringToUint(branch, &tmp) &&
base::StringToUint(patch, &tmp)) {
std::vector<std::string> parts = {milestone, build, branch, patch};
*version = JoinString(parts, '.');
return true;
}
DLOG(INFO) << "The milestone, build, branch or patch is not a positive "
<< "number.";
return false;
}
DLOG(INFO) << "Field missing from /etc/lsb-release";
return false;
}
bool SystemProfileCache::GetHardwareId(std::string* hwid) {
CHECK(hwid);
if (testing_) {
// if we are in test mode, we do not call crossystem directly.
DLOG(INFO) << "skipping hardware id";
*hwid = "";
return true;
}
char buffer[128];
if (buffer != VbGetSystemPropertyString("hwid", buffer, sizeof(buffer))) {
LOG(ERROR) << "error getting hwid";
return false;
}
*hwid = std::string(buffer);
return true;
}
bool SystemProfileCache::GetProductId(int* product_id) const {
chromeos::OsReleaseReader reader;
if (testing_) {
base::FilePath root(config_root_);
reader.LoadTestingOnly(root);
} else {
reader.Load();
}
std::string id;
if (reader.GetString(kProductIdFieldName, &id)) {
CHECK(base::StringToInt(id, product_id)) << "Failed to convert product_id "
<< id << " to int.";
return true;
}
return false;
}
metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString(
const std::string& channel) {
if (channel == "stable-channel") {
return metrics::SystemProfileProto::CHANNEL_STABLE;
} else if (channel == "dev-channel") {
return metrics::SystemProfileProto::CHANNEL_DEV;
} else if (channel == "beta-channel") {
return metrics::SystemProfileProto::CHANNEL_BETA;
} else if (channel == "canary-channel") {
return metrics::SystemProfileProto::CHANNEL_CANARY;
}
DLOG(INFO) << "unknown channel: " << channel;
return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
}

View File

@ -0,0 +1,91 @@
// 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 "chromeos/osrelease_reader.h"
#include "metrics/persistent_integer.h"
#include "metrics/uploader/proto/system_profile.pb.h"
#include "metrics/uploader/system_profile_setter.h"
namespace metrics {
class ChromeUserMetricsExtension;
}
struct SystemProfile {
std::string os_name;
std::string os_version;
metrics::SystemProfileProto::Channel channel;
std::string app_version;
std::string hardware_class;
std::string client_id;
int32_t session_id;
int32_t product_id;
};
// Retrieves general system informations needed by the protobuf for context and
// remembers them to avoid expensive calls.
//
// The cache is populated lazily. The only method needed is Populate.
class SystemProfileCache : public SystemProfileSetter {
public:
SystemProfileCache();
SystemProfileCache(bool testing, const std::string& config_root);
// Populates the ProfileSystem protobuf with system information.
void Populate(metrics::ChromeUserMetricsExtension* profile_proto) override;
// Converts a string representation of the channel (|channel|-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();
// Gets the hardware ID using crossystem
bool GetHardwareId(std::string* hwid);
// Gets the product ID from the GOOGLE_METRICS_PRODUCT_ID field.
bool GetProductId(int* product_id) const;
// Generate the formatted chromeos version from the fields in
// /etc/lsb-release. The format is A.B.C.D where A, B, C and D are positive
// integer representing:
// * the chrome milestone
// * the build number
// * the branch number
// * the patch number
bool GetChromeOSVersion(std::string* version);
bool initialized_;
bool testing_;
std::string config_root_;
scoped_ptr<chromeos_metrics::PersistentInteger> session_id_;
SystemProfile profile_;
};
#endif // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_

View File

@ -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_

View File

@ -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 "metrics/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 "metrics/serialization/metric_sample.h"
#include "metrics/serialization/serialization_utils.h"
#include "metrics/uploader/metrics_log.h"
#include "metrics/uploader/sender_http.h"
#include "metrics/uploader/system_profile_cache.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();
}

View File

@ -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 "metrics/uploader/metrics_log.h"
#include "metrics/uploader/sender.h"
#include "metrics/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_

View File

@ -0,0 +1,257 @@
// 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/metrics_library_mock.h"
#include "metrics/serialization/metric_sample.h"
#include "metrics/uploader/metrics_log.h"
#include "metrics/uploader/mock/mock_system_profile_setter.h"
#include "metrics/uploader/mock/sender_mock.h"
#include "metrics/uploader/proto/chrome_user_metrics_extension.pb.h"
#include "metrics/uploader/proto/histogram_event.pb.h"
#include "metrics/uploader/proto/system_profile.pb.h"
#include "metrics/uploader/system_profile_cache.h"
#include "metrics/uploader/upload_service.h"
static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
static const char kMetricsFilePath[] = "/var/run/metrics/uma-events";
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();
}