am 4c0ebdf2: am 15b8a778: Merge branch \'rewrite-metrics\' into merge-metrics
* commit '4c0ebdf2ed3ae5ea61887c5ef97efc363b85d3ad': (200 commits) metrics: Import protobufs from Chromium. metrics: replace "Logging.*" with "Platform.*" platform2: Fix issues with new version of libchrome init: fix typo and prevent startup error metrics: Add a check for abnormally small messages to prevent crashes libchromeos: Switch http Response to use streams Revert "metrics: Fix the import path of protobufs." metrics: Fix the import path of protobufs. platform2: Switch over to libchrome r323904 metrics: add TPM.EarlyResetDuringCommand CrOS event. delete __STDC_{FORMAT,LIMIT}_MACROS metrics: make metrics library also use /var/lib/metrics/uma-events metrics: tell what file we can't open on error metrics: move sample file from /var/run to /var/lib metrics: don't upload metrics when metrics are disabled metrics: Disable uploader on non-official build. platform2: Switch over to using base64 functions from libchromeos metrics: fix -Winconsistent-missing-override warning metrics: Fix upload_service to work with base::MessageLoop metrics: generate app_version from standard lsb-release fields ...
This commit is contained in:
commit
625bf08c63
|
@ -0,0 +1,3 @@
|
|||
semenzato@chromium.org
|
||||
derat@chromium.org
|
||||
bsimonnet@chromium.org
|
|
@ -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.
|
|
@ -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']
|
||||
},
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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_
|
|
@ -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}
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
'variables': {
|
||||
'libbase_ver': 334380,
|
||||
},
|
||||
'includes': [
|
||||
'libmetrics.gypi',
|
||||
],
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
'target_defaults': {
|
||||
'variables': {
|
||||
'deps': [
|
||||
'libchrome-<(libbase_ver)',
|
||||
'libchromeos-<(libbase_ver)',
|
||||
]
|
||||
},
|
||||
'cflags_cc': [
|
||||
'-fno-exceptions',
|
||||
],
|
||||
},
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'libmetrics-<(libbase_ver)',
|
||||
'type': 'shared_library',
|
||||
'cflags': [
|
||||
'-fvisibility=default',
|
||||
],
|
||||
'libraries+': [
|
||||
'-lpolicy-<(libbase_ver)',
|
||||
],
|
||||
'sources': [
|
||||
'c_metrics_library.cc',
|
||||
'metrics_library.cc',
|
||||
'serialization/metric_sample.cc',
|
||||
'serialization/serialization_utils.cc',
|
||||
'timer.cc',
|
||||
],
|
||||
'include_dirs': ['.'],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
bslot=@BSLOT@
|
||||
|
||||
Name: libmetrics
|
||||
Description: Chrome OS metrics library
|
||||
Version: ${bslot}
|
||||
Requires.private: libchrome-${bslot}
|
||||
Libs: -lmetrics-${bslot}
|
|
@ -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}"
|
|
@ -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': ['.'],
|
||||
},
|
||||
],
|
||||
}],
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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).
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue