Merge "Remove crash_reporter and metricsd"

This commit is contained in:
Bill Yi 2016-09-12 16:19:48 +00:00 committed by Gerrit Code Review
commit 07572a92b9
120 changed files with 0 additions and 15500 deletions

View File

@ -1 +0,0 @@
crash

View File

@ -1,6 +0,0 @@
ACTION=="change", SUBSYSTEM=="drm", KERNEL=="card0", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=KERNEL=card0:SUBSYSTEM=drm:ACTION=change"
# For detecting cypress trackpad issue. Passing into crash_reporter SUBSYSTEM=i2c-cyapa since crash_reporter does not handle DRIVER string.
ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="cyapa", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-cyapa:ACTION=change"
# For detecting Atmel trackpad/touchscreen issue. Passing into crash_reporter SUBSYSTEM=i2c-atmel_mxt_ts since crash_reporter does not handle DRIVER string.
ACTION=="change", SUBSYSTEM=="i2c", DRIVER=="atmel_mxt_ts", ENV{ERROR}=="1", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=i2c-atmel_mxt_ts:ACTION=change"
ACTION=="add", SUBSYSTEM=="devcoredump", RUN+="/sbin/crash_reporter --udev=SUBSYSTEM=devcoredump:ACTION=add:KERNEL_NUMBER=%n"

View File

@ -1,144 +0,0 @@
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH := $(call my-dir)
crash_reporter_cpp_extension := .cc
crash_reporter_src := crash_collector.cc \
kernel_collector.cc \
kernel_warning_collector.cc \
unclean_shutdown_collector.cc \
user_collector.cc
crash_reporter_includes := external/gtest/include
crash_reporter_test_src := crash_collector_test.cc \
crash_reporter_logs_test.cc \
kernel_collector_test.cc \
testrunner.cc \
unclean_shutdown_collector_test.cc \
user_collector_test.cc
warn_collector_src := warn_collector.l
# Crash reporter static library.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libcrash
LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
LOCAL_C_INCLUDES := $(crash_reporter_includes)
LOCAL_SHARED_LIBRARIES := libchrome \
libbinder \
libbrillo \
libcutils \
libmetrics \
libpcrecpp
LOCAL_STATIC_LIBRARIES := libmetricscollectorservice
LOCAL_SRC_FILES := $(crash_reporter_src)
include $(BUILD_STATIC_LIBRARY)
# Crash reporter client.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := crash_reporter
LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
LOCAL_C_INCLUDES := $(crash_reporter_includes)
LOCAL_REQUIRED_MODULES := core2md \
crash_reporter_logs.conf \
crash_sender \
crash_server
LOCAL_INIT_RC := crash_reporter.rc
LOCAL_SHARED_LIBRARIES := libchrome \
libbinder \
libbrillo \
libcutils \
libmetrics \
libpcrecpp \
libutils
LOCAL_SRC_FILES := crash_reporter.cc
LOCAL_STATIC_LIBRARIES := libcrash \
libmetricscollectorservice
include $(BUILD_EXECUTABLE)
# Crash sender script.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := crash_sender
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
LOCAL_REQUIRED_MODULES := curl grep periodic_scheduler
LOCAL_SRC_FILES := crash_sender
include $(BUILD_PREBUILT)
# Warn collector client.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := warn_collector
LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
LOCAL_SHARED_LIBRARIES := libmetrics
LOCAL_SRC_FILES := $(warn_collector_src)
include $(BUILD_EXECUTABLE)
# /etc/os-release.d/crash_server configuration file.
# ========================================================
ifdef OSRELEASED_DIRECTORY
include $(CLEAR_VARS)
LOCAL_MODULE := crash_server
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/$(OSRELEASED_DIRECTORY)
include $(BUILD_SYSTEM)/base_rules.mk
# Optionally populate the BRILLO_CRASH_SERVER variable from a product
# configuration file: brillo/crash_server.
LOADED_BRILLO_CRASH_SERVER := $(call cfgtree-get-if-exists,brillo/crash_server)
# If the crash server isn't set, use a blank value. crash_sender
# will log it as a configuration error.
$(LOCAL_BUILT_MODULE): BRILLO_CRASH_SERVER ?= "$(LOADED_BRILLO_CRASH_SERVER)"
$(LOCAL_BUILT_MODULE):
$(hide)mkdir -p $(dir $@)
echo $(BRILLO_CRASH_SERVER) > $@
endif
# Crash reporter logs conf file.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := crash_reporter_logs.conf
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(PRODUCT_OUT)/system/etc
LOCAL_SRC_FILES := crash_reporter_logs.conf
include $(BUILD_PREBUILT)
# Periodic Scheduler.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := periodic_scheduler
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
LOCAL_SRC_FILES := periodic_scheduler
include $(BUILD_PREBUILT)
# Crash reporter tests.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := crash_reporter_tests
LOCAL_CPP_EXTENSION := $(crash_reporter_cpp_extension)
LOCAL_SHARED_LIBRARIES := libchrome \
libbrillo \
libcutils \
libpcrecpp
LOCAL_SRC_FILES := $(crash_reporter_test_src)
LOCAL_STATIC_LIBRARIES := libcrash libgmock
include $(BUILD_NATIVE_TEST)

View File

@ -1,2 +0,0 @@
set noparent
vapier@chromium.org

View File

@ -1,61 +0,0 @@
# crash_reporter
`crash_reporter` is a deamon running on the device that saves the call stack of
crashing programs. It makes use of the
[Breakpad](https://chromium.googlesource.com/breakpad/breakpad/) library.
During a build, Breakpad symbol files are generated for all binaries. They are
packaged into a zip file when running `m dist`, so that a developer can upload
them to the crash server.
On a device, if the user has opted in to metrics and crash reporting, a
Breakpad minidump is generated when an executable crashes, which is then
uploaded to the crash server.
On the crash server, it compares the minidump's signature to the symbol files
that the developer has uploaded, and extracts and symbolizes the stack trace
from the minidump.
## SELinux policies
In order to correctly generate a minidump, `crash_reporter` needs to be given
the proper SELinux permissions for accessing the domain of the crashing
executable. By default, `crash_reporter` has only been given access to a select
number of system domains, such as `metricsd`, `weave`, and `update_engine`. If
a developer wants their executable's crashes to be caught by `crash_reporter`,
they will have to set their SELinux policies in their .te file to allow
`crash_reporter` access to their domain. This can be done through a simple
[macro](https://android.googlesource.com/device/generic/brillo/+/master/sepolicy/te_macros):
allow_crash_reporter(domain_name)
Replace *domain_name* with whatever domain is assigned to the executable in
the `file_contexts` file.
## Configuration
`crash_reporter` has a few different configuration options that have to be set.
- Crashes are only handled and uploaded if analytics reporting is enabled,
either via the weave call to set `_metrics.enableAnalyticsReporting` or by
manually creating the file `/data/misc/metrics/enabled` (for testing only).
- The `BRILLO_CRASH_SERVER` make variable should be set in the `product.mk`
file to the URL of the crash server. For Brillo builds, it is set
automatically through the product configuration. Setting this variable will
populate the `/etc/os-release.d/crash_server` file on the device, which is
read by `crash_sender`.
- The `BRILLO_PRODUCT_ID` make variable should be set in the `product.mk` file
to the product's ID. For Brillo builds, it is set automatically through the
product configuration. Setting this variable will populate the
`/etc/os-release.d/product_id`, which is read by `crash_sender`.
## Uploading crash reports in *eng* builds
By default, crash reports are only uploaded to the server for production
*user* and *userdebug* images. In *eng* builds, with crash reporting enabled
the device will generate minidumps for any crashing executables but will not
send them to the crash server. If a developer does want to force an upload,
they can do so by issuing the command `SECONDS_SEND_SPREAD=5 FORCE_OFFICIAL=1
crash_sender` from an ADB shell. This will send the report to the server, with
the *image_type* field set to *force-official* so that these reports can be
differentiated from normal reports.

View File

@ -1,31 +0,0 @@
Apr 31 25:25:25 localhost kernel: [117959.226729] [<ffffffff810e16bf>] do_vfs_ioctl+0x469/0x4b3
Apr 31 25:25:25 localhost kernel: [117959.226738] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
Apr 31 25:25:25 localhost kernel: [117959.226747] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
Apr 31 25:25:25 localhost kernel: [117959.226756] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
Apr 31 25:25:25 localhost kernel: [117959.226765] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
Apr 31 25:25:25 localhost kernel: [117959.226774] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
Apr 31 25:25:25 localhost kernel: [117959.226782] ---[ end trace f16822cad7406cec ]---
Apr 31 25:25:25 localhost kernel: [117959.231085] ------------[ cut here ]------------
Apr 31 25:25:25 localhost kernel: [117959.231100] WARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
Apr 31 25:25:25 localhost kernel: [117959.231113] Hardware name: Link
Apr 31 25:25:25 localhost kernel: [117959.231117] eDP powered off while attempting aux channel communication.
Apr 31 25:25:25 localhost kernel: [117959.231240] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
Apr 31 25:25:25 localhost kernel: [117959.231247] Call Trace:
Apr 31 25:25:25 localhost kernel: [117959.231393] [<ffffffff810d3117>] ? fsnotify_access+0x58/0x60
Apr 31 25:25:25 localhost kernel: [117959.231402] [<ffffffff810d3791>] ? vfs_read+0xad/0xd7
Apr 31 25:25:25 localhost kernel: [117959.231411] [<ffffffff810e175f>] sys_ioctl+0x56/0x7b
Apr 31 25:25:25 localhost kernel: [117959.231420] [<ffffffff810d37fe>] ? sys_read+0x43/0x73
Apr 31 25:25:25 localhost kernel: [117959.231431] [<ffffffff8146b7d2>] system_call_fastpath+0x16/0x1b
Apr 31 25:25:25 localhost kernel: [117959.231439] ---[ end trace f16822cad7406ced ]---
Apr 31 25:25:25 localhost kernel: [117959.231450] ------------[ cut here ]------------
Apr 31 25:25:25 localhost kernel: [117959.231458] BARNING: at /mnt/host/source/src/third_party/kernel/files/drivers/gpu/drm/i915/intel_dp.c:351 intel_dp_check_edp+0x6b/0xb9()
Apr 31 25:25:25 localhost kernel: [117959.231458] ("BARNING" above is intentional)
Apr 31 25:25:25 localhost kernel: [117959.231471] Hardware name: Link
Apr 31 25:25:25 localhost kernel: [117959.231475] eDP powered off while attempting aux channel communication.
Apr 31 25:25:25 localhost kernel: [117959.231482] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat rfcomm i2c_dev ath9k_btcoex snd_hda_codec_hdmi snd_hda_codec_ca0132 mac80211 snd_hda_intel ath9k_common_btcoex snd_hda_codec ath9k_hw_btcoex aesni_intel cryptd snd_hwdep ath snd_pcm aes_x86_64 isl29018(C) memconsole snd_timer snd_page_alloc industrialio(C) cfg80211 rtc_cmos nm10_gpio zram(C) zsmalloc(C) lzo_decompress lzo_compress fuse nf_conntrack_ipv6 nf_defrag_ipv6 ip6table_filter ip6_tables xt_mark option usb_wwan cdc_ether usbnet ath3k btusb bluetooth uvcvideo videobuf2_core videodev videobuf2_vmalloc videobuf2_memops joydev
Apr 31 25:25:25 localhost kernel: [117959.231588] Pid: 10508, comm: X Tainted: G WC 3.4.0 #1
Apr 31 25:25:25 localhost kernel: [117959.231595] Call Trace:
Apr 31 25:25:25 localhost kernel: [117959.231601] [<ffffffff8102a931>] warn_slowpath_common+0x83/0x9c
Apr 31 25:25:25 localhost kernel: [117959.231610] [<ffffffff8102a9ed>] warn_slowpath_fmt+0x46/0x48
Apr 31 25:25:25 localhost kernel: [117959.231620] [<ffffffff812af495>] intel_dp_check_edp+0x6b/0xb9
Apr 31 25:25:25 localhost kernel: [117959.231629] [<ffffffff8102a9ed>] ? warn_slowpath_fmt+

View File

@ -1,454 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "crash_collector.h"
#include <dirent.h>
#include <fcntl.h> // For file creation modes.
#include <inttypes.h>
#include <linux/limits.h> // PATH_MAX
#include <pwd.h> // For struct passwd.
#include <sys/types.h> // for mode_t.
#include <sys/wait.h> // For waitpid.
#include <unistd.h> // For execv and fork.
#include <set>
#include <utility>
#include <vector>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/key_value_store.h>
#include <brillo/osrelease_reader.h>
#include <brillo/process.h>
namespace {
const char kCollectChromeFile[] =
"/mnt/stateful_partition/etc/collect_chrome_crashes";
const char kCrashTestInProgressPath[] =
"/data/misc/crash_reporter/tmp/crash-test-in-progress";
const char kDefaultLogConfig[] = "/etc/crash_reporter_logs.conf";
const char kDefaultUserName[] = "chronos";
const char kLeaveCoreFile[] = "/data/misc/crash_reporter/.leave_core";
const char kShellPath[] = "/system/bin/sh";
const char kSystemCrashPath[] = "/data/misc/crash_reporter/crash";
const char kUploadVarPrefix[] = "upload_var_";
const char kUploadFilePrefix[] = "upload_file_";
// Product information keys in the /etc/os-release.d folder.
static const char kBdkVersionKey[] = "bdk_version";
static const char kProductIDKey[] = "product_id";
static const char kProductVersionKey[] = "product_version";
// Normally this path is not used. Unfortunately, there are a few edge cases
// where we need this. Any process that runs as kDefaultUserName that crashes
// is consider a "user crash". That includes the initial Chrome browser that
// runs the login screen. If that blows up, there is no logged in user yet,
// so there is no per-user dir for us to stash things in. Instead we fallback
// to this path as it is at least encrypted on a per-system basis.
//
// This also comes up when running autotests. The GUI is sitting at the login
// screen while tests are sshing in, changing users, and triggering crashes as
// the user (purposefully).
const char kFallbackUserCrashPath[] = "/home/chronos/crash";
// Directory mode of the user crash spool directory.
const mode_t kUserCrashPathMode = 0755;
// Directory mode of the system crash spool directory.
const mode_t kSystemCrashPathMode = 01755;
const uid_t kRootOwner = 0;
const uid_t kRootGroup = 0;
} // namespace
// Maximum crash reports per crash spool directory. Note that this is
// a separate maximum from the maximum rate at which we upload these
// diagnostics. The higher this rate is, the more space we allow for
// core files, minidumps, and kcrash logs, and equivalently the more
// processor and I/O bandwidth we dedicate to handling these crashes when
// many occur at once. Also note that if core files are configured to
// be left on the file system, we stop adding crashes when either the
// number of core files or minidumps reaches this number.
const int CrashCollector::kMaxCrashDirectorySize = 32;
using base::FilePath;
using base::StringPrintf;
CrashCollector::CrashCollector()
: log_config_path_(kDefaultLogConfig) {
}
CrashCollector::~CrashCollector() {
}
void CrashCollector::Initialize(
CrashCollector::CountCrashFunction count_crash_function,
CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function) {
CHECK(count_crash_function);
CHECK(is_feedback_allowed_function);
count_crash_function_ = count_crash_function;
is_feedback_allowed_function_ = is_feedback_allowed_function;
}
int CrashCollector::WriteNewFile(const FilePath &filename,
const char *data,
int size) {
int fd = HANDLE_EINTR(open(filename.value().c_str(),
O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0666));
if (fd < 0) {
return -1;
}
int rv = base::WriteFileDescriptor(fd, data, size) ? size : -1;
IGNORE_EINTR(close(fd));
return rv;
}
std::string CrashCollector::Sanitize(const std::string &name) {
// Make sure the sanitized name does not include any periods.
// The logic in crash_sender relies on this.
std::string result = name;
for (size_t i = 0; i < name.size(); ++i) {
if (!isalnum(result[i]) && result[i] != '_')
result[i] = '_';
}
return result;
}
std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
time_t timestamp,
pid_t pid) {
struct tm tm;
localtime_r(&timestamp, &tm);
std::string sanitized_exec_name = Sanitize(exec_name);
return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
sanitized_exec_name.c_str(),
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
pid);
}
FilePath CrashCollector::GetCrashPath(const FilePath &crash_directory,
const std::string &basename,
const std::string &extension) {
return crash_directory.Append(StringPrintf("%s.%s",
basename.c_str(),
extension.c_str()));
}
FilePath CrashCollector::GetCrashDirectoryInfo(
mode_t *mode,
uid_t *directory_owner,
gid_t *directory_group) {
*mode = kSystemCrashPathMode;
*directory_owner = kRootOwner;
*directory_group = kRootGroup;
return FilePath(kSystemCrashPath);
}
bool CrashCollector::GetUserInfoFromName(const std::string &name,
uid_t *uid,
gid_t *gid) {
char storage[256];
struct passwd passwd_storage;
struct passwd *passwd_result = nullptr;
if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
&passwd_result) != 0 || passwd_result == nullptr) {
LOG(ERROR) << "Cannot find user named " << name;
return false;
}
*uid = passwd_result->pw_uid;
*gid = passwd_result->pw_gid;
return true;
}
bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid __unused,
FilePath *crash_directory,
bool *out_of_capacity) {
if (out_of_capacity) *out_of_capacity = false;
// For testing.
if (!forced_crash_directory_.empty()) {
*crash_directory = forced_crash_directory_;
return true;
}
mode_t directory_mode;
uid_t directory_owner;
gid_t directory_group;
*crash_directory =
GetCrashDirectoryInfo(&directory_mode,
&directory_owner,
&directory_group);
if (!base::PathExists(*crash_directory)) {
// Create the spool directory with the appropriate mode (regardless of
// umask) and ownership.
mode_t old_mask = umask(0);
if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
chown(crash_directory->value().c_str(),
directory_owner,
directory_group) < 0) {
LOG(ERROR) << "Unable to create appropriate crash directory";
return false;
}
umask(old_mask);
}
if (!base::PathExists(*crash_directory)) {
LOG(ERROR) << "Unable to create crash directory "
<< crash_directory->value().c_str();
return false;
}
if (!CheckHasCapacity(*crash_directory)) {
if (out_of_capacity) *out_of_capacity = true;
LOG(ERROR) << "Directory " << crash_directory->value()
<< " is out of capacity.";
return false;
}
return true;
}
FilePath CrashCollector::GetProcessPath(pid_t pid) {
return FilePath(StringPrintf("/proc/%d", pid));
}
bool CrashCollector::GetSymlinkTarget(const FilePath &symlink,
FilePath *target) {
ssize_t max_size = 64;
std::vector<char> buffer;
while (true) {
buffer.resize(max_size + 1);
ssize_t size = readlink(symlink.value().c_str(), buffer.data(), max_size);
if (size < 0) {
int saved_errno = errno;
LOG(ERROR) << "Readlink failed on " << symlink.value() << " with "
<< saved_errno;
return false;
}
buffer[size] = 0;
if (size == max_size) {
max_size *= 2;
if (max_size > PATH_MAX) {
return false;
}
continue;
}
break;
}
*target = FilePath(buffer.data());
return true;
}
bool CrashCollector::GetExecutableBaseNameFromPid(pid_t pid,
std::string *base_name) {
FilePath target;
FilePath process_path = GetProcessPath(pid);
FilePath exe_path = process_path.Append("exe");
if (!GetSymlinkTarget(exe_path, &target)) {
LOG(INFO) << "GetSymlinkTarget failed - Path " << process_path.value()
<< " DirectoryExists: "
<< base::DirectoryExists(process_path);
// Try to further diagnose exe readlink failure cause.
struct stat buf;
int stat_result = stat(exe_path.value().c_str(), &buf);
int saved_errno = errno;
if (stat_result < 0) {
LOG(INFO) << "stat " << exe_path.value() << " failed: " << stat_result
<< " " << saved_errno;
} else {
LOG(INFO) << "stat " << exe_path.value() << " succeeded: st_mode="
<< buf.st_mode;
}
return false;
}
*base_name = target.BaseName().value();
return true;
}
// Return true if the given crash directory has not already reached
// maximum capacity.
bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
DIR* dir = opendir(crash_directory.value().c_str());
if (!dir) {
LOG(WARNING) << "Unable to open crash directory "
<< crash_directory.value();
return false;
}
struct dirent ent_buf;
struct dirent* ent;
bool full = false;
std::set<std::string> basenames;
while (readdir_r(dir, &ent_buf, &ent) == 0 && ent) {
if ((strcmp(ent->d_name, ".") == 0) ||
(strcmp(ent->d_name, "..") == 0))
continue;
std::string filename(ent->d_name);
size_t last_dot = filename.rfind('.');
std::string basename;
// If there is a valid looking extension, use the base part of the
// name. If the only dot is the first byte (aka a dot file), treat
// it as unique to avoid allowing a directory full of dot files
// from accumulating.
if (last_dot != std::string::npos && last_dot != 0)
basename = filename.substr(0, last_dot);
else
basename = filename;
basenames.insert(basename);
if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
LOG(WARNING) << "Crash directory " << crash_directory.value()
<< " already full with " << kMaxCrashDirectorySize
<< " pending reports";
full = true;
break;
}
}
closedir(dir);
return !full;
}
bool CrashCollector::GetLogContents(const FilePath &config_path,
const std::string &exec_name,
const FilePath &output_file) {
brillo::KeyValueStore store;
if (!store.Load(config_path)) {
LOG(INFO) << "Unable to read log configuration file "
<< config_path.value();
return false;
}
std::string command;
if (!store.GetString(exec_name, &command))
return false;
brillo::ProcessImpl diag_process;
diag_process.AddArg(kShellPath);
diag_process.AddStringOption("-c", command);
diag_process.RedirectOutput(output_file.value());
const int result = diag_process.Run();
if (result != 0) {
LOG(INFO) << "Log command \"" << command << "\" exited with " << result;
return false;
}
return true;
}
void CrashCollector::AddCrashMetaData(const std::string &key,
const std::string &value) {
extra_metadata_.append(StringPrintf("%s=%s\n", key.c_str(), value.c_str()));
}
void CrashCollector::AddCrashMetaUploadFile(const std::string &key,
const std::string &path) {
if (!path.empty())
AddCrashMetaData(kUploadFilePrefix + key, path);
}
void CrashCollector::AddCrashMetaUploadData(const std::string &key,
const std::string &value) {
if (!value.empty())
AddCrashMetaData(kUploadVarPrefix + key, value);
}
void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
const std::string &exec_name,
const std::string &payload_path) {
int64_t payload_size = -1;
base::GetFileSize(FilePath(payload_path), &payload_size);
brillo::OsReleaseReader reader;
if (!forced_osreleased_directory_.empty()) {
reader.LoadTestingOnly(forced_osreleased_directory_);
} else {
reader.Load();
}
std::string bdk_version = "undefined";
std::string product_id = "undefined";
std::string product_version = "undefined";
if (!reader.GetString(kBdkVersionKey, &bdk_version)) {
LOG(ERROR) << "Could not read " << kBdkVersionKey
<< " from /etc/os-release.d/";
}
if (!reader.GetString(kProductIDKey, &product_id)) {
LOG(ERROR) << "Could not read " << kProductIDKey
<< " from /etc/os-release.d/";
}
if (!reader.GetString(kProductVersionKey, &product_version)) {
LOG(ERROR) << "Could not read " << kProductVersionKey
<< " from /etc/os-release.d/";
}
std::string meta_data = StringPrintf("%sexec_name=%s\n"
"payload=%s\n"
"payload_size=%" PRId64 "\n"
"%s=%s\n"
"%s=%s\n"
"%s=%s\n"
"done=1\n",
extra_metadata_.c_str(),
exec_name.c_str(),
payload_path.c_str(),
payload_size,
kBdkVersionKey,
bdk_version.c_str(),
kProductIDKey,
product_id.c_str(),
kProductVersionKey,
product_version.c_str());
// We must use WriteNewFile instead of base::WriteFile as we
// do not want to write with root access to a symlink that an attacker
// might have created.
if (WriteNewFile(meta_path, meta_data.c_str(), meta_data.size()) < 0) {
LOG(ERROR) << "Unable to write " << meta_path.value();
}
}
bool CrashCollector::IsCrashTestInProgress() {
return base::PathExists(FilePath(kCrashTestInProgressPath));
}
bool CrashCollector::IsDeveloperImage() {
// If we're testing crash reporter itself, we don't want to special-case
// for developer images.
if (IsCrashTestInProgress())
return false;
return base::PathExists(FilePath(kLeaveCoreFile));
}

View File

@ -1,174 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_CRASH_COLLECTOR_H_
#define CRASH_REPORTER_CRASH_COLLECTOR_H_
#include <sys/stat.h>
#include <map>
#include <string>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
// User crash collector.
class CrashCollector {
public:
typedef void (*CountCrashFunction)();
typedef bool (*IsFeedbackAllowedFunction)();
CrashCollector();
virtual ~CrashCollector();
// Initialize the crash collector for detection of crashes, given a
// crash counting function, and metrics collection enabled oracle.
void Initialize(CountCrashFunction count_crash,
IsFeedbackAllowedFunction is_metrics_allowed);
protected:
friend class CrashCollectorTest;
FRIEND_TEST(ChromeCollectorTest, HandleCrash);
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityCorrectBasename);
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityStrangeNames);
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityUsual);
FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfo);
FRIEND_TEST(CrashCollectorTest, GetCrashPath);
FRIEND_TEST(CrashCollectorTest, GetLogContents);
FRIEND_TEST(CrashCollectorTest, ForkExecAndPipe);
FRIEND_TEST(CrashCollectorTest, FormatDumpBasename);
FRIEND_TEST(CrashCollectorTest, Initialize);
FRIEND_TEST(CrashCollectorTest, MetaData);
FRIEND_TEST(CrashCollectorTest, Sanitize);
FRIEND_TEST(CrashCollectorTest, WriteNewFile);
FRIEND_TEST(ForkExecAndPipeTest, Basic);
FRIEND_TEST(ForkExecAndPipeTest, NonZeroReturnValue);
FRIEND_TEST(ForkExecAndPipeTest, BadOutputFile);
FRIEND_TEST(ForkExecAndPipeTest, ExistingOutputFile);
FRIEND_TEST(ForkExecAndPipeTest, BadExecutable);
FRIEND_TEST(ForkExecAndPipeTest, StderrCaptured);
FRIEND_TEST(ForkExecAndPipeTest, NULLParam);
FRIEND_TEST(ForkExecAndPipeTest, NoParams);
FRIEND_TEST(ForkExecAndPipeTest, SegFaultHandling);
// Set maximum enqueued crashes in a crash directory.
static const int kMaxCrashDirectorySize;
// Writes |data| of |size| to |filename|, which must be a new file.
// If the file already exists or writing fails, return a negative value.
// Otherwise returns the number of bytes written.
int WriteNewFile(const base::FilePath &filename, const char *data, int size);
// Return a filename that has only [a-z0-1_] characters by mapping
// all others into '_'.
std::string Sanitize(const std::string &name);
// For testing, set the directory always returned by
// GetCreatedCrashDirectoryByEuid.
void ForceCrashDirectory(const base::FilePath &forced_directory) {
forced_crash_directory_ = forced_directory;
}
// For testing, set the root directory to read etc/os-release.d properties
// from.
void ForceOsReleaseDDirectory(const base::FilePath &forced_directory) {
forced_osreleased_directory_ = forced_directory;
}
base::FilePath GetCrashDirectoryInfo(mode_t *mode,
uid_t *directory_owner,
gid_t *directory_group);
bool GetUserInfoFromName(const std::string &name,
uid_t *uid,
gid_t *gid);
// Determines the crash directory for given euid, and creates the
// directory if necessary with appropriate permissions. If
// |out_of_capacity| is not nullptr, it is set to indicate if the call
// failed due to not having capacity in the crash directory. Returns
// true whether or not directory needed to be created, false on any
// failure. If the crash directory is at capacity, returns false.
bool GetCreatedCrashDirectoryByEuid(uid_t euid,
base::FilePath *crash_file_path,
bool *out_of_capacity);
// Format crash name based on components.
std::string FormatDumpBasename(const std::string &exec_name,
time_t timestamp,
pid_t pid);
// Create a file path to a file in |crash_directory| with the given
// |basename| and |extension|.
base::FilePath GetCrashPath(const base::FilePath &crash_directory,
const std::string &basename,
const std::string &extension);
base::FilePath GetProcessPath(pid_t pid);
bool GetSymlinkTarget(const base::FilePath &symlink,
base::FilePath *target);
bool GetExecutableBaseNameFromPid(pid_t pid,
std::string *base_name);
// Check given crash directory still has remaining capacity for another
// crash.
bool CheckHasCapacity(const base::FilePath &crash_directory);
// Write a log applicable to |exec_name| to |output_file| based on the
// log configuration file at |config_path|.
bool GetLogContents(const base::FilePath &config_path,
const std::string &exec_name,
const base::FilePath &output_file);
// Add non-standard meta data to the crash metadata file. Call
// before calling WriteCrashMetaData. Key must not contain "=" or
// "\n" characters. Value must not contain "\n" characters.
void AddCrashMetaData(const std::string &key, const std::string &value);
// Add a file to be uploaded to the crash reporter server. The file must
// persist until the crash report is sent; ideally it should live in the same
// place as the .meta file, so it can be cleaned up automatically.
void AddCrashMetaUploadFile(const std::string &key, const std::string &path);
// Add non-standard meta data to the crash metadata file.
// Data added though this call will be uploaded to the crash reporter server,
// appearing as a form field.
void AddCrashMetaUploadData(const std::string &key, const std::string &value);
// Write a file of metadata about crash.
void WriteCrashMetaData(const base::FilePath &meta_path,
const std::string &exec_name,
const std::string &payload_path);
// Returns true if the a crash test is currently running.
bool IsCrashTestInProgress();
// Returns true if we should consider ourselves to be running on a
// developer image.
bool IsDeveloperImage();
CountCrashFunction count_crash_function_;
IsFeedbackAllowedFunction is_feedback_allowed_function_;
std::string extra_metadata_;
base::FilePath forced_crash_directory_;
base::FilePath forced_osreleased_directory_;
base::FilePath log_config_path_;
private:
DISALLOW_COPY_AND_ASSIGN(CrashCollector);
};
#endif // CRASH_REPORTER_CRASH_COLLECTOR_H_

View File

@ -1,269 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "crash_collector_test.h"
#include <unistd.h>
#include <utility>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/syslog_logging.h>
#include <gtest/gtest.h>
#include "crash_collector.h"
using base::FilePath;
using base::StringPrintf;
using brillo::FindLog;
using ::testing::Invoke;
using ::testing::Return;
namespace {
void CountCrash() {
ADD_FAILURE();
}
bool IsMetrics() {
ADD_FAILURE();
return false;
}
} // namespace
class CrashCollectorTest : public ::testing::Test {
public:
void SetUp() {
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(Return());
collector_.Initialize(CountCrash, IsMetrics);
EXPECT_TRUE(test_dir_.CreateUniqueTempDir());
brillo::ClearLog();
}
bool CheckHasCapacity();
protected:
CrashCollectorMock collector_;
// Temporary directory used for tests.
base::ScopedTempDir test_dir_;
};
TEST_F(CrashCollectorTest, Initialize) {
ASSERT_TRUE(CountCrash == collector_.count_crash_function_);
ASSERT_TRUE(IsMetrics == collector_.is_feedback_allowed_function_);
}
TEST_F(CrashCollectorTest, WriteNewFile) {
FilePath test_file = test_dir_.path().Append("test_new");
const char kBuffer[] = "buffer";
unsigned int numBytesWritten = collector_.WriteNewFile(
test_file,
kBuffer,
strlen(kBuffer));
EXPECT_EQ(strlen(kBuffer), numBytesWritten);
EXPECT_LT(collector_.WriteNewFile(test_file,
kBuffer,
strlen(kBuffer)), 0);
}
TEST_F(CrashCollectorTest, Sanitize) {
EXPECT_EQ("chrome", collector_.Sanitize("chrome"));
EXPECT_EQ("CHROME", collector_.Sanitize("CHROME"));
EXPECT_EQ("1chrome2", collector_.Sanitize("1chrome2"));
EXPECT_EQ("chrome__deleted_", collector_.Sanitize("chrome (deleted)"));
EXPECT_EQ("foo_bar", collector_.Sanitize("foo.bar"));
EXPECT_EQ("", collector_.Sanitize(""));
EXPECT_EQ("_", collector_.Sanitize(" "));
}
TEST_F(CrashCollectorTest, FormatDumpBasename) {
struct tm tm = {};
tm.tm_sec = 15;
tm.tm_min = 50;
tm.tm_hour = 13;
tm.tm_mday = 23;
tm.tm_mon = 4;
tm.tm_year = 110;
tm.tm_isdst = -1;
std::string basename =
collector_.FormatDumpBasename("foo", mktime(&tm), 100);
ASSERT_EQ("foo.20100523.135015.100", basename);
}
TEST_F(CrashCollectorTest, GetCrashPath) {
EXPECT_EQ("/var/spool/crash/myprog.20100101.1200.1234.core",
collector_.GetCrashPath(FilePath("/var/spool/crash"),
"myprog.20100101.1200.1234",
"core").value());
EXPECT_EQ("/home/chronos/user/crash/chrome.20100101.1200.1234.dmp",
collector_.GetCrashPath(FilePath("/home/chronos/user/crash"),
"chrome.20100101.1200.1234",
"dmp").value());
}
bool CrashCollectorTest::CheckHasCapacity() {
const char* kFullMessage =
StringPrintf("Crash directory %s already full",
test_dir_.path().value().c_str()).c_str();
bool has_capacity = collector_.CheckHasCapacity(test_dir_.path());
bool has_message = FindLog(kFullMessage);
EXPECT_EQ(has_message, !has_capacity);
return has_capacity;
}
TEST_F(CrashCollectorTest, CheckHasCapacityUsual) {
// Test kMaxCrashDirectorySize - 1 non-meta files can be added.
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
base::WriteFile(test_dir_.path().Append(StringPrintf("file%d.core", i)),
"", 0);
EXPECT_TRUE(CheckHasCapacity());
}
// Test an additional kMaxCrashDirectorySize - 1 meta files fit.
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
base::WriteFile(test_dir_.path().Append(StringPrintf("file%d.meta", i)),
"", 0);
EXPECT_TRUE(CheckHasCapacity());
}
// Test an additional kMaxCrashDirectorySize meta files don't fit.
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize; ++i) {
base::WriteFile(test_dir_.path().Append(StringPrintf("overage%d.meta", i)),
"", 0);
EXPECT_FALSE(CheckHasCapacity());
}
}
TEST_F(CrashCollectorTest, CheckHasCapacityCorrectBasename) {
// Test kMaxCrashDirectorySize - 1 files can be added.
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
base::WriteFile(test_dir_.path().Append(StringPrintf("file.%d.core", i)),
"", 0);
EXPECT_TRUE(CheckHasCapacity());
}
base::WriteFile(test_dir_.path().Append("file.last.core"), "", 0);
EXPECT_FALSE(CheckHasCapacity());
}
TEST_F(CrashCollectorTest, CheckHasCapacityStrangeNames) {
// Test many files with different extensions and same base fit.
for (int i = 0; i < 5 * CrashCollector::kMaxCrashDirectorySize; ++i) {
base::WriteFile(test_dir_.path().Append(StringPrintf("a.%d", i)), "", 0);
EXPECT_TRUE(CheckHasCapacity());
}
// Test dot files are treated as individual files.
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 2; ++i) {
base::WriteFile(test_dir_.path().Append(StringPrintf(".file%d", i)), "", 0);
EXPECT_TRUE(CheckHasCapacity());
}
base::WriteFile(test_dir_.path().Append("normal.meta"), "", 0);
EXPECT_FALSE(CheckHasCapacity());
}
TEST_F(CrashCollectorTest, MetaData) {
const char kMetaFileBasename[] = "generated.meta";
FilePath meta_file = test_dir_.path().Append(kMetaFileBasename);
FilePath payload_file = test_dir_.path().Append("payload-file");
FilePath osreleased_directory =
test_dir_.path().Append("etc").Append("os-release.d");
ASSERT_TRUE(base::CreateDirectory(osreleased_directory));
collector_.ForceOsReleaseDDirectory(test_dir_.path());
std::string contents;
const char kPayload[] = "foo";
ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
const char kBdkVersion[] = "1";
ASSERT_TRUE(base::WriteFile(osreleased_directory.Append("bdk_version"),
kBdkVersion,
strlen(kBdkVersion)));
const char kProductId[] = "baz";
ASSERT_TRUE(base::WriteFile(osreleased_directory.Append("product_id"),
kProductId,
strlen(kProductId)));
const char kProductVersion[] = "1.2.3.4";
ASSERT_TRUE(base::WriteFile(osreleased_directory.Append("product_version"),
kProductVersion,
strlen(kProductVersion)));
collector_.AddCrashMetaData("foo", "bar");
collector_.WriteCrashMetaData(meta_file, "kernel", payload_file.value());
EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
const std::string kExpectedMeta =
StringPrintf("foo=bar\n"
"exec_name=kernel\n"
"payload=%s\n"
"payload_size=3\n"
"bdk_version=1\n"
"product_id=baz\n"
"product_version=1.2.3.4\n"
"done=1\n",
test_dir_.path().Append("payload-file").value().c_str());
EXPECT_EQ(kExpectedMeta, contents);
// Test target of symlink is not overwritten.
payload_file = test_dir_.path().Append("payload2-file");
ASSERT_TRUE(base::WriteFile(payload_file, kPayload, strlen(kPayload)));
FilePath meta_symlink_path = test_dir_.path().Append("symlink.meta");
ASSERT_EQ(0,
symlink(kMetaFileBasename,
meta_symlink_path.value().c_str()));
ASSERT_TRUE(base::PathExists(meta_symlink_path));
brillo::ClearLog();
collector_.WriteCrashMetaData(meta_symlink_path,
"kernel",
payload_file.value());
// Target metadata contents should have stayed the same.
contents.clear();
EXPECT_TRUE(base::ReadFileToString(meta_file, &contents));
EXPECT_EQ(kExpectedMeta, contents);
EXPECT_TRUE(FindLog("Unable to write"));
// Test target of dangling symlink is not created.
base::DeleteFile(meta_file, false);
ASSERT_FALSE(base::PathExists(meta_file));
brillo::ClearLog();
collector_.WriteCrashMetaData(meta_symlink_path, "kernel",
payload_file.value());
EXPECT_FALSE(base::PathExists(meta_file));
EXPECT_TRUE(FindLog("Unable to write"));
}
TEST_F(CrashCollectorTest, GetLogContents) {
FilePath config_file = test_dir_.path().Append("crash_config");
FilePath output_file = test_dir_.path().Append("crash_log");
const char kConfigContents[] =
"foobar=echo hello there | \\\n sed -e \"s/there/world/\"";
ASSERT_TRUE(
base::WriteFile(config_file, kConfigContents, strlen(kConfigContents)));
base::DeleteFile(FilePath(output_file), false);
EXPECT_FALSE(collector_.GetLogContents(config_file,
"barfoo",
output_file));
EXPECT_FALSE(base::PathExists(output_file));
base::DeleteFile(FilePath(output_file), false);
EXPECT_TRUE(collector_.GetLogContents(config_file,
"foobar",
output_file));
ASSERT_TRUE(base::PathExists(output_file));
std::string contents;
EXPECT_TRUE(base::ReadFileToString(output_file, &contents));
EXPECT_EQ("hello world\n", contents);
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
#define CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_
#include "crash_collector.h"
#include <map>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class CrashCollectorMock : public CrashCollector {
public:
MOCK_METHOD0(SetUpDBus, void());
MOCK_METHOD1(GetActiveUserSessions,
bool(std::map<std::string, std::string> *sessions));
};
#endif // CRASH_REPORTER_CRASH_COLLECTOR_TEST_H_

View File

@ -1,331 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fcntl.h> // for open
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/guid.h>
#include <base/logging.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <binder/IServiceManager.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include <metrics/metrics_collector_service_client.h>
#include <metrics/metrics_library.h>
#include <utils/String16.h>
#include "kernel_collector.h"
#include "kernel_warning_collector.h"
#include "unclean_shutdown_collector.h"
#include "user_collector.h"
#if !defined(__ANDROID__)
#include "udev_collector.h"
#endif
static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
static const char kKernelCrashDetected[] =
"/data/misc/crash_reporter/run/kernel-crash-detected";
static const char kUncleanShutdownDetected[] =
"/var/run/unclean-shutdown-detected";
static const char kGUIDFileName[] = "/data/misc/crash_reporter/guid";
// Enumeration of kinds of crashes to be used in the CrashCounter histogram.
enum CrashKinds {
kCrashKindUncleanShutdown = 1,
kCrashKindUser = 2,
kCrashKindKernel = 3,
kCrashKindUdev = 4,
kCrashKindKernelWarning = 5,
kCrashKindMax
};
static MetricsLibrary s_metrics_lib;
using android::brillo::metrics::IMetricsCollectorService;
using base::FilePath;
using base::StringPrintf;
static bool IsFeedbackAllowed() {
return s_metrics_lib.AreMetricsEnabled();
}
static bool TouchFile(const FilePath &file_path) {
return base::WriteFile(file_path, "", 0) == 0;
}
static void SendCrashMetrics(CrashKinds type, const char* name) {
// TODO(kmixter): We can remove this histogram as part of
// crosbug.com/11163.
s_metrics_lib.SendEnumToUMA(kCrashCounterHistogram, type, kCrashKindMax);
s_metrics_lib.SendCrashToUMA(name);
}
static void CountKernelCrash() {
SendCrashMetrics(kCrashKindKernel, "kernel");
}
static void CountUdevCrash() {
SendCrashMetrics(kCrashKindUdev, "udevcrash");
}
static void CountUncleanShutdown() {
SendCrashMetrics(kCrashKindUncleanShutdown, "uncleanshutdown");
}
static void CountUserCrash() {
SendCrashMetrics(kCrashKindUser, "user");
// Tell the metrics collector about the user crash, in order to log active
// use time between crashes.
MetricsCollectorServiceClient metrics_collector_service;
if (metrics_collector_service.Init())
metrics_collector_service.notifyUserCrash();
else
LOG(ERROR) << "Failed to send user crash notification to metrics_collector";
}
static int Initialize(KernelCollector *kernel_collector,
UserCollector *user_collector,
UncleanShutdownCollector *unclean_shutdown_collector,
const bool unclean_check,
const bool clean_shutdown) {
CHECK(!clean_shutdown) << "Incompatible options";
// Try to read the GUID from kGUIDFileName. If the file doesn't exist, is
// blank, or the read fails, generate a new GUID and write it to the file.
std::string guid;
base::FilePath filepath(kGUIDFileName);
if (!base::ReadFileToString(filepath, &guid) || guid.empty()) {
guid = base::GenerateGUID();
// If we can't read or write the file, log an error. However it is not
// a fatal error, as the crash server will assign a random GUID based
// on a hash of the IP address if one is not provided in the report.
if (base::WriteFile(filepath, guid.c_str(), guid.size()) <= 0) {
LOG(ERROR) << "Could not write guid " << guid << " to file "
<< filepath.value();
}
}
bool was_kernel_crash = false;
bool was_unclean_shutdown = false;
kernel_collector->Enable();
if (kernel_collector->is_enabled()) {
was_kernel_crash = kernel_collector->Collect();
}
if (unclean_check) {
was_unclean_shutdown = unclean_shutdown_collector->Collect();
}
// Touch a file to notify the metrics daemon that a kernel
// crash has been detected so that it can log the time since
// the last kernel crash.
if (IsFeedbackAllowed()) {
if (was_kernel_crash) {
TouchFile(FilePath(kKernelCrashDetected));
} else if (was_unclean_shutdown) {
// We only count an unclean shutdown if it did not come with
// an associated kernel crash.
TouchFile(FilePath(kUncleanShutdownDetected));
}
}
// Must enable the unclean shutdown collector *after* collecting.
unclean_shutdown_collector->Enable();
user_collector->Enable();
return 0;
}
static int HandleUserCrash(UserCollector *user_collector,
const std::string& user, const bool crash_test) {
// Handle a specific user space crash.
CHECK(!user.empty()) << "--user= must be set";
// Make it possible to test what happens when we crash while
// handling a crash.
if (crash_test) {
*(volatile char *)0 = 0;
return 0;
}
// Accumulate logs to help in diagnosing failures during user collection.
brillo::LogToString(true);
// Handle the crash, get the name of the process from procfs.
bool handled = user_collector->HandleCrash(user, nullptr);
brillo::LogToString(false);
if (!handled)
return 1;
return 0;
}
#if !defined(__ANDROID__)
static int HandleUdevCrash(UdevCollector *udev_collector,
const std::string& udev_event) {
// Handle a crash indicated by a udev event.
CHECK(!udev_event.empty()) << "--udev= must be set";
// Accumulate logs to help in diagnosing failures during user collection.
brillo::LogToString(true);
bool handled = udev_collector->HandleCrash(udev_event);
brillo::LogToString(false);
if (!handled)
return 1;
return 0;
}
#endif
static int HandleKernelWarning(KernelWarningCollector
*kernel_warning_collector) {
// Accumulate logs to help in diagnosing failures during collection.
brillo::LogToString(true);
bool handled = kernel_warning_collector->Collect();
brillo::LogToString(false);
if (!handled)
return 1;
return 0;
}
// Interactive/diagnostics mode for generating kernel crash signatures.
static int GenerateKernelSignature(KernelCollector *kernel_collector,
const std::string& kernel_signature_file) {
std::string kcrash_contents;
std::string signature;
if (!base::ReadFileToString(FilePath(kernel_signature_file),
&kcrash_contents)) {
fprintf(stderr, "Could not read file.\n");
return 1;
}
if (!kernel_collector->ComputeKernelStackSignature(
kcrash_contents,
&signature,
true)) {
fprintf(stderr, "Signature could not be generated.\n");
return 1;
}
printf("Kernel crash signature is \"%s\".\n", signature.c_str());
return 0;
}
// Ensure stdout, stdin, and stderr are open file descriptors. If
// they are not, any code which writes to stderr/stdout may write out
// to files opened during execution. In particular, when
// crash_reporter is run by the kernel coredump pipe handler (via
// kthread_create/kernel_execve), it will not have file table entries
// 1 and 2 (stdout and stderr) populated. We populate them here.
static void OpenStandardFileDescriptors() {
int new_fd = -1;
// We open /dev/null to fill in any of the standard [0, 2] file
// descriptors. We leave these open for the duration of the
// process. This works because open returns the lowest numbered
// invalid fd.
do {
new_fd = open("/dev/null", 0);
CHECK_GE(new_fd, 0) << "Unable to open /dev/null";
} while (new_fd >= 0 && new_fd <= 2);
close(new_fd);
}
int main(int argc, char *argv[]) {
DEFINE_bool(init, false, "Initialize crash logging");
DEFINE_bool(clean_shutdown, false, "Signal clean shutdown");
DEFINE_string(generate_kernel_signature, "",
"Generate signature from given kcrash file");
DEFINE_bool(crash_test, false, "Crash test");
DEFINE_string(user, "", "User crash info (pid:signal:exec_name)");
DEFINE_bool(unclean_check, true, "Check for unclean shutdown");
#if !defined(__ANDROID__)
DEFINE_string(udev, "", "Udev event description (type:device:subsystem)");
#endif
DEFINE_bool(kernel_warning, false, "Report collected kernel warning");
DEFINE_string(pid, "", "PID of crashing process");
DEFINE_string(uid, "", "UID of crashing process");
DEFINE_string(exe, "", "Executable name of crashing process");
DEFINE_bool(core2md_failure, false, "Core2md failure test");
DEFINE_bool(directory_failure, false, "Spool directory failure test");
DEFINE_string(filter_in, "",
"Ignore all crashes but this for testing");
OpenStandardFileDescriptors();
FilePath my_path = base::MakeAbsoluteFilePath(FilePath(argv[0]));
s_metrics_lib.Init();
brillo::FlagHelper::Init(argc, argv, "Chromium OS Crash Reporter");
brillo::OpenLog(my_path.BaseName().value().c_str(), true);
brillo::InitLog(brillo::kLogToSyslog);
KernelCollector kernel_collector;
kernel_collector.Initialize(CountKernelCrash, IsFeedbackAllowed);
UserCollector user_collector;
user_collector.Initialize(CountUserCrash,
my_path.value(),
IsFeedbackAllowed,
true, // generate_diagnostics
FLAGS_core2md_failure,
FLAGS_directory_failure,
FLAGS_filter_in);
UncleanShutdownCollector unclean_shutdown_collector;
unclean_shutdown_collector.Initialize(CountUncleanShutdown,
IsFeedbackAllowed);
#if !defined(__ANDROID__)
UdevCollector udev_collector;
udev_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
#endif
KernelWarningCollector kernel_warning_collector;
kernel_warning_collector.Initialize(CountUdevCrash, IsFeedbackAllowed);
if (FLAGS_init) {
return Initialize(&kernel_collector,
&user_collector,
&unclean_shutdown_collector,
FLAGS_unclean_check,
FLAGS_clean_shutdown);
}
if (FLAGS_clean_shutdown) {
unclean_shutdown_collector.Disable();
user_collector.Disable();
return 0;
}
if (!FLAGS_generate_kernel_signature.empty()) {
return GenerateKernelSignature(&kernel_collector,
FLAGS_generate_kernel_signature);
}
#if !defined(__ANDROID__)
if (!FLAGS_udev.empty()) {
return HandleUdevCrash(&udev_collector, FLAGS_udev);
}
#endif
if (FLAGS_kernel_warning) {
return HandleKernelWarning(&kernel_warning_collector);
}
return HandleUserCrash(&user_collector, FLAGS_user, FLAGS_crash_test);
}

View File

@ -1,37 +0,0 @@
on property:crash_reporter.coredump.enabled=1
write /proc/sys/kernel/core_pattern \
"|/system/bin/crash_reporter --user=%P:%s:%u:%g:%e"
on property:crash_reporter.coredump.enabled=0
write /proc/sys/kernel/core_pattern "core"
on post-fs-data
# Allow catching multiple unrelated concurrent crashes, but use a finite
# number to prevent infinitely recursing on crash handling.
write /proc/sys/kernel/core_pipe_limit 4
# Remove any previous orphaned locks.
rmdir /data/misc/crash_reporter/lock/crash_sender
# Remove any previous run files.
rm /data/misc/crash_reporter/run/kernel-crash-detected
rmdir /data/misc/crash_reporter/run
# Create crash directories.
# These directories are group-writable by root so that crash_reporter can
# still access them when it switches users.
mkdir /data/misc/crash_reporter 0770 root root
mkdir /data/misc/crash_reporter/crash 0770 root root
mkdir /data/misc/crash_reporter/lock 0700 root root
mkdir /data/misc/crash_reporter/log 0700 root root
mkdir /data/misc/crash_reporter/run 0700 root root
mkdir /data/misc/crash_reporter/tmp 0770 root root
service crash_reporter /system/bin/crash_reporter --init
class late_start
oneshot
service crash_sender /system/bin/periodic_scheduler 3600 14400 crash_sender \
/system/bin/crash_sender
class late_start
group system

View File

@ -1,122 +0,0 @@
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This file is parsed by chromeos::KeyValueStore. It has the format:
#
# <basename>=<shell command>\n
#
# Commands may be split across multiple lines using trailing backslashes.
#
# When an executable named <basename> crashes, the corresponding command is
# executed and its standard output and standard error are attached to the crash
# report.
#
# Use caution in modifying this file. Only run common Unix commands here, as
# these commands will be run when a crash has recently occurred and we should
# avoid running anything that might cause another crash. Similarly, these
# commands block notification of the crash to parent processes, so commands
# should execute quickly.
update_engine=cat $(ls -1tr /var/log/update_engine | tail -5 | \
sed s.^./var/log/update_engine/.) | tail -c 50000
# The cros_installer output is logged into the update engine log file,
# so it is handled in the same way as update_engine.
cros_installer=cat $(ls -1tr /var/log/update_engine | tail -5 | \
sed s.^./var/log/update_engine/.) | tail -c 50000
# Dump the last 20 lines of the last two files in Chrome's system and user log
# directories, along with the last 20 messages from the session manager.
chrome=\
for f in $(ls -1rt /var/log/chrome/chrome_[0-9]* | tail -2) \
$(ls -1rt /home/chronos/u-*/log/chrome_[0-9]* 2>/dev/null | tail -2); do \
echo "===$f (tail)==="; \
tail -20 $f; \
echo EOF; \
echo; \
done; \
echo "===session_manager (tail)==="; \
awk '$3 ~ "^session_manager\[" { print }' /var/log/messages | tail -20; \
echo EOF
# The following rule is used for generating additional diagnostics when
# collection of user crashes fails. This output should not be too large
# as it is stored in memory. The output format specified for 'ps' is the
# same as with the "u" ("user-oriented") option, except it doesn't show
# the commands' arguments (i.e. "comm" instead of "command").
crash_reporter-user-collection=\
echo "===ps output==="; \
ps axw -o user,pid,%cpu,%mem,vsz,rss,tname,stat,start_time,bsdtime,comm | \
tail -c 25000; \
echo "===meminfo==="; \
cat /proc/meminfo
# This rule is similar to the crash_reporter-user-collection rule, except it is
# run for kernel errors reported through udev events.
crash_reporter-udev-collection-change-card0-drm=\
for dri in /sys/kernel/debug/dri/*; do \
echo "===$dri/i915_error_state==="; \
cat $dri/i915_error_state; \
done
# When trackpad driver cyapa detects some abnormal behavior, we collect
# additional logs from kernel messages.
crash_reporter-udev-collection-change--i2c-cyapa=\
/usr/sbin/kernel_log_collector.sh cyapa 30
# When trackpad/touchscreen driver atmel_mxt_ts detects some abnormal behavior,
# we collect additional logs from kernel messages.
crash_reporter-udev-collection-change--i2c-atmel_mxt_ts=\
/usr/sbin/kernel_log_collector.sh atmel 30
# When touch device noise are detected, we collect relevant logs.
# (crosbug.com/p/16788)
crash_reporter-udev-collection---TouchNoise=cat /var/log/touch_noise.log
# Periodically collect touch event log for debugging (crosbug.com/p/17244)
crash_reporter-udev-collection---TouchEvent=cat /var/log/touch_event.log
# Collect the last 50 lines of /var/log/messages and /var/log/net.log for
# intel wifi driver (iwlwifi) for debugging purpose.
crash_reporter-udev-collection-devcoredump-iwlwifi=\
echo "===/var/log/messages==="; \
tail -n 50 /var/log/messages; \
echo "===/var/log/net.log==="; \
tail -n 50 /var/log/net.log; \
echo EOF
# Dump the last 50 lines of the last two powerd log files -- if the job has
# already restarted, we want to see the end of the previous instance's logs.
powerd=\
for f in $(ls -1tr /var/log/power_manager/powerd.[0-9]* | tail -2); do \
echo "===$(basename $f) (tail)==="; \
tail -50 $f; \
echo EOF; \
done
# If power_supply_info aborts (due to e.g. a bad battery), its failure message
# could end up in various places depending on which process was running it.
# Attach the end of powerd's log since it might've also logged the underlying
# problem.
power_supply_info=\
echo "===powerd.LATEST (tail)==="; \
tail -50 /var/log/power_manager/powerd.LATEST; \
echo EOF
# powerd_setuid_helper gets run by powerd, so its stdout/stderr will be mixed in
# with powerd's stdout/stderr.
powerd_setuid_helper=\
echo "===powerd.OUT (tail)==="; \
tail -50 /var/log/powerd.out; \
echo EOF
# The following rules are only for testing purposes.
crash_log_test=echo hello world
crash_log_recursion_test=sleep 1 && \
/usr/local/autotest/tests/crash_log_recursion_test

View File

@ -1,41 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string>
#include <base/files/file_path.h>
#include <brillo/key_value_store.h>
#include <gtest/gtest.h>
namespace {
// Name of the checked-in configuration file containing log-collection commands.
const char kConfigFile[] = "/system/etc/crash_reporter_logs.conf";
// Signature name for crash_reporter user collection.
// kConfigFile is expected to contain this entry.
const char kUserCollectorSignature[] = "crash_reporter-user-collection";
} // namespace
// Tests that the config file is parsable and that Chrome is listed.
TEST(CrashReporterLogsTest, ReadConfig) {
brillo::KeyValueStore store;
ASSERT_TRUE(store.Load(base::FilePath(kConfigFile)));
std::string command;
EXPECT_TRUE(store.GetString(kUserCollectorSignature, &command));
EXPECT_FALSE(command.empty());
}

View File

@ -1,719 +0,0 @@
#!/system/bin/sh
# Copyright (C) 2010 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
# Default product ID in crash report (used if GOOGLE_CRASH_* is undefined).
BRILLO_PRODUCT=Brillo
# Base directory that contains any crash reporter state files.
CRASH_STATE_DIR="/data/misc/crash_reporter"
# File containing crash_reporter's anonymized guid.
GUID_FILE="${CRASH_STATE_DIR}/guid"
# Crash sender lock in case the sender is already running.
CRASH_SENDER_LOCK="${CRASH_STATE_DIR}/lock/crash_sender"
# Path to file that indicates a crash test is currently running.
CRASH_TEST_IN_PROGRESS_FILE="${CRASH_STATE_DIR}/tmp/crash-test-in-progress"
# Set this to 1 in the environment to allow uploading crash reports
# for unofficial versions.
FORCE_OFFICIAL=${FORCE_OFFICIAL:-0}
# Path to hardware class description.
HWCLASS_PATH="/sys/devices/platform/chromeos_acpi/HWID"
# Path to file that indicates this is a developer image.
LEAVE_CORE_FILE="${CRASH_STATE_DIR}/.leave_core"
# Path to list_proxies.
LIST_PROXIES="list_proxies"
# Maximum crashes to send per day.
MAX_CRASH_RATE=${MAX_CRASH_RATE:-32}
# File whose existence mocks crash sending. If empty we pretend the
# crash sending was successful, otherwise unsuccessful.
MOCK_CRASH_SENDING="${CRASH_STATE_DIR}/tmp/mock-crash-sending"
# Set this to 1 in the environment to pretend to have booted in developer
# mode. This is used by autotests.
MOCK_DEVELOPER_MODE=${MOCK_DEVELOPER_MODE:-0}
# Ignore PAUSE_CRASH_SENDING file if set.
OVERRIDE_PAUSE_SENDING=${OVERRIDE_PAUSE_SENDING:-0}
# File whose existence causes crash sending to be delayed (for testing).
# Must be stateful to enable testing kernel crashes.
PAUSE_CRASH_SENDING="${CRASH_STATE_DIR}/lock/crash_sender_paused"
# Path to a directory of restricted certificates which includes
# a certificate for the crash server.
RESTRICTED_CERTIFICATES_PATH="/system/etc/security/cacerts"
RESTRICTED_CERTIFICATES_PATH_GOOGLE="/system/etc/security/cacerts_google"
# File whose existence implies we're running and not to start again.
RUN_FILE="${CRASH_STATE_DIR}/run/crash_sender.pid"
# Maximum time to sleep between sends.
SECONDS_SEND_SPREAD=${SECONDS_SEND_SPREAD:-600}
# Set this to 1 to allow uploading of device coredumps.
DEVCOREDUMP_UPLOAD_FLAG_FILE="${CRASH_STATE_DIR}/device_coredump_upload_allowed"
# The weave configuration file.
WEAVE_CONF_FILE="/etc/weaved/weaved.conf"
# The os-release.d folder.
OSRELEASED_FOLDER="/etc/os-release.d"
# The syslog tag for all logging we emit.
TAG="$(basename $0)[$$]"
# Directory to store timestamp files indicating the uploads in the past 24
# hours.
TIMESTAMPS_DIR="${CRASH_STATE_DIR}/crash_sender"
# Temp directory for this process.
TMP_DIR=""
# Crash report log file.
CRASH_LOG="${CRASH_STATE_DIR}/log/uploads.log"
lecho() {
log -t "${TAG}" "$@"
}
lwarn() {
lecho -psyslog.warn "$@"
}
# Returns true if mock is enabled.
is_mock() {
[ -f "${MOCK_CRASH_SENDING}" ] && return 0
return 1
}
is_mock_successful() {
local mock_in=$(cat "${MOCK_CRASH_SENDING}")
[ "${mock_in}" = "" ] && return 0 # empty file means success
return 1
}
cleanup() {
if [ -n "${TMP_DIR}" ]; then
rm -rf "${TMP_DIR}"
fi
rm -f "${RUN_FILE}"
if [ -n "${CRASH_SENDER_LOCK}" ]; then
rm -rf "${CRASH_SENDER_LOCK}"
fi
crash_done
}
crash_done() {
if is_mock; then
# For testing purposes, emit a message to log so that we
# know when the test has received all the messages from this run.
lecho "crash_sender done."
fi
}
is_official_image() {
[ ${FORCE_OFFICIAL} -ne 0 ] && return 0
if [ "$(getprop ro.secure)" = "1" ]; then
return 0
else
return 1
fi
}
# Returns 0 if the a crash test is currently running. NOTE: Mirrors
# crash_collector.cc:CrashCollector::IsCrashTestInProgress().
is_crash_test_in_progress() {
[ -f "${CRASH_TEST_IN_PROGRESS_FILE}" ] && return 0
return 1
}
# Returns 0 if we should consider ourselves to be running on a developer
# image. NOTE: Mirrors crash_collector.cc:CrashCollector::IsDeveloperImage().
is_developer_image() {
# If we're testing crash reporter itself, we don't want to special-case
# for developer images.
is_crash_test_in_progress && return 1
[ -f "${LEAVE_CORE_FILE}" ] && return 0
return 1
}
# Returns 0 if we should consider ourselves to be running on a test image.
is_test_image() {
# If we're testing crash reporter itself, we don't want to special-case
# for test images.
is_crash_test_in_progress && return 1
case $(get_channel) in
test*) return 0;;
esac
return 1
}
# Returns 0 if the machine booted up in developer mode.
is_developer_mode() {
[ ${MOCK_DEVELOPER_MODE} -ne 0 ] && return 0
# If we're testing crash reporter itself, we don't want to special-case
# for developer mode.
is_crash_test_in_progress && return 1
if [ "$(getprop ro.debuggable)" = "1" ]; then
return 0
else
return 1
fi
}
# Returns the path of the certificates directory to be used when sending
# reports to the crash server.
# If crash_reporter.full_certs=1, return the full certificates path.
# Otherwise return the Google-specific certificates path.
get_certificates_path() {
if [ "$(getprop crash_reporter.full_certs)" = "1" ]; then
echo "${RESTRICTED_CERTIFICATES_PATH}"
else
echo "${RESTRICTED_CERTIFICATES_PATH_GOOGLE}"
fi
}
# Return 0 if the uploading of device coredumps is allowed.
is_device_coredump_upload_allowed() {
[ -f "${DEVCOREDUMP_UPLOAD_FLAG_FILE}" ] && return 0
return 1
}
# Generate a uniform random number in 0..max-1.
# POSIX arithmetic expansion requires support of at least signed long integers.
# On 32-bit systems, that may mean 32-bit signed integers, in which case the
# 32-bit random number read from /dev/urandom may be interpreted as negative
# when used inside an arithmetic expansion (since the high bit might be set).
# mksh at least is known to behave this way.
# For this case, simply take the absolute value, which will still give a
# roughly uniform random distribution for the modulo (as we are merely ignoring
# the high/sign bit).
# See corresponding Arithmetic Expansion and Arithmetic Expression sections:
# POSIX: http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_04
# mksh: http://linux.die.net/man/1/mksh
generate_uniform_random() {
local max=$1
local random="$(od -An -N4 -tu /dev/urandom)"
echo $(((random < 0 ? -random : random) % max))
}
# Check if sending a crash now does not exceed the maximum 24hr rate and
# commit to doing so, if not.
check_rate() {
mkdir -p ${TIMESTAMPS_DIR}
# Only consider minidumps written in the past 24 hours by removing all older.
find "${TIMESTAMPS_DIR}" -mindepth 1 -mtime +1 \
-exec rm -- '{}' ';'
local sends_in_24hrs=$(echo "${TIMESTAMPS_DIR}"/* | wc -w)
lecho "Current send rate: ${sends_in_24hrs}sends/24hrs"
if [ ${sends_in_24hrs} -ge ${MAX_CRASH_RATE} ]; then
lecho "Cannot send more crashes:"
lecho " current ${sends_in_24hrs}send/24hrs >= " \
"max ${MAX_CRASH_RATE}send/24hrs"
return 1
fi
mktemp "${TIMESTAMPS_DIR}"/XXXXXX > /dev/null
return 0
}
# Gets the base part of a crash report file, such as name.01234.5678.9012 from
# name.01234.5678.9012.meta or name.01234.5678.9012.log.tar.xz. We make sure
# "name" is sanitized in CrashCollector::Sanitize to not include any periods.
get_base() {
echo "$1" | cut -d. -f-4
}
get_extension() {
local extension="${1##*.}"
local filename="${1%.*}"
# For gzipped file, we ignore .gz and get the real extension
if [ "${extension}" = "gz" ]; then
echo "${filename##*.}"
else
echo "${extension}"
fi
}
# Return which kind of report the given metadata file relates to
get_kind() {
local payload="$(get_key_value "$1" "payload")"
if [ ! -r "${payload}" ]; then
lecho "Missing payload: ${payload}"
echo "undefined"
return
fi
local kind="$(get_extension "${payload}")"
if [ "${kind}" = "dmp" ]; then
echo "minidump"
return
fi
echo "${kind}"
}
get_key_value() {
local file="$1" key="$2" value
if [ -f "${file}/${key}" ]; then
# Get the value from a folder where each key is its own file. The key
# file's entire contents is the value.
value=$(cat "${file}/${key}")
elif [ -f "${file}" ]; then
# Get the value from a file that has multiple key=value combinations.
# Return the first entry. There shouldn't be more than one anyways.
# Substr at length($1) + 2 skips past the key and following = sign (awk
# uses 1-based indexes), but preserves embedded = characters.
value=$(sed -n "/^${key}[[:space:]]*=/{s:^[^=]*=::p;q}" "${file}")
fi
echo "${value:-undefined}"
}
get_keys() {
local file="$1" regex="$2"
cut -d '=' -f1 "${file}" | grep --color=never "${regex}"
}
# Return the channel name (sans "-channel" suffix).
get_channel() {
getprop ro.product.channel | sed 's:-channel$::'
}
# Return the hardware class or "undefined".
get_hardware_class() {
if [ -r "${HWCLASS_PATH}" ]; then
cat "${HWCLASS_PATH}"
else
echo "undefined"
fi
}
# Return the log string filtered with only JSON-safe white-listed characters.
filter_log_string() {
echo "$1" | tr -cd '[:alnum:]_.\-:;'
}
send_crash() {
local meta_path="$1"
local report_payload="$(get_key_value "${meta_path}" "payload")"
local kind="$(get_kind "${meta_path}")"
local exec_name="$(get_key_value "${meta_path}" "exec_name")"
local url="$(get_key_value "${OSRELEASED_FOLDER}" "crash_server")"
local bdk_version="$(get_key_value "${meta_path}" "bdk_version")"
local hwclass="$(get_hardware_class)"
local write_payload_size="$(get_key_value "${meta_path}" "payload_size")"
local log="$(get_key_value "${meta_path}" "log")"
local sig="$(get_key_value "${meta_path}" "sig")"
local send_payload_size="$(stat -c "%s" "${report_payload}" 2>/dev/null)"
local product="$(get_key_value "${meta_path}" "product_id")"
local version="$(get_key_value "${meta_path}" "product_version")"
local upload_prefix="$(get_key_value "${meta_path}" "upload_prefix")"
local guid
local model_manifest_id="$(get_key_value "${WEAVE_CONF_FILE}" "model_id")"
# If crash_reporter.server is not set return with an error.
if [ -z "${url}" ]; then
lecho "Configuration error: crash_reporter.server not set."
return 1
fi
set -- \
-F "write_payload_size=${write_payload_size}" \
-F "send_payload_size=${send_payload_size}"
if [ "${sig}" != "undefined" ]; then
set -- "$@" \
-F "sig=${sig}" \
-F "sig2=${sig}"
fi
if [ -r "${report_payload}" ]; then
set -- "$@" \
-F "upload_file_${kind}=@${report_payload}"
fi
if [ "${log}" != "undefined" -a -r "${log}" ]; then
set -- "$@" \
-F "log=@${log}"
fi
if [ "${upload_prefix}" = "undefined" ]; then
upload_prefix=""
fi
# Grab any variable that begins with upload_.
local v
for k in $(get_keys "${meta_path}" "^upload_"); do
v="$(get_key_value "${meta_path}" "${k}")"
case ${k} in
# Product & version are handled separately.
upload_var_prod) ;;
upload_var_ver) ;;
upload_var_*)
set -- "$@" -F "${upload_prefix}${k#upload_var_}=${v}"
;;
upload_file_*)
if [ -r "${v}" ]; then
set -- "$@" -F "${upload_prefix}${k#upload_file_}=@${v}"
fi
;;
esac
done
# If ID or VERSION_ID is undefined, we use the default product name
# and bdk_version from /etc/os-release.d.
if [ "${product}" = "undefined" ]; then
product="${BRILLO_PRODUCT}"
fi
if [ "${version}" = "undefined" ]; then
version="${bdk_version}"
fi
local image_type
if is_test_image; then
image_type="test"
elif is_developer_image; then
image_type="dev"
elif [ ${FORCE_OFFICIAL} -ne 0 ]; then
image_type="force-official"
elif is_mock && ! is_mock_successful; then
image_type="mock-fail"
fi
local boot_mode
if is_developer_mode; then
boot_mode="dev"
fi
# Need to strip dashes ourselves as Chrome preserves it in the file
# nowadays. This is also what the Chrome breakpad client does.
guid=$(tr -d '-' < "${GUID_FILE}")
local error_type="$(get_key_value "${meta_path}" "error_type")"
[ "${error_type}" = "undefined" ] && error_type=
lecho "Sending crash:"
if [ "${product}" != "${BRILLO_PRODUCT}" ]; then
lecho " Sending crash report on behalf of ${product}"
fi
lecho " Metadata: ${meta_path} (${kind})"
lecho " Payload: ${report_payload}"
lecho " Version: ${version}"
lecho " Bdk Version: ${bdk_version}"
[ -n "${image_type}" ] && lecho " Image type: ${image_type}"
[ -n "${boot_mode}" ] && lecho " Boot mode: ${boot_mode}"
if is_mock; then
lecho " Product: ${product}"
lecho " URL: ${url}"
lecho " HWClass: ${hwclass}"
lecho " write_payload_size: ${write_payload_size}"
lecho " send_payload_size: ${send_payload_size}"
if [ "${log}" != "undefined" ]; then
lecho " log: @${log}"
fi
if [ "${sig}" != "undefined" ]; then
lecho " sig: ${sig}"
fi
fi
lecho " Exec name: ${exec_name}"
[ -n "${error_type}" ] && lecho " Error type: ${error_type}"
if is_mock; then
if ! is_mock_successful; then
lecho "Mocking unsuccessful send"
return 1
fi
lecho "Mocking successful send"
return 0
fi
# Read in the first proxy, if any, for a given URL. NOTE: The
# double-quotes are necessary due to a bug in dash with the "local"
# builtin command and values that have spaces in them (see
# "https://bugs.launchpad.net/ubuntu/+source/dash/+bug/139097").
if [ -f "${LIST_PROXIES}" ]; then
local proxy ret
proxy=$("${LIST_PROXIES}" --quiet "${url}")
ret=$?
if [ ${ret} -ne 0 ]; then
proxy=''
lwarn "Listing proxies failed with exit code ${ret}"
else
proxy=$(echo "${proxy}" | head -1)
fi
fi
# if a direct connection should be used, unset the proxy variable.
[ "${proxy}" = "direct://" ] && proxy=
local report_id="${TMP_DIR}/report_id"
local curl_stderr="${TMP_DIR}/curl_stderr"
set +e
curl "${url}" -f -v ${proxy:+--proxy "$proxy"} \
--capath "$(get_certificates_path)" --ciphers HIGH \
-F "prod=${product}" \
-F "ver=${version}" \
-F "bdk_version=${bdk_version}" \
-F "hwclass=${hwclass}" \
-F "exec_name=${exec_name}" \
-F "model_manifest_id=${model_manifest_id}" \
${image_type:+-F "image_type=${image_type}"} \
${boot_mode:+-F "boot_mode=${boot_mode}"} \
${error_type:+-F "error_type=${error_type}"} \
-F "guid=${guid}" \
-o "${report_id}" \
"$@" \
2>"${curl_stderr}"
curl_result=$?
set -e
if [ ${curl_result} -eq 0 ]; then
local id="$(cat "${report_id}")"
local timestamp="$(date +%s)"
local filter_prod="$(filter_log_string "${product}")"
local filter_exec="$(filter_log_string "${exec_name}")"
if [ "${filter_prod}" != "${product}" ]; then
lwarn "Product name filtered to: ${filter_prod}."
fi
if [ "${filter_exec}" != "${exec_name}" ]; then
lwarn "Exec name filtered to: ${filter_exec}."
fi
printf "{'time':%s,'id':'%s','product':'%s','exec_name':'%s'}\n" \
"${timestamp}" "${id}" "${filter_prod}" "${filter_exec}" >> "${CRASH_LOG}"
lecho "Crash report receipt ID ${id}"
else
lecho "Crash sending failed with exit code ${curl_result}: " \
"$(cat "${curl_stderr}")"
fi
rm -f "${report_id}"
return ${curl_result}
}
# *.meta files always end with done=1 so we can tell if they are complete.
is_complete_metadata() {
grep -q "done=1" "$1"
}
# Remove the given report path.
remove_report() {
local base="${1%.*}"
rm -f -- "${base}".*
}
# Send all crashes from the given directory. This applies even when we're on a
# 3G connection (see crosbug.com/3304 for discussion).
send_crashes() {
local dir="$1"
lecho "Sending crashes for ${dir}"
if [ ! -d "${dir}" ]; then
return
fi
# Consider any old files which still have no corresponding meta file
# as orphaned, and remove them.
for old_file in $(find "${dir}" -mindepth 1 \
-mtime +1 -type f); do
if [ ! -e "$(get_base "${old_file}").meta" ]; then
lecho "Removing old orphaned file: ${old_file}."
rm -f -- "${old_file}"
fi
done
# Look through all metadata (*.meta) files, oldest first. That way, the rate
# limit does not stall old crashes if there's a high amount of new crashes
# coming in.
# For each crash report, first evaluate conditions that might lead to its
# removal to honor user choice and to free disk space as soon as possible,
# then decide whether it should be sent right now or kept for later sending.
for meta_path in $(ls -1tr "${dir}"/*.meta 2>/dev/null); do
lecho "Considering metadata ${meta_path}."
local kind=$(get_kind "${meta_path}")
if [ "${kind}" != "minidump" ] && \
[ "${kind}" != "kcrash" ] && \
[ "${kind}" != "log" ] &&
[ "${kind}" != "devcore" ]; then
lecho "Unknown report kind ${kind}. Removing report."
remove_report "${meta_path}"
continue
fi
if ! is_complete_metadata "${meta_path}"; then
# This report is incomplete, so if it's old, just remove it.
local old_meta=$(find "${dir}" -mindepth 1 -name \
$(basename "${meta_path}") -mtime +1 -type f)
if [ -n "${old_meta}" ]; then
lecho "Removing old incomplete metadata."
remove_report "${meta_path}"
else
lecho "Ignoring recent incomplete metadata."
fi
continue
fi
# Ignore device coredump if device coredump uploading is not allowed.
if [ "${kind}" = "devcore" ] && ! is_device_coredump_upload_allowed; then
lecho "Ignoring device coredump. Device coredump upload not allowed."
continue
fi
if ! is_mock && ! is_official_image; then
lecho "Not an official OS version. Removing crash."
remove_report "${meta_path}"
continue
fi
# Remove existing crashes in case user consent has not (yet) been given or
# has been revoked. This must come after the guest mode check because
# metrics_client always returns "not consented" in guest mode.
if ! metrics_client -c; then
lecho "Crash reporting is disabled. Removing crash."
remove_report "${meta_path}"
continue
fi
# Skip report if the upload rate is exceeded. (Don't exit right now because
# subsequent reports may be candidates for deletion.)
if ! check_rate; then
lecho "Sending ${meta_path} would exceed rate. Leaving for later."
continue
fi
# The .meta file should be written *after* all to-be-uploaded files that it
# references. Nevertheless, as a safeguard, a hold-off time of thirty
# seconds after writing the .meta file is ensured. Also, sending of crash
# reports is spread out randomly by up to SECONDS_SEND_SPREAD. Thus, for
# the sleep call the greater of the two delays is used.
local now=$(date +%s)
local holdoff_time=$(($(stat -c "%Y" "${meta_path}") + 30 - ${now}))
local spread_time=$(generate_uniform_random "${SECONDS_SEND_SPREAD}")
local sleep_time
if [ ${spread_time} -gt ${holdoff_time} ]; then
sleep_time="${spread_time}"
else
sleep_time="${holdoff_time}"
fi
lecho "Scheduled to send in ${sleep_time}s."
if ! is_mock; then
if ! sleep "${sleep_time}"; then
lecho "Sleep failed"
return 1
fi
fi
# Try to upload.
if ! send_crash "${meta_path}"; then
lecho "Problem sending ${meta_path}, not removing."
continue
fi
# Send was successful, now remove.
lecho "Successfully sent crash ${meta_path} and removing."
remove_report "${meta_path}"
done
}
usage() {
cat <<EOF
Usage: crash_sender [options]
Options:
-e <var>=<val> Set env |var| to |val| (only some vars)
EOF
exit ${1:-1}
}
parseargs() {
# Parse the command line arguments.
while [ $# -gt 0 ]; do
case $1 in
-e)
shift
case $1 in
FORCE_OFFICIAL=*|\
MAX_CRASH_RATE=*|\
MOCK_DEVELOPER_MODE=*|\
OVERRIDE_PAUSE_SENDING=*|\
SECONDS_SEND_SPREAD=*)
export "$1"
;;
*)
lecho "Unknown var passed to -e: $1"
exit 1
;;
esac
;;
-h)
usage 0
;;
*)
lecho "Unknown options: $*"
exit 1
;;
esac
shift
done
}
main() {
parseargs "$@"
if [ -e "${PAUSE_CRASH_SENDING}" ] && \
[ ${OVERRIDE_PAUSE_SENDING} -eq 0 ]; then
lecho "Exiting early due to ${PAUSE_CRASH_SENDING}."
exit 1
fi
if is_test_image; then
lecho "Exiting early due to test image."
exit 1
fi
# We don't perform checks on this because we have a master lock with the
# CRASH_SENDER_LOCK file. This pid file is for the system to keep track
# (like with autotests) that we're still running.
echo $$ > "${RUN_FILE}"
for dependency in "$(get_certificates_path)"; do
if [ ! -x "${dependency}" ]; then
lecho "Fatal: Crash sending disabled: ${dependency} not found."
exit 1
fi
done
TMP_DIR="$(mktemp -d "${CRASH_STATE_DIR}/tmp/crash_sender.XXXXXX")"
# Send system-wide crashes
send_crashes "${CRASH_STATE_DIR}/crash"
}
trap cleanup EXIT INT TERM
#TODO(http://b/23937249): Change the locking logic back to using flock.
if ! mkdir "${CRASH_SENDER_LOCK}" 2>/dev/null; then
lecho "Already running; quitting."
crash_done
exit 1
fi
main "$@"

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<node name="/org/chromium/LibCrosService"
xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
<interface name="org.chromium.LibCrosServiceInterface">
<method name="ResolveNetworkProxy">
<arg name="source_url" type="s" direction="in"/>
<arg name="signal_interface" type="s" direction="in"/>
<arg name="signal_name" type="s" direction="in"/>
<annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
</method>
</interface>
<interface name="org.chromium.CrashReporterLibcrosProxyResolvedInterface">
<signal name="ProxyResolved">
<arg name="source_url" type="s" direction="out"/>
<arg name="proxy_info" type="s" direction="out"/>
<arg name="error_message" type="s" direction="out"/>
</signal>
</interface>
</node>

View File

@ -1,27 +0,0 @@
# 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.
description "Initialize crash reporting services"
author "chromium-os-dev@chromium.org"
# This job merely initializes its service and then terminates; the
# actual checking and reporting of crash dumps is triggered by an
# hourly cron job.
start on starting system-services
pre-start script
mkdir -p /var/spool
# Only allow device coredumps on a "developer system".
if ! is_developer_end_user; then
# consumer end-user - disable device coredumps, if driver exists.
echo 1 > /sys/class/devcoredump/disabled || true
fi
end script
# crash_reporter uses argv[0] as part of the command line for
# /proc/sys/kernel/core_pattern. That command line is invoked by
# the kernel, and can't rely on PATH, so argv[0] must be a full
# path; we invoke it as such here.
exec /sbin/crash_reporter --init

View File

@ -1,11 +0,0 @@
# 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 "Run the crash sender periodically"
author "chromium-os-dev@chromium.org"
start on starting system-services
stop on stopping system-services
exec periodic_scheduler 3600 14400 crash_sender /sbin/crash_sender

View File

@ -1,12 +0,0 @@
# Copyright (c) 2013 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 "Runs a daemon which collects and reports kernel warnings"
author "chromium-os-dev@chromium.org"
start on started system-services
stop on stopping system-services
respawn
exec warn_collector

View File

@ -1,603 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "kernel_collector.h"
#include <map>
#include <sys/stat.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
using base::FilePath;
using base::StringPrintf;
namespace {
const char kDefaultKernelStackSignature[] = "kernel-UnspecifiedStackSignature";
const char kDumpParentPath[] = "/sys/fs";
const char kDumpPath[] = "/sys/fs/pstore";
const char kDumpFormat[] = "dmesg-ramoops-%zu";
const char kKernelExecName[] = "kernel";
// Maximum number of records to examine in the kDumpPath.
const size_t kMaxDumpRecords = 100;
const pid_t kKernelPid = 0;
const char kKernelSignatureKey[] = "sig";
// Byte length of maximum human readable portion of a kernel crash signature.
const int kMaxHumanStringLength = 40;
const uid_t kRootUid = 0;
// Time in seconds from the final kernel log message for a call stack
// to count towards the signature of the kcrash.
const int kSignatureTimestampWindow = 2;
// Kernel log timestamp regular expression.
const char kTimestampRegex[] = "^<.*>\\[\\s*(\\d+\\.\\d+)\\]";
//
// These regular expressions enable to us capture the PC in a backtrace.
// The backtrace is obtained through dmesg or the kernel's preserved/kcrashmem
// feature.
//
// For ARM we see:
// "<5>[ 39.458982] PC is at write_breakme+0xd0/0x1b4"
// For MIPS we see:
// "<5>[ 3378.552000] epc : 804010f0 lkdtm_do_action+0x68/0x3f8"
// For x86:
// "<0>[ 37.474699] EIP: [<790ed488>] write_breakme+0x80/0x108
// SS:ESP 0068:e9dd3efc"
//
const char* const kPCRegex[] = {
0,
" PC is at ([^\\+ ]+).*",
" epc\\s+:\\s+\\S+\\s+([^\\+ ]+).*", // MIPS has an exception program counter
" EIP: \\[<.*>\\] ([^\\+ ]+).*", // X86 uses EIP for the program counter
" RIP \\[<.*>\\] ([^\\+ ]+).*", // X86_64 uses RIP for the program counter
};
static_assert(arraysize(kPCRegex) == KernelCollector::kArchCount,
"Missing Arch PC regexp");
} // namespace
KernelCollector::KernelCollector()
: is_enabled_(false),
ramoops_dump_path_(kDumpPath),
records_(0),
// We expect crash dumps in the format of architecture we are built for.
arch_(GetCompilerArch()) {
}
KernelCollector::~KernelCollector() {
}
void KernelCollector::OverridePreservedDumpPath(const FilePath &file_path) {
ramoops_dump_path_ = file_path;
}
bool KernelCollector::ReadRecordToString(std::string *contents,
size_t current_record,
bool *record_found) {
// A record is a ramoops dump. It has an associated size of "record_size".
std::string record;
std::string captured;
// Ramoops appends a header to a crash which contains ==== followed by a
// timestamp. Ignore the header.
pcrecpp::RE record_re(
"====\\d+\\.\\d+\n(.*)",
pcrecpp::RE_Options().set_multiline(true).set_dotall(true));
pcrecpp::RE sanity_check_re("\n<\\d+>\\[\\s*(\\d+\\.\\d+)\\]");
FilePath ramoops_record;
GetRamoopsRecordPath(&ramoops_record, current_record);
if (!base::ReadFileToString(ramoops_record, &record)) {
LOG(ERROR) << "Unable to open " << ramoops_record.value();
return false;
}
*record_found = false;
if (record_re.FullMatch(record, &captured)) {
// Found a ramoops header, so strip the header and append the rest.
contents->append(captured);
*record_found = true;
} else if (sanity_check_re.PartialMatch(record.substr(0, 1024))) {
// pstore compression has been added since kernel 3.12. In order to
// decompress dmesg correctly, ramoops driver has to strip the header
// before handing over the record to the pstore driver, so we don't
// need to do it here anymore. However, the sanity check is needed because
// sometimes a pstore record is just a chunk of uninitialized memory which
// is not the result of a kernel crash. See crbug.com/443764
contents->append(record);
*record_found = true;
} else {
LOG(WARNING) << "Found invalid record at " << ramoops_record.value();
}
// Remove the record from pstore after it's found.
if (*record_found)
base::DeleteFile(ramoops_record, false);
return true;
}
void KernelCollector::GetRamoopsRecordPath(FilePath *path,
size_t record) {
// Disable error "format not a string literal, argument types not checked"
// because this is valid, but GNU apparently doesn't bother checking a const
// format string.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
*path = ramoops_dump_path_.Append(StringPrintf(kDumpFormat, record));
#pragma GCC diagnostic pop
}
bool KernelCollector::LoadParameters() {
// Discover how many ramoops records are being exported by the driver.
size_t count;
for (count = 0; count < kMaxDumpRecords; ++count) {
FilePath ramoops_record;
GetRamoopsRecordPath(&ramoops_record, count);
if (!base::PathExists(ramoops_record))
break;
}
records_ = count;
return (records_ > 0);
}
bool KernelCollector::LoadPreservedDump(std::string *contents) {
// Load dumps from the preserved memory and save them in contents.
// Since the system is set to restart on oops we won't actually ever have
// multiple records (only 0 or 1), but check in case we don't restart on
// oops in the future.
bool any_records_found = false;
bool record_found = false;
// clear contents since ReadFileToString actually appends to the string.
contents->clear();
for (size_t i = 0; i < records_; ++i) {
if (!ReadRecordToString(contents, i, &record_found)) {
break;
}
if (record_found) {
any_records_found = true;
}
}
if (!any_records_found) {
LOG(ERROR) << "No valid records found in " << ramoops_dump_path_.value();
return false;
}
return true;
}
void KernelCollector::StripSensitiveData(std::string *kernel_dump) {
// Strip any data that the user might not want sent up to the crash servers.
// We'll read in from kernel_dump and also place our output there.
//
// At the moment, the only sensitive data we strip is MAC addresses.
// Get rid of things that look like MAC addresses, since they could possibly
// give information about where someone has been. This is strings that look
// like this: 11:22:33:44:55:66
// Complications:
// - Within a given kernel_dump, want to be able to tell when the same MAC
// was used more than once. Thus, we'll consistently replace the first
// MAC found with 00:00:00:00:00:01, the second with ...:02, etc.
// - ACPI commands look like MAC addresses. We'll specifically avoid getting
// rid of those.
std::ostringstream result;
std::string pre_mac_str;
std::string mac_str;
std::map<std::string, std::string> mac_map;
pcrecpp::StringPiece input(*kernel_dump);
// This RE will find the next MAC address and can return us the data preceding
// the MAC and the MAC itself.
pcrecpp::RE mac_re("(.*?)("
"[0-9a-fA-F][0-9a-fA-F]:"
"[0-9a-fA-F][0-9a-fA-F]:"
"[0-9a-fA-F][0-9a-fA-F]:"
"[0-9a-fA-F][0-9a-fA-F]:"
"[0-9a-fA-F][0-9a-fA-F]:"
"[0-9a-fA-F][0-9a-fA-F])",
pcrecpp::RE_Options()
.set_multiline(true)
.set_dotall(true));
// This RE will identify when the 'pre_mac_str' shows that the MAC address
// was really an ACPI cmd. The full string looks like this:
// ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES) filtered out
pcrecpp::RE acpi_re("ACPI cmd ef/$",
pcrecpp::RE_Options()
.set_multiline(true)
.set_dotall(true));
// Keep consuming, building up a result string as we go.
while (mac_re.Consume(&input, &pre_mac_str, &mac_str)) {
if (acpi_re.PartialMatch(pre_mac_str)) {
// We really saw an ACPI command; add to result w/ no stripping.
result << pre_mac_str << mac_str;
} else {
// Found a MAC address; look up in our hash for the mapping.
std::string replacement_mac = mac_map[mac_str];
if (replacement_mac == "") {
// It wasn't present, so build up a replacement string.
int mac_id = mac_map.size();
// Handle up to 2^32 unique MAC address; overkill, but doesn't hurt.
replacement_mac = StringPrintf("00:00:%02x:%02x:%02x:%02x",
(mac_id & 0xff000000) >> 24,
(mac_id & 0x00ff0000) >> 16,
(mac_id & 0x0000ff00) >> 8,
(mac_id & 0x000000ff));
mac_map[mac_str] = replacement_mac;
}
// Dump the string before the MAC and the fake MAC address into result.
result << pre_mac_str << replacement_mac;
}
}
// One last bit of data might still be in the input.
result << input;
// We'll just assign right back to kernel_dump.
*kernel_dump = result.str();
}
bool KernelCollector::DumpDirMounted() {
struct stat st_parent;
if (stat(kDumpParentPath, &st_parent)) {
PLOG(WARNING) << "Could not stat " << kDumpParentPath;
return false;
}
struct stat st_dump;
if (stat(kDumpPath, &st_dump)) {
PLOG(WARNING) << "Could not stat " << kDumpPath;
return false;
}
if (st_parent.st_dev == st_dump.st_dev) {
LOG(WARNING) << "Dump dir " << kDumpPath << " not mounted";
return false;
}
return true;
}
bool KernelCollector::Enable() {
if (arch_ == kArchUnknown || arch_ >= kArchCount ||
kPCRegex[arch_] == nullptr) {
LOG(WARNING) << "KernelCollector does not understand this architecture";
return false;
}
if (!DumpDirMounted()) {
LOG(WARNING) << "Kernel does not support crash dumping";
return false;
}
// To enable crashes, we will eventually need to set
// the chnv bit in BIOS, but it does not yet work.
LOG(INFO) << "Enabling kernel crash handling";
is_enabled_ = true;
return true;
}
// Hash a string to a number. We define our own hash function to not
// be dependent on a C++ library that might change. This function
// uses basically the same approach as tr1/functional_hash.h but with
// a larger prime number (16127 vs 131).
static unsigned HashString(const std::string &input) {
unsigned hash = 0;
for (size_t i = 0; i < input.length(); ++i)
hash = hash * 16127 + input[i];
return hash;
}
void KernelCollector::ProcessStackTrace(
pcrecpp::StringPiece kernel_dump,
bool print_diagnostics,
unsigned *hash,
float *last_stack_timestamp,
bool *is_watchdog_crash) {
pcrecpp::RE line_re("(.+)", pcrecpp::MULTILINE());
pcrecpp::RE stack_trace_start_re(std::string(kTimestampRegex) +
" (Call Trace|Backtrace):$");
// Match lines such as the following and grab out "function_name".
// The ? may or may not be present.
//
// For ARM:
// <4>[ 3498.731164] [<c0057220>] ? (function_name+0x20/0x2c) from
// [<c018062c>] (foo_bar+0xdc/0x1bc)
//
// For MIPS:
// <5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8
//
// For X86:
// <4>[ 6066.849504] [<7937bcee>] ? function_name+0x66/0x6c
//
pcrecpp::RE stack_entry_re(std::string(kTimestampRegex) +
"\\s+\\[<[[:xdigit:]]+>\\]" // Matches " [<7937bcee>]"
"([\\s\\?(]+)" // Matches " ? (" (ARM) or " ? " (X86)
"([^\\+ )]+)"); // Matches until delimiter reached
std::string line;
std::string hashable;
std::string previous_hashable;
bool is_watchdog = false;
*hash = 0;
*last_stack_timestamp = 0;
// Find the last and second-to-last stack traces. The latter is used when
// the panic is from a watchdog timeout.
while (line_re.FindAndConsume(&kernel_dump, &line)) {
std::string certainty;
std::string function_name;
if (stack_trace_start_re.PartialMatch(line, last_stack_timestamp)) {
if (print_diagnostics) {
printf("Stack trace starting.%s\n",
hashable.empty() ? "" : " Saving prior trace.");
}
previous_hashable = hashable;
hashable.clear();
is_watchdog = false;
} else if (stack_entry_re.PartialMatch(line,
last_stack_timestamp,
&certainty,
&function_name)) {
bool is_certain = certainty.find('?') == std::string::npos;
if (print_diagnostics) {
printf("@%f: stack entry for %s (%s)\n",
*last_stack_timestamp,
function_name.c_str(),
is_certain ? "certain" : "uncertain");
}
// Do not include any uncertain (prefixed by '?') frames in our hash.
if (!is_certain)
continue;
if (!hashable.empty())
hashable.append("|");
if (function_name == "watchdog_timer_fn" ||
function_name == "watchdog") {
is_watchdog = true;
}
hashable.append(function_name);
}
}
// If the last stack trace contains a watchdog function we assume the panic
// is from the watchdog timer, and we hash the previous stack trace rather
// than the last one, assuming that the previous stack is that of the hung
// thread.
//
// In addition, if the hashable is empty (meaning all frames are uncertain,
// for whatever reason) also use the previous frame, as it cannot be any
// worse.
if (is_watchdog || hashable.empty()) {
hashable = previous_hashable;
}
*hash = HashString(hashable);
*is_watchdog_crash = is_watchdog;
if (print_diagnostics) {
printf("Hash based on stack trace: \"%s\" at %f.\n",
hashable.c_str(), *last_stack_timestamp);
}
}
// static
KernelCollector::ArchKind KernelCollector::GetCompilerArch() {
#if defined(COMPILER_GCC) && defined(ARCH_CPU_ARM_FAMILY)
return kArchArm;
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_MIPS_FAMILY)
return kArchMips;
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_64)
return kArchX86_64;
#elif defined(COMPILER_GCC) && defined(ARCH_CPU_X86_FAMILY)
return kArchX86;
#else
return kArchUnknown;
#endif
}
bool KernelCollector::FindCrashingFunction(
pcrecpp::StringPiece kernel_dump,
bool print_diagnostics,
float stack_trace_timestamp,
std::string *crashing_function) {
float timestamp = 0;
// Use the correct regex for this architecture.
pcrecpp::RE eip_re(std::string(kTimestampRegex) + kPCRegex[arch_],
pcrecpp::MULTILINE());
while (eip_re.FindAndConsume(&kernel_dump, &timestamp, crashing_function)) {
if (print_diagnostics) {
printf("@%f: found crashing function %s\n",
timestamp,
crashing_function->c_str());
}
}
if (timestamp == 0) {
if (print_diagnostics) {
printf("Found no crashing function.\n");
}
return false;
}
if (stack_trace_timestamp != 0 &&
abs(static_cast<int>(stack_trace_timestamp - timestamp))
> kSignatureTimestampWindow) {
if (print_diagnostics) {
printf("Found crashing function but not within window.\n");
}
return false;
}
if (print_diagnostics) {
printf("Found crashing function %s\n", crashing_function->c_str());
}
return true;
}
bool KernelCollector::FindPanicMessage(pcrecpp::StringPiece kernel_dump,
bool print_diagnostics,
std::string *panic_message) {
// Match lines such as the following and grab out "Fatal exception"
// <0>[ 342.841135] Kernel panic - not syncing: Fatal exception
pcrecpp::RE kernel_panic_re(std::string(kTimestampRegex) +
" Kernel panic[^\\:]*\\:\\s*(.*)",
pcrecpp::MULTILINE());
float timestamp = 0;
while (kernel_panic_re.FindAndConsume(&kernel_dump,
&timestamp,
panic_message)) {
if (print_diagnostics) {
printf("@%f: panic message %s\n",
timestamp,
panic_message->c_str());
}
}
if (timestamp == 0) {
if (print_diagnostics) {
printf("Found no panic message.\n");
}
return false;
}
return true;
}
bool KernelCollector::ComputeKernelStackSignature(
const std::string &kernel_dump,
std::string *kernel_signature,
bool print_diagnostics) {
unsigned stack_hash = 0;
float last_stack_timestamp = 0;
std::string human_string;
bool is_watchdog_crash;
ProcessStackTrace(kernel_dump,
print_diagnostics,
&stack_hash,
&last_stack_timestamp,
&is_watchdog_crash);
if (!FindCrashingFunction(kernel_dump,
print_diagnostics,
last_stack_timestamp,
&human_string)) {
if (!FindPanicMessage(kernel_dump, print_diagnostics, &human_string)) {
if (print_diagnostics) {
printf("Found no human readable string, using empty string.\n");
}
human_string.clear();
}
}
if (human_string.empty() && stack_hash == 0) {
if (print_diagnostics) {
printf("Found neither a stack nor a human readable string, failing.\n");
}
return false;
}
human_string = human_string.substr(0, kMaxHumanStringLength);
*kernel_signature = StringPrintf("%s-%s%s-%08X",
kKernelExecName,
(is_watchdog_crash ? "(HANG)-" : ""),
human_string.c_str(),
stack_hash);
return true;
}
bool KernelCollector::Collect() {
std::string kernel_dump;
FilePath root_crash_directory;
if (!LoadParameters()) {
return false;
}
if (!LoadPreservedDump(&kernel_dump)) {
return false;
}
StripSensitiveData(&kernel_dump);
if (kernel_dump.empty()) {
return false;
}
std::string signature;
if (!ComputeKernelStackSignature(kernel_dump, &signature, false)) {
signature = kDefaultKernelStackSignature;
}
std::string reason = "handling";
bool feedback = true;
if (IsDeveloperImage()) {
reason = "developer build - always dumping";
feedback = true;
} else if (!is_feedback_allowed_function_()) {
reason = "ignoring - no consent";
feedback = false;
}
LOG(INFO) << "Received prior crash notification from "
<< "kernel (signature " << signature << ") (" << reason << ")";
if (feedback) {
count_crash_function_();
if (!GetCreatedCrashDirectoryByEuid(kRootUid,
&root_crash_directory,
nullptr)) {
return true;
}
std::string dump_basename =
FormatDumpBasename(kKernelExecName, time(nullptr), kKernelPid);
FilePath kernel_crash_path = root_crash_directory.Append(
StringPrintf("%s.kcrash", dump_basename.c_str()));
// We must use WriteNewFile instead of base::WriteFile as we
// do not want to write with root access to a symlink that an attacker
// might have created.
if (WriteNewFile(kernel_crash_path,
kernel_dump.data(),
kernel_dump.length()) !=
static_cast<int>(kernel_dump.length())) {
LOG(INFO) << "Failed to write kernel dump to "
<< kernel_crash_path.value().c_str();
return true;
}
AddCrashMetaData(kKernelSignatureKey, signature);
WriteCrashMetaData(
root_crash_directory.Append(
StringPrintf("%s.meta", dump_basename.c_str())),
kKernelExecName,
kernel_crash_path.value());
LOG(INFO) << "Stored kcrash to " << kernel_crash_path.value();
}
return true;
}

View File

@ -1,123 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_H_
#define CRASH_REPORTER_KERNEL_COLLECTOR_H_
#include <pcrecpp.h>
#include <string>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "crash_collector.h"
// Kernel crash collector.
class KernelCollector : public CrashCollector {
public:
// Enumeration to specify architecture type.
enum ArchKind {
kArchUnknown,
kArchArm,
kArchMips,
kArchX86,
kArchX86_64,
kArchCount // Number of architectures.
};
KernelCollector();
~KernelCollector() override;
void OverridePreservedDumpPath(const base::FilePath &file_path);
// Enable collection.
bool Enable();
// Returns true if the kernel collection currently enabled.
bool is_enabled() const { return is_enabled_; }
// Collect any preserved kernel crash dump. Returns true if there was
// a dump (even if there were problems storing the dump), false otherwise.
bool Collect();
// Compute a stack signature string from a kernel dump.
bool ComputeKernelStackSignature(const std::string &kernel_dump,
std::string *kernel_signature,
bool print_diagnostics);
// Set the architecture of the crash dumps we are looking at.
void set_arch(ArchKind arch) { arch_ = arch; }
ArchKind arch() const { return arch_; }
private:
friend class KernelCollectorTest;
FRIEND_TEST(KernelCollectorTest, LoadPreservedDump);
FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBasic);
FRIEND_TEST(KernelCollectorTest, StripSensitiveDataBulk);
FRIEND_TEST(KernelCollectorTest, StripSensitiveDataSample);
FRIEND_TEST(KernelCollectorTest, CollectOK);
virtual bool DumpDirMounted();
bool LoadPreservedDump(std::string *contents);
void StripSensitiveData(std::string *kernel_dump);
void GetRamoopsRecordPath(base::FilePath *path, size_t record);
bool LoadParameters();
bool HasMoreRecords();
// Read a record to string, modified from file_utils since that didn't
// provide a way to restrict the read length.
// Return value indicates (only) error state:
// * false when we get an error (can't read from dump location).
// * true if no error occured.
// Not finding a valid record is not an error state and is signaled by the
// record_found output parameter.
bool ReadRecordToString(std::string *contents,
size_t current_record,
bool *record_found);
void ProcessStackTrace(pcrecpp::StringPiece kernel_dump,
bool print_diagnostics,
unsigned *hash,
float *last_stack_timestamp,
bool *is_watchdog_crash);
bool FindCrashingFunction(pcrecpp::StringPiece kernel_dump,
bool print_diagnostics,
float stack_trace_timestamp,
std::string *crashing_function);
bool FindPanicMessage(pcrecpp::StringPiece kernel_dump,
bool print_diagnostics,
std::string *panic_message);
// Returns the architecture kind for which we are built.
static ArchKind GetCompilerArch();
bool is_enabled_;
base::FilePath ramoops_dump_path_;
size_t records_;
// The architecture of kernel dump strings we are working with.
ArchKind arch_;
DISALLOW_COPY_AND_ASSIGN(KernelCollector);
};
#endif // CRASH_REPORTER_KERNEL_COLLECTOR_H_

View File

@ -1,679 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "kernel_collector_test.h"
#include <unistd.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/syslog_logging.h>
#include <gtest/gtest.h>
using base::FilePath;
using base::StringPrintf;
using brillo::FindLog;
using brillo::GetLog;
namespace {
int s_crashes = 0;
bool s_metrics = false;
void CountCrash() {
++s_crashes;
}
bool IsMetrics() {
return s_metrics;
}
} // namespace
class KernelCollectorTest : public ::testing::Test {
protected:
void WriteStringToFile(const FilePath &file_path,
const char *data) {
unsigned int numBytesWritten =
base::WriteFile(file_path, data, strlen(data));
ASSERT_EQ(strlen(data), numBytesWritten);
}
void SetUpSuccessfulCollect();
void ComputeKernelStackSignatureCommon();
const FilePath &kcrash_file() const { return test_kcrash_; }
const FilePath &test_crash_directory() const { return test_crash_directory_; }
KernelCollectorMock collector_;
private:
void SetUp() override {
s_crashes = 0;
s_metrics = true;
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
collector_.Initialize(CountCrash, IsMetrics);
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
test_kcrash_ = scoped_temp_dir_.path().Append("kcrash");
ASSERT_TRUE(base::CreateDirectory(test_kcrash_));
collector_.OverridePreservedDumpPath(test_kcrash_);
test_kcrash_ = test_kcrash_.Append("dmesg-ramoops-0");
ASSERT_FALSE(base::PathExists(test_kcrash_));
test_crash_directory_ = scoped_temp_dir_.path().Append("crash_directory");
ASSERT_TRUE(base::CreateDirectory(test_crash_directory_));
brillo::ClearLog();
}
FilePath test_kcrash_;
FilePath test_crash_directory_;
base::ScopedTempDir scoped_temp_dir_;
};
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureBase) {
// Make sure the normal build architecture is detected
EXPECT_NE(KernelCollector::kArchUnknown, collector_.arch());
}
TEST_F(KernelCollectorTest, LoadPreservedDump) {
ASSERT_FALSE(base::PathExists(kcrash_file()));
std::string dump;
dump.clear();
WriteStringToFile(kcrash_file(),
"CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]");
ASSERT_TRUE(collector_.LoadParameters());
ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
ASSERT_EQ("CrashRecordWithoutRamoopsHeader\n<6>[ 0.078852]", dump);
WriteStringToFile(kcrash_file(), "====1.1\nsomething");
ASSERT_TRUE(collector_.LoadParameters());
ASSERT_TRUE(collector_.LoadPreservedDump(&dump));
ASSERT_EQ("something", dump);
WriteStringToFile(kcrash_file(), "\x01\x02\xfe\xff random blob");
ASSERT_TRUE(collector_.LoadParameters());
ASSERT_FALSE(collector_.LoadPreservedDump(&dump));
ASSERT_EQ("", dump);
}
TEST_F(KernelCollectorTest, EnableMissingKernel) {
ASSERT_FALSE(collector_.Enable());
ASSERT_FALSE(collector_.is_enabled());
ASSERT_TRUE(FindLog(
"Kernel does not support crash dumping"));
ASSERT_EQ(s_crashes, 0);
}
TEST_F(KernelCollectorTest, EnableOK) {
WriteStringToFile(kcrash_file(), "");
EXPECT_CALL(collector_, DumpDirMounted()).WillOnce(::testing::Return(true));
ASSERT_TRUE(collector_.Enable());
ASSERT_TRUE(collector_.is_enabled());
ASSERT_TRUE(FindLog("Enabling kernel crash handling"));
ASSERT_EQ(s_crashes, 0);
}
TEST_F(KernelCollectorTest, StripSensitiveDataBasic) {
// Basic tests of StripSensitiveData...
// Make sure we work OK with a string w/ no MAC addresses.
const std::string kCrashWithNoMacsOrig =
"<7>[111566.131728] PM: Entering mem sleep\n";
std::string crash_with_no_macs(kCrashWithNoMacsOrig);
collector_.StripSensitiveData(&crash_with_no_macs);
EXPECT_EQ(kCrashWithNoMacsOrig, crash_with_no_macs);
// Make sure that we handle the case where there's nothing before/after the
// MAC address.
const std::string kJustAMacOrig =
"11:22:33:44:55:66";
const std::string kJustAMacStripped =
"00:00:00:00:00:01";
std::string just_a_mac(kJustAMacOrig);
collector_.StripSensitiveData(&just_a_mac);
EXPECT_EQ(kJustAMacStripped, just_a_mac);
// Test MAC addresses crammed together to make sure it gets both of them.
//
// I'm not sure that the code does ideal on these two test cases (they don't
// look like two MAC addresses to me), but since we don't see them I think
// it's OK to behave as shown here.
const std::string kCrammedMacs1Orig =
"11:22:33:44:55:66:11:22:33:44:55:66";
const std::string kCrammedMacs1Stripped =
"00:00:00:00:00:01:00:00:00:00:00:01";
std::string crammed_macs_1(kCrammedMacs1Orig);
collector_.StripSensitiveData(&crammed_macs_1);
EXPECT_EQ(kCrammedMacs1Stripped, crammed_macs_1);
const std::string kCrammedMacs2Orig =
"11:22:33:44:55:6611:22:33:44:55:66";
const std::string kCrammedMacs2Stripped =
"00:00:00:00:00:0100:00:00:00:00:01";
std::string crammed_macs_2(kCrammedMacs2Orig);
collector_.StripSensitiveData(&crammed_macs_2);
EXPECT_EQ(kCrammedMacs2Stripped, crammed_macs_2);
// Test case-sensitiveness (we shouldn't be case-senstive).
const std::string kCapsMacOrig =
"AA:BB:CC:DD:EE:FF";
const std::string kCapsMacStripped =
"00:00:00:00:00:01";
std::string caps_mac(kCapsMacOrig);
collector_.StripSensitiveData(&caps_mac);
EXPECT_EQ(kCapsMacStripped, caps_mac);
const std::string kLowerMacOrig =
"aa:bb:cc:dd:ee:ff";
const std::string kLowerMacStripped =
"00:00:00:00:00:01";
std::string lower_mac(kLowerMacOrig);
collector_.StripSensitiveData(&lower_mac);
EXPECT_EQ(kLowerMacStripped, lower_mac);
}
TEST_F(KernelCollectorTest, StripSensitiveDataBulk) {
// Test calling StripSensitiveData w/ lots of MAC addresses in the "log".
// Test that stripping code handles more than 256 unique MAC addresses, since
// that overflows past the last byte...
// We'll write up some code that generates 258 unique MAC addresses. Sorta
// cheating since the code is very similar to the current code in
// StripSensitiveData(), but would catch if someone changed that later.
std::string lotsa_macs_orig;
std::string lotsa_macs_stripped;
int i;
for (i = 0; i < 258; i++) {
lotsa_macs_orig += StringPrintf(" 11:11:11:11:%02X:%02x",
(i & 0xff00) >> 8, i & 0x00ff);
lotsa_macs_stripped += StringPrintf(" 00:00:00:00:%02X:%02x",
((i+1) & 0xff00) >> 8, (i+1) & 0x00ff);
}
std::string lotsa_macs(lotsa_macs_orig);
collector_.StripSensitiveData(&lotsa_macs);
EXPECT_EQ(lotsa_macs_stripped, lotsa_macs);
}
TEST_F(KernelCollectorTest, StripSensitiveDataSample) {
// Test calling StripSensitiveData w/ some actual lines from a real crash;
// included two MAC addresses (though replaced them with some bogusness).
const std::string kCrashWithMacsOrig =
"<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
" filtered out\n"
"<7>[108539.540144] wlan0: authenticate with 11:22:33:44:55:66 (try 1)\n"
"<7>[108539.554973] wlan0: associate with 11:22:33:44:55:66 (try 1)\n"
"<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
" QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
"<7>[110964.314648] wlan0: deauthenticated from 11:22:33:44:55:66"
" (Reason: 6)\n"
"<7>[110964.325057] phy0: Removed STA 11:22:33:44:55:66\n"
"<7>[110964.325115] phy0: Destroyed STA 11:22:33:44:55:66\n"
"<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
" QCUSBNet Ethernet Device, 99:88:77:66:55:44\n"
"<7>[111566.131728] PM: Entering mem sleep\n";
const std::string kCrashWithMacsStripped =
"<6>[111567.195339] ata1.00: ACPI cmd ef/10:03:00:00:00:a0 (SET FEATURES)"
" filtered out\n"
"<7>[108539.540144] wlan0: authenticate with 00:00:00:00:00:01 (try 1)\n"
"<7>[108539.554973] wlan0: associate with 00:00:00:00:00:01 (try 1)\n"
"<6>[110136.587583] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
" QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
"<7>[110964.314648] wlan0: deauthenticated from 00:00:00:00:00:01"
" (Reason: 6)\n"
"<7>[110964.325057] phy0: Removed STA 00:00:00:00:00:01\n"
"<7>[110964.325115] phy0: Destroyed STA 00:00:00:00:00:01\n"
"<6>[110969.219172] usb0: register 'QCUSBNet2k' at usb-0000:00:1d.7-2,"
" QCUSBNet Ethernet Device, 00:00:00:00:00:02\n"
"<7>[111566.131728] PM: Entering mem sleep\n";
std::string crash_with_macs(kCrashWithMacsOrig);
collector_.StripSensitiveData(&crash_with_macs);
EXPECT_EQ(kCrashWithMacsStripped, crash_with_macs);
}
TEST_F(KernelCollectorTest, CollectPreservedFileMissing) {
ASSERT_FALSE(collector_.Collect());
ASSERT_FALSE(FindLog("Stored kcrash to "));
ASSERT_EQ(0, s_crashes);
}
void KernelCollectorTest::SetUpSuccessfulCollect() {
collector_.ForceCrashDirectory(test_crash_directory());
WriteStringToFile(kcrash_file(), "====1.1\nsomething");
ASSERT_EQ(0, s_crashes);
}
TEST_F(KernelCollectorTest, CollectOptedOut) {
SetUpSuccessfulCollect();
s_metrics = false;
ASSERT_TRUE(collector_.Collect());
ASSERT_TRUE(FindLog("(ignoring - no consent)"));
ASSERT_EQ(0, s_crashes);
}
TEST_F(KernelCollectorTest, CollectOK) {
SetUpSuccessfulCollect();
ASSERT_TRUE(collector_.Collect());
ASSERT_EQ(1, s_crashes);
ASSERT_TRUE(FindLog("(handling)"));
static const char kNamePrefix[] = "Stored kcrash to ";
std::string log = brillo::GetLog();
size_t pos = log.find(kNamePrefix);
ASSERT_NE(std::string::npos, pos)
<< "Did not find string \"" << kNamePrefix << "\" in log: {\n"
<< log << "}";
pos += strlen(kNamePrefix);
std::string filename = log.substr(pos, std::string::npos);
// Take the name up until \n
size_t end_pos = filename.find_first_of('\n');
ASSERT_NE(std::string::npos, end_pos);
filename = filename.substr(0, end_pos);
ASSERT_EQ(0U, filename.find(test_crash_directory().value()));
ASSERT_TRUE(base::PathExists(FilePath(filename)));
std::string contents;
ASSERT_TRUE(base::ReadFileToString(FilePath(filename), &contents));
ASSERT_EQ("something", contents);
}
// Perform tests which are common across architectures
void KernelCollectorTest::ComputeKernelStackSignatureCommon() {
std::string signature;
const char kStackButNoPC[] =
"<4>[ 6066.829029] [<790340af>] __do_softirq+0xa6/0x143\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kStackButNoPC, &signature, false));
EXPECT_EQ("kernel--83615F0A", signature);
const char kMissingEverything[] =
"<4>[ 6066.829029] [<790340af>] ? __do_softirq+0xa6/0x143\n";
EXPECT_FALSE(
collector_.ComputeKernelStackSignature(kMissingEverything,
&signature,
false));
// Long message.
const char kTruncatedMessage[] =
"<0>[ 87.485611] Kernel panic - not syncing: 01234567890123456789"
"01234567890123456789X\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kTruncatedMessage,
&signature,
false));
EXPECT_EQ("kernel-0123456789012345678901234567890123456789-00000000",
signature);
}
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureARM) {
const char kBugToPanic[] =
"<5>[ 123.412524] Modules linked in:\n"
"<5>[ 123.412534] CPU: 0 Tainted: G W "
"(2.6.37-01030-g51cee64 #153)\n"
"<5>[ 123.412552] PC is at write_breakme+0xd0/0x1b4\n"
"<5>[ 123.412560] LR is at write_breakme+0xc8/0x1b4\n"
"<5>[ 123.412569] pc : [<c0058220>] lr : [<c005821c>] "
"psr: 60000013\n"
"<5>[ 123.412574] sp : f4e0ded8 ip : c04d104c fp : 000e45e0\n"
"<5>[ 123.412581] r10: 400ff000 r9 : f4e0c000 r8 : 00000004\n"
"<5>[ 123.412589] r7 : f4e0df80 r6 : f4820c80 r5 : 00000004 "
"r4 : f4e0dee8\n"
"<5>[ 123.412598] r3 : 00000000 r2 : f4e0decc r1 : c05f88a9 "
"r0 : 00000039\n"
"<5>[ 123.412608] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA "
"ARM Segment user\n"
"<5>[ 123.412617] Control: 10c53c7d Table: 34dcc04a DAC: 00000015\n"
"<0>[ 123.412626] Process bash (pid: 1014, stack limit = 0xf4e0c2f8)\n"
"<0>[ 123.412634] Stack: (0xf4e0ded8 to 0xf4e0e000)\n"
"<0>[ 123.412641] dec0: "
" f4e0dee8 c0183678\n"
"<0>[ 123.412654] dee0: 00000000 00000000 00677562 0000081f c06a6a78 "
"400ff000 f4e0dfb0 00000000\n"
"<0>[ 123.412666] df00: bec7ab44 000b1719 bec7ab0c c004f498 bec7a314 "
"c024acc8 00000001 c018359c\n"
"<0>[ 123.412679] df20: f4e0df34 c04d10fc f5803c80 271beb39 000e45e0 "
"f5803c80 c018359c c017bfe0\n"
"<0>[ 123.412691] df40: 00000004 f4820c80 400ff000 f4e0df80 00000004 "
"f4e0c000 00000000 c01383e4\n"
"<0>[ 123.412703] df60: f4820c80 400ff000 f4820c80 400ff000 00000000 "
"00000000 00000004 c0138578\n"
"<0>[ 123.412715] df80: 00000000 00000000 00000004 00000000 00000004 "
"402f95d0 00000004 00000004\n"
"<0>[ 123.412727] dfa0: c0054984 c00547c0 00000004 402f95d0 00000001 "
"400ff000 00000004 00000000\n"
"<0>[ 123.412739] dfc0: 00000004 402f95d0 00000004 00000004 400ff000 "
"000c194c bec7ab58 000e45e0\n"
"<0>[ 123.412751] dfe0: 00000000 bec7aad8 40232520 40284e9c 60000010 "
"00000001 00000000 00000000\n"
"<5>[ 39.496577] Backtrace:\n"
"<5>[ 123.412782] [<c0058220>] (__bug+0x20/0x2c) from [<c0183678>] "
"(write_breakme+0xdc/0x1bc)\n"
"<5>[ 123.412798] [<c0183678>] (write_breakme+0xdc/0x1bc) from "
"[<c017bfe0>] (proc_reg_write+0x88/0x9c)\n";
std::string signature;
collector_.set_arch(KernelCollector::kArchArm);
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
EXPECT_EQ("kernel-write_breakme-97D3E92F", signature);
ComputeKernelStackSignatureCommon();
}
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureMIPS) {
const char kBugToPanic[] =
"<5>[ 3378.472000] lkdtm: Performing direct entry BUG\n"
"<5>[ 3378.476000] Kernel bug detected[#1]:\n"
"<5>[ 3378.484000] CPU: 0 PID: 185 Comm: dash Not tainted 3.14.0 #1\n"
"<5>[ 3378.488000] task: 8fed5220 ti: 8ec4a000 task.ti: 8ec4a000\n"
"<5>[ 3378.496000] $ 0 : 00000000 804018b8 804010f0 7785b507\n"
"<5>[ 3378.500000] $ 4 : 8061ab64 81204478 81205b20 00000000\n"
"<5>[ 3378.508000] $ 8 : 80830000 20746365 72746e65 55422079\n"
"<5>[ 3378.512000] $12 : 8ec4be94 000000fc 00000000 00000048\n"
"<5>[ 3378.520000] $16 : 00000004 8ef54000 80710000 00000002\n"
"<5>[ 3378.528000] $20 : 7765b6d4 00000004 7fffffff 00000002\n"
"<5>[ 3378.532000] $24 : 00000001 803dc0dc \n"
"<5>[ 3378.540000] $28 : 8ec4a000 8ec4be20 7775438d 804018b8\n"
"<5>[ 3378.544000] Hi : 00000000\n"
"<5>[ 3378.548000] Lo : 49bf8080\n"
"<5>[ 3378.552000] epc : 804010f0 lkdtm_do_action+0x68/0x3f8\n"
"<5>[ 3378.560000] Not tainted\n"
"<5>[ 3378.564000] ra : 804018b8 direct_entry+0x110/0x154\n"
"<5>[ 3378.568000] Status: 3100dc03 KERNEL EXL IE \n"
"<5>[ 3378.572000] Cause : 10800024\n"
"<5>[ 3378.576000] PrId : 0001a120 (MIPS interAptiv (multi))\n"
"<5>[ 3378.580000] Modules linked in: uinput cfg80211 nf_conntrack_ipv6 "
"nf_defrag_ipv6 ip6table_filter ip6_tables pcnet32 mii fuse "
"ppp_async ppp_generic slhc tun\n"
"<5>[ 3378.600000] Process dash (pid: 185, threadinfo=8ec4a000, "
"task=8fed5220, tls=77632490)\n"
"<5>[ 3378.608000] Stack : 00000006 ffffff9c 00000000 00000000 00000000 "
"00000000 8083454a 00000022\n"
"<5> 7765baa1 00001fee 80710000 8ef54000 8ec4bf08 00000002 "
"7765b6d4 00000004\n"
"<5> 7fffffff 00000002 7775438d 805e5158 7fffffff 00000002 "
"00000000 7785b507\n"
"<5> 806a96bc 00000004 8ef54000 8ec4bf08 00000002 804018b8 "
"80710000 806a98bc\n"
"<5> 00000002 00000020 00000004 8d515600 77756450 00000004 "
"8ec4bf08 802377e4\n"
"<5> ...\n"
"<5>[ 3378.652000] Call Trace:\n"
"<5>[ 3378.656000] [<804010f0>] lkdtm_do_action+0x68/0x3f8\n"
"<5>[ 3378.660000] [<804018b8>] direct_entry+0x110/0x154\n"
"<5>[ 3378.664000] [<802377e4>] vfs_write+0xe0/0x1bc\n"
"<5>[ 3378.672000] [<80237f90>] SyS_write+0x78/0xf8\n"
"<5>[ 3378.676000] [<80111888>] handle_sys+0x128/0x14c\n"
"<5>[ 3378.680000] \n"
"<5>[ 3378.684000] \n"
"<5>Code: 3c04806b 0c1793aa 248494f0 <000c000d> 3c04806b 248494fc "
"0c04cc7f 2405017a 08100514 \n"
"<5>[ 3378.696000] ---[ end trace 75067432f24bbc93 ]---\n";
std::string signature;
collector_.set_arch(KernelCollector::kArchMips);
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
EXPECT_EQ("kernel-lkdtm_do_action-5E600A6B", signature);
ComputeKernelStackSignatureCommon();
}
TEST_F(KernelCollectorTest, ComputeKernelStackSignatureX86) {
const char kBugToPanic[] =
"<4>[ 6066.829029] [<79039d16>] ? run_timer_softirq+0x165/0x1e6\n"
"<4>[ 6066.829029] [<790340af>] ignore_old_stack+0xa6/0x143\n"
"<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+"
"0xa3/0xb5 [mac80211] SS:ESP 0068:7951febc\n"
"<0>[ 6066.829029] CR2: 00000000323038a7\n"
"<4>[ 6066.845422] ---[ end trace 12b058bb46c43500 ]---\n"
"<0>[ 6066.845747] Kernel panic - not syncing: Fatal exception "
"in interrupt\n"
"<0>[ 6066.846902] Call Trace:\n"
"<4>[ 6066.846902] [<7937a07b>] ? printk+0x14/0x19\n"
"<4>[ 6066.949779] [<79379fc1>] panic+0x3e/0xe4\n"
"<4>[ 6066.949971] [<7937c5c5>] oops_end+0x73/0x81\n"
"<4>[ 6066.950208] [<7901b260>] no_context+0x10d/0x117\n";
std::string signature;
collector_.set_arch(KernelCollector::kArchX86);
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kBugToPanic, &signature, false));
EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-DE253569", signature);
const char kPCButNoStack[] =
"<0>[ 6066.829029] EIP: [<b82d7c15>] ieee80211_stop_tx_ba_session+";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kPCButNoStack, &signature, false));
EXPECT_EQ("kernel-ieee80211_stop_tx_ba_session-00000000", signature);
const char kBreakmeBug[] =
"<4>[ 180.492137] [<790970c6>] ? handle_mm_fault+0x67f/0x96d\n"
"<4>[ 180.492137] [<790dcdfe>] ? proc_reg_write+0x5f/0x73\n"
"<4>[ 180.492137] [<790e2224>] ? write_breakme+0x0/0x108\n"
"<4>[ 180.492137] [<790dcd9f>] ? proc_reg_write+0x0/0x73\n"
"<4>[ 180.492137] [<790ac0aa>] vfs_write+0x85/0xe4\n"
"<0>[ 180.492137] Code: c6 44 05 b2 00 89 d8 e8 0c ef 09 00 85 c0 75 "
"0b c7 00 00 00 00 00 e9 8e 00 00 00 ba e6 75 4b 79 89 d8 e8 f1 ee 09 "
"00 85 c0 75 04 <0f> 0b eb fe ba 58 47 49 79 89 d8 e8 dd ee 09 00 85 "
"c0 75 0a 68\n"
"<0>[ 180.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
"0068:aa3e9efc\n"
"<4>[ 180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
"<0>[ 180.502026] Kernel panic - not syncing: Fatal exception\n"
"<4>[ 180.502026] Call Trace:\n"
"<4>[ 180.502806] [<79379aba>] ? printk+0x14/0x1a\n"
"<4>[ 180.503033] [<79379a00>] panic+0x3e/0xe4\n"
"<4>[ 180.503287] [<7937c005>] oops_end+0x73/0x81\n"
"<4>[ 180.503520] [<790055dd>] die+0x58/0x5e\n"
"<4>[ 180.503538] [<7937b96c>] do_trap+0x8e/0xa7\n"
"<4>[ 180.503555] [<79003d70>] ? do_invalid_op+0x0/0x80\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kBreakmeBug, &signature, false));
EXPECT_EQ("kernel-write_breakme-122AB3CD", signature);
const char kPCLineTooOld[] =
"<4>[ 174.492137] [<790970c6>] ignored_function+0x67f/0x96d\n"
"<4>[ 175.492137] [<790970c6>] ignored_function2+0x67f/0x96d\n"
"<0>[ 174.492137] EIP: [<790e22a4>] write_breakme+0x80/0x108 SS:ESP "
"0068:aa3e9efc\n"
"<4>[ 180.501800] ---[ end trace 2a6b72965e1b1523 ]---\n"
"<4>[ 180.502026] Call Trace:\n"
"<0>[ 180.502026] Kernel panic - not syncing: Fatal exception\n"
"<4>[ 180.502806] [<79379aba>] printk+0x14/0x1a\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kPCLineTooOld, &signature, false));
EXPECT_EQ("kernel-Fatal exception-ED4C84FE", signature);
// Panic without EIP line.
const char kExamplePanicOnly[] =
"<0>[ 87.485611] Kernel panic - not syncing: Testing panic\n"
"<4>[ 87.485630] Pid: 2825, comm: bash Tainted: G "
"C 2.6.32.23+drm33.10 #1\n"
"<4>[ 87.485639] Call Trace:\n"
"<4>[ 87.485660] [<8133f71d>] ? printk+0x14/0x17\n"
"<4>[ 87.485674] [<8133f663>] panic+0x3e/0xe4\n"
"<4>[ 87.485689] [<810d062e>] write_breakme+0xaa/0x124\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kExamplePanicOnly,
&signature,
false));
EXPECT_EQ("kernel-Testing panic-E0FC3552", signature);
// Panic from hung task.
const char kHungTaskBreakMe[] =
"<3>[ 720.459157] INFO: task bash:2287 blocked blah blah\n"
"<5>[ 720.459282] Call Trace:\n"
"<5>[ 720.459307] [<810a457b>] ? __dentry_open+0x186/0x23e\n"
"<5>[ 720.459323] [<810b9c71>] ? mntput_no_expire+0x29/0xe2\n"
"<5>[ 720.459336] [<810b9d48>] ? mntput+0x1e/0x20\n"
"<5>[ 720.459350] [<810ad135>] ? path_put+0x1a/0x1d\n"
"<5>[ 720.459366] [<8137cacc>] schedule+0x4d/0x4f\n"
"<5>[ 720.459379] [<8137ccfb>] schedule_timeout+0x26/0xaf\n"
"<5>[ 720.459394] [<8102127e>] ? should_resched+0xd/0x27\n"
"<5>[ 720.459409] [<81174d1f>] ? _copy_from_user+0x3c/0x50\n"
"<5>[ 720.459423] [<8137cd9e>] "
"schedule_timeout_uninterruptible+0x1a/0x1c\n"
"<5>[ 720.459438] [<810dee63>] write_breakme+0xb3/0x178\n"
"<5>[ 720.459453] [<810dedb0>] ? meminfo_proc_show+0x2f2/0x2f2\n"
"<5>[ 720.459467] [<810d94ae>] proc_reg_write+0x6d/0x87\n"
"<5>[ 720.459481] [<810d9441>] ? proc_reg_poll+0x76/0x76\n"
"<5>[ 720.459493] [<810a5e9e>] vfs_write+0x79/0xa5\n"
"<5>[ 720.459505] [<810a6011>] sys_write+0x40/0x65\n"
"<5>[ 720.459519] [<8137e677>] sysenter_do_call+0x12/0x26\n"
"<0>[ 720.459530] Kernel panic - not syncing: hung_task: blocked tasks\n"
"<5>[ 720.459768] Pid: 31, comm: khungtaskd Tainted: "
"G C 3.0.8 #1\n"
"<5>[ 720.459998] Call Trace:\n"
"<5>[ 720.460140] [<81378a35>] panic+0x53/0x14a\n"
"<5>[ 720.460312] [<8105f875>] watchdog+0x15b/0x1a0\n"
"<5>[ 720.460495] [<8105f71a>] ? hung_task_panic+0x16/0x16\n"
"<5>[ 720.460693] [<81043af3>] kthread+0x67/0x6c\n"
"<5>[ 720.460862] [<81043a8c>] ? __init_kthread_worker+0x2d/0x2d\n"
"<5>[ 720.461106] [<8137eb9e>] kernel_thread_helper+0x6/0x10\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kHungTaskBreakMe,
&signature,
false));
EXPECT_EQ("kernel-(HANG)-hung_task: blocked tasks-600B37EA", signature);
// Panic with all question marks in the last stack trace.
const char kUncertainStackTrace[] =
"<0>[56279.689669] ------------[ cut here ]------------\n"
"<2>[56279.689677] kernel BUG at /build/x86-alex/tmp/portage/"
"sys-kernel/chromeos-kernel-0.0.1-r516/work/chromeos-kernel-0.0.1/"
"kernel/timer.c:844!\n"
"<0>[56279.689683] invalid opcode: 0000 [#1] SMP \n"
"<0>[56279.689688] last sysfs file: /sys/power/state\n"
"<5>[56279.689692] Modules linked in: nls_iso8859_1 nls_cp437 vfat fat "
"gobi usbnet tsl2583(C) industrialio(C) snd_hda_codec_realtek "
"snd_hda_intel i2c_dev snd_hda_codec snd_hwdep qcserial snd_pcm usb_wwan "
"i2c_i801 snd_timer nm10_gpio snd_page_alloc rtc_cmos fuse "
"nf_conntrack_ipv6 nf_defrag_ipv6 uvcvideo videodev ip6table_filter "
"ath9k ip6_tables ipv6 mac80211 ath9k_common ath9k_hw ath cfg80211 "
"xt_mark\n"
"<5>[56279.689731] \n"
"<5>[56279.689738] Pid: 24607, comm: powerd_suspend Tainted: G "
"WC 2.6.38.3+ #1 SAMSUNG ELECTRONICS CO., LTD. Alex/G100 \n"
"<5>[56279.689748] EIP: 0060:[<8103e3ea>] EFLAGS: 00210286 CPU: 3\n"
"<5>[56279.689758] EIP is at add_timer+0xd/0x1b\n"
"<5>[56279.689762] EAX: f5e00684 EBX: f5e003c0 ECX: 00000002 EDX: "
"00200246\n"
"<5>[56279.689767] ESI: f5e003c0 EDI: d28bc03c EBP: d2be5e40 ESP: "
"d2be5e40\n"
"<5>[56279.689772] DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068\n"
"<0>[56279.689778] Process powerd_suspend (pid: 24607, ti=d2be4000 "
"task=f5dc9b60 task.ti=d2be4000)\n"
"<0>[56279.689782] Stack:\n"
"<5>[56279.689785] d2be5e4c f8dccced f4ac02c0 d2be5e70 f8ddc752 "
"f5e003c0 f4ac0458 f4ac092c\n"
"<5>[56279.689797] f4ac043c f4ac02c0 f4ac0000 f4ac007c d2be5e7c "
"f8dd4a33 f4ac0164 d2be5e94\n"
"<5>[56279.689809] f87e0304 f69ff0cc f4ac0164 f87e02a4 f4ac0164 "
"d2be5eb0 81248968 00000000\n"
"<0>[56279.689821] Call Trace:\n"
"<5>[56279.689840] [<f8dccced>] ieee80211_sta_restart+0x25/0x8c "
"[mac80211]\n"
"<5>[56279.689854] [<f8ddc752>] ieee80211_reconfig+0x2e9/0x339 "
"[mac80211]\n"
"<5>[56279.689869] [<f8dd4a33>] ieee80211_aes_cmac+0x182d/0x184e "
"[mac80211]\n"
"<5>[56279.689883] [<f87e0304>] cfg80211_get_dev_from_info+0x29b/0x2c0 "
"[cfg80211]\n"
"<5>[56279.689895] [<f87e02a4>] ? "
"cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
"<5>[56279.689904] [<81248968>] legacy_resume+0x25/0x5d\n"
"<5>[56279.689910] [<812490ae>] device_resume+0xdd/0x110\n"
"<5>[56279.689917] [<812491c2>] dpm_resume_end+0xe1/0x271\n"
"<5>[56279.689925] [<81060481>] suspend_devices_and_enter+0x18b/0x1de\n"
"<5>[56279.689932] [<810605ba>] enter_state+0xe6/0x132\n"
"<5>[56279.689939] [<8105fd4b>] state_store+0x91/0x9d\n"
"<5>[56279.689945] [<8105fcba>] ? state_store+0x0/0x9d\n"
"<5>[56279.689953] [<81178fb1>] kobj_attr_store+0x16/0x22\n"
"<5>[56279.689961] [<810eea5e>] sysfs_write_file+0xc1/0xec\n"
"<5>[56279.689969] [<810af443>] vfs_write+0x8f/0x101\n"
"<5>[56279.689975] [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
"<5>[56279.689982] [<810af556>] sys_write+0x40/0x65\n"
"<5>[56279.689989] [<81002d57>] sysenter_do_call+0x12/0x26\n"
"<0>[56279.689993] Code: c1 d3 e2 4a 89 55 f4 f7 d2 21 f2 6a 00 31 c9 89 "
"d8 e8 6e fd ff ff 5a 8d 65 f8 5b 5e 5d c3 55 89 e5 3e 8d 74 26 00 83 38 "
"00 74 04 <0f> 0b eb fe 8b 50 08 e8 6f ff ff ff 5d c3 55 89 e5 3e 8d 74 "
"26 \n"
"<0>[56279.690009] EIP: [<8103e3ea>] add_timer+0xd/0x1b SS:ESP "
"0068:d2be5e40\n"
"<4>[56279.690113] ---[ end trace b71141bb67c6032a ]---\n"
"<7>[56279.694069] wlan0: deauthenticated from 00:00:00:00:00:01 "
"(Reason: 6)\n"
"<0>[56279.703465] Kernel panic - not syncing: Fatal exception\n"
"<5>[56279.703471] Pid: 24607, comm: powerd_suspend Tainted: G D "
"WC 2.6.38.3+ #1\n"
"<5>[56279.703475] Call Trace:\n"
"<5>[56279.703483] [<8136648c>] ? panic+0x55/0x152\n"
"<5>[56279.703491] [<810057fa>] ? oops_end+0x73/0x81\n"
"<5>[56279.703497] [<81005a44>] ? die+0xed/0xf5\n"
"<5>[56279.703503] [<810033cb>] ? do_trap+0x7a/0x80\n"
"<5>[56279.703509] [<8100369b>] ? do_invalid_op+0x0/0x80\n"
"<5>[56279.703515] [<81003711>] ? do_invalid_op+0x76/0x80\n"
"<5>[56279.703522] [<8103e3ea>] ? add_timer+0xd/0x1b\n"
"<5>[56279.703529] [<81025e23>] ? check_preempt_curr+0x2e/0x69\n"
"<5>[56279.703536] [<8102ef28>] ? ttwu_post_activation+0x5a/0x11b\n"
"<5>[56279.703543] [<8102fa8d>] ? try_to_wake_up+0x213/0x21d\n"
"<5>[56279.703550] [<81368b7f>] ? error_code+0x67/0x6c\n"
"<5>[56279.703557] [<8103e3ea>] ? add_timer+0xd/0x1b\n"
"<5>[56279.703577] [<f8dccced>] ? ieee80211_sta_restart+0x25/0x8c "
"[mac80211]\n"
"<5>[56279.703591] [<f8ddc752>] ? ieee80211_reconfig+0x2e9/0x339 "
"[mac80211]\n"
"<5>[56279.703605] [<f8dd4a33>] ? ieee80211_aes_cmac+0x182d/0x184e "
"[mac80211]\n"
"<5>[56279.703618] [<f87e0304>] ? "
"cfg80211_get_dev_from_info+0x29b/0x2c0 [cfg80211]\n"
"<5>[56279.703630] [<f87e02a4>] ? "
"cfg80211_get_dev_from_info+0x23b/0x2c0 [cfg80211]\n"
"<5>[56279.703637] [<81248968>] ? legacy_resume+0x25/0x5d\n"
"<5>[56279.703643] [<812490ae>] ? device_resume+0xdd/0x110\n"
"<5>[56279.703649] [<812491c2>] ? dpm_resume_end+0xe1/0x271\n"
"<5>[56279.703657] [<81060481>] ? "
"suspend_devices_and_enter+0x18b/0x1de\n"
"<5>[56279.703663] [<810605ba>] ? enter_state+0xe6/0x132\n"
"<5>[56279.703670] [<8105fd4b>] ? state_store+0x91/0x9d\n"
"<5>[56279.703676] [<8105fcba>] ? state_store+0x0/0x9d\n"
"<5>[56279.703683] [<81178fb1>] ? kobj_attr_store+0x16/0x22\n"
"<5>[56279.703690] [<810eea5e>] ? sysfs_write_file+0xc1/0xec\n"
"<5>[56279.703697] [<810af443>] ? vfs_write+0x8f/0x101\n"
"<5>[56279.703703] [<810ee99d>] ? sysfs_write_file+0x0/0xec\n"
"<5>[56279.703709] [<810af556>] ? sys_write+0x40/0x65\n"
"<5>[56279.703716] [<81002d57>] ? sysenter_do_call+0x12/0x26\n";
EXPECT_TRUE(
collector_.ComputeKernelStackSignature(kUncertainStackTrace,
&signature,
false));
// The first trace contains only uncertain entries and its hash is 00000000,
// so, if we used that, the signature would be kernel-add_timer-00000000.
// Instead we use the second-to-last trace for the hash.
EXPECT_EQ("kernel-add_timer-B5178878", signature);
ComputeKernelStackSignatureCommon();
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
#define CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_
#include "kernel_collector.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class KernelCollectorMock : public KernelCollector {
public:
MOCK_METHOD0(DumpDirMounted, bool());
MOCK_METHOD0(SetUpDBus, void());
};
#endif // CRASH_REPORTER_KERNEL_COLLECTOR_TEST_H_

View File

@ -1,59 +0,0 @@
#!/bin/sh
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Usage example: "kernel_log_collector.sh XXX YYY"
# This script searches logs in the /var/log/messages which have the keyword XXX.
# And only those logs which are within the last YYY seconds of the latest log
# that has the keyword XXX are printed.
# Kernel log has the possible formats:
# 2013-06-14T16:31:40.514513-07:00 localhost kernel: [ 2.682472] MSG MSG ...
# 2013-06-19T20:38:58.661826+00:00 localhost kernel: [ 1.668092] MSG MSG ...
search_key=$1
time_duration=$2
msg_pattern="^[0-9-]*T[0-9:.+-]* localhost kernel"
die() {
echo "kernel_log_collector: $*" >&2
exit 1
}
get_timestamp() {
timestamp="$(echo $1 | cut -d " " -f 1)"
timestamp="$(date -d "${timestamp}" +%s)" || exit $?
echo "${timestamp}"
}
last_line=$(grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | tail -n 1)
if [ -n "${last_line}" ]; then
if ! allowed_timestamp=$(get_timestamp "${last_line}"); then
die "coule not get timestamp from: ${last_line}"
fi
: $(( allowed_timestamp -= ${time_duration} ))
grep "${msg_pattern}" /var/log/messages | grep -- "${search_key}" | while read line; do
if ! timestamp=$(get_timestamp "${line}"); then
die "could not get timestamp from: ${line}"
fi
if [ ${timestamp} -gt ${allowed_timestamp} ]; then
echo "${line}"
fi
done
fi
echo "END-OF-LOG"

View File

@ -1,113 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "kernel_warning_collector.h"
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
namespace {
const char kExecName[] = "kernel-warning";
const char kKernelWarningSignatureKey[] = "sig";
const char kKernelWarningPath[] = "/var/run/kwarn/warning";
const pid_t kKernelPid = 0;
const uid_t kRootUid = 0;
} // namespace
using base::FilePath;
using base::StringPrintf;
KernelWarningCollector::KernelWarningCollector() {
}
KernelWarningCollector::~KernelWarningCollector() {
}
bool KernelWarningCollector::LoadKernelWarning(std::string *content,
std::string *signature) {
FilePath kernel_warning_path(kKernelWarningPath);
if (!base::ReadFileToString(kernel_warning_path, content)) {
LOG(ERROR) << "Could not open " << kKernelWarningPath;
return false;
}
// The signature is in the first line.
std::string::size_type end_position = content->find('\n');
if (end_position == std::string::npos) {
LOG(ERROR) << "unexpected kernel warning format";
return false;
}
*signature = content->substr(0, end_position);
return true;
}
bool KernelWarningCollector::Collect() {
std::string reason = "normal collection";
bool feedback = true;
if (IsDeveloperImage()) {
reason = "always collect from developer builds";
feedback = true;
} else if (!is_feedback_allowed_function_()) {
reason = "no user consent";
feedback = false;
}
LOG(INFO) << "Processing kernel warning: " << reason;
if (!feedback) {
return true;
}
std::string kernel_warning;
std::string warning_signature;
if (!LoadKernelWarning(&kernel_warning, &warning_signature)) {
return true;
}
FilePath root_crash_directory;
if (!GetCreatedCrashDirectoryByEuid(kRootUid, &root_crash_directory,
nullptr)) {
return true;
}
std::string dump_basename =
FormatDumpBasename(kExecName, time(nullptr), kKernelPid);
FilePath kernel_crash_path = root_crash_directory.Append(
StringPrintf("%s.kcrash", dump_basename.c_str()));
// We must use WriteNewFile instead of base::WriteFile as we
// do not want to write with root access to a symlink that an attacker
// might have created.
if (WriteNewFile(kernel_crash_path,
kernel_warning.data(),
kernel_warning.length()) !=
static_cast<int>(kernel_warning.length())) {
LOG(INFO) << "Failed to write kernel warning to "
<< kernel_crash_path.value().c_str();
return true;
}
AddCrashMetaData(kKernelWarningSignatureKey, warning_signature);
WriteCrashMetaData(
root_crash_directory.Append(
StringPrintf("%s.meta", dump_basename.c_str())),
kExecName, kernel_crash_path.value());
LOG(INFO) << "Stored kernel warning into " << kernel_crash_path.value();
return true;
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
#define CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_
#include <string>
#include <base/macros.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "crash_collector.h"
// Kernel warning collector.
class KernelWarningCollector : public CrashCollector {
public:
KernelWarningCollector();
~KernelWarningCollector() override;
// Collects warning.
bool Collect();
private:
friend class KernelWarningCollectorTest;
FRIEND_TEST(KernelWarningCollectorTest, CollectOK);
// Reads the full content of the kernel warn dump and its signature.
bool LoadKernelWarning(std::string *content, std::string *signature);
DISALLOW_COPY_AND_ASSIGN(KernelWarningCollector);
};
#endif // CRASH_REPORTER_KERNEL_WARNING_COLLECTOR_H_

View File

@ -1,302 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sysexits.h>
#include <unistd.h> // for isatty()
#include <string>
#include <vector>
#include <base/cancelable_callback.h>
#include <base/command_line.h>
#include <base/files/file_util.h>
#include <base/memory/weak_ptr.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_tokenizer.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include <brillo/daemons/dbus_daemon.h>
#include <brillo/syslog_logging.h>
#include "libcrosservice/dbus-proxies.h"
using std::unique_ptr;
namespace {
const char kLibCrosProxyResolvedSignalInterface[] =
"org.chromium.CrashReporterLibcrosProxyResolvedInterface";
const char kLibCrosProxyResolvedName[] = "ProxyResolved";
const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
const char kNoProxy[] = "direct://";
const int kTimeoutDefaultSeconds = 5;
const char kHelp[] = "help";
const char kQuiet[] = "quiet";
const char kTimeout[] = "timeout";
const char kVerbose[] = "verbose";
// Help message to show when the --help command line switch is specified.
const char kHelpMessage[] =
"Chromium OS Crash helper: proxy lister\n"
"\n"
"Available Switches:\n"
" --quiet Only print the proxies\n"
" --verbose Print additional messages even when not run from a TTY\n"
" --timeout=N Set timeout for browser resolving proxies (default is 5)\n"
" --help Show this help.\n";
// Copied from src/update_engine/chrome_browser_proxy_resolver.cc
// Parses the browser's answer for resolved proxies. It returns a
// list of strings, each of which is a resolved proxy.
std::vector<std::string> ParseProxyString(const std::string& input) {
std::vector<std::string> ret;
// Some of this code taken from
// http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
// http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
base::StringTokenizer entry_tok(input, ";");
while (entry_tok.GetNext()) {
std::string token = entry_tok.token();
base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
// Start by finding the first space (if any).
std::string::iterator space;
for (space = token.begin(); space != token.end(); ++space) {
if (base::IsAsciiWhitespace(*space)) {
break;
}
}
std::string scheme = base::ToLowerASCII(std::string(token.begin(), space));
// Chrome uses "socks" to mean socks4 and "proxy" to mean http.
if (scheme == "socks")
scheme += "4";
else if (scheme == "proxy")
scheme = "http";
else if (scheme != "https" &&
scheme != "socks4" &&
scheme != "socks5" &&
scheme != "direct")
continue; // Invalid proxy scheme
std::string host_and_port = std::string(space, token.end());
base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
if (scheme != "direct" && host_and_port.empty())
continue; // Must supply host/port when non-direct proxy used.
ret.push_back(scheme + "://" + host_and_port);
}
if (ret.empty() || *ret.rbegin() != kNoProxy)
ret.push_back(kNoProxy);
return ret;
}
// A class for interfacing with Chrome to resolve proxies for a given source
// url. The class is initialized with the given source url to check, the
// signal interface and name that Chrome will reply to, and how long to wait
// for the resolve request to timeout. Once initialized, the Run() function
// must be called, which blocks on the D-Bus call to Chrome. The call returns
// after either the timeout or the proxy has been resolved. The resolved
// proxies can then be accessed through the proxies() function.
class ProxyResolver : public brillo::DBusDaemon {
public:
ProxyResolver(const std::string& source_url,
const std::string& signal_interface,
const std::string& signal_name,
base::TimeDelta timeout)
: source_url_(source_url),
signal_interface_(signal_interface),
signal_name_(signal_name),
timeout_(timeout),
weak_ptr_factory_(this),
timeout_callback_(base::Bind(&ProxyResolver::HandleBrowserTimeout,
weak_ptr_factory_.GetWeakPtr())) {}
~ProxyResolver() override {}
const std::vector<std::string>& proxies() {
return proxies_;
}
int Run() override {
// Add task for if the browser proxy call times out.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
timeout_callback_.callback(),
timeout_);
return brillo::DBusDaemon::Run();
}
protected:
// If the browser times out, quit the run loop.
void HandleBrowserTimeout() {
LOG(ERROR) << "Timeout while waiting for browser to resolve proxy";
Quit();
}
// If the signal handler connects successfully, call the browser's
// ResolveNetworkProxy D-Bus method. Otherwise, don't do anything and let
// the timeout task quit the run loop.
void HandleDBusSignalConnected(const std::string& interface,
const std::string& signal,
bool success) {
if (!success) {
LOG(ERROR) << "Could not connect to signal " << interface << "."
<< signal;
timeout_callback_.Cancel();
Quit();
return;
}
brillo::ErrorPtr error;
call_proxy_->ResolveNetworkProxy(source_url_,
signal_interface_,
signal_name_,
&error);
if (error) {
LOG(ERROR) << "Call to ResolveNetworkProxy failed: "
<< error->GetMessage();
timeout_callback_.Cancel();
Quit();
}
}
// Handle incoming ProxyResolved signal.
void HandleProxyResolvedSignal(const std::string& source_url,
const std::string& proxy_info,
const std::string& error_message) {
timeout_callback_.Cancel();
proxies_ = ParseProxyString(proxy_info);
LOG(INFO) << "Found proxies via browser signal: "
<< base::JoinString(proxies_, "x");
Quit();
}
int OnInit() override {
int return_code = brillo::DBusDaemon::OnInit();
if (return_code != EX_OK)
return return_code;
// Initialize D-Bus proxies.
call_proxy_.reset(
new org::chromium::LibCrosServiceInterfaceProxy(bus_,
kLibCrosServiceName));
signal_proxy_.reset(
new org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy(
bus_,
kLibCrosServiceName));
// Set up the D-Bus signal handler.
// TODO(crbug.com/446115): Update ResolveNetworkProxy call to use an
// asynchronous return value rather than a return signal.
signal_proxy_->RegisterProxyResolvedSignalHandler(
base::Bind(&ProxyResolver::HandleProxyResolvedSignal,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&ProxyResolver::HandleDBusSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
return EX_OK;
}
private:
unique_ptr<org::chromium::LibCrosServiceInterfaceProxy> call_proxy_;
unique_ptr<org::chromium::CrashReporterLibcrosProxyResolvedInterfaceProxy>
signal_proxy_;
const std::string source_url_;
const std::string signal_interface_;
const std::string signal_name_;
base::TimeDelta timeout_;
std::vector<std::string> proxies_;
base::WeakPtrFactory<ProxyResolver> weak_ptr_factory_;
base::CancelableClosure timeout_callback_;
DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
};
static bool ShowBrowserProxies(std::string url, base::TimeDelta timeout) {
// Initialize and run the proxy resolver to watch for signals.
ProxyResolver resolver(url,
kLibCrosProxyResolvedSignalInterface,
kLibCrosProxyResolvedName,
timeout);
resolver.Run();
std::vector<std::string> proxies = resolver.proxies();
// If proxies is empty, then the timeout was reached waiting for the proxy
// resolved signal. If no proxies are defined, proxies will be populated
// with "direct://".
if (proxies.empty())
return false;
for (const auto& proxy : proxies) {
printf("%s\n", proxy.c_str());
}
return true;
}
} // namespace
int main(int argc, char *argv[]) {
base::CommandLine::Init(argc, argv);
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
if (cl->HasSwitch(kHelp)) {
LOG(INFO) << kHelpMessage;
return 0;
}
bool quiet = cl->HasSwitch(kQuiet);
bool verbose = cl->HasSwitch(kVerbose);
int timeout = kTimeoutDefaultSeconds;
std::string str_timeout = cl->GetSwitchValueASCII(kTimeout);
if (!str_timeout.empty() && !base::StringToInt(str_timeout, &timeout)) {
LOG(ERROR) << "Invalid timeout value: " << str_timeout;
return 1;
}
// Default to logging to syslog.
int init_flags = brillo::kLogToSyslog;
// Log to stderr if a TTY (and "-quiet" wasn't passed), or if "-verbose"
// was passed.
if ((!quiet && isatty(STDERR_FILENO)) || verbose)
init_flags |= brillo::kLogToStderr;
brillo::InitLog(init_flags);
std::string url;
base::CommandLine::StringVector urls = cl->GetArgs();
if (!urls.empty()) {
url = urls[0];
LOG(INFO) << "Resolving proxies for URL: " << url;
} else {
LOG(INFO) << "Resolving proxies without URL";
}
if (!ShowBrowserProxies(url, base::TimeDelta::FromSeconds(timeout))) {
LOG(ERROR) << "Error resolving proxies via the browser";
LOG(INFO) << "Assuming direct proxy";
printf("%s\n", kNoProxy);
}
return 0;
}

View File

@ -1,80 +0,0 @@
#!/system/bin/sh
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Run tasks periodically.
# Usage: $0 <delay_seconds> <timeout_seconds> <task_name> <task_binary>
#
# Executes task <task_name> by running <task_binary> every <delay_seconds>.
set -e -u
SCRIPT_NAME="$(basename "$0")"
CHECK_DELAY=300 # Check every 5 minutes.
KILL_DELAY=10 # How long to let the job clean up after a timeout.
# Let the unittests override.
: ${SPOOL_DIR:=/data/misc/crash_reporter/spool/cron-lite}
loginfo() {
log -p i -t "${SCRIPT_NAME}" "$@"
}
trap "loginfo 'exiting'" EXIT
check_and_fix_spool_paths() {
# Avoid weird spool paths if possible.
rm -f "$(dirname "${SPOOL_DIR}")" "${SPOOL_DIR}" 2>/dev/null || :
mkdir -p "${SPOOL_DIR}"
if [ ! -O "${SPOOL_DIR}" -o ! -d "${SPOOL_DIR}" ]; then
loginfo "Spool directory is damaged. Aborting!"
exit 1
fi
}
main() {
local delay="$1"
local timeout="$2"
local name="$3"
local spool_file="${SPOOL_DIR}/${name}"
shift 3
[ -z "${delay}" ] && exit 1
[ -z "${timeout}" ] && exit 1
[ -z "${name}" ] && exit 1
[ $# -eq 0 ] && exit 1
check_and_fix_spool_paths
while true; do
# Allow the sleep to be killed manually without terminating the handler.
# Send stderr to /dev/null to suppress the shell's "Terminated" message.
sleep $(( CHECK_DELAY + KILL_DELAY )) 2>/dev/null || true
[ ! -e "${spool_file}" ] && touch "${spool_file}"
local last_rotation="$(stat -c "%Y" "${spool_file}" 2>/dev/null || echo 0)"
local now="$(date +%s)"
local time_diff=$((now - last_rotation))
if [ ${time_diff} -gt ${delay} ]; then
rm "${spool_file}" || true
touch "${spool_file}"
loginfo "${name}: running $*"
timeout -k ${KILL_DELAY} ${timeout} "$@" || true
loginfo "${name}: job completed"
fi
done
}
main "$@"

View File

@ -1,23 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <brillo/test_helpers.h>
#include <gtest/gtest.h>
int main(int argc, char** argv) {
SetUpTests(&argc, argv, true);
return RUN_ALL_TESTS();
}

View File

@ -1,244 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "udev_collector.h"
#include <map>
#include <utility>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/process.h>
using base::FilePath;
namespace {
const char kCollectUdevSignature[] = "crash_reporter-udev-collection";
const char kGzipPath[] = "/bin/gzip";
const char kUdevExecName[] = "udev";
const char kUdevSignatureKey[] = "sig";
const char kUdevSubsystemDevCoredump[] = "devcoredump";
const char kDefaultDevCoredumpDirectory[] = "/sys/class/devcoredump";
const char kDevCoredumpFilePrefixFormat[] = "devcoredump_%s";
} // namespace
UdevCollector::UdevCollector()
: dev_coredump_directory_(kDefaultDevCoredumpDirectory) {}
UdevCollector::~UdevCollector() {}
bool UdevCollector::HandleCrash(const std::string &udev_event) {
if (IsDeveloperImage()) {
LOG(INFO) << "developer image - collect udev crash info.";
} else if (is_feedback_allowed_function_()) {
LOG(INFO) << "Consent given - collect udev crash info.";
} else {
LOG(INFO) << "Ignoring - Non-developer image and no consent given.";
return false;
}
// Process the udev event string.
// First get all the key-value pairs.
std::vector<std::pair<std::string, std::string>> udev_event_keyval;
base::SplitStringIntoKeyValuePairs(udev_event, '=', ':', &udev_event_keyval);
std::vector<std::pair<std::string, std::string>>::const_iterator iter;
std::map<std::string, std::string> udev_event_map;
for (iter = udev_event_keyval.begin();
iter != udev_event_keyval.end();
++iter) {
udev_event_map[iter->first] = iter->second;
}
// Make sure the crash directory exists, or create it if it doesn't.
FilePath crash_directory;
if (!GetCreatedCrashDirectoryByEuid(0, &crash_directory, nullptr)) {
LOG(ERROR) << "Could not get crash directory.";
return false;
}
if (udev_event_map["SUBSYSTEM"] == kUdevSubsystemDevCoredump) {
int instance_number;
if (!base::StringToInt(udev_event_map["KERNEL_NUMBER"], &instance_number)) {
LOG(ERROR) << "Invalid kernel number: "
<< udev_event_map["KERNEL_NUMBER"];
return false;
}
return ProcessDevCoredump(crash_directory, instance_number);
}
return ProcessUdevCrashLogs(crash_directory,
udev_event_map["ACTION"],
udev_event_map["KERNEL"],
udev_event_map["SUBSYSTEM"]);
}
bool UdevCollector::ProcessUdevCrashLogs(const FilePath& crash_directory,
const std::string& action,
const std::string& kernel,
const std::string& subsystem) {
// Construct the basename string for crash_reporter_logs.conf:
// "crash_reporter-udev-collection-[action]-[name]-[subsystem]"
// If a udev field is not provided, "" is used in its place, e.g.:
// "crash_reporter-udev-collection-[action]--[subsystem]"
// Hence, "" is used as a wildcard name string.
// TODO(sque, crosbug.com/32238): Implement wildcard checking.
std::string basename = action + "-" + kernel + "-" + subsystem;
std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
basename;
// Create the destination path.
std::string log_file_name =
FormatDumpBasename(basename, time(nullptr), 0);
FilePath crash_path = GetCrashPath(crash_directory, log_file_name, "log");
// Handle the crash.
bool result = GetLogContents(log_config_path_, udev_log_name, crash_path);
if (!result) {
LOG(ERROR) << "Error reading udev log info " << udev_log_name;
return false;
}
// Compress the output using gzip.
brillo::ProcessImpl gzip_process;
gzip_process.AddArg(kGzipPath);
gzip_process.AddArg(crash_path.value());
int process_result = gzip_process.Run();
FilePath crash_path_zipped = FilePath(crash_path.value() + ".gz");
// If the zip file was not created, use the uncompressed file.
if (process_result != 0 || !base::PathExists(crash_path_zipped))
LOG(ERROR) << "Could not create zip file " << crash_path_zipped.value();
else
crash_path = crash_path_zipped;
std::string exec_name = std::string(kUdevExecName) + "-" + subsystem;
AddCrashMetaData(kUdevSignatureKey, udev_log_name);
WriteCrashMetaData(GetCrashPath(crash_directory, log_file_name, "meta"),
exec_name, crash_path.value());
return true;
}
bool UdevCollector::ProcessDevCoredump(const FilePath& crash_directory,
int instance_number) {
FilePath coredump_path =
FilePath(base::StringPrintf("%s/devcd%d/data",
dev_coredump_directory_.c_str(),
instance_number));
if (!base::PathExists(coredump_path)) {
LOG(ERROR) << "Device coredump file " << coredump_path.value()
<< " does not exist";
return false;
}
// Add coredump file to the crash directory.
if (!AppendDevCoredump(crash_directory, coredump_path, instance_number)) {
ClearDevCoredump(coredump_path);
return false;
}
// Clear the coredump data to allow generation of future device coredumps
// without having to wait for the 5-minutes timeout.
return ClearDevCoredump(coredump_path);
}
bool UdevCollector::AppendDevCoredump(const FilePath& crash_directory,
const FilePath& coredump_path,
int instance_number) {
// Retrieve the driver name of the failing device.
std::string driver_name = GetFailingDeviceDriverName(instance_number);
if (driver_name.empty()) {
LOG(ERROR) << "Failed to obtain driver name for instance: "
<< instance_number;
return false;
}
std::string coredump_prefix =
base::StringPrintf(kDevCoredumpFilePrefixFormat, driver_name.c_str());
std::string dump_basename = FormatDumpBasename(coredump_prefix,
time(nullptr),
instance_number);
FilePath core_path = GetCrashPath(crash_directory, dump_basename, "devcore");
FilePath log_path = GetCrashPath(crash_directory, dump_basename, "log");
FilePath meta_path = GetCrashPath(crash_directory, dump_basename, "meta");
// Collect coredump data.
if (!base::CopyFile(coredump_path, core_path)) {
LOG(ERROR) << "Failed to copy device coredumpm file from "
<< coredump_path.value() << " to " << core_path.value();
return false;
}
// Collect additional logs if one is specified in the config file.
std::string udev_log_name = std::string(kCollectUdevSignature) + '-' +
kUdevSubsystemDevCoredump + '-' + driver_name;
bool result = GetLogContents(log_config_path_, udev_log_name, log_path);
if (result) {
AddCrashMetaUploadFile("logs", log_path.value());
}
WriteCrashMetaData(meta_path, coredump_prefix, core_path.value());
return true;
}
bool UdevCollector::ClearDevCoredump(const FilePath& coredump_path) {
if (!base::WriteFile(coredump_path, "0", 1)) {
LOG(ERROR) << "Failed to delete the coredump data file "
<< coredump_path.value();
return false;
}
return true;
}
std::string UdevCollector::GetFailingDeviceDriverName(int instance_number) {
FilePath failing_uevent_path =
FilePath(base::StringPrintf("%s/devcd%d/failing_device/uevent",
dev_coredump_directory_.c_str(),
instance_number));
if (!base::PathExists(failing_uevent_path)) {
LOG(ERROR) << "Failing uevent path " << failing_uevent_path.value()
<< " does not exist";
return "";
}
std::string uevent_content;
if (!base::ReadFileToString(failing_uevent_path, &uevent_content)) {
LOG(ERROR) << "Failed to read uevent file " << failing_uevent_path.value();
return "";
}
// Parse uevent file contents as key-value pairs.
std::vector<std::pair<std::string, std::string>> uevent_keyval;
base::SplitStringIntoKeyValuePairs(uevent_content, '=', '\n', &uevent_keyval);
std::vector<std::pair<std::string, std::string>>::const_iterator iter;
for (iter = uevent_keyval.begin();
iter != uevent_keyval.end();
++iter) {
if (iter->first == "DRIVER") {
return iter->second;
}
}
return "";
}

View File

@ -1,76 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_UDEV_COLLECTOR_H_
#define CRASH_REPORTER_UDEV_COLLECTOR_H_
#include <string>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "crash_collector.h"
// Udev crash collector.
class UdevCollector : public CrashCollector {
public:
UdevCollector();
~UdevCollector() override;
// The udev event string should be formatted as follows:
// "ACTION=[action]:KERNEL=[name]:SUBSYSTEM=[subsystem]"
// The values don't have to be in any particular order. One or more of them
// could be omitted, in which case it would be treated as a wildcard (*).
bool HandleCrash(const std::string& udev_event);
protected:
std::string dev_coredump_directory_;
private:
friend class UdevCollectorTest;
// Process udev crash logs, collecting log files according to the config
// file (crash_reporter_logs.conf).
bool ProcessUdevCrashLogs(const base::FilePath& crash_directory,
const std::string& action,
const std::string& kernel,
const std::string& subsystem);
// Process device coredump, collecting device coredump file.
// |instance_number| is the kernel number of the virtual device for the device
// coredump instance.
bool ProcessDevCoredump(const base::FilePath& crash_directory,
int instance_number);
// Copy device coredump file to crash directory, and perform necessary
// coredump file management.
bool AppendDevCoredump(const base::FilePath& crash_directory,
const base::FilePath& coredump_path,
int instance_number);
// Clear the device coredump file by performing a dummy write to it.
bool ClearDevCoredump(const base::FilePath& coredump_path);
// Return the driver name of the device that generates the coredump.
std::string GetFailingDeviceDriverName(int instance_number);
// Mutator for unit testing.
void set_log_config_path(const std::string& path) {
log_config_path_ = base::FilePath(path);
}
DISALLOW_COPY_AND_ASSIGN(UdevCollector);
};
#endif // CRASH_REPORTER_UDEV_COLLECTOR_H_

View File

@ -1,183 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/stringprintf.h>
#include <brillo/syslog_logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "udev_collector.h"
using base::FilePath;
namespace {
// Dummy log config file name.
const char kLogConfigFileName[] = "log_config_file";
// Dummy directory for storing device coredumps.
const char kDevCoredumpDirectory[] = "devcoredump";
// A bunch of random rules to put into the dummy log config file.
const char kLogConfigFileContents[] =
"crash_reporter-udev-collection-change-card0-drm=echo change card0 drm\n"
"crash_reporter-udev-collection-add-state0-cpu=echo change state0 cpu\n"
"crash_reporter-udev-collection-devcoredump-iwlwifi=echo devcoredump\n"
"cros_installer=echo not for udev";
const char kCrashLogFilePattern[] = "*.log.gz";
const char kDevCoredumpFilePattern[] = "*.devcore";
// Dummy content for device coredump data file.
const char kDevCoredumpDataContents[] = "coredump";
// Content for failing device's uevent file.
const char kFailingDeviceUeventContents[] = "DRIVER=iwlwifi\n";
void CountCrash() {}
bool s_consent_given = true;
bool IsMetrics() {
return s_consent_given;
}
// Returns the number of files found in the given path that matches the
// specified file name pattern.
int GetNumFiles(const FilePath& path, const std::string& file_pattern) {
base::FileEnumerator enumerator(path, false, base::FileEnumerator::FILES,
file_pattern);
int num_files = 0;
for (FilePath file_path = enumerator.Next();
!file_path.value().empty();
file_path = enumerator.Next()) {
num_files++;
}
return num_files;
}
} // namespace
class UdevCollectorMock : public UdevCollector {
public:
MOCK_METHOD0(SetUpDBus, void());
};
class UdevCollectorTest : public ::testing::Test {
protected:
base::ScopedTempDir temp_dir_generator_;
void HandleCrash(const std::string &udev_event) {
collector_.HandleCrash(udev_event);
}
void GenerateDevCoredump(const std::string& device_name) {
// Generate coredump data file.
ASSERT_TRUE(CreateDirectory(
FilePath(base::StringPrintf("%s/%s",
collector_.dev_coredump_directory_.c_str(),
device_name.c_str()))));
FilePath data_path =
FilePath(base::StringPrintf("%s/%s/data",
collector_.dev_coredump_directory_.c_str(),
device_name.c_str()));
ASSERT_EQ(strlen(kDevCoredumpDataContents),
base::WriteFile(data_path,
kDevCoredumpDataContents,
strlen(kDevCoredumpDataContents)));
// Generate uevent file for failing device.
ASSERT_TRUE(CreateDirectory(
FilePath(base::StringPrintf("%s/%s/failing_device",
collector_.dev_coredump_directory_.c_str(),
device_name.c_str()))));
FilePath uevent_path =
FilePath(base::StringPrintf("%s/%s/failing_device/uevent",
collector_.dev_coredump_directory_.c_str(),
device_name.c_str()));
ASSERT_EQ(strlen(kFailingDeviceUeventContents),
base::WriteFile(uevent_path,
kFailingDeviceUeventContents,
strlen(kFailingDeviceUeventContents)));
}
private:
void SetUp() override {
s_consent_given = true;
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
collector_.Initialize(CountCrash, IsMetrics);
ASSERT_TRUE(temp_dir_generator_.CreateUniqueTempDir());
FilePath log_config_path =
temp_dir_generator_.path().Append(kLogConfigFileName);
collector_.log_config_path_ = log_config_path;
collector_.ForceCrashDirectory(temp_dir_generator_.path());
FilePath dev_coredump_path =
temp_dir_generator_.path().Append(kDevCoredumpDirectory);
collector_.dev_coredump_directory_ = dev_coredump_path.value();
// Write to a dummy log config file.
ASSERT_EQ(strlen(kLogConfigFileContents),
base::WriteFile(log_config_path,
kLogConfigFileContents,
strlen(kLogConfigFileContents)));
brillo::ClearLog();
}
UdevCollectorMock collector_;
};
TEST_F(UdevCollectorTest, TestNoConsent) {
s_consent_given = false;
HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
}
TEST_F(UdevCollectorTest, TestNoMatch) {
// No rule should match this.
HandleCrash("ACTION=change:KERNEL=foo:SUBSYSTEM=bar");
EXPECT_EQ(0, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
}
TEST_F(UdevCollectorTest, TestMatches) {
// Try multiple udev events in sequence. The number of log files generated
// should increase.
HandleCrash("ACTION=change:KERNEL=card0:SUBSYSTEM=drm");
EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
HandleCrash("ACTION=add:KERNEL=state0:SUBSYSTEM=cpu");
EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(), kCrashLogFilePattern));
}
TEST_F(UdevCollectorTest, TestDevCoredump) {
GenerateDevCoredump("devcd0");
HandleCrash("ACTION=add:KERNEL_NUMBER=0:SUBSYSTEM=devcoredump");
EXPECT_EQ(1, GetNumFiles(temp_dir_generator_.path(),
kDevCoredumpFilePattern));
GenerateDevCoredump("devcd1");
HandleCrash("ACTION=add:KERNEL_NUMBER=1:SUBSYSTEM=devcoredump");
EXPECT_EQ(2, GetNumFiles(temp_dir_generator_.path(),
kDevCoredumpFilePattern));
}
// TODO(sque, crosbug.com/32238) - test wildcard cases, multiple identical udev
// events.

View File

@ -1,93 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "unclean_shutdown_collector.h"
#include <base/files/file_util.h>
#include <base/logging.h>
static const char kUncleanShutdownFile[] =
"/var/lib/crash_reporter/pending_clean_shutdown";
// Files created by power manager used for crash reporting.
static const char kPowerdTracePath[] = "/var/lib/power_manager";
// Presence of this file indicates that the system was suspended
static const char kPowerdSuspended[] = "powerd_suspended";
using base::FilePath;
UncleanShutdownCollector::UncleanShutdownCollector()
: unclean_shutdown_file_(kUncleanShutdownFile),
powerd_trace_path_(kPowerdTracePath),
powerd_suspended_file_(powerd_trace_path_.Append(kPowerdSuspended)) {
}
UncleanShutdownCollector::~UncleanShutdownCollector() {
}
bool UncleanShutdownCollector::Enable() {
FilePath file_path(unclean_shutdown_file_);
base::CreateDirectory(file_path.DirName());
if (base::WriteFile(file_path, "", 0) != 0) {
LOG(ERROR) << "Unable to create shutdown check file";
return false;
}
return true;
}
bool UncleanShutdownCollector::DeleteUncleanShutdownFiles() {
if (!base::DeleteFile(FilePath(unclean_shutdown_file_), false)) {
LOG(ERROR) << "Failed to delete unclean shutdown file "
<< unclean_shutdown_file_;
return false;
}
// Delete power manager state file if it exists.
base::DeleteFile(powerd_suspended_file_, false);
return true;
}
bool UncleanShutdownCollector::Collect() {
FilePath unclean_file_path(unclean_shutdown_file_);
if (!base::PathExists(unclean_file_path)) {
return false;
}
LOG(WARNING) << "Last shutdown was not clean";
if (DeadBatteryCausedUncleanShutdown()) {
DeleteUncleanShutdownFiles();
return false;
}
DeleteUncleanShutdownFiles();
if (is_feedback_allowed_function_()) {
count_crash_function_();
}
return true;
}
bool UncleanShutdownCollector::Disable() {
LOG(INFO) << "Clean shutdown signalled";
return DeleteUncleanShutdownFiles();
}
bool UncleanShutdownCollector::DeadBatteryCausedUncleanShutdown() {
// Check for case of battery running out while suspended.
if (base::PathExists(powerd_suspended_file_)) {
LOG(INFO) << "Unclean shutdown occurred while suspended. Not counting "
<< "toward unclean shutdown statistic.";
return true;
}
return false;
}

View File

@ -1,62 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
#define CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_
#include <string>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "crash_collector.h"
// Unclean shutdown collector.
class UncleanShutdownCollector : public CrashCollector {
public:
UncleanShutdownCollector();
~UncleanShutdownCollector() override;
// Enable collection - signal that a boot has started.
bool Enable();
// Collect if there is was an unclean shutdown. Returns true if
// there was, false otherwise.
bool Collect();
// Disable collection - signal that the system has been shutdown cleanly.
bool Disable();
private:
friend class UncleanShutdownCollectorTest;
FRIEND_TEST(UncleanShutdownCollectorTest, EnableCannotWrite);
FRIEND_TEST(UncleanShutdownCollectorTest, CollectDeadBatterySuspended);
bool DeleteUncleanShutdownFiles();
// Check for unclean shutdown due to battery running out by analyzing powerd
// trace files.
bool DeadBatteryCausedUncleanShutdown();
const char *unclean_shutdown_file_;
base::FilePath powerd_trace_path_;
base::FilePath powerd_suspended_file_;
DISALLOW_COPY_AND_ASSIGN(UncleanShutdownCollector);
};
#endif // CRASH_REPORTER_UNCLEAN_SHUTDOWN_COLLECTOR_H_

View File

@ -1,155 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "unclean_shutdown_collector.h"
#include <unistd.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_util.h>
#include <brillo/syslog_logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using base::FilePath;
using ::brillo::FindLog;
namespace {
int s_crashes = 0;
bool s_metrics = true;
void CountCrash() {
++s_crashes;
}
bool IsMetrics() {
return s_metrics;
}
} // namespace
class UncleanShutdownCollectorMock : public UncleanShutdownCollector {
public:
MOCK_METHOD0(SetUpDBus, void());
};
class UncleanShutdownCollectorTest : public ::testing::Test {
void SetUp() {
s_crashes = 0;
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
collector_.Initialize(CountCrash,
IsMetrics);
EXPECT_TRUE(test_dir_.CreateUniqueTempDir());
test_directory_ = test_dir_.path().Append("test");
test_unclean_ = test_dir_.path().Append("test/unclean");
collector_.unclean_shutdown_file_ = test_unclean_.value().c_str();
base::DeleteFile(test_unclean_, true);
// Set up an alternate power manager state file as well
collector_.powerd_suspended_file_ =
test_dir_.path().Append("test/suspended");
brillo::ClearLog();
}
protected:
void WriteStringToFile(const FilePath &file_path,
const char *data) {
unsigned int numBytesWritten =
base::WriteFile(file_path, data, strlen(data));
ASSERT_EQ(strlen(data), numBytesWritten);
}
UncleanShutdownCollectorMock collector_;
// Temporary directory used for tests.
base::ScopedTempDir test_dir_;
FilePath test_directory_;
FilePath test_unclean_;
};
TEST_F(UncleanShutdownCollectorTest, EnableWithoutParent) {
ASSERT_TRUE(collector_.Enable());
ASSERT_TRUE(base::PathExists(test_unclean_));
}
TEST_F(UncleanShutdownCollectorTest, EnableWithParent) {
mkdir(test_directory_.value().c_str(), 0777);
ASSERT_TRUE(collector_.Enable());
ASSERT_TRUE(base::PathExists(test_unclean_));
}
TEST_F(UncleanShutdownCollectorTest, EnableCannotWrite) {
collector_.unclean_shutdown_file_ = "/bad/path";
ASSERT_FALSE(collector_.Enable());
ASSERT_TRUE(FindLog("Unable to create shutdown check file"));
}
TEST_F(UncleanShutdownCollectorTest, CollectTrue) {
ASSERT_TRUE(collector_.Enable());
ASSERT_TRUE(base::PathExists(test_unclean_));
ASSERT_TRUE(collector_.Collect());
ASSERT_FALSE(base::PathExists(test_unclean_));
ASSERT_EQ(1, s_crashes);
ASSERT_TRUE(FindLog("Last shutdown was not clean"));
}
TEST_F(UncleanShutdownCollectorTest, CollectFalse) {
ASSERT_FALSE(collector_.Collect());
ASSERT_EQ(0, s_crashes);
}
TEST_F(UncleanShutdownCollectorTest, CollectDeadBatterySuspended) {
ASSERT_TRUE(collector_.Enable());
ASSERT_TRUE(base::PathExists(test_unclean_));
base::WriteFile(collector_.powerd_suspended_file_, "", 0);
ASSERT_FALSE(collector_.Collect());
ASSERT_FALSE(base::PathExists(test_unclean_));
ASSERT_FALSE(base::PathExists(collector_.powerd_suspended_file_));
ASSERT_EQ(0, s_crashes);
ASSERT_TRUE(FindLog("Unclean shutdown occurred while suspended."));
}
TEST_F(UncleanShutdownCollectorTest, Disable) {
ASSERT_TRUE(collector_.Enable());
ASSERT_TRUE(base::PathExists(test_unclean_));
ASSERT_TRUE(collector_.Disable());
ASSERT_FALSE(base::PathExists(test_unclean_));
ASSERT_FALSE(collector_.Collect());
}
TEST_F(UncleanShutdownCollectorTest, DisableWhenNotEnabled) {
ASSERT_TRUE(collector_.Disable());
}
TEST_F(UncleanShutdownCollectorTest, CantDisable) {
mkdir(test_directory_.value().c_str(), 0700);
if (mkdir(test_unclean_.value().c_str(), 0700)) {
ASSERT_EQ(EEXIST, errno)
<< "Error while creating directory '" << test_unclean_.value()
<< "': " << strerror(errno);
}
ASSERT_EQ(0, base::WriteFile(test_unclean_.Append("foo"), "", 0))
<< "Error while creating empty file '"
<< test_unclean_.Append("foo").value() << "': " << strerror(errno);
ASSERT_FALSE(collector_.Disable());
rmdir(test_unclean_.value().c_str());
}

View File

@ -1,640 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "user_collector.h"
#include <elf.h>
#include <fcntl.h>
#include <grp.h> // For struct group.
#include <pcrecpp.h>
#include <pwd.h> // For struct passwd.
#include <stdint.h>
#include <sys/cdefs.h> // For __WORDSIZE
#include <sys/fsuid.h>
#include <sys/types.h> // For getpwuid_r, getgrnam_r, WEXITSTATUS.
#include <unistd.h> // For setgroups
#include <iostream> // For std::oct
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/process.h>
#include <brillo/syslog_logging.h>
#include <cutils/properties.h>
#include <private/android_filesystem_config.h>
static const char kCollectionErrorSignature[] =
"crash_reporter-user-collection";
static const char kCorePatternProperty[] = "crash_reporter.coredump.enabled";
static const char kCoreToMinidumpConverterPath[] = "/system/bin/core2md";
static const char kStatePrefix[] = "State:\t";
static const char kCoreTempFolder[] = "/data/misc/crash_reporter/tmp";
// Define an otherwise invalid value that represents an unknown UID and GID.
static const uid_t kUnknownUid = -1;
static const gid_t kUnknownGid = -1;
const char *UserCollector::kUserId = "Uid:\t";
const char *UserCollector::kGroupId = "Gid:\t";
using base::FilePath;
using base::StringPrintf;
UserCollector::UserCollector()
: generate_diagnostics_(false),
initialized_(false) {
}
void UserCollector::Initialize(
UserCollector::CountCrashFunction count_crash_function,
const std::string &our_path,
UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
bool generate_diagnostics,
bool core2md_failure,
bool directory_failure,
const std::string &filter_in) {
CrashCollector::Initialize(count_crash_function,
is_feedback_allowed_function);
our_path_ = our_path;
initialized_ = true;
generate_diagnostics_ = generate_diagnostics;
core2md_failure_ = core2md_failure;
directory_failure_ = directory_failure;
filter_in_ = filter_in;
gid_t groups[] = { AID_ROOT, AID_SYSTEM, AID_DBUS, AID_READPROC };
if (setgroups(arraysize(groups), groups) != 0) {
PLOG(FATAL) << "Unable to set groups to root, system, dbus, and readproc";
}
}
UserCollector::~UserCollector() {
}
std::string UserCollector::GetErrorTypeSignature(ErrorType error_type) const {
switch (error_type) {
case kErrorSystemIssue:
return "system-issue";
case kErrorReadCoreData:
return "read-core-data";
case kErrorUnusableProcFiles:
return "unusable-proc-files";
case kErrorInvalidCoreFile:
return "invalid-core-file";
case kErrorUnsupported32BitCoreFile:
return "unsupported-32bit-core-file";
case kErrorCore2MinidumpConversion:
return "core2md-conversion";
default:
return "";
}
}
bool UserCollector::SetUpInternal(bool enabled) {
CHECK(initialized_);
LOG(INFO) << (enabled ? "Enabling" : "Disabling") << " user crash handling";
property_set(kCorePatternProperty, enabled ? "1" : "0");
return true;
}
bool UserCollector::GetFirstLineWithPrefix(
const std::vector<std::string> &lines,
const char *prefix, std::string *line) {
std::vector<std::string>::const_iterator line_iterator;
for (line_iterator = lines.begin(); line_iterator != lines.end();
++line_iterator) {
if (line_iterator->find(prefix) == 0) {
*line = *line_iterator;
return true;
}
}
return false;
}
bool UserCollector::GetIdFromStatus(
const char *prefix, IdKind kind,
const std::vector<std::string> &status_lines, int *id) {
// From fs/proc/array.c:task_state(), this file contains:
// \nUid:\t<uid>\t<euid>\t<suid>\t<fsuid>\n
std::string id_line;
if (!GetFirstLineWithPrefix(status_lines, prefix, &id_line)) {
return false;
}
std::string id_substring = id_line.substr(strlen(prefix), std::string::npos);
std::vector<std::string> ids = base::SplitString(
id_substring, "\t", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (ids.size() != kIdMax || kind < 0 || kind >= kIdMax) {
return false;
}
const char *number = ids[kind].c_str();
char *end_number = nullptr;
*id = strtol(number, &end_number, 10);
if (*end_number != '\0') {
return false;
}
return true;
}
bool UserCollector::GetStateFromStatus(
const std::vector<std::string> &status_lines, std::string *state) {
std::string state_line;
if (!GetFirstLineWithPrefix(status_lines, kStatePrefix, &state_line)) {
return false;
}
*state = state_line.substr(strlen(kStatePrefix), std::string::npos);
return true;
}
void UserCollector::EnqueueCollectionErrorLog(pid_t pid,
ErrorType error_type,
const std::string &exec) {
FilePath crash_path;
LOG(INFO) << "Writing conversion problems as separate crash report.";
if (!GetCreatedCrashDirectoryByEuid(0, &crash_path, nullptr)) {
LOG(ERROR) << "Could not even get log directory; out of space?";
return;
}
AddCrashMetaData("sig", kCollectionErrorSignature);
AddCrashMetaData("error_type", GetErrorTypeSignature(error_type));
std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
std::string error_log = brillo::GetLog();
FilePath diag_log_path = GetCrashPath(crash_path, dump_basename, "diaglog");
if (GetLogContents(FilePath(log_config_path_), kCollectionErrorSignature,
diag_log_path)) {
// We load the contents of diag_log into memory and append it to
// the error log. We cannot just append to files because we need
// to always create new files to prevent attack.
std::string diag_log_contents;
base::ReadFileToString(diag_log_path, &diag_log_contents);
error_log.append(diag_log_contents);
base::DeleteFile(diag_log_path, false);
}
FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
// We must use WriteNewFile instead of base::WriteFile as we do
// not want to write with root access to a symlink that an attacker
// might have created.
if (WriteNewFile(log_path, error_log.data(), error_log.length()) < 0) {
LOG(ERROR) << "Error writing new file " << log_path.value();
return;
}
WriteCrashMetaData(meta_path, exec, log_path.value());
}
bool UserCollector::CopyOffProcFiles(pid_t pid,
const FilePath &container_dir) {
if (!base::CreateDirectory(container_dir)) {
PLOG(ERROR) << "Could not create " << container_dir.value();
return false;
}
int dir_mask = base::FILE_PERMISSION_READ_BY_USER
| base::FILE_PERMISSION_WRITE_BY_USER
| base::FILE_PERMISSION_EXECUTE_BY_USER
| base::FILE_PERMISSION_READ_BY_GROUP
| base::FILE_PERMISSION_WRITE_BY_GROUP;
if (!base::SetPosixFilePermissions(container_dir,
base::FILE_PERMISSION_MASK & dir_mask)) {
PLOG(ERROR) << "Could not set permissions for " << container_dir.value()
<< " to " << std::oct
<< (base::FILE_PERMISSION_MASK & dir_mask);
return false;
}
FilePath process_path = GetProcessPath(pid);
if (!base::PathExists(process_path)) {
LOG(ERROR) << "Path " << process_path.value() << " does not exist";
return false;
}
static const char *proc_files[] = {
"auxv",
"cmdline",
"environ",
"maps",
"status"
};
for (unsigned i = 0; i < arraysize(proc_files); ++i) {
if (!base::CopyFile(process_path.Append(proc_files[i]),
container_dir.Append(proc_files[i]))) {
LOG(ERROR) << "Could not copy " << proc_files[i] << " file";
return false;
}
}
return true;
}
bool UserCollector::ValidateProcFiles(const FilePath &container_dir) const {
// Check if the maps file is empty, which could be due to the crashed
// process being reaped by the kernel before finishing a core dump.
int64_t file_size = 0;
if (!base::GetFileSize(container_dir.Append("maps"), &file_size)) {
LOG(ERROR) << "Could not get the size of maps file";
return false;
}
if (file_size == 0) {
LOG(ERROR) << "maps file is empty";
return false;
}
return true;
}
UserCollector::ErrorType UserCollector::ValidateCoreFile(
const FilePath &core_path) const {
int fd = HANDLE_EINTR(open(core_path.value().c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "Could not open core file " << core_path.value();
return kErrorInvalidCoreFile;
}
char e_ident[EI_NIDENT];
bool read_ok = base::ReadFromFD(fd, e_ident, sizeof(e_ident));
IGNORE_EINTR(close(fd));
if (!read_ok) {
LOG(ERROR) << "Could not read header of core file";
return kErrorInvalidCoreFile;
}
if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
LOG(ERROR) << "Invalid core file";
return kErrorInvalidCoreFile;
}
#if __WORDSIZE == 64
// TODO(benchan, mkrebs): Remove this check once core2md can
// handles both 32-bit and 64-bit ELF on a 64-bit platform.
if (e_ident[EI_CLASS] == ELFCLASS32) {
LOG(ERROR) << "Conversion of 32-bit core file on 64-bit platform is "
<< "currently not supported";
return kErrorUnsupported32BitCoreFile;
}
#endif
return kErrorNone;
}
bool UserCollector::GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
FilePath *crash_file_path,
bool *out_of_capacity) {
FilePath process_path = GetProcessPath(pid);
std::string status;
if (directory_failure_) {
LOG(ERROR) << "Purposefully failing to create spool directory";
return false;
}
uid_t uid;
if (base::ReadFileToString(process_path.Append("status"), &status)) {
std::vector<std::string> status_lines = base::SplitString(
status, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
std::string process_state;
if (!GetStateFromStatus(status_lines, &process_state)) {
LOG(ERROR) << "Could not find process state in status file";
return false;
}
LOG(INFO) << "State of crashed process [" << pid << "]: " << process_state;
// Get effective UID of crashing process.
int id;
if (!GetIdFromStatus(kUserId, kIdEffective, status_lines, &id)) {
LOG(ERROR) << "Could not find euid in status file";
return false;
}
uid = id;
} else if (supplied_ruid != kUnknownUid) {
LOG(INFO) << "Using supplied UID " << supplied_ruid
<< " for crashed process [" << pid
<< "] due to error reading status file";
uid = supplied_ruid;
} else {
LOG(ERROR) << "Could not read status file and kernel did not supply UID";
LOG(INFO) << "Path " << process_path.value() << " DirectoryExists: "
<< base::DirectoryExists(process_path);
return false;
}
if (!GetCreatedCrashDirectoryByEuid(uid, crash_file_path, out_of_capacity)) {
LOG(ERROR) << "Could not create crash directory";
return false;
}
return true;
}
bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) {
// Copy off all stdin to a core file.
FilePath stdin_path("/proc/self/fd/0");
if (base::CopyFile(stdin_path, core_path)) {
return true;
}
PLOG(ERROR) << "Could not write core file";
// If the file system was full, make sure we remove any remnants.
base::DeleteFile(core_path, false);
return false;
}
bool UserCollector::RunCoreToMinidump(const FilePath &core_path,
const FilePath &procfs_directory,
const FilePath &minidump_path,
const FilePath &temp_directory) {
FilePath output_path = temp_directory.Append("output");
brillo::ProcessImpl core2md;
core2md.RedirectOutput(output_path.value());
core2md.AddArg(kCoreToMinidumpConverterPath);
core2md.AddArg(core_path.value());
core2md.AddArg(procfs_directory.value());
if (!core2md_failure_) {
core2md.AddArg(minidump_path.value());
} else {
// To test how core2md errors are propagaged, cause an error
// by forgetting a required argument.
}
int errorlevel = core2md.Run();
std::string output;
base::ReadFileToString(output_path, &output);
if (errorlevel != 0) {
LOG(ERROR) << "Problem during " << kCoreToMinidumpConverterPath
<< " [result=" << errorlevel << "]: " << output;
return false;
}
if (!base::PathExists(minidump_path)) {
LOG(ERROR) << "Minidump file " << minidump_path.value()
<< " was not created";
return false;
}
return true;
}
UserCollector::ErrorType UserCollector::ConvertCoreToMinidump(
pid_t pid,
const FilePath &container_dir,
const FilePath &core_path,
const FilePath &minidump_path) {
// If proc files are unuable, we continue to read the core file from stdin,
// but only skip the core-to-minidump conversion, so that we may still use
// the core file for debugging.
bool proc_files_usable =
CopyOffProcFiles(pid, container_dir) && ValidateProcFiles(container_dir);
// Switch back to the original UID/GID.
gid_t rgid, egid, sgid;
if (getresgid(&rgid, &egid, &sgid) != 0) {
PLOG(FATAL) << "Unable to read saved gid";
}
if (setresgid(sgid, sgid, -1) != 0) {
PLOG(FATAL) << "Unable to set real group ID back to saved gid";
} else {
if (getresgid(&rgid, &egid, &sgid) != 0) {
// If the groups cannot be read at this point, the rgid variable will
// contain the previously read group ID from before changing it. This
// will cause the chown call below to set the incorrect group for
// non-root crashes. But do not treat this as a fatal error, so that
// the rest of the collection will continue for potential manual
// collection by a developer.
PLOG(ERROR) << "Unable to read real group ID after setting it";
}
}
uid_t ruid, euid, suid;
if (getresuid(&ruid, &euid, &suid) != 0) {
PLOG(FATAL) << "Unable to read saved uid";
}
if (setresuid(suid, suid, -1) != 0) {
PLOG(FATAL) << "Unable to set real user ID back to saved uid";
} else {
if (getresuid(&ruid, &euid, &suid) != 0) {
// If the user ID cannot be read at this point, the ruid variable will
// contain the previously read user ID from before changing it. This
// will cause the chown call below to set the incorrect user for
// non-root crashes. But do not treat this as a fatal error, so that
// the rest of the collection will continue for potential manual
// collection by a developer.
PLOG(ERROR) << "Unable to read real user ID after setting it";
}
}
if (!CopyStdinToCoreFile(core_path)) {
return kErrorReadCoreData;
}
if (!proc_files_usable) {
LOG(INFO) << "Skipped converting core file to minidump due to "
<< "unusable proc files";
return kErrorUnusableProcFiles;
}
ErrorType error = ValidateCoreFile(core_path);
if (error != kErrorNone) {
return error;
}
// Chown the temp container directory back to the original user/group that
// crash_reporter is run as, so that additional files can be written to
// the temp folder.
if (chown(container_dir.value().c_str(), ruid, rgid) < 0) {
PLOG(ERROR) << "Could not set owner for " << container_dir.value();
}
if (!RunCoreToMinidump(core_path,
container_dir, // procfs directory
minidump_path,
container_dir)) { // temporary directory
return kErrorCore2MinidumpConversion;
}
LOG(INFO) << "Stored minidump to " << minidump_path.value();
return kErrorNone;
}
UserCollector::ErrorType UserCollector::ConvertAndEnqueueCrash(
pid_t pid, const std::string &exec, uid_t supplied_ruid,
bool *out_of_capacity) {
FilePath crash_path;
if (!GetCreatedCrashDirectory(pid, supplied_ruid, &crash_path,
out_of_capacity)) {
LOG(ERROR) << "Unable to find/create process-specific crash path";
return kErrorSystemIssue;
}
// Directory like /tmp/crash_reporter/1234 which contains the
// procfs entries and other temporary files used during conversion.
FilePath container_dir(StringPrintf("%s/%d", kCoreTempFolder, pid));
// Delete a pre-existing directory from crash reporter that may have
// been left around for diagnostics from a failed conversion attempt.
// If we don't, existing files can cause forking to fail.
base::DeleteFile(container_dir, true);
std::string dump_basename = FormatDumpBasename(exec, time(nullptr), pid);
FilePath core_path = GetCrashPath(crash_path, dump_basename, "core");
FilePath meta_path = GetCrashPath(crash_path, dump_basename, "meta");
FilePath minidump_path = GetCrashPath(crash_path, dump_basename, "dmp");
FilePath log_path = GetCrashPath(crash_path, dump_basename, "log");
if (GetLogContents(FilePath(log_config_path_), exec, log_path))
AddCrashMetaData("log", log_path.value());
ErrorType error_type =
ConvertCoreToMinidump(pid, container_dir, core_path, minidump_path);
if (error_type != kErrorNone) {
LOG(INFO) << "Leaving core file at " << core_path.value()
<< " due to conversion error";
return error_type;
}
// Here we commit to sending this file. We must not return false
// after this point or we will generate a log report as well as a
// crash report.
WriteCrashMetaData(meta_path,
exec,
minidump_path.value());
if (!IsDeveloperImage()) {
base::DeleteFile(core_path, false);
} else {
LOG(INFO) << "Leaving core file at " << core_path.value()
<< " due to developer image";
}
base::DeleteFile(container_dir, true);
return kErrorNone;
}
bool UserCollector::ParseCrashAttributes(const std::string &crash_attributes,
pid_t *pid, int *signal, uid_t *uid,
gid_t *gid,
std::string *kernel_supplied_name) {
pcrecpp::RE re("(\\d+):(\\d+):(\\d+):(\\d+):(.*)");
if (re.FullMatch(crash_attributes, pid, signal, uid, gid,
kernel_supplied_name))
return true;
LOG(INFO) << "Falling back to parsing crash attributes '"
<< crash_attributes << "' without UID and GID";
pcrecpp::RE re_without_uid("(\\d+):(\\d+):(.*)");
*uid = kUnknownUid;
*gid = kUnknownGid;
return re_without_uid.FullMatch(crash_attributes, pid, signal,
kernel_supplied_name);
}
bool UserCollector::ShouldDump(bool has_owner_consent,
bool is_developer,
std::string *reason) {
reason->clear();
// For developer builds, we always want to keep the crash reports unless
// we're testing the crash facilities themselves. This overrides
// feedback. Crash sending still obeys consent.
if (is_developer) {
*reason = "developer build - not testing - always dumping";
return true;
}
if (!has_owner_consent) {
*reason = "ignoring - no consent";
return false;
}
*reason = "handling";
return true;
}
bool UserCollector::HandleCrash(const std::string &crash_attributes,
const char *force_exec) {
CHECK(initialized_);
pid_t pid = 0;
int signal = 0;
uid_t supplied_ruid = kUnknownUid;
gid_t supplied_rgid = kUnknownGid;
std::string kernel_supplied_name;
if (!ParseCrashAttributes(crash_attributes, &pid, &signal, &supplied_ruid,
&supplied_rgid, &kernel_supplied_name)) {
LOG(ERROR) << "Invalid parameter: --user=" << crash_attributes;
return false;
}
// Switch to the group and user that ran the crashing binary in order to
// access their /proc files. Do not set suid/sgid, so that we can switch
// back after copying the necessary files.
if (setresgid(supplied_rgid, supplied_rgid, -1) != 0) {
PLOG(FATAL) << "Unable to set real group ID to access process files";
}
if (setresuid(supplied_ruid, supplied_ruid, -1) != 0) {
PLOG(FATAL) << "Unable to set real user ID to access process files";
}
std::string exec;
if (force_exec) {
exec.assign(force_exec);
} else if (!GetExecutableBaseNameFromPid(pid, &exec)) {
// If we cannot find the exec name, use the kernel supplied name.
// We don't always use the kernel's since it truncates the name to
// 16 characters.
exec = StringPrintf("supplied_%s", kernel_supplied_name.c_str());
}
// Allow us to test the crash reporting mechanism successfully even if
// other parts of the system crash.
if (!filter_in_.empty() &&
(filter_in_ == "none" ||
filter_in_ != exec)) {
// We use a different format message to make it more obvious in tests
// which crashes are test generated and which are real.
LOG(WARNING) << "Ignoring crash from " << exec << "[" << pid << "] while "
<< "filter_in=" << filter_in_ << ".";
return true;
}
std::string reason;
bool dump = ShouldDump(is_feedback_allowed_function_(),
IsDeveloperImage(),
&reason);
LOG(WARNING) << "Received crash notification for " << exec << "[" << pid
<< "] sig " << signal << ", user " << supplied_ruid
<< " (" << reason << ")";
if (dump) {
count_crash_function_();
if (generate_diagnostics_) {
bool out_of_capacity = false;
ErrorType error_type =
ConvertAndEnqueueCrash(pid, exec, supplied_ruid, &out_of_capacity);
if (error_type != kErrorNone) {
if (!out_of_capacity)
EnqueueCollectionErrorLog(pid, error_type, exec);
return false;
}
}
}
return true;
}

View File

@ -1,189 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CRASH_REPORTER_USER_COLLECTOR_H_
#define CRASH_REPORTER_USER_COLLECTOR_H_
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "crash_collector.h"
class SystemLogging;
// User crash collector.
class UserCollector : public CrashCollector {
public:
UserCollector();
// Initialize the user crash collector for detection of crashes,
// given a crash counting function, the path to this executable,
// metrics collection enabled oracle, and system logger facility.
// Crash detection/reporting is not enabled until Enable is called.
// |generate_diagnostics| is used to indicate whether or not to try
// to generate a minidump from crashes.
void Initialize(CountCrashFunction count_crash,
const std::string &our_path,
IsFeedbackAllowedFunction is_metrics_allowed,
bool generate_diagnostics,
bool core2md_failure,
bool directory_failure,
const std::string &filter_in);
~UserCollector() override;
// Enable collection.
bool Enable() { return SetUpInternal(true); }
// Disable collection.
bool Disable() { return SetUpInternal(false); }
// Handle a specific user crash. Returns true on success.
bool HandleCrash(const std::string &crash_attributes,
const char *force_exec);
private:
friend class UserCollectorTest;
FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPath);
FRIEND_TEST(UserCollectorTest, CopyOffProcFilesBadPid);
FRIEND_TEST(UserCollectorTest, CopyOffProcFilesOK);
FRIEND_TEST(UserCollectorTest, GetExecutableBaseNameFromPid);
FRIEND_TEST(UserCollectorTest, GetFirstLineWithPrefix);
FRIEND_TEST(UserCollectorTest, GetIdFromStatus);
FRIEND_TEST(UserCollectorTest, GetStateFromStatus);
FRIEND_TEST(UserCollectorTest, GetProcessPath);
FRIEND_TEST(UserCollectorTest, GetSymlinkTarget);
FRIEND_TEST(UserCollectorTest, GetUserInfoFromName);
FRIEND_TEST(UserCollectorTest, ParseCrashAttributes);
FRIEND_TEST(UserCollectorTest, ShouldDumpChromeOverridesDeveloperImage);
FRIEND_TEST(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent);
FRIEND_TEST(UserCollectorTest, ShouldDumpUseConsentProductionImage);
FRIEND_TEST(UserCollectorTest, ValidateProcFiles);
FRIEND_TEST(UserCollectorTest, ValidateCoreFile);
// Enumeration to pass to GetIdFromStatus. Must match the order
// that the kernel lists IDs in the status file.
enum IdKind {
kIdReal = 0, // uid and gid
kIdEffective = 1, // euid and egid
kIdSet = 2, // suid and sgid
kIdFileSystem = 3, // fsuid and fsgid
kIdMax
};
enum ErrorType {
kErrorNone,
kErrorSystemIssue,
kErrorReadCoreData,
kErrorUnusableProcFiles,
kErrorInvalidCoreFile,
kErrorUnsupported32BitCoreFile,
kErrorCore2MinidumpConversion,
};
static const int kForkProblem = 255;
// Returns an error type signature for a given |error_type| value,
// which is reported to the crash server along with the
// crash_reporter-user-collection signature.
std::string GetErrorTypeSignature(ErrorType error_type) const;
bool SetUpInternal(bool enabled);
// Returns, via |line|, the first line in |lines| that starts with |prefix|.
// Returns true if a line is found, or false otherwise.
bool GetFirstLineWithPrefix(const std::vector<std::string> &lines,
const char *prefix, std::string *line);
// Returns the identifier of |kind|, via |id|, found in |status_lines| on
// the line starting with |prefix|. |status_lines| contains the lines in
// the status file. Returns true if the identifier can be determined.
bool GetIdFromStatus(const char *prefix,
IdKind kind,
const std::vector<std::string> &status_lines,
int *id);
// Returns the process state, via |state|, found in |status_lines|, which
// contains the lines in the status file. Returns true if the process state
// can be determined.
bool GetStateFromStatus(const std::vector<std::string> &status_lines,
std::string *state);
void LogCollectionError(const std::string &error_message);
void EnqueueCollectionErrorLog(pid_t pid, ErrorType error_type,
const std::string &exec_name);
bool CopyOffProcFiles(pid_t pid, const base::FilePath &container_dir);
// Validates the proc files at |container_dir| and returns true if they
// are usable for the core-to-minidump conversion later. For instance, if
// a process is reaped by the kernel before the copying of its proc files
// takes place, some proc files like /proc/<pid>/maps may contain nothing
// and thus become unusable.
bool ValidateProcFiles(const base::FilePath &container_dir) const;
// Validates the core file at |core_path| and returns kErrorNone if
// the file contains the ELF magic bytes and an ELF class that matches the
// platform (i.e. 32-bit ELF on a 32-bit platform or 64-bit ELF on a 64-bit
// platform), which is due to the limitation in core2md. It returns an error
// type otherwise.
ErrorType ValidateCoreFile(const base::FilePath &core_path) const;
// Determines the crash directory for given pid based on pid's owner,
// and creates the directory if necessary with appropriate permissions.
// Returns true whether or not directory needed to be created, false on
// any failure.
bool GetCreatedCrashDirectory(pid_t pid, uid_t supplied_ruid,
base::FilePath *crash_file_path,
bool *out_of_capacity);
bool CopyStdinToCoreFile(const base::FilePath &core_path);
bool RunCoreToMinidump(const base::FilePath &core_path,
const base::FilePath &procfs_directory,
const base::FilePath &minidump_path,
const base::FilePath &temp_directory);
ErrorType ConvertCoreToMinidump(pid_t pid,
const base::FilePath &container_dir,
const base::FilePath &core_path,
const base::FilePath &minidump_path);
ErrorType ConvertAndEnqueueCrash(pid_t pid, const std::string &exec_name,
uid_t supplied_ruid, bool *out_of_capacity);
bool ParseCrashAttributes(const std::string &crash_attributes,
pid_t *pid, int *signal, uid_t *uid, gid_t *gid,
std::string *kernel_supplied_name);
bool ShouldDump(bool has_owner_consent,
bool is_developer,
std::string *reason);
bool generate_diagnostics_;
std::string our_path_;
bool initialized_;
bool core2md_failure_;
bool directory_failure_;
std::string filter_in_;
static const char *kUserId;
static const char *kGroupId;
DISALLOW_COPY_AND_ASSIGN(UserCollector);
};
#endif // CRASH_REPORTER_USER_COLLECTOR_H_

View File

@ -1,440 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "user_collector.h"
#include <elf.h>
#include <sys/cdefs.h> // For __WORDSIZE
#include <unistd.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_split.h>
#include <brillo/syslog_logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using base::FilePath;
using brillo::FindLog;
namespace {
int s_crashes = 0;
bool s_metrics = false;
const char kFilePath[] = "/my/path";
void CountCrash() {
++s_crashes;
}
bool IsMetrics() {
return s_metrics;
}
} // namespace
class UserCollectorMock : public UserCollector {
public:
MOCK_METHOD0(SetUpDBus, void());
};
class UserCollectorTest : public ::testing::Test {
void SetUp() {
s_crashes = 0;
EXPECT_CALL(collector_, SetUpDBus()).WillRepeatedly(testing::Return());
collector_.Initialize(CountCrash,
kFilePath,
IsMetrics,
false,
false,
false,
"");
EXPECT_TRUE(test_dir_.CreateUniqueTempDir());
mkdir(test_dir_.path().Append("test").value().c_str(), 0777);
pid_ = getpid();
brillo::ClearLog();
}
protected:
void ExpectFileEquals(const char *golden,
const FilePath &file_path) {
std::string contents;
EXPECT_TRUE(base::ReadFileToString(file_path, &contents));
EXPECT_EQ(golden, contents);
}
std::vector<std::string> SplitLines(const std::string &lines) const {
return base::SplitString(lines, "\n", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
}
UserCollectorMock collector_;
pid_t pid_;
base::ScopedTempDir test_dir_;
};
TEST_F(UserCollectorTest, ParseCrashAttributes) {
pid_t pid;
int signal;
uid_t uid;
gid_t gid;
std::string exec_name;
EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:1000:2000:foobar",
&pid, &signal, &uid, &gid, &exec_name));
EXPECT_EQ(123456, pid);
EXPECT_EQ(11, signal);
EXPECT_EQ(1000U, uid);
EXPECT_EQ(2000U, gid);
EXPECT_EQ("foobar", exec_name);
EXPECT_TRUE(collector_.ParseCrashAttributes("4321:6:barfoo",
&pid, &signal, &uid, &gid, &exec_name));
EXPECT_EQ(4321, pid);
EXPECT_EQ(6, signal);
EXPECT_EQ(-1U, uid);
EXPECT_EQ(-1U, gid);
EXPECT_EQ("barfoo", exec_name);
EXPECT_FALSE(collector_.ParseCrashAttributes("123456:11",
&pid, &signal, &uid, &gid, &exec_name));
EXPECT_TRUE(collector_.ParseCrashAttributes("123456:11:exec:extra",
&pid, &signal, &uid, &gid, &exec_name));
EXPECT_EQ("exec:extra", exec_name);
EXPECT_FALSE(collector_.ParseCrashAttributes("12345p:11:foobar",
&pid, &signal, &uid, &gid, &exec_name));
EXPECT_FALSE(collector_.ParseCrashAttributes("123456:1 :foobar",
&pid, &signal, &uid, &gid, &exec_name));
EXPECT_FALSE(collector_.ParseCrashAttributes("123456::foobar",
&pid, &signal, &uid, &gid, &exec_name));
}
TEST_F(UserCollectorTest, ShouldDumpDeveloperImageOverridesConsent) {
std::string reason;
EXPECT_TRUE(collector_.ShouldDump(false, true, &reason));
EXPECT_EQ("developer build - not testing - always dumping", reason);
// When running a crash test, behave as normal.
EXPECT_FALSE(collector_.ShouldDump(false, false, &reason));
EXPECT_EQ("ignoring - no consent", reason);
}
TEST_F(UserCollectorTest, ShouldDumpUseConsentProductionImage) {
std::string result;
EXPECT_FALSE(collector_.ShouldDump(false, false, &result));
EXPECT_EQ("ignoring - no consent", result);
EXPECT_TRUE(collector_.ShouldDump(true, false, &result));
EXPECT_EQ("handling", result);
}
TEST_F(UserCollectorTest, HandleCrashWithoutConsent) {
s_metrics = false;
collector_.HandleCrash("20:10:ignored", "foobar");
EXPECT_TRUE(FindLog(
"Received crash notification for foobar[20] sig 10"));
ASSERT_EQ(s_crashes, 0);
}
TEST_F(UserCollectorTest, HandleNonChromeCrashWithConsent) {
s_metrics = true;
collector_.HandleCrash("5:2:ignored", "chromeos-wm");
EXPECT_TRUE(FindLog(
"Received crash notification for chromeos-wm[5] sig 2"));
ASSERT_EQ(s_crashes, 1);
}
TEST_F(UserCollectorTest, GetProcessPath) {
FilePath path = collector_.GetProcessPath(100);
ASSERT_EQ("/proc/100", path.value());
}
TEST_F(UserCollectorTest, GetSymlinkTarget) {
FilePath result;
ASSERT_FALSE(collector_.GetSymlinkTarget(FilePath("/does_not_exist"),
&result));
ASSERT_TRUE(FindLog(
"Readlink failed on /does_not_exist with 2"));
std::string long_link = test_dir_.path().value();
for (int i = 0; i < 50; ++i)
long_link += "0123456789";
long_link += "/gold";
for (size_t len = 1; len <= long_link.size(); ++len) {
std::string this_link;
static const char* kLink =
test_dir_.path().Append("test/this_link").value().c_str();
this_link.assign(long_link.c_str(), len);
ASSERT_EQ(len, this_link.size());
ASSERT_EQ(0, symlink(this_link.c_str(), kLink));
ASSERT_TRUE(collector_.GetSymlinkTarget(FilePath(kLink), &result));
ASSERT_EQ(this_link, result.value());
unlink(kLink);
}
}
TEST_F(UserCollectorTest, GetExecutableBaseNameFromPid) {
std::string base_name;
EXPECT_FALSE(collector_.GetExecutableBaseNameFromPid(0, &base_name));
EXPECT_TRUE(FindLog(
"Readlink failed on /proc/0/exe with 2"));
EXPECT_TRUE(FindLog(
"GetSymlinkTarget failed - Path /proc/0 DirectoryExists: 0"));
EXPECT_TRUE(FindLog("stat /proc/0/exe failed: -1 2"));
brillo::ClearLog();
pid_t my_pid = getpid();
EXPECT_TRUE(collector_.GetExecutableBaseNameFromPid(my_pid, &base_name));
EXPECT_FALSE(FindLog("Readlink failed"));
EXPECT_EQ("crash_reporter_tests", base_name);
}
TEST_F(UserCollectorTest, GetFirstLineWithPrefix) {
std::vector<std::string> lines;
std::string line;
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
EXPECT_EQ("", line);
lines.push_back("Name:\tls");
lines.push_back("State:\tR (running)");
lines.push_back(" Foo:\t1000");
line.clear();
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "Name:", &line));
EXPECT_EQ(lines[0], line);
line.clear();
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, "State:", &line));
EXPECT_EQ(lines[1], line);
line.clear();
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Foo:", &line));
EXPECT_EQ("", line);
line.clear();
EXPECT_TRUE(collector_.GetFirstLineWithPrefix(lines, " Foo:", &line));
EXPECT_EQ(lines[2], line);
line.clear();
EXPECT_FALSE(collector_.GetFirstLineWithPrefix(lines, "Bar:", &line));
EXPECT_EQ("", line);
}
TEST_F(UserCollectorTest, GetIdFromStatus) {
int id = 1;
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdEffective,
SplitLines("nothing here"),
&id));
EXPECT_EQ(id, 1);
// Not enough parameters.
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdReal,
SplitLines("line 1\nUid:\t1\n"),
&id));
const std::vector<std::string> valid_contents =
SplitLines("\nUid:\t1\t2\t3\t4\nGid:\t5\t6\t7\t8\n");
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdReal,
valid_contents,
&id));
EXPECT_EQ(1, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdEffective,
valid_contents,
&id));
EXPECT_EQ(2, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdFileSystem,
valid_contents,
&id));
EXPECT_EQ(4, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
UserCollector::kIdEffective,
valid_contents,
&id));
EXPECT_EQ(6, id);
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kGroupId,
UserCollector::kIdSet,
valid_contents,
&id));
EXPECT_EQ(7, id);
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
UserCollector::IdKind(5),
valid_contents,
&id));
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kGroupId,
UserCollector::IdKind(-1),
valid_contents,
&id));
// Fail if junk after number
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdReal,
SplitLines("Uid:\t1f\t2\t3\t4\n"),
&id));
EXPECT_TRUE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdReal,
SplitLines("Uid:\t1\t2\t3\t4\n"),
&id));
EXPECT_EQ(1, id);
// Fail if more than 4 numbers.
EXPECT_FALSE(collector_.GetIdFromStatus(UserCollector::kUserId,
UserCollector::kIdReal,
SplitLines("Uid:\t1\t2\t3\t4\t5\n"),
&id));
}
TEST_F(UserCollectorTest, GetStateFromStatus) {
std::string state;
EXPECT_FALSE(collector_.GetStateFromStatus(SplitLines("nothing here"),
&state));
EXPECT_EQ("", state);
EXPECT_TRUE(collector_.GetStateFromStatus(SplitLines("State:\tR (running)"),
&state));
EXPECT_EQ("R (running)", state);
EXPECT_TRUE(collector_.GetStateFromStatus(
SplitLines("Name:\tls\nState:\tZ (zombie)\n"), &state));
EXPECT_EQ("Z (zombie)", state);
}
TEST_F(UserCollectorTest, GetUserInfoFromName) {
gid_t gid = 100;
uid_t uid = 100;
EXPECT_TRUE(collector_.GetUserInfoFromName("root", &uid, &gid));
EXPECT_EQ(0U, uid);
EXPECT_EQ(0U, gid);
}
TEST_F(UserCollectorTest, CopyOffProcFilesBadPath) {
// Try a path that is not writable.
ASSERT_FALSE(collector_.CopyOffProcFiles(pid_, FilePath("/bad/path")));
EXPECT_TRUE(FindLog("Could not create /bad/path"));
}
TEST_F(UserCollectorTest, CopyOffProcFilesBadPid) {
FilePath container_path(test_dir_.path().Append("test/container"));
ASSERT_FALSE(collector_.CopyOffProcFiles(0, container_path));
EXPECT_TRUE(FindLog("Path /proc/0 does not exist"));
}
TEST_F(UserCollectorTest, CopyOffProcFilesOK) {
FilePath container_path(test_dir_.path().Append("test/container"));
ASSERT_TRUE(collector_.CopyOffProcFiles(pid_, container_path));
EXPECT_FALSE(FindLog("Could not copy"));
static struct {
const char *name;
bool exists;
} expectations[] = {
{ "auxv", true },
{ "cmdline", true },
{ "environ", true },
{ "maps", true },
{ "mem", false },
{ "mounts", false },
{ "sched", false },
{ "status", true }
};
for (unsigned i = 0; i < sizeof(expectations)/sizeof(expectations[0]); ++i) {
EXPECT_EQ(expectations[i].exists,
base::PathExists(
container_path.Append(expectations[i].name)));
}
}
TEST_F(UserCollectorTest, ValidateProcFiles) {
FilePath container_dir = test_dir_.path();
// maps file not exists (i.e. GetFileSize fails)
EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
// maps file is empty
FilePath maps_file = container_dir.Append("maps");
ASSERT_EQ(0, base::WriteFile(maps_file, nullptr, 0));
ASSERT_TRUE(base::PathExists(maps_file));
EXPECT_FALSE(collector_.ValidateProcFiles(container_dir));
// maps file is not empty
const char data[] = "test data";
unsigned int numBytesWritten =
base::WriteFile(maps_file, data, sizeof(data));
ASSERT_EQ(sizeof(data), numBytesWritten);
ASSERT_TRUE(base::PathExists(maps_file));
EXPECT_TRUE(collector_.ValidateProcFiles(container_dir));
}
TEST_F(UserCollectorTest, ValidateCoreFile) {
FilePath container_dir = test_dir_.path();
FilePath core_file = container_dir.Append("core");
// Core file does not exist
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
collector_.ValidateCoreFile(core_file));
char e_ident[EI_NIDENT];
e_ident[EI_MAG0] = ELFMAG0;
e_ident[EI_MAG1] = ELFMAG1;
e_ident[EI_MAG2] = ELFMAG2;
e_ident[EI_MAG3] = ELFMAG3;
#if __WORDSIZE == 32
e_ident[EI_CLASS] = ELFCLASS32;
#elif __WORDSIZE == 64
e_ident[EI_CLASS] = ELFCLASS64;
#else
#error Unknown/unsupported value of __WORDSIZE.
#endif
// Core file has the expected header
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
EXPECT_EQ(UserCollector::kErrorNone,
collector_.ValidateCoreFile(core_file));
#if __WORDSIZE == 64
// 32-bit core file on 64-bit platform
e_ident[EI_CLASS] = ELFCLASS32;
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
EXPECT_EQ(UserCollector::kErrorUnsupported32BitCoreFile,
collector_.ValidateCoreFile(core_file));
e_ident[EI_CLASS] = ELFCLASS64;
#endif
// Invalid core files
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident) - 1));
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
collector_.ValidateCoreFile(core_file));
e_ident[EI_MAG0] = 0;
ASSERT_TRUE(base::WriteFile(core_file, e_ident, sizeof(e_ident)));
EXPECT_EQ(UserCollector::kErrorInvalidCoreFile,
collector_.ValidateCoreFile(core_file));
}

View File

@ -1,335 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This flex program reads /var/log/messages as it grows and saves kernel
* warnings to files. It keeps track of warnings it has seen (based on
* file/line only, ignoring differences in the stack trace), and reports only
* the first warning of each kind, but maintains a count of all warnings by
* using their hashes as buckets in a UMA sparse histogram. It also invokes
* the crash collector, which collects the warnings and prepares them for later
* shipment to the crash server.
*/
%option noyywrap
%{
#include <fcntl.h>
#include <inttypes.h>
#include <pwd.h>
#include <stdarg.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "metrics/c_metrics_library.h"
int WarnStart(void);
void WarnEnd(void);
void WarnInput(char *buf, yy_size_t *result, size_t max_size);
#define YY_INPUT(buf, result, max_size) WarnInput(buf, &result, max_size)
%}
/* Define a few useful regular expressions. */
D [0-9]
PREFIX .*" kernel: [ "*{D}+"."{D}+"]"
CUT_HERE {PREFIX}" ------------[ cut here".*
WARNING {PREFIX}" WARNING: at "
END_TRACE {PREFIX}" ---[ end trace".*
/* Use exclusive start conditions. */
%x PRE_WARN WARN
%%
/* The scanner itself. */
^{CUT_HERE}\n{WARNING} BEGIN(PRE_WARN);
.|\n /* ignore all other input in state 0 */
<PRE_WARN>[^ ]+.[^ ]+\n if (WarnStart()) {
/* yytext is
"file:line func+offset/offset()\n" */
BEGIN(WARN); ECHO;
} else {
BEGIN(0);
}
/* Assume the warning ends at the "end trace" line */
<WARN>^{END_TRACE}\n ECHO; BEGIN(0); WarnEnd();
<WARN>^.*\n ECHO;
%%
#define HASH_BITMAP_SIZE (1 << 15) /* size in bits */
#define HASH_BITMAP_MASK (HASH_BITMAP_SIZE - 1)
const char warn_hist_name[] = "Platform.KernelWarningHashes";
uint32_t hash_bitmap[HASH_BITMAP_SIZE / 32];
CMetricsLibrary metrics_library;
const char *prog_name; /* the name of this program */
int yyin_fd; /* instead of FILE *yyin to avoid buffering */
int i_fd; /* for inotify, to detect file changes */
int testing; /* 1 if running test */
int filter; /* 1 when using as filter (for development) */
int fifo; /* 1 when reading from fifo (for devel) */
int draining; /* 1 when draining renamed log file */
const char *msg_path = "/var/log/messages";
const char warn_dump_dir[] = "/var/run/kwarn";
const char *warn_dump_path = "/var/run/kwarn/warning";
const char *crash_reporter_command;
__attribute__((__format__(__printf__, 1, 2)))
static void Die(const char *format, ...) {
va_list ap;
va_start(ap, format);
fprintf(stderr, "%s: ", prog_name);
vfprintf(stderr, format, ap);
exit(1);
}
static void RunCrashReporter(void) {
int status = system(crash_reporter_command);
if (status != 0)
Die("%s exited with status %d\n", crash_reporter_command, status);
}
static uint32_t StringHash(const char *string) {
uint32_t hash = 0;
while (*string != '\0') {
hash = (hash << 5) + hash + *string++;
}
return hash;
}
/* We expect only a handful of different warnings per boot session, so the
* probability of a collision is very low, and statistically it won't matter
* (unless warnings with the same hash also happens in tandem, which is even
* rarer).
*/
static int HashSeen(uint32_t hash) {
int word_index = (hash & HASH_BITMAP_MASK) / 32;
int bit_index = (hash & HASH_BITMAP_MASK) % 32;
return hash_bitmap[word_index] & 1 << bit_index;
}
static void SetHashSeen(uint32_t hash) {
int word_index = (hash & HASH_BITMAP_MASK) / 32;
int bit_index = (hash & HASH_BITMAP_MASK) % 32;
hash_bitmap[word_index] |= 1 << bit_index;
}
#pragma GCC diagnostic ignored "-Wwrite-strings"
int WarnStart(void) {
uint32_t hash;
char *spacep;
if (filter)
return 1;
hash = StringHash(yytext);
if (!(testing || fifo || filter)) {
CMetricsLibrarySendSparseToUMA(metrics_library, warn_hist_name, (int) hash);
}
if (HashSeen(hash))
return 0;
SetHashSeen(hash);
yyout = fopen(warn_dump_path, "w");
if (yyout == NULL)
Die("fopen %s failed: %s\n", warn_dump_path, strerror(errno));
spacep = strchr(yytext, ' ');
if (spacep == NULL || spacep[1] == '\0')
spacep = "unknown-function";
fprintf(yyout, "%08x-%s\n", hash, spacep + 1);
return 1;
}
void WarnEnd(void) {
if (filter)
return;
fclose(yyout);
yyout = stdout; /* for debugging */
RunCrashReporter();
}
static void WarnOpenInput(const char *path) {
yyin_fd = open(path, O_RDONLY);
if (yyin_fd < 0)
Die("could not open %s: %s\n", path, strerror(errno));
if (!fifo) {
/* Go directly to the end of the file. We don't want to parse the same
* warnings multiple times on reboot/restart. We might miss some
* warnings, but so be it---it's too hard to keep track reliably of the
* last parsed position in the syslog.
*/
if (lseek(yyin_fd, 0, SEEK_END) < 0)
Die("could not lseek %s: %s\n", path, strerror(errno));
/* Set up notification of file growth and rename. */
i_fd = inotify_init();
if (i_fd < 0)
Die("inotify_init: %s\n", strerror(errno));
if (inotify_add_watch(i_fd, path, IN_MODIFY | IN_MOVE_SELF) < 0)
Die("inotify_add_watch: %s\n", strerror(errno));
}
}
/* We replace the default YY_INPUT() for the following reasons:
*
* 1. We want to read data as soon as it becomes available, but the default
* YY_INPUT() uses buffered I/O.
*
* 2. We want to block on end of input and wait for the file to grow.
*
* 3. We want to detect log rotation, and reopen the input file as needed.
*/
void WarnInput(char *buf, yy_size_t *result, size_t max_size) {
while (1) {
ssize_t ret = read(yyin_fd, buf, max_size);
if (ret < 0)
Die("read: %s", strerror(errno));
*result = ret;
if (*result > 0 || fifo || filter)
return;
if (draining) {
/* Assume we're done with this log, and move to next
* log. Rsyslogd may keep writing to the old log file
* for a while, but we don't care since we don't have
* to be exact.
*/
close(yyin_fd);
if (YYSTATE == WARN) {
/* Be conservative in case we lose the warn
* terminator during the switch---or we may
* collect personally identifiable information.
*/
WarnEnd();
}
BEGIN(0); /* see above comment */
sleep(1); /* avoid race with log rotator */
WarnOpenInput(msg_path);
draining = 0;
continue;
}
/* Nothing left to read, so we must wait. */
struct inotify_event event;
while (1) {
int n = read(i_fd, &event, sizeof(event));
if (n <= 0) {
if (errno == EINTR)
continue;
else
Die("inotify: %s\n", strerror(errno));
} else
break;
}
if (event.mask & IN_MOVE_SELF) {
/* The file has been renamed. Before switching
* to the new one, we process any remaining
* content of this file.
*/
draining = 1;
}
}
}
int main(int argc, char **argv) {
int result;
struct passwd *user;
prog_name = argv[0];
if (argc == 2 && strcmp(argv[1], "--test") == 0)
testing = 1;
else if (argc == 2 && strcmp(argv[1], "--filter") == 0)
filter = 1;
else if (argc == 2 && strcmp(argv[1], "--fifo") == 0) {
fifo = 1;
} else if (argc != 1) {
fprintf(stderr,
"usage: %s [single-flag]\n"
"flags (for testing only):\n"
"--fifo\tinput is fifo \"fifo\", output is stdout\n"
"--filter\tinput is stdin, output is stdout\n"
"--test\trun self-test\n",
prog_name);
exit(1);
}
metrics_library = CMetricsLibraryNew();
CMetricsLibraryInit(metrics_library);
crash_reporter_command = testing ?
"./warn_collector_test_reporter.sh" :
"/sbin/crash_reporter --kernel_warning";
/* When filtering with --filter (for development) use stdin for input.
* Otherwise read input from a file or a fifo.
*/
yyin_fd = fileno(stdin);
if (testing) {
msg_path = "messages";
warn_dump_path = "warning";
}
if (fifo) {
msg_path = "fifo";
}
if (!filter) {
WarnOpenInput(msg_path);
}
/* Create directory for dump file. Still need to be root here. */
unlink(warn_dump_path);
if (!testing && !fifo && !filter) {
rmdir(warn_dump_dir);
result = mkdir(warn_dump_dir, 0755);
if (result < 0)
Die("could not create %s: %s\n",
warn_dump_dir, strerror(errno));
}
if (0) {
/* TODO(semenzato): put this back in once we decide it's safe
* to make /var/spool/crash rwxrwxrwx root, or use a different
* owner and setuid for the crash reporter as well.
*/
/* Get low privilege uid, gid. */
user = getpwnam("chronos");
if (user == NULL)
Die("getpwnam failed\n");
/* Change dump directory ownership. */
if (chown(warn_dump_dir, user->pw_uid, user->pw_gid) < 0)
Die("chown: %s\n", strerror(errno));
/* Drop privileges. */
if (setuid(user->pw_uid) < 0) {
Die("setuid: %s\n", strerror(errno));
}
}
/* Go! */
return yylex();
}
/* Flex should really know not to generate these functions.
*/
void UnusedFunctionWarningSuppressor(void) {
yyunput(0, 0);
}

View File

@ -1,25 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Test driver for the warn_collector daemon.
*/
#include <stdlib.h>
int main(int ac, char **av) {
int status = system("exec \"${SRC}\"/warn_collector_test.sh");
return status < 0 ? EXIT_FAILURE : WEXITSTATUS(status);
}

View File

@ -1,90 +0,0 @@
#! /bin/bash
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Test for warn_collector. Run the warn collector in the background, emulate
# the kernel by appending lines to the log file "messages", and observe the log
# of the (fake) crash reporter each time is run by the warn collector daemon.
set -e
fail() {
printf '[ FAIL ] %b\n' "$*"
exit 1
}
if [[ -z ${SYSROOT} ]]; then
fail "SYSROOT must be set for this test to work"
fi
: ${OUT:=${PWD}}
cd "${OUT}"
PATH=${OUT}:${PATH}
TESTLOG="${OUT}/warn-test-log"
echo "Testing: $(which warn_collector)"
cleanup() {
# Kill daemon (if started) on exit
kill %
}
check_log() {
local n_expected=$1
if [[ ! -f ${TESTLOG} ]]; then
fail "${TESTLOG} was not created"
fi
if [[ $(wc -l < "${TESTLOG}") -ne ${n_expected} ]]; then
fail "expected ${n_expected} lines in ${TESTLOG}, found this instead:
$(<"${TESTLOG}")"
fi
if egrep -qv '^[0-9a-f]{8}' "${TESTLOG}"; then
fail "found bad lines in ${TESTLOG}:
$(<"${TESTLOG}")"
fi
}
rm -f "${TESTLOG}"
cp "${SRC}/warn_collector_test_reporter.sh" .
cp "${SRC}/TEST_WARNING" .
cp TEST_WARNING messages
# Start the collector daemon. With the --test option, the daemon reads input
# from ./messages, writes the warning into ./warning, and invokes
# ./warn_collector_test_reporter.sh to report the warning.
warn_collector --test &
trap cleanup EXIT
# After a while, check that the first warning has been collected.
sleep 1
check_log 1
# Add the same warning to messages, verify that it is NOT collected
cat TEST_WARNING >> messages
sleep 1
check_log 1
# Add a slightly different warning to messages, check that it is collected.
sed s/intel_dp.c/intel_xx.c/ < TEST_WARNING >> messages
sleep 1
check_log 2
# Emulate log rotation, add a warning, and check.
mv messages messages.1
sed s/intel_dp.c/intel_xy.c/ < TEST_WARNING > messages
sleep 2
check_log 3
# Success!
exit 0

View File

@ -1,27 +0,0 @@
#! /bin/sh
# Copyright (C) 2013 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Replacement for the crash reporter, for testing. Log the first line of the
# "warning" file, which by convention contains the warning hash, and remove the
# file.
set -e
exec 1>> warn-test-log
exec 2>> warn-test-log
head -1 warning
rm warning

View File

@ -1 +0,0 @@
../../../build/tools/brillo-clang-format

View File

@ -1,226 +0,0 @@
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH := $(call my-dir)
metrics_cpp_extension := .cc
libmetrics_sources := \
c_metrics_library.cc \
metrics_library.cc \
timer.cc
metrics_client_sources := \
metrics_client.cc
metrics_collector_common := \
collectors/averaged_statistics_collector.cc \
collectors/cpu_usage_collector.cc \
collectors/disk_usage_collector.cc \
metrics_collector.cc \
metrics_collector_service_impl.cc \
persistent_integer.cc
metricsd_common := \
persistent_integer.cc \
uploader/bn_metricsd_impl.cc \
uploader/crash_counters.cc \
uploader/metrics_hashes.cc \
uploader/metrics_log_base.cc \
uploader/metrics_log.cc \
uploader/metricsd_service_runner.cc \
uploader/sender_http.cc \
uploader/system_profile_cache.cc \
uploader/upload_service.cc
metrics_collector_tests_sources := \
collectors/averaged_statistics_collector_test.cc \
collectors/cpu_usage_collector_test.cc \
metrics_collector_test.cc \
metrics_library_test.cc \
persistent_integer_test.cc \
timer_test.cc
metricsd_tests_sources := \
uploader/metrics_hashes_unittest.cc \
uploader/metrics_log_base_unittest.cc \
uploader/mock/sender_mock.cc \
uploader/upload_service_test.cc
metrics_CFLAGS := -Wall \
-Wno-char-subscripts \
-Wno-missing-field-initializers \
-Wno-unused-parameter \
-Werror \
-fvisibility=default
metrics_CPPFLAGS := -Wno-non-virtual-dtor \
-Wno-sign-promo \
-Wno-strict-aliasing \
-fvisibility=default
metrics_includes := external/gtest/include \
$(LOCAL_PATH)/include
libmetrics_shared_libraries := libchrome libbinder libbrillo libutils
metrics_collector_shared_libraries := $(libmetrics_shared_libraries) \
libbrillo-binder \
libbrillo-http \
libmetrics \
librootdev \
libweaved
metrics_collector_static_libraries := libmetricscollectorservice
metricsd_shared_libraries := \
libbinder \
libbrillo \
libbrillo-binder \
libbrillo-http \
libchrome \
libprotobuf-cpp-lite \
libupdate_engine_client \
libutils
# Static proxy library for the metricsd binder interface.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metricsd_binder_proxy
LOCAL_SHARED_LIBRARIES := libbinder libutils
LOCAL_SRC_FILES := aidl/android/brillo/metrics/IMetricsd.aidl
include $(BUILD_STATIC_LIBRARY)
# Static library for the metrics_collector binder interface.
# ==========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libmetricscollectorservice
LOCAL_CLANG := true
LOCAL_SHARED_LIBRARIES := libbinder libbrillo-binder libchrome libutils
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := \
aidl/android/brillo/metrics/IMetricsCollectorService.aidl \
metrics_collector_service_client.cc
include $(BUILD_STATIC_LIBRARY)
# Shared library for metrics.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := libmetrics
LOCAL_C_INCLUDES := $(metrics_includes)
LOCAL_CFLAGS := $(metrics_CFLAGS)
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries)
LOCAL_SRC_FILES := $(libmetrics_sources)
LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
include $(BUILD_SHARED_LIBRARY)
# CLI client for metrics.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metrics_client
LOCAL_C_INCLUDES := $(metrics_includes)
LOCAL_CFLAGS := $(metrics_CFLAGS)
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries) \
libmetrics
LOCAL_SRC_FILES := $(metrics_client_sources)
LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
include $(BUILD_EXECUTABLE)
# Protobuf library for metricsd.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metricsd_protos
LOCAL_MODULE_CLASS := STATIC_LIBRARIES
generated_sources_dir := $(call local-generated-sources-dir)
LOCAL_EXPORT_C_INCLUDE_DIRS += \
$(generated_sources_dir)/proto/system/core/metricsd
LOCAL_SRC_FILES := $(call all-proto-files-under,uploader/proto)
include $(BUILD_STATIC_LIBRARY)
# metrics_collector daemon.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metrics_collector
LOCAL_C_INCLUDES := $(metrics_includes)
LOCAL_CFLAGS := $(metrics_CFLAGS)
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
LOCAL_INIT_RC := metrics_collector.rc
LOCAL_REQUIRED_MODULES := metrics.json
LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
LOCAL_SRC_FILES := $(metrics_collector_common) \
metrics_collector_main.cc
LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy \
$(metrics_collector_static_libraries)
include $(BUILD_EXECUTABLE)
# metricsd daemon.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metricsd
LOCAL_C_INCLUDES := $(metrics_includes)
LOCAL_CFLAGS := $(metrics_CFLAGS)
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
LOCAL_INIT_RC := metricsd.rc
LOCAL_REQUIRED_MODULES := \
metrics_collector
LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
LOCAL_STATIC_LIBRARIES := metricsd_protos metricsd_binder_proxy
LOCAL_SRC_FILES := $(metricsd_common) \
metricsd_main.cc
include $(BUILD_EXECUTABLE)
# Unit tests for metricsd.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metricsd_tests
LOCAL_CFLAGS := $(metrics_CFLAGS)
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare
LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
LOCAL_SRC_FILES := $(metricsd_tests_sources) $(metricsd_common)
LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_protos metricsd_binder_proxy
include $(BUILD_NATIVE_TEST)
# Unit tests for metrics_collector.
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metrics_collector_tests
LOCAL_CFLAGS := $(metrics_CFLAGS)
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare
LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
LOCAL_SRC_FILES := $(metrics_collector_tests_sources) \
$(metrics_collector_common)
LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy \
$(metrics_collector_static_libraries)
include $(BUILD_NATIVE_TEST)
# Weave schema files
# ========================================================
include $(CLEAR_VARS)
LOCAL_MODULE := metrics.json
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits
LOCAL_SRC_FILES := etc/weaved/traits/$(LOCAL_MODULE)
include $(BUILD_PREBUILT)

View File

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

View File

@ -1,124 +0,0 @@
Metricsd
========
The metricsd daemon is used to gather metrics from the platform and application,
aggregate them and upload them periodically to a server.
The metrics will then be available in their aggregated form to the developer
for analysis.
Three components are provided to interact with `metricsd`: `libmetrics`,
`metrics_collector` and `metrics_client`.
The Metrics Library: libmetrics
-------------------------------
`libmetrics` is a small library that implements the basic C++ API for
metrics collection. All metrics collection is funneled through this library. The
easiest and recommended way for a client-side module to collect user metrics is
to link `libmetrics` and use its APIs to send metrics to `metricsd` for transport to
UMA. In order to use the library in a module, you need to do the following:
- Add a dependency on the shared library in your Android.mk file:
`LOCAL_SHARED_LIBRARIES += libmetrics`
- To access the metrics library API in the module, include the
<metrics/metrics_library.h> header file.
- The API is documented in `metrics_library.h`. Before using the API methods, a
MetricsLibrary object needs to be constructed and initialized through its
Init method.
- Samples are uploaded only if the `/data/misc/metrics/enabled` file exists.
Server Side
-----------
You will be able to see all uploaded metrics on the metrics dashboard,
accessible via the developer console.
*** note
It usually takes a day for metrics to be available on the dashboard.
***
The Metrics Client: metrics_client
----------------------------------
`metrics_client` is a simple shell command-line utility for sending histogram
samples and querying `metricsd`. It's installed under `/system/bin` on the target
platform and uses `libmetrics`.
For usage information and command-line options, run `metrics_client` on the
target platform or look for "Usage:" in `metrics_client.cc`.
The Metrics Daemon: metricsd
----------------------------
`metricsd` is the daemon that listens for metrics logging calls (via Binder),
aggregates the metrics and uploads them periodically. This daemon should start as
early as possible so that depending daemons can log at any time.
`metricsd` is made of two threads that work as follows:
* The binder thread listens for one-way Binder calls, aggregates the metrics in
memory (via `base::StatisticsRecorder`) and increments the crash counters when a
crash is reported. This thread is kept as simple as possible to ensure the
maximum throughput possible.
* The uploader thread takes care of backing up the metrics to disk periodically
(to avoid losing metrics on crashes), collecting metadata about the client
(version number, channel, etc..) and uploading the metrics periodically to the
server.
The Metrics Collector: metrics_collector
----------------------------------------
metrics_collector is a daemon that runs in the background on the target platform,
gathers health information about the system and maintains long running counters
(ex: number of crashes per week).
The recommended way to generate metrics data from a module is to link and use
libmetrics directly. However, we may not want to add a dependency on libmetrics
to some modules (ex: kernel). In this case, we can add a collector to
metrics_collector that will, for example, take measurements and report them
periodically to metricsd (this is the case for the disk utilization histogram).
FAQ
---
### What should my histogram's |min| and |max| values be set at?
You should set the values to a range that covers the vast majority of samples
that would appear in the field. Note that samples below the |min| will still
be collected in the underflow bucket and samples above the |max| will end up
in the overflow bucket. Also, the reported mean of the data will be correct
regardless of the range.
### How many buckets should I use in my histogram?
You should allocate as many buckets as necessary to perform proper analysis
on the collected data. Note, however, that the memory allocated in metricsd
for each histogram is proportional to the number of buckets. Therefore, it is
strongly recommended to keep this number low (e.g., 50 is normal, while 100
is probably high).
### When should I use an enumeration (linear) histogram vs. a regular (exponential) histogram?
Enumeration histograms should really be used only for sampling enumerated
events and, in some cases, percentages. Normally, you should use a regular
histogram with exponential bucket layout that provides higher resolution at
the low end of the range and lower resolution at the high end. Regular
histograms are generally used for collecting performance data (e.g., timing,
memory usage, power) as well as aggregated event counts.
### How can I test that my histogram was reported correctly?
* Make sure no error messages appear in logcat when you log a sample.
* Run `metrics_client -d` to dump the currently aggregated metrics. Your
histogram should appear in the list.
* Make sure that the aggregated metrics were uploaded to the server successfully
(check for an OK message from `metricsd` in logcat).
* After a day, your histogram should be available on the dashboard.

View File

@ -1,16 +0,0 @@
# See http://dev.chromium.org/developers/contributing-code/watchlists for
# a description of this file's format.
# Please keep these keys in alphabetical order.
{
'WATCHLIST_DEFINITIONS': {
'all': {
'filepath': '.',
},
},
'WATCHLISTS': {
'all': ['petkov@chromium.org',
'semenzato@chromium.org',
'sosa@chromium.org']
},
}

View File

@ -1,21 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.brillo.metrics;
interface IMetricsCollectorService {
oneway void notifyUserCrash();
}

View File

@ -1,26 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.brillo.metrics;
interface IMetricsd {
oneway void recordHistogram(String name, int sample, int min, int max,
int nbuckets);
oneway void recordLinearHistogram(String name, int sample, int max);
oneway void recordSparseHistogram(String name, int sample);
oneway void recordCrash(String type);
String getHistogramsDump();
}

View File

@ -1,82 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// C wrapper to libmetrics
//
#include "metrics/c_metrics_library.h"
#include <string>
#include "metrics/metrics_library.h"
extern "C" CMetricsLibrary CMetricsLibraryNew(void) {
MetricsLibrary* lib = new MetricsLibrary;
return reinterpret_cast<CMetricsLibrary>(lib);
}
extern "C" void CMetricsLibraryDelete(CMetricsLibrary handle) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
delete lib;
}
extern "C" void CMetricsLibraryInit(CMetricsLibrary handle) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
if (lib != NULL)
lib->Init();
}
extern "C" int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
const char* name, int sample,
int min, int max, int nbuckets) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
if (lib == NULL)
return 0;
return lib->SendToUMA(std::string(name), sample, min, max, nbuckets);
}
extern "C" int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
const char* name, int sample,
int max) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
if (lib == NULL)
return 0;
return lib->SendEnumToUMA(std::string(name), sample, max);
}
extern "C" int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
const char* name, int sample) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
if (lib == NULL)
return 0;
return lib->SendSparseToUMA(std::string(name), sample);
}
extern "C" int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
const char* crash_kind) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
if (lib == NULL)
return 0;
return lib->SendCrashToUMA(crash_kind);
}
extern "C" int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle) {
MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
if (lib == NULL)
return 0;
return lib->AreMetricsEnabled();
}

View File

@ -1,217 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "averaged_statistics_collector.h"
#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/files/file_path.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include "metrics_collector.h"
namespace {
// disk stats metrics
// The {Read,Write}Sectors numbers are in sectors/second.
// A sector is usually 512 bytes.
const char kReadSectorsHistogramName[] = "Platform.ReadSectors";
const char kWriteSectorsHistogramName[] = "Platform.WriteSectors";
const int kDiskMetricsStatItemCount = 11;
// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
// sectors.
const int kSectorsIOMax = 500000; // sectors/second
const int kSectorsBuckets = 50; // buckets
// Page size is 4k, sector size is 0.5k. We're not interested in page fault
// rates that the disk cannot sustain.
const int kPageFaultsMax = kSectorsIOMax / 8; // Page faults/second
const int kPageFaultsBuckets = 50;
// Major page faults, i.e. the ones that require data to be read from disk.
const char kPageFaultsHistogramName[] = "Platform.PageFaults";
// Swap in and Swap out
const char kSwapInHistogramName[] = "Platform.SwapIn";
const char kSwapOutHistogramName[] = "Platform.SwapOut";
const int kIntervalBetweenCollection = 60; // seconds
const int kCollectionDuration = 1; // seconds
} // namespace
AveragedStatisticsCollector::AveragedStatisticsCollector(
MetricsLibraryInterface* metrics_library,
const std::string& diskstats_path,
const std::string& vmstats_path) :
metrics_lib_(metrics_library),
diskstats_path_(diskstats_path),
vmstats_path_(vmstats_path) {
}
void AveragedStatisticsCollector::ScheduleWait() {
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&AveragedStatisticsCollector::WaitCallback,
base::Unretained(this)),
base::TimeDelta::FromSeconds(
kIntervalBetweenCollection - kCollectionDuration));
}
void AveragedStatisticsCollector::ScheduleCollect() {
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&AveragedStatisticsCollector::CollectCallback,
base::Unretained(this)),
base::TimeDelta::FromSeconds(kCollectionDuration));
}
void AveragedStatisticsCollector::WaitCallback() {
ReadInitialValues();
ScheduleCollect();
}
void AveragedStatisticsCollector::CollectCallback() {
Collect();
ScheduleWait();
}
void AveragedStatisticsCollector::ReadInitialValues() {
stats_start_time_ = MetricsCollector::GetActiveTime();
DiskStatsReadStats(&read_sectors_, &write_sectors_);
VmStatsReadStats(&vmstats_);
}
bool AveragedStatisticsCollector::DiskStatsReadStats(
uint64_t* read_sectors, uint64_t* write_sectors) {
CHECK(read_sectors);
CHECK(write_sectors);
std::string line;
if (diskstats_path_.empty()) {
return false;
}
if (!base::ReadFileToString(base::FilePath(diskstats_path_), &line)) {
PLOG(WARNING) << "Could not read disk stats from "
<< diskstats_path_.value();
return false;
}
std::vector<std::string> parts = base::SplitString(
line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (parts.size() != kDiskMetricsStatItemCount) {
LOG(ERROR) << "Could not parse disk stat correctly. Expected "
<< kDiskMetricsStatItemCount << " elements but got "
<< parts.size();
return false;
}
if (!base::StringToUint64(parts[2], read_sectors)) {
LOG(ERROR) << "Couldn't convert read sectors " << parts[2] << " to uint64";
return false;
}
if (!base::StringToUint64(parts[6], write_sectors)) {
LOG(ERROR) << "Couldn't convert write sectors " << parts[6] << " to uint64";
return false;
}
return true;
}
bool AveragedStatisticsCollector::VmStatsParseStats(
const char* stats, struct VmstatRecord* record) {
CHECK(stats);
CHECK(record);
base::StringPairs pairs;
base::SplitStringIntoKeyValuePairs(stats, ' ', '\n', &pairs);
for (base::StringPairs::iterator it = pairs.begin();
it != pairs.end(); ++it) {
if (it->first == "pgmajfault" &&
!base::StringToUint64(it->second, &record->page_faults)) {
return false;
}
if (it->first == "pswpin" &&
!base::StringToUint64(it->second, &record->swap_in)) {
return false;
}
if (it->first == "pswpout" &&
!base::StringToUint64(it->second, &record->swap_out)) {
return false;
}
}
return true;
}
bool AveragedStatisticsCollector::VmStatsReadStats(struct VmstatRecord* stats) {
CHECK(stats);
std::string value_string;
if (!base::ReadFileToString(vmstats_path_, &value_string)) {
LOG(WARNING) << "cannot read " << vmstats_path_.value();
return false;
}
return VmStatsParseStats(value_string.c_str(), stats);
}
void AveragedStatisticsCollector::Collect() {
uint64_t read_sectors_now, write_sectors_now;
struct VmstatRecord vmstats_now;
double time_now = MetricsCollector::GetActiveTime();
double delta_time = time_now - stats_start_time_;
bool diskstats_success = DiskStatsReadStats(&read_sectors_now,
&write_sectors_now);
int delta_read = read_sectors_now - read_sectors_;
int delta_write = write_sectors_now - write_sectors_;
int read_sectors_per_second = delta_read / delta_time;
int write_sectors_per_second = delta_write / delta_time;
bool vmstats_success = VmStatsReadStats(&vmstats_now);
uint64_t delta_faults = vmstats_now.page_faults - vmstats_.page_faults;
uint64_t delta_swap_in = vmstats_now.swap_in - vmstats_.swap_in;
uint64_t delta_swap_out = vmstats_now.swap_out - vmstats_.swap_out;
uint64_t page_faults_per_second = delta_faults / delta_time;
uint64_t swap_in_per_second = delta_swap_in / delta_time;
uint64_t swap_out_per_second = delta_swap_out / delta_time;
if (diskstats_success) {
metrics_lib_->SendToUMA(kReadSectorsHistogramName,
read_sectors_per_second,
1,
kSectorsIOMax,
kSectorsBuckets);
metrics_lib_->SendToUMA(kWriteSectorsHistogramName,
write_sectors_per_second,
1,
kSectorsIOMax,
kSectorsBuckets);
}
if (vmstats_success) {
metrics_lib_->SendToUMA(kPageFaultsHistogramName,
page_faults_per_second,
1,
kPageFaultsMax,
kPageFaultsBuckets);
metrics_lib_->SendToUMA(kSwapInHistogramName,
swap_in_per_second,
1,
kPageFaultsMax,
kPageFaultsBuckets);
metrics_lib_->SendToUMA(kSwapOutHistogramName,
swap_out_per_second,
1,
kPageFaultsMax,
kPageFaultsBuckets);
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
#define METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
#include "metrics/metrics_library.h"
class AveragedStatisticsCollector {
public:
AveragedStatisticsCollector(MetricsLibraryInterface* metrics_library,
const std::string& diskstats_path,
const std::string& vmstat_path);
// Schedule a wait period.
void ScheduleWait();
// Schedule a collection period.
void ScheduleCollect();
// Callback used by the main loop.
void CollectCallback();
// Callback used by the main loop.
void WaitCallback();
// Read and store the initial values at the beginning of a collection cycle.
void ReadInitialValues();
// Collect the disk usage statistics and report them.
void Collect();
private:
friend class AveragedStatisticsTest;
FRIEND_TEST(AveragedStatisticsTest, ParseDiskStats);
FRIEND_TEST(AveragedStatisticsTest, ParseVmStats);
// Record for retrieving and reporting values from /proc/vmstat
struct VmstatRecord {
uint64_t page_faults; // major faults
uint64_t swap_in; // pages swapped in
uint64_t swap_out; // pages swapped out
};
// Read the disk read/write statistics for the main disk.
bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors);
// Parse the content of the vmstats file into |record|.
bool VmStatsParseStats(const char* stats, struct VmstatRecord* record);
// Read the vmstats into |stats|.
bool VmStatsReadStats(struct VmstatRecord* stats);
MetricsLibraryInterface* metrics_lib_;
base::FilePath diskstats_path_;
base::FilePath vmstats_path_;
// Values observed at the beginning of the collection period.
uint64_t read_sectors_;
uint64_t write_sectors_;
struct VmstatRecord vmstats_;
double stats_start_time_;
};
#endif // METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_

View File

@ -1,100 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "averaged_statistics_collector.h"
#include <memory>
#include <inttypes.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/stringprintf.h>
#include <gtest/gtest.h>
static const char kFakeDiskStatsFormat[] =
" 1793 1788 %" PRIu64 " 105580 "
" 196 175 %" PRIu64 " 30290 "
" 0 44060 135850\n";
static const uint64_t kFakeReadSectors[] = {80000, 100000};
static const uint64_t kFakeWriteSectors[] = {3000, 4000};
class AveragedStatisticsTest : public testing::Test {
protected:
std::string kFakeDiskStats0;
std::string kFakeDiskStats1;
virtual void SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
disk_stats_path_ = temp_dir_.path().Append("disk_stats");
collector_.reset(new AveragedStatisticsCollector(
&metrics_lib_, disk_stats_path_.value(), ""));
kFakeDiskStats0 = base::StringPrintf(kFakeDiskStatsFormat,
kFakeReadSectors[0],
kFakeWriteSectors[0]);
kFakeDiskStats1 = base::StringPrintf(kFakeDiskStatsFormat,
kFakeReadSectors[1],
kFakeWriteSectors[1]);
CreateFakeDiskStatsFile(kFakeDiskStats0);
}
// Creates or overwrites an input file containing fake disk stats.
void CreateFakeDiskStatsFile(const std::string& fake_stats) {
EXPECT_EQ(base::WriteFile(disk_stats_path_,
fake_stats.data(), fake_stats.size()),
fake_stats.size());
}
// Collector used for tests.
std::unique_ptr<AveragedStatisticsCollector> collector_;
// Temporary directory used for tests.
base::ScopedTempDir temp_dir_;
// Path for the fake files.
base::FilePath disk_stats_path_;
MetricsLibrary metrics_lib_;
};
TEST_F(AveragedStatisticsTest, ParseDiskStats) {
uint64_t read_sectors_now, write_sectors_now;
CreateFakeDiskStatsFile(kFakeDiskStats0);
ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now,
&write_sectors_now));
EXPECT_EQ(read_sectors_now, kFakeReadSectors[0]);
EXPECT_EQ(write_sectors_now, kFakeWriteSectors[0]);
CreateFakeDiskStatsFile(kFakeDiskStats1);
ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now,
&write_sectors_now));
EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]);
EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]);
}
TEST_F(AveragedStatisticsTest, ParseVmStats) {
static char kVmStats[] = "pswpin 1345\npswpout 8896\n"
"foo 100\nbar 200\npgmajfault 42\netcetc 300\n";
struct AveragedStatisticsCollector::VmstatRecord stats;
EXPECT_TRUE(collector_->VmStatsParseStats(kVmStats, &stats));
EXPECT_EQ(stats.page_faults, 42);
EXPECT_EQ(stats.swap_in, 1345);
EXPECT_EQ(stats.swap_out, 8896);
}

View File

@ -1,126 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "collectors/cpu_usage_collector.h"
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/message_loop/message_loop.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/sys_info.h>
#include "metrics/metrics_library.h"
namespace {
const char kCpuUsagePercent[] = "Platform.CpuUsage.Percent";
const char kMetricsProcStatFileName[] = "/proc/stat";
const int kMetricsProcStatFirstLineItemsCount = 11;
// Collect every minute.
const int kCollectionIntervalSecs = 60;
} // namespace
using base::TimeDelta;
CpuUsageCollector::CpuUsageCollector(MetricsLibraryInterface* metrics_library) {
CHECK(metrics_library);
metrics_lib_ = metrics_library;
collect_interval_ = TimeDelta::FromSeconds(kCollectionIntervalSecs);
}
void CpuUsageCollector::Init() {
num_cpu_ = base::SysInfo::NumberOfProcessors();
// Get ticks per second (HZ) on this system.
// Sysconf cannot fail, so no sanity checks are needed.
ticks_per_second_ = sysconf(_SC_CLK_TCK);
CHECK_GT(ticks_per_second_, uint64_t(0))
<< "Number of ticks per seconds should be positive.";
latest_cpu_use_ = GetCumulativeCpuUse();
}
void CpuUsageCollector::CollectCallback() {
Collect();
Schedule();
}
void CpuUsageCollector::Schedule() {
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&CpuUsageCollector::CollectCallback, base::Unretained(this)),
collect_interval_);
}
void CpuUsageCollector::Collect() {
TimeDelta cpu_use = GetCumulativeCpuUse();
TimeDelta diff_per_cpu = (cpu_use - latest_cpu_use_) / num_cpu_;
latest_cpu_use_ = cpu_use;
// Report the cpu usage as a percentage of the total cpu usage possible.
int percent_use = diff_per_cpu.InMilliseconds() * 100 /
(kCollectionIntervalSecs * 1000);
metrics_lib_->SendEnumToUMA(kCpuUsagePercent, percent_use, 101);
}
TimeDelta CpuUsageCollector::GetCumulativeCpuUse() {
base::FilePath proc_stat_path(kMetricsProcStatFileName);
std::string proc_stat_string;
if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
return TimeDelta();
}
uint64_t user_ticks, user_nice_ticks, system_ticks;
if (!ParseProcStat(proc_stat_string, &user_ticks, &user_nice_ticks,
&system_ticks)) {
return TimeDelta();
}
uint64_t total = user_ticks + user_nice_ticks + system_ticks;
return TimeDelta::FromMicroseconds(
total * 1000 * 1000 / ticks_per_second_);
}
bool CpuUsageCollector::ParseProcStat(const std::string& stat_content,
uint64_t *user_ticks,
uint64_t *user_nice_ticks,
uint64_t *system_ticks) {
std::vector<std::string> proc_stat_lines = base::SplitString(
stat_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (proc_stat_lines.empty()) {
LOG(WARNING) << "No lines found in " << kMetricsProcStatFileName;
return false;
}
std::vector<std::string> proc_stat_totals =
base::SplitString(proc_stat_lines[0], base::kWhitespaceASCII,
base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount ||
proc_stat_totals[0] != "cpu" ||
!base::StringToUint64(proc_stat_totals[1], user_ticks) ||
!base::StringToUint64(proc_stat_totals[2], user_nice_ticks) ||
!base::StringToUint64(proc_stat_totals[3], system_ticks)) {
LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0];
return false;
}
return true;
}

View File

@ -1,59 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
#define METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
#include <base/time/time.h>
#include "metrics/metrics_library.h"
class CpuUsageCollector {
public:
explicit CpuUsageCollector(MetricsLibraryInterface* metrics_library);
// Initialize this collector's state.
void Init();
// Schedule a collection interval.
void Schedule();
// Callback called at the end of the collection interval.
void CollectCallback();
// Measure the cpu use and report it.
void Collect();
// Gets the current cumulated Cpu usage.
base::TimeDelta GetCumulativeCpuUse();
private:
FRIEND_TEST(CpuUsageTest, ParseProcStat);
bool ParseProcStat(const std::string& stat_content,
uint64_t *user_ticks,
uint64_t *user_nice_ticks,
uint64_t *system_ticks);
int num_cpu_;
uint32_t ticks_per_second_;
base::TimeDelta collect_interval_;
base::TimeDelta latest_cpu_use_;
MetricsLibraryInterface* metrics_lib_;
};
#endif // METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include "collectors/cpu_usage_collector.h"
#include "metrics/metrics_library_mock.h"
TEST(CpuUsageTest, ParseProcStat) {
MetricsLibraryMock metrics_lib_mock;
CpuUsageCollector collector(&metrics_lib_mock);
std::vector<std::string> invalid_contents = {
"",
// First line does not start with cpu.
"spu 17191 11 36579 151118 289 0 2 0 0 0\n"
"cpu0 1564 2 866 48650 68 0 2 0 0 0\n"
"cpu1 14299 0 35116 1844 81 0 0 0 0 0\n",
// One of the field is not a number.
"cpu a17191 11 36579 151118 289 0 2 0 0 0",
// To many numbers in the first line.
"cpu 17191 11 36579 151118 289 0 2 0 0 0 102"
};
uint64_t user, nice, system;
for (int i = 0; i < invalid_contents.size(); i++) {
ASSERT_FALSE(collector.ParseProcStat(invalid_contents[i], &user, &nice,
&system));
}
ASSERT_TRUE(collector.ParseProcStat(
std::string("cpu 17191 11 36579 151118 289 0 2 0 0 0"),
&user, &nice, &system));
ASSERT_EQ(17191, user);
ASSERT_EQ(11, nice);
ASSERT_EQ(36579, system);
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "collectors/disk_usage_collector.h"
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/message_loop/message_loop.h>
#include <sys/statvfs.h>
#include "metrics/metrics_library.h"
namespace {
const char kDiskUsageMB[] = "Platform.DataPartitionUsed.MB";
const char kDiskUsagePercent[] = "Platform.DataPartitionUsed.Percent";
const char kDataPartitionPath[] = "/data";
// Collect every 15 minutes.
const int kDiskUsageCollectorIntervalSeconds = 900;
} // namespace
DiskUsageCollector::DiskUsageCollector(
MetricsLibraryInterface* metrics_library) {
collect_interval_ = base::TimeDelta::FromSeconds(
kDiskUsageCollectorIntervalSeconds);
CHECK(metrics_library);
metrics_lib_ = metrics_library;
}
void DiskUsageCollector::Collect() {
struct statvfs buf;
int result = statvfs(kDataPartitionPath, &buf);
if (result != 0) {
PLOG(ERROR) << "Failed to check the available space in "
<< kDataPartitionPath;
return;
}
unsigned long total_space = buf.f_blocks * buf.f_bsize;
unsigned long used_space = (buf.f_blocks - buf.f_bfree) * buf.f_bsize;
int percent_used = (used_space * 100) / total_space;
metrics_lib_->SendToUMA(kDiskUsageMB,
used_space / (1024 * 1024),
0,
1024, // up to 1 GB.
100);
metrics_lib_->SendEnumToUMA(kDiskUsagePercent, percent_used, 101);
}
void DiskUsageCollector::CollectCallback() {
Collect();
Schedule();
}
void DiskUsageCollector::Schedule() {
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&DiskUsageCollector::CollectCallback, base::Unretained(this)),
collect_interval_);
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
#define METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
#include <base/time/time.h>
#include "metrics/metrics_library.h"
class DiskUsageCollector {
public:
explicit DiskUsageCollector(MetricsLibraryInterface* metrics_library);
// Schedule the next collection.
void Schedule();
// Callback used by the main loop.
void CollectCallback();
// Collect the disk usage statistics and report them.
void Collect();
private:
base::TimeDelta collect_interval_;
MetricsLibraryInterface* metrics_lib_;
};
#endif // METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_

View File

@ -1,42 +0,0 @@
//
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef METRICS_CONSTANTS_H_
#define METRICS_CONSTANTS_H_
namespace metrics {
static const char kSharedMetricsDirectory[] = "/data/misc/metrics/";
static const char kMetricsdDirectory[] = "/data/misc/metricsd/";
static const char kMetricsCollectorDirectory[] =
"/data/misc/metrics_collector/";
static const char kMetricsGUIDFileName[] = "Sysinfo.GUID";
static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
static const char kConsentFileName[] = "enabled";
static const char kStagedLogName[] = "staged_log";
static const char kSavedLogName[] = "saved_log";
static const char kFailedUploadCountName[] = "failed_upload_count";
static const char kDefaultVersion[] = "0.0.0.0";
// Build time properties name.
static const char kProductId[] = "product_id";
static const char kProductVersion[] = "product_version";
// Weave configuration.
static const char kWeaveConfigurationFile[] = "/system/etc/weaved/weaved.conf";
static const char kModelManifestId[] = "model_id";
} // namespace metrics
#endif // METRICS_CONSTANTS_H_

View File

@ -1,20 +0,0 @@
{
"_metrics": {
"commands": {
"enableAnalyticsReporting": {
"minimalRole": "manager",
"parameters": {}
},
"disableAnalyticsReporting": {
"minimalRole": "manager",
"parameters": {}
}
},
"state": {
"analyticsReportingState": {
"type": "string",
"enum": [ "enabled", "disabled" ]
}
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_C_METRICS_LIBRARY_H_
#define METRICS_C_METRICS_LIBRARY_H_
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct CMetricsLibraryOpaque* CMetricsLibrary;
// C wrapper for MetricsLibrary::MetricsLibrary.
CMetricsLibrary CMetricsLibraryNew(void);
// C wrapper for MetricsLibrary::~MetricsLibrary.
void CMetricsLibraryDelete(CMetricsLibrary handle);
// C wrapper for MetricsLibrary::Init.
void CMetricsLibraryInit(CMetricsLibrary handle);
// C wrapper for MetricsLibrary::SendToUMA.
int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
const char* name, int sample,
int min, int max, int nbuckets);
// C wrapper for MetricsLibrary::SendEnumToUMA.
int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
const char* name, int sample, int max);
// C wrapper for MetricsLibrary::SendSparseToUMA.
int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
const char* name, int sample);
// C wrapper for MetricsLibrary::SendCrashToUMA.
int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
const char* crash_kind);
// C wrapper for MetricsLibrary::AreMetricsEnabled.
int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle);
#if defined(__cplusplus)
}
#endif
#endif // METRICS_C_METRICS_LIBRARY_H_

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Client interface to IMetricsCollectorService.
#ifndef METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
#define METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
#include "android/brillo/metrics/IMetricsCollectorService.h"
class MetricsCollectorServiceClient {
public:
MetricsCollectorServiceClient() = default;
~MetricsCollectorServiceClient() = default;
// Initialize. Returns true if OK, or false if IMetricsCollectorService
// is not registered.
bool Init();
// Called by crash_reporter to report a userspace crash event. Returns
// true if successfully called the IMetricsCollectorService method of the
// same name, or false if the service was not registered at Init() time.
bool notifyUserCrash();
private:
// IMetricsCollectorService binder proxy
android::sp<android::brillo::metrics::IMetricsCollectorService>
metrics_collector_service_;
};
#endif // METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_

View File

@ -1,175 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_METRICS_LIBRARY_H_
#define METRICS_METRICS_LIBRARY_H_
#include <sys/types.h>
#include <string>
#include <unistd.h>
#include <base/compiler_specific.h>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <binder/IServiceManager.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
namespace android {
namespace brillo {
namespace metrics {
class IMetricsd;
} // namespace metrics
} // namespace brillo
} // namespace android
class MetricsLibraryInterface {
public:
virtual void Init() = 0;
virtual bool AreMetricsEnabled() = 0;
virtual bool SendToUMA(const std::string& name, int sample,
int min, int max, int nbuckets) = 0;
virtual bool SendEnumToUMA(const std::string& name, int sample, int max) = 0;
virtual bool SendBoolToUMA(const std::string& name, bool sample) = 0;
virtual bool SendSparseToUMA(const std::string& name, int sample) = 0;
virtual ~MetricsLibraryInterface() {}
};
// Library used to send metrics to Chrome/UMA.
class MetricsLibrary : public MetricsLibraryInterface {
public:
MetricsLibrary();
virtual ~MetricsLibrary();
// Initializes the library.
void Init() override;
// Initializes the library and disables the cache of whether or not the
// metrics collection is enabled.
// By disabling this, we may have to check for the metrics status more often
// but the result will never be stale.
void InitWithNoCaching();
// Returns whether or not the machine is running in guest mode.
bool IsGuestMode();
// Returns whether or not metrics collection is enabled.
bool AreMetricsEnabled() override;
// Sends histogram data to Chrome for transport to UMA and returns
// true on success. This method results in the equivalent of an
// asynchronous non-blocking RPC to UMA_HISTOGRAM_CUSTOM_COUNTS
// inside Chrome (see base/histogram.h).
//
// |sample| is the sample value to be recorded (|min| <= |sample| < |max|).
// |min| is the minimum value of the histogram samples (|min| > 0).
// |max| is the maximum value of the histogram samples.
// |nbuckets| is the number of histogram buckets.
// [0,min) is the implicit underflow bucket.
// [|max|,infinity) is the implicit overflow bucket.
//
// Note that the memory allocated in Chrome for each histogram is
// proportional to the number of buckets. Therefore, it is strongly
// recommended to keep this number low (e.g., 50 is normal, while
// 100 is high).
bool SendToUMA(const std::string& name, int sample,
int min, int max, int nbuckets) override;
// Sends linear histogram data to Chrome for transport to UMA and
// returns true on success. This method results in the equivalent of
// an asynchronous non-blocking RPC to UMA_HISTOGRAM_ENUMERATION
// inside Chrome (see base/histogram.h).
//
// |sample| is the sample value to be recorded (1 <= |sample| < |max|).
// |max| is the maximum value of the histogram samples.
// 0 is the implicit underflow bucket.
// [|max|,infinity) is the implicit overflow bucket.
//
// An enumeration histogram requires |max| + 1 number of
// buckets. Note that the memory allocated in Chrome for each
// histogram is proportional to the number of buckets. Therefore, it
// is strongly recommended to keep this number low (e.g., 50 is
// normal, while 100 is high).
bool SendEnumToUMA(const std::string& name, int sample, int max) override;
// Specialization of SendEnumToUMA for boolean values.
bool SendBoolToUMA(const std::string& name, bool sample) override;
// Sends sparse histogram sample to Chrome for transport to UMA. Returns
// true on success.
//
// |sample| is the 32-bit integer value to be recorded.
bool SendSparseToUMA(const std::string& name, int sample) override;
// Sends a signal to UMA that a crash of the given |crash_kind|
// has occurred. Used by UMA to generate stability statistics.
bool SendCrashToUMA(const char *crash_kind);
// Sends a "generic Chrome OS event" to UMA. This is an event name
// that is translated into an enumerated histogram entry. Event names
// are added to metrics_library.cc. Optionally, they can be added
// to histograms.xml---but part of the reason for this is to simplify
// the addition of events (at the cost of having to look them up by
// number in the histograms dashboard).
bool SendCrosEventToUMA(const std::string& event);
// Debugging only.
// Dumps the histograms aggregated since metricsd started into |dump|.
// Returns true iff the dump succeeds.
bool GetHistogramsDump(std::string* dump);
private:
friend class CMetricsLibraryTest;
friend class MetricsLibraryTest;
friend class UploadServiceTest;
FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled);
FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabledNoCaching);
FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted);
FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
void InitForTest(const base::FilePath& metrics_directory);
// Sets |*result| to whether or not the |mounts_file| indicates that
// the |device_name| is currently mounted. Uses |buffer| of
// |buffer_size| to read the file. Returns false if any error.
bool IsDeviceMounted(const char* device_name,
const char* mounts_file,
char* buffer, int buffer_size,
bool* result);
// Connects to IMetricsd if the proxy does not exist or is not alive.
// Don't block if we fail to get the proxy for any reason.
bool CheckService();
// Time at which we last checked if metrics were enabled.
time_t cached_enabled_time_;
// Cached state of whether or not metrics were enabled.
bool cached_enabled_;
// True iff we should cache the enabled/disabled status.
bool use_caching_;
android::sp<android::IServiceManager> manager_;
android::sp<android::brillo::metrics::IMetricsd> metricsd_proxy_;
base::FilePath consent_file_;
DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
};
#endif // METRICS_METRICS_LIBRARY_H_

View File

@ -1,41 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_METRICS_LIBRARY_MOCK_H_
#define METRICS_METRICS_LIBRARY_MOCK_H_
#include <string>
#include "metrics/metrics_library.h"
#include <gmock/gmock.h>
class MetricsLibraryMock : public MetricsLibraryInterface {
public:
bool metrics_enabled_ = true;
MOCK_METHOD0(Init, void());
MOCK_METHOD5(SendToUMA, bool(const std::string& name, int sample,
int min, int max, int nbuckets));
MOCK_METHOD3(SendEnumToUMA, bool(const std::string& name, int sample,
int max));
MOCK_METHOD2(SendBoolToUMA, bool(const std::string& name, bool sample));
MOCK_METHOD2(SendSparseToUMA, bool(const std::string& name, int sample));
bool AreMetricsEnabled() override {return metrics_enabled_;};
};
#endif // METRICS_METRICS_LIBRARY_MOCK_H_

View File

@ -1,170 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Timer - class that provides timer tracking.
#ifndef METRICS_TIMER_H_
#define METRICS_TIMER_H_
#include <memory>
#include <string>
#include <base/macros.h>
#include <base/time/time.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
class MetricsLibraryInterface;
namespace chromeos_metrics {
class TimerInterface {
public:
virtual ~TimerInterface() {}
virtual bool Start() = 0;
virtual bool Stop() = 0;
virtual bool Reset() = 0;
virtual bool HasStarted() const = 0;
};
// Wrapper for calls to the system clock.
class ClockWrapper {
public:
ClockWrapper() {}
virtual ~ClockWrapper() {}
// Returns the current time from the system.
virtual base::TimeTicks GetCurrentTime() const;
private:
DISALLOW_COPY_AND_ASSIGN(ClockWrapper);
};
// Implements a Timer.
class Timer : public TimerInterface {
public:
Timer();
virtual ~Timer() {}
// Starts the timer. If a timer is already running, also resets current
// timer. Always returns true.
virtual bool Start();
// Stops the timer and calculates the total time elapsed between now and when
// Start() was called. Note that this method needs a prior call to Start().
// Otherwise, it fails (returns false).
virtual bool Stop();
// Pauses a timer. If the timer is stopped, this call starts the timer in
// the paused state. Fails (returns false) if the timer is already paused.
virtual bool Pause();
// Restarts a paused timer (or starts a stopped timer). This method fails
// (returns false) if the timer is already running; otherwise, returns true.
virtual bool Resume();
// Resets the timer, erasing the current duration being tracked. Always
// returns true.
virtual bool Reset();
// Returns whether the timer has started or not.
virtual bool HasStarted() const;
// Stores the current elapsed time in |elapsed_time|. If timer is stopped,
// stores the elapsed time from when Stop() was last called. Otherwise,
// calculates and stores the elapsed time since the last Start().
// Returns false if the timer was never Start()'ed or if called with a null
// pointer argument.
virtual bool GetElapsedTime(base::TimeDelta* elapsed_time) const;
private:
enum TimerState { kTimerStopped, kTimerRunning, kTimerPaused };
friend class TimerTest;
friend class TimerReporterTest;
FRIEND_TEST(TimerReporterTest, StartStopReport);
FRIEND_TEST(TimerTest, InvalidElapsedTime);
FRIEND_TEST(TimerTest, InvalidStop);
FRIEND_TEST(TimerTest, PauseResumeStop);
FRIEND_TEST(TimerTest, PauseStartStopResume);
FRIEND_TEST(TimerTest, PauseStop);
FRIEND_TEST(TimerTest, Reset);
FRIEND_TEST(TimerTest, ReStart);
FRIEND_TEST(TimerTest, ResumeStartStopPause);
FRIEND_TEST(TimerTest, SeparatedTimers);
FRIEND_TEST(TimerTest, StartPauseResumePauseResumeStop);
FRIEND_TEST(TimerTest, StartPauseResumePauseStop);
FRIEND_TEST(TimerTest, StartPauseResumeStop);
FRIEND_TEST(TimerTest, StartPauseStop);
FRIEND_TEST(TimerTest, StartResumeStop);
FRIEND_TEST(TimerTest, StartStop);
// Elapsed time of the last use of the timer.
base::TimeDelta elapsed_time_;
// Starting time value.
base::TimeTicks start_time_;
// Whether the timer is running, stopped, or paused.
TimerState timer_state_;
// Wrapper for the calls to the system clock.
std::unique_ptr<ClockWrapper> clock_wrapper_;
DISALLOW_COPY_AND_ASSIGN(Timer);
};
// Extends the Timer class to report the elapsed time in milliseconds through
// the UMA metrics library.
class TimerReporter : public Timer {
public:
// Initializes the timer by providing a |histogram_name| to report to with
// |min|, |max| and |num_buckets| attributes for the histogram.
TimerReporter(const std::string& histogram_name, int min, int max,
int num_buckets);
virtual ~TimerReporter() {}
// Sets the metrics library used by all instances of this class.
static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
metrics_lib_ = metrics_lib;
}
// Reports the current duration to UMA, in milliseconds. Returns false if
// there is nothing to report, e.g. a metrics library is not set.
virtual bool ReportMilliseconds() const;
// Accessor methods.
const std::string& histogram_name() const { return histogram_name_; }
int min() const { return min_; }
int max() const { return max_; }
int num_buckets() const { return num_buckets_; }
private:
friend class TimerReporterTest;
FRIEND_TEST(TimerReporterTest, StartStopReport);
FRIEND_TEST(TimerReporterTest, InvalidReport);
static MetricsLibraryInterface* metrics_lib_;
std::string histogram_name_;
int min_;
int max_;
int num_buckets_;
DISALLOW_COPY_AND_ASSIGN(TimerReporter);
};
} // namespace chromeos_metrics
#endif // METRICS_TIMER_H_

View File

@ -1,59 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_TIMER_MOCK_H_
#define METRICS_TIMER_MOCK_H_
#include <string>
#include <gmock/gmock.h>
#include "metrics/timer.h"
namespace chromeos_metrics {
class TimerMock : public Timer {
public:
MOCK_METHOD0(Start, bool());
MOCK_METHOD0(Stop, bool());
MOCK_METHOD0(Reset, bool());
MOCK_CONST_METHOD0(HasStarted, bool());
MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
};
class TimerReporterMock : public TimerReporter {
public:
TimerReporterMock() : TimerReporter("", 0, 0, 0) {}
MOCK_METHOD0(Start, bool());
MOCK_METHOD0(Stop, bool());
MOCK_METHOD0(Reset, bool());
MOCK_CONST_METHOD0(HasStarted, bool());
MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
MOCK_CONST_METHOD0(ReportMilliseconds, bool());
MOCK_CONST_METHOD0(histogram_name, std::string&());
MOCK_CONST_METHOD0(min, int());
MOCK_CONST_METHOD0(max, int());
MOCK_CONST_METHOD0(num_buckets, int());
};
class ClockWrapperMock : public ClockWrapper {
public:
MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks());
};
} // namespace chromeos_metrics
#endif // METRICS_TIMER_MOCK_H_

View File

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

View File

@ -1,33 +0,0 @@
{
'target_defaults': {
'variables': {
'deps': [
'libbrillo-<(libbase_ver)',
'libchrome-<(libbase_ver)',
]
},
'cflags_cc': [
'-fno-exceptions',
],
},
'targets': [
{
'target_name': 'libmetrics-<(libbase_ver)',
'type': 'shared_library',
'cflags': [
'-fvisibility=default',
],
'libraries+': [
'-lpolicy-<(libbase_ver)',
],
'sources': [
'c_metrics_library.cc',
'metrics_library.cc',
'serialization/metric_sample.cc',
'serialization/serialization_utils.cc',
'timer.cc',
],
'include_dirs': ['.'],
},
],
}

View File

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

View File

@ -1,184 +0,0 @@
{
'target_defaults': {
'variables': {
'deps': [
'dbus-1',
'libbrillo-<(libbase_ver)',
'libchrome-<(libbase_ver)',
]
},
'cflags_cc': [
'-fno-exceptions',
],
},
'targets': [
{
'target_name': 'libmetrics_daemon',
'type': 'static_library',
'dependencies': [
'../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
'libupload_service',
'metrics_proto',
],
'link_settings': {
'libraries': [
'-lrootdev',
],
},
'sources': [
'persistent_integer.cc',
'metrics_daemon.cc',
'metrics_daemon_main.cc',
],
'include_dirs': ['.'],
},
{
'target_name': 'metrics_client',
'type': 'executable',
'dependencies': [
'../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
],
'sources': [
'metrics_client.cc',
]
},
{
'target_name': 'libupload_service',
'type': 'static_library',
'dependencies': [
'metrics_proto',
'../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
],
'link_settings': {
'libraries': [
'-lvboot_host',
],
},
'variables': {
'exported_deps': [
'protobuf-lite',
],
'deps': [
'<@(exported_deps)',
],
},
'all_dependent_settings': {
'variables': {
'deps+': [
'<@(exported_deps)',
],
},
},
'sources': [
'uploader/upload_service.cc',
'uploader/metrics_hashes.cc',
'uploader/metrics_log.cc',
'uploader/metrics_log_base.cc',
'uploader/system_profile_cache.cc',
'uploader/sender_http.cc',
],
'include_dirs': ['.']
},
{
'target_name': 'metrics_proto',
'type': 'static_library',
'variables': {
'proto_in_dir': 'uploader/proto',
'proto_out_dir': 'include/metrics/uploader/proto',
},
'sources': [
'<(proto_in_dir)/chrome_user_metrics_extension.proto',
'<(proto_in_dir)/histogram_event.proto',
'<(proto_in_dir)/system_profile.proto',
'<(proto_in_dir)/user_action_event.proto',
],
'includes': [
'../common-mk/protoc.gypi'
],
},
],
'conditions': [
['USE_passive_metrics == 1', {
'targets': [
{
'target_name': 'metrics_daemon',
'type': 'executable',
'dependencies': ['libmetrics_daemon'],
},
],
}],
['USE_test == 1', {
'targets': [
{
'target_name': 'persistent_integer_test',
'type': 'executable',
'includes': ['../common-mk/common_test.gypi'],
'sources': [
'persistent_integer.cc',
'persistent_integer_test.cc',
]
},
{
'target_name': 'metrics_library_test',
'type': 'executable',
'dependencies': [
'../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
],
'includes': ['../common-mk/common_test.gypi'],
'sources': [
'metrics_library_test.cc',
'serialization/serialization_utils_unittest.cc',
],
'link_settings': {
'libraries': [
'-lpolicy-<(libbase_ver)',
]
}
},
{
'target_name': 'timer_test',
'type': 'executable',
'includes': ['../common-mk/common_test.gypi'],
'sources': [
'timer.cc',
'timer_test.cc',
]
},
{
'target_name': 'upload_service_test',
'type': 'executable',
'sources': [
'persistent_integer.cc',
'uploader/metrics_hashes_unittest.cc',
'uploader/metrics_log_base_unittest.cc',
'uploader/mock/sender_mock.cc',
'uploader/upload_service_test.cc',
],
'dependencies': [
'libupload_service',
],
'includes':[
'../common-mk/common_test.gypi',
],
'include_dirs': ['.']
},
],
}],
['USE_passive_metrics == 1 and USE_test == 1', {
'targets': [
{
'target_name': 'metrics_daemon_test',
'type': 'executable',
'dependencies': [
'libmetrics_daemon',
],
'includes': ['../common-mk/common_test.gypi'],
'sources': [
'metrics_daemon_test.cc',
],
'include_dirs': ['.'],
},
],
}],
]
}

View File

@ -1,214 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstdio>
#include <cstdlib>
#include "constants.h"
#include "metrics/metrics_library.h"
enum Mode {
kModeDumpHistograms,
kModeSendSample,
kModeSendEnumSample,
kModeSendSparseSample,
kModeSendCrosEvent,
kModeHasConsent,
kModeIsGuestMode,
};
void ShowUsage() {
fprintf(stderr,
"Usage: metrics_client [-t] name sample min max nbuckets\n"
" metrics_client -e name sample max\n"
" metrics_client -s name sample\n"
" metrics_client -v event\n"
" metrics_client [-cdg]\n"
"\n"
" default: send metric with integer values \n"
" |min| > 0, |min| <= sample < |max|\n"
" -c: return exit status 0 if user consents to stats, 1 otherwise,\n"
" in guest mode always return 1\n"
" -d: dump the histograms recorded by metricsd to stdout\n"
" -e: send linear/enumeration histogram data\n"
" -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
" -s: send a sparse histogram sample\n"
" -t: convert sample from double seconds to int milliseconds\n"
" -v: send a Platform.CrOSEvent enum histogram sample\n");
exit(1);
}
static int ParseInt(const char *arg) {
char *endptr;
int value = strtol(arg, &endptr, 0);
if (*endptr != '\0') {
fprintf(stderr, "metrics client: bad integer \"%s\"\n", arg);
ShowUsage();
}
return value;
}
static double ParseDouble(const char *arg) {
char *endptr;
double value = strtod(arg, &endptr);
if (*endptr != '\0') {
fprintf(stderr, "metrics client: bad double \"%s\"\n", arg);
ShowUsage();
}
return value;
}
static int DumpHistograms() {
MetricsLibrary metrics_lib;
metrics_lib.Init();
std::string dump;
if (!metrics_lib.GetHistogramsDump(&dump)) {
printf("Failed to dump the histograms.");
return 1;
}
printf("%s\n", dump.c_str());
return 0;
}
static int SendStats(char* argv[],
int name_index,
enum Mode mode,
bool secs_to_msecs) {
const char* name = argv[name_index];
int sample;
if (secs_to_msecs) {
sample = static_cast<int>(ParseDouble(argv[name_index + 1]) * 1000.0);
} else {
sample = ParseInt(argv[name_index + 1]);
}
MetricsLibrary metrics_lib;
metrics_lib.Init();
if (mode == kModeSendSparseSample) {
metrics_lib.SendSparseToUMA(name, sample);
} else if (mode == kModeSendEnumSample) {
int max = ParseInt(argv[name_index + 2]);
metrics_lib.SendEnumToUMA(name, sample, max);
} else {
int min = ParseInt(argv[name_index + 2]);
int max = ParseInt(argv[name_index + 3]);
int nbuckets = ParseInt(argv[name_index + 4]);
metrics_lib.SendToUMA(name, sample, min, max, nbuckets);
}
return 0;
}
static int SendCrosEvent(char* argv[], int action_index) {
const char* event = argv[action_index];
bool result;
MetricsLibrary metrics_lib;
metrics_lib.Init();
result = metrics_lib.SendCrosEventToUMA(event);
if (!result) {
fprintf(stderr, "metrics_client: could not send event %s\n", event);
return 1;
}
return 0;
}
static int HasConsent() {
MetricsLibrary metrics_lib;
metrics_lib.Init();
return metrics_lib.AreMetricsEnabled() ? 0 : 1;
}
static int IsGuestMode() {
MetricsLibrary metrics_lib;
metrics_lib.Init();
return metrics_lib.IsGuestMode() ? 0 : 1;
}
int main(int argc, char** argv) {
enum Mode mode = kModeSendSample;
bool secs_to_msecs = false;
// Parse arguments
int flag;
while ((flag = getopt(argc, argv, "abcdegstv")) != -1) {
switch (flag) {
case 'c':
mode = kModeHasConsent;
break;
case 'd':
mode = kModeDumpHistograms;
break;
case 'e':
mode = kModeSendEnumSample;
break;
case 'g':
mode = kModeIsGuestMode;
break;
case 's':
mode = kModeSendSparseSample;
break;
case 't':
secs_to_msecs = true;
break;
case 'v':
mode = kModeSendCrosEvent;
break;
default:
ShowUsage();
break;
}
}
int arg_index = optind;
int expected_args = 0;
if (mode == kModeSendSample)
expected_args = 5;
else if (mode == kModeSendEnumSample)
expected_args = 3;
else if (mode == kModeSendSparseSample)
expected_args = 2;
else if (mode == kModeSendCrosEvent)
expected_args = 1;
if ((arg_index + expected_args) != argc) {
ShowUsage();
}
switch (mode) {
case kModeDumpHistograms:
return DumpHistograms();
case kModeSendSample:
case kModeSendEnumSample:
case kModeSendSparseSample:
if ((mode != kModeSendSample) && secs_to_msecs) {
ShowUsage();
}
return SendStats(argv,
arg_index,
mode,
secs_to_msecs);
case kModeSendCrosEvent:
return SendCrosEvent(argv, arg_index);
case kModeHasConsent:
return HasConsent();
case kModeIsGuestMode:
return IsGuestMode();
default:
ShowUsage();
return 0;
}
}

View File

@ -1,756 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "metrics_collector.h"
#include <sysexits.h>
#include <time.h>
#include <memory>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/hash.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/binder_watcher.h>
#include <brillo/osrelease_reader.h>
#include "constants.h"
#include "metrics_collector_service_impl.h"
using base::FilePath;
using base::StringPrintf;
using base::Time;
using base::TimeDelta;
using base::TimeTicks;
using chromeos_metrics::PersistentInteger;
using std::map;
using std::string;
using std::vector;
namespace {
const int kSecondsPerMinute = 60;
const int kMinutesPerHour = 60;
const int kHoursPerDay = 24;
const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour;
const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay;
const int kDaysPerWeek = 7;
const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek;
// Interval between calls to UpdateStats().
const uint32_t kUpdateStatsIntervalMs = 300000;
const char kKernelCrashDetectedFile[] =
"/data/misc/crash_reporter/run/kernel-crash-detected";
const char kUncleanShutdownDetectedFile[] =
"/var/run/unclean-shutdown-detected";
const int kMetricMeminfoInterval = 30; // seconds
const char kMeminfoFileName[] = "/proc/meminfo";
const char kVmStatFileName[] = "/proc/vmstat";
const char kWeaveComponent[] = "metrics";
const char kWeaveTrait[] = "_metrics";
} // namespace
// Zram sysfs entries.
const char MetricsCollector::kComprDataSizeName[] = "compr_data_size";
const char MetricsCollector::kOrigDataSizeName[] = "orig_data_size";
const char MetricsCollector::kZeroPagesName[] = "zero_pages";
// Memory use stats collection intervals. We collect some memory use interval
// at these intervals after boot, and we stop collecting after the last one,
// with the assumption that in most cases the memory use won't change much
// after that.
static const int kMemuseIntervals[] = {
1 * kSecondsPerMinute, // 1 minute mark
4 * kSecondsPerMinute, // 5 minute mark
25 * kSecondsPerMinute, // 0.5 hour mark
120 * kSecondsPerMinute, // 2.5 hour mark
600 * kSecondsPerMinute, // 12.5 hour mark
};
MetricsCollector::MetricsCollector()
: memuse_final_time_(0),
memuse_interval_index_(0) {}
MetricsCollector::~MetricsCollector() {
}
// static
double MetricsCollector::GetActiveTime() {
struct timespec ts;
int r = clock_gettime(CLOCK_MONOTONIC, &ts);
if (r < 0) {
PLOG(WARNING) << "clock_gettime(CLOCK_MONOTONIC) failed";
return 0;
} else {
return ts.tv_sec + static_cast<double>(ts.tv_nsec) / (1000 * 1000 * 1000);
}
}
int MetricsCollector::Run() {
if (CheckSystemCrash(kKernelCrashDetectedFile)) {
ProcessKernelCrash();
}
if (CheckSystemCrash(kUncleanShutdownDetectedFile)) {
ProcessUncleanShutdown();
}
// On OS version change, clear version stats (which are reported daily).
int32_t version = GetOsVersionHash();
if (version_cycle_->Get() != version) {
version_cycle_->Set(version);
kernel_crashes_version_count_->Set(0);
version_cumulative_active_use_->Set(0);
version_cumulative_cpu_use_->Set(0);
}
// Start metricscollectorservice
android::sp<BnMetricsCollectorServiceImpl> metrics_collector_service =
new BnMetricsCollectorServiceImpl(this);
android::status_t status = android::defaultServiceManager()->addService(
metrics_collector_service->getInterfaceDescriptor(),
metrics_collector_service);
CHECK(status == android::OK)
<< "failed to register service metricscollectorservice";
// Watch Binder events in the main loop
brillo::BinderWatcher binder_watcher;
CHECK(binder_watcher.Init()) << "Binder FD watcher init failed";
return brillo::Daemon::Run();
}
uint32_t MetricsCollector::GetOsVersionHash() {
brillo::OsReleaseReader reader;
reader.Load();
string version;
if (!reader.GetString(metrics::kProductVersion, &version)) {
LOG(ERROR) << "failed to read the product version.";
version = metrics::kDefaultVersion;
}
uint32_t version_hash = base::Hash(version);
if (testing_) {
version_hash = 42; // return any plausible value for the hash
}
return version_hash;
}
void MetricsCollector::Init(bool testing, MetricsLibraryInterface* metrics_lib,
const string& diskstats_path,
const base::FilePath& private_metrics_directory,
const base::FilePath& shared_metrics_directory) {
CHECK(metrics_lib);
testing_ = testing;
shared_metrics_directory_ = shared_metrics_directory;
metrics_lib_ = metrics_lib;
daily_active_use_.reset(new PersistentInteger("Platform.UseTime.PerDay",
private_metrics_directory));
version_cumulative_active_use_.reset(new PersistentInteger(
"Platform.CumulativeUseTime", private_metrics_directory));
version_cumulative_cpu_use_.reset(new PersistentInteger(
"Platform.CumulativeCpuTime", private_metrics_directory));
kernel_crash_interval_.reset(new PersistentInteger(
"Platform.KernelCrashInterval", private_metrics_directory));
unclean_shutdown_interval_.reset(new PersistentInteger(
"Platform.UncleanShutdownInterval", private_metrics_directory));
user_crash_interval_.reset(new PersistentInteger("Platform.UserCrashInterval",
private_metrics_directory));
any_crashes_daily_count_.reset(new PersistentInteger(
"Platform.AnyCrashes.PerDay", private_metrics_directory));
any_crashes_weekly_count_.reset(new PersistentInteger(
"Platform.AnyCrashes.PerWeek", private_metrics_directory));
user_crashes_daily_count_.reset(new PersistentInteger(
"Platform.UserCrashes.PerDay", private_metrics_directory));
user_crashes_weekly_count_.reset(new PersistentInteger(
"Platform.UserCrashes.PerWeek", private_metrics_directory));
kernel_crashes_daily_count_.reset(new PersistentInteger(
"Platform.KernelCrashes.PerDay", private_metrics_directory));
kernel_crashes_weekly_count_.reset(new PersistentInteger(
"Platform.KernelCrashes.PerWeek", private_metrics_directory));
kernel_crashes_version_count_.reset(new PersistentInteger(
"Platform.KernelCrashesSinceUpdate", private_metrics_directory));
unclean_shutdowns_daily_count_.reset(new PersistentInteger(
"Platform.UncleanShutdown.PerDay", private_metrics_directory));
unclean_shutdowns_weekly_count_.reset(new PersistentInteger(
"Platform.UncleanShutdowns.PerWeek", private_metrics_directory));
daily_cycle_.reset(
new PersistentInteger("daily.cycle", private_metrics_directory));
weekly_cycle_.reset(
new PersistentInteger("weekly.cycle", private_metrics_directory));
version_cycle_.reset(
new PersistentInteger("version.cycle", private_metrics_directory));
disk_usage_collector_.reset(new DiskUsageCollector(metrics_lib_));
averaged_stats_collector_.reset(
new AveragedStatisticsCollector(metrics_lib_, diskstats_path,
kVmStatFileName));
cpu_usage_collector_.reset(new CpuUsageCollector(metrics_lib_));
}
int MetricsCollector::OnInit() {
int return_code = brillo::Daemon::OnInit();
if (return_code != EX_OK)
return return_code;
StatsReporterInit();
// Start collecting meminfo stats.
ScheduleMeminfoCallback(kMetricMeminfoInterval);
memuse_final_time_ = GetActiveTime() + kMemuseIntervals[0];
ScheduleMemuseCallback(kMemuseIntervals[0]);
if (testing_)
return EX_OK;
weave_service_subscription_ = weaved::Service::Connect(
brillo::MessageLoop::current(),
base::Bind(&MetricsCollector::OnWeaveServiceConnected,
weak_ptr_factory_.GetWeakPtr()));
latest_cpu_use_microseconds_ = cpu_usage_collector_->GetCumulativeCpuUse();
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&MetricsCollector::HandleUpdateStatsTimeout,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
return EX_OK;
}
void MetricsCollector::OnWeaveServiceConnected(
const std::weak_ptr<weaved::Service>& service) {
service_ = service;
auto weave_service = service_.lock();
if (!weave_service)
return;
weave_service->AddComponent(kWeaveComponent, {kWeaveTrait}, nullptr);
weave_service->AddCommandHandler(
kWeaveComponent, kWeaveTrait, "enableAnalyticsReporting",
base::Bind(&MetricsCollector::OnEnableMetrics,
weak_ptr_factory_.GetWeakPtr()));
weave_service->AddCommandHandler(
kWeaveComponent, kWeaveTrait, "disableAnalyticsReporting",
base::Bind(&MetricsCollector::OnDisableMetrics,
weak_ptr_factory_.GetWeakPtr()));
UpdateWeaveState();
}
void MetricsCollector::OnEnableMetrics(
std::unique_ptr<weaved::Command> command) {
if (base::WriteFile(
shared_metrics_directory_.Append(metrics::kConsentFileName), "", 0) !=
0) {
PLOG(ERROR) << "Could not create the consent file.";
command->Abort("metrics_error", "Could not create the consent file",
nullptr);
return;
}
UpdateWeaveState();
command->Complete({}, nullptr);
}
void MetricsCollector::OnDisableMetrics(
std::unique_ptr<weaved::Command> command) {
if (!base::DeleteFile(
shared_metrics_directory_.Append(metrics::kConsentFileName), false)) {
PLOG(ERROR) << "Could not delete the consent file.";
command->Abort("metrics_error", "Could not delete the consent file",
nullptr);
return;
}
UpdateWeaveState();
command->Complete({}, nullptr);
}
void MetricsCollector::UpdateWeaveState() {
auto weave_service = service_.lock();
if (!weave_service)
return;
std::string enabled =
metrics_lib_->AreMetricsEnabled() ? "enabled" : "disabled";
if (!weave_service->SetStateProperty(kWeaveComponent, kWeaveTrait,
"analyticsReportingState",
*brillo::ToValue(enabled),
nullptr)) {
LOG(ERROR) << "failed to update weave's state";
}
}
void MetricsCollector::ProcessUserCrash() {
// Counts the active time up to now.
UpdateStats(TimeTicks::Now(), Time::Now());
// Reports the active use time since the last crash and resets it.
SendAndResetCrashIntervalSample(user_crash_interval_);
any_crashes_daily_count_->Add(1);
any_crashes_weekly_count_->Add(1);
user_crashes_daily_count_->Add(1);
user_crashes_weekly_count_->Add(1);
}
void MetricsCollector::ProcessKernelCrash() {
// Counts the active time up to now.
UpdateStats(TimeTicks::Now(), Time::Now());
// Reports the active use time since the last crash and resets it.
SendAndResetCrashIntervalSample(kernel_crash_interval_);
any_crashes_daily_count_->Add(1);
any_crashes_weekly_count_->Add(1);
kernel_crashes_daily_count_->Add(1);
kernel_crashes_weekly_count_->Add(1);
kernel_crashes_version_count_->Add(1);
}
void MetricsCollector::ProcessUncleanShutdown() {
// Counts the active time up to now.
UpdateStats(TimeTicks::Now(), Time::Now());
// Reports the active use time since the last crash and resets it.
SendAndResetCrashIntervalSample(unclean_shutdown_interval_);
unclean_shutdowns_daily_count_->Add(1);
unclean_shutdowns_weekly_count_->Add(1);
any_crashes_daily_count_->Add(1);
any_crashes_weekly_count_->Add(1);
}
bool MetricsCollector::CheckSystemCrash(const string& crash_file) {
FilePath crash_detected(crash_file);
if (!base::PathExists(crash_detected))
return false;
// Deletes the crash-detected file so that the daemon doesn't report
// another kernel crash in case it's restarted.
base::DeleteFile(crash_detected, false); // not recursive
return true;
}
void MetricsCollector::StatsReporterInit() {
disk_usage_collector_->Schedule();
cpu_usage_collector_->Init();
cpu_usage_collector_->Schedule();
// Don't start a collection cycle during the first run to avoid delaying the
// boot.
averaged_stats_collector_->ScheduleWait();
}
void MetricsCollector::ScheduleMeminfoCallback(int wait) {
if (testing_) {
return;
}
base::TimeDelta waitDelta = base::TimeDelta::FromSeconds(wait);
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&MetricsCollector::MeminfoCallback,
weak_ptr_factory_.GetWeakPtr(), waitDelta),
waitDelta);
}
void MetricsCollector::MeminfoCallback(base::TimeDelta wait) {
string meminfo_raw;
const FilePath meminfo_path(kMeminfoFileName);
if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
return;
}
// Make both calls even if the first one fails.
if (ProcessMeminfo(meminfo_raw)) {
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&MetricsCollector::MeminfoCallback,
weak_ptr_factory_.GetWeakPtr(), wait),
wait);
}
}
// static
bool MetricsCollector::ReadFileToUint64(const base::FilePath& path,
uint64_t* value) {
std::string content;
if (!base::ReadFileToString(path, &content)) {
PLOG(WARNING) << "cannot read " << path.MaybeAsASCII();
return false;
}
// Remove final newline.
base::TrimWhitespaceASCII(content, base::TRIM_TRAILING, &content);
if (!base::StringToUint64(content, value)) {
LOG(WARNING) << "invalid integer: " << content;
return false;
}
return true;
}
bool MetricsCollector::ReportZram(const base::FilePath& zram_dir) {
// Data sizes are in bytes. |zero_pages| is in number of pages.
uint64_t compr_data_size, orig_data_size, zero_pages;
const size_t page_size = 4096;
if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName),
&compr_data_size) ||
!ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) ||
!ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) {
return false;
}
// |orig_data_size| does not include zero-filled pages.
orig_data_size += zero_pages * page_size;
const int compr_data_size_mb = compr_data_size >> 20;
const int savings_mb = (orig_data_size - compr_data_size) >> 20;
const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size;
// Report compressed size in megabytes. 100 MB or less has little impact.
SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50);
SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50);
// The compression ratio is multiplied by 100 for better resolution. The
// ratios of interest are between 1 and 6 (100% and 600% as reported). We
// don't want samples when very little memory is being compressed.
if (compr_data_size_mb >= 1) {
SendSample("Platform.ZramCompressionRatioPercent",
orig_data_size * 100 / compr_data_size, 100, 600, 50);
}
// The values of interest for zero_pages are between 1MB and 1GB. The units
// are number of pages.
SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50);
SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50);
return true;
}
bool MetricsCollector::ProcessMeminfo(const string& meminfo_raw) {
static const MeminfoRecord fields_array[] = {
{ "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
{ "MemFree", "MemFree" },
{ "Buffers", "Buffers" },
{ "Cached", "Cached" },
// { "SwapCached", "SwapCached" },
{ "Active", "Active" },
{ "Inactive", "Inactive" },
{ "ActiveAnon", "Active(anon)" },
{ "InactiveAnon", "Inactive(anon)" },
{ "ActiveFile" , "Active(file)" },
{ "InactiveFile", "Inactive(file)" },
{ "Unevictable", "Unevictable", kMeminfoOp_HistLog },
// { "Mlocked", "Mlocked" },
{ "SwapTotal", "SwapTotal", kMeminfoOp_SwapTotal },
{ "SwapFree", "SwapFree", kMeminfoOp_SwapFree },
// { "Dirty", "Dirty" },
// { "Writeback", "Writeback" },
{ "AnonPages", "AnonPages" },
{ "Mapped", "Mapped" },
{ "Shmem", "Shmem", kMeminfoOp_HistLog },
{ "Slab", "Slab", kMeminfoOp_HistLog },
// { "SReclaimable", "SReclaimable" },
// { "SUnreclaim", "SUnreclaim" },
};
vector<MeminfoRecord> fields(fields_array,
fields_array + arraysize(fields_array));
if (!FillMeminfo(meminfo_raw, &fields)) {
return false;
}
int total_memory = fields[0].value;
if (total_memory == 0) {
// this "cannot happen"
LOG(WARNING) << "borked meminfo parser";
return false;
}
int swap_total = 0;
int swap_free = 0;
// Send all fields retrieved, except total memory.
for (unsigned int i = 1; i < fields.size(); i++) {
string metrics_name = base::StringPrintf("Platform.Meminfo%s",
fields[i].name);
int percent;
switch (fields[i].op) {
case kMeminfoOp_HistPercent:
// report value as percent of total memory
percent = fields[i].value * 100 / total_memory;
SendLinearSample(metrics_name, percent, 100, 101);
break;
case kMeminfoOp_HistLog:
// report value in kbytes, log scale, 4Gb max
SendSample(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100);
break;
case kMeminfoOp_SwapTotal:
swap_total = fields[i].value;
case kMeminfoOp_SwapFree:
swap_free = fields[i].value;
break;
}
}
if (swap_total > 0) {
int swap_used = swap_total - swap_free;
int swap_used_percent = swap_used * 100 / swap_total;
SendSample("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100);
SendLinearSample("Platform.MeminfoSwapUsed.Percent", swap_used_percent,
100, 101);
}
return true;
}
bool MetricsCollector::FillMeminfo(const string& meminfo_raw,
vector<MeminfoRecord>* fields) {
vector<std::string> lines =
base::SplitString(meminfo_raw, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
// Scan meminfo output and collect field values. Each field name has to
// match a meminfo entry (case insensitive) after removing non-alpha
// characters from the entry.
size_t ifield = 0;
for (size_t iline = 0;
iline < lines.size() && ifield < fields->size();
iline++) {
vector<string> tokens =
base::SplitString(lines[iline], ": ", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) {
// Name matches. Parse value and save.
if (!base::StringToInt(tokens[1], &(*fields)[ifield].value)) {
LOG(WARNING) << "Cound not convert " << tokens[1] << " to int";
return false;
}
ifield++;
}
}
if (ifield < fields->size()) {
// End of input reached while scanning.
LOG(WARNING) << "cannot find field " << (*fields)[ifield].match
<< " and following";
return false;
}
return true;
}
void MetricsCollector::ScheduleMemuseCallback(double interval) {
if (testing_) {
return;
}
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&MetricsCollector::MemuseCallback,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(interval));
}
void MetricsCollector::MemuseCallback() {
// Since we only care about active time (i.e. uptime minus sleep time) but
// the callbacks are driven by real time (uptime), we check if we should
// reschedule this callback due to intervening sleep periods.
double now = GetActiveTime();
// Avoid intervals of less than one second.
double remaining_time = ceil(memuse_final_time_ - now);
if (remaining_time > 0) {
ScheduleMemuseCallback(remaining_time);
} else {
// Report stats and advance the measurement interval unless there are
// errors or we've completed the last interval.
if (MemuseCallbackWork() &&
memuse_interval_index_ < arraysize(kMemuseIntervals)) {
double interval = kMemuseIntervals[memuse_interval_index_++];
memuse_final_time_ = now + interval;
ScheduleMemuseCallback(interval);
}
}
}
bool MetricsCollector::MemuseCallbackWork() {
string meminfo_raw;
const FilePath meminfo_path(kMeminfoFileName);
if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
return false;
}
return ProcessMemuse(meminfo_raw);
}
bool MetricsCollector::ProcessMemuse(const string& meminfo_raw) {
static const MeminfoRecord fields_array[] = {
{ "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory
{ "ActiveAnon", "Active(anon)" },
{ "InactiveAnon", "Inactive(anon)" },
};
vector<MeminfoRecord> fields(fields_array,
fields_array + arraysize(fields_array));
if (!FillMeminfo(meminfo_raw, &fields)) {
return false;
}
int total = fields[0].value;
int active_anon = fields[1].value;
int inactive_anon = fields[2].value;
if (total == 0) {
// this "cannot happen"
LOG(WARNING) << "borked meminfo parser";
return false;
}
string metrics_name = base::StringPrintf("Platform.MemuseAnon%d",
memuse_interval_index_);
SendLinearSample(metrics_name, (active_anon + inactive_anon) * 100 / total,
100, 101);
return true;
}
void MetricsCollector::SendSample(const string& name, int sample,
int min, int max, int nbuckets) {
metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
}
void MetricsCollector::SendKernelCrashesCumulativeCountStats() {
// Report the number of crashes for this OS version, but don't clear the
// counter. It is cleared elsewhere on version change.
int64_t crashes_count = kernel_crashes_version_count_->Get();
SendSample(kernel_crashes_version_count_->Name(),
crashes_count,
1, // value of first bucket
500, // value of last bucket
100); // number of buckets
int64_t cpu_use_ms = version_cumulative_cpu_use_->Get();
SendSample(version_cumulative_cpu_use_->Name(),
cpu_use_ms / 1000, // stat is in seconds
1, // device may be used very little...
8 * 1000 * 1000, // ... or a lot (a little over 90 days)
100);
// On the first run after an autoupdate, cpu_use_ms and active_use_seconds
// can be zero. Avoid division by zero.
if (cpu_use_ms > 0) {
// Send the crash frequency since update in number of crashes per CPU year.
SendSample("Logging.KernelCrashesPerCpuYear",
crashes_count * kSecondsPerDay * 365 * 1000 / cpu_use_ms,
1,
1000 * 1000, // about one crash every 30s of CPU time
100);
}
int64_t active_use_seconds = version_cumulative_active_use_->Get();
if (active_use_seconds > 0) {
SendSample(version_cumulative_active_use_->Name(),
active_use_seconds,
1, // device may be used very little...
8 * 1000 * 1000, // ... or a lot (about 90 days)
100);
// Same as above, but per year of active time.
SendSample("Logging.KernelCrashesPerActiveYear",
crashes_count * kSecondsPerDay * 365 / active_use_seconds,
1,
1000 * 1000, // about one crash every 30s of active time
100);
}
}
void MetricsCollector::SendAndResetDailyUseSample(
const unique_ptr<PersistentInteger>& use) {
SendSample(use->Name(),
use->GetAndClear(),
1, // value of first bucket
kSecondsPerDay, // value of last bucket
50); // number of buckets
}
void MetricsCollector::SendAndResetCrashIntervalSample(
const unique_ptr<PersistentInteger>& interval) {
SendSample(interval->Name(),
interval->GetAndClear(),
1, // value of first bucket
4 * kSecondsPerWeek, // value of last bucket
50); // number of buckets
}
void MetricsCollector::SendAndResetCrashFrequencySample(
const unique_ptr<PersistentInteger>& frequency) {
SendSample(frequency->Name(),
frequency->GetAndClear(),
1, // value of first bucket
100, // value of last bucket
50); // number of buckets
}
void MetricsCollector::SendLinearSample(const string& name, int sample,
int max, int nbuckets) {
// TODO(semenzato): add a proper linear histogram to the Chrome external
// metrics API.
LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale";
metrics_lib_->SendEnumToUMA(name, sample, max);
}
void MetricsCollector::UpdateStats(TimeTicks now_ticks,
Time now_wall_time) {
const int elapsed_seconds = (now_ticks - last_update_stats_time_).InSeconds();
daily_active_use_->Add(elapsed_seconds);
version_cumulative_active_use_->Add(elapsed_seconds);
user_crash_interval_->Add(elapsed_seconds);
kernel_crash_interval_->Add(elapsed_seconds);
TimeDelta cpu_use = cpu_usage_collector_->GetCumulativeCpuUse();
version_cumulative_cpu_use_->Add(
(cpu_use - latest_cpu_use_microseconds_).InMilliseconds());
latest_cpu_use_microseconds_ = cpu_use;
last_update_stats_time_ = now_ticks;
const TimeDelta since_epoch = now_wall_time - Time::UnixEpoch();
const int day = since_epoch.InDays();
const int week = day / 7;
if (daily_cycle_->Get() != day) {
daily_cycle_->Set(day);
SendAndResetDailyUseSample(daily_active_use_);
SendAndResetCrashFrequencySample(any_crashes_daily_count_);
SendAndResetCrashFrequencySample(user_crashes_daily_count_);
SendAndResetCrashFrequencySample(kernel_crashes_daily_count_);
SendAndResetCrashFrequencySample(unclean_shutdowns_daily_count_);
SendKernelCrashesCumulativeCountStats();
}
if (weekly_cycle_->Get() != week) {
weekly_cycle_->Set(week);
SendAndResetCrashFrequencySample(any_crashes_weekly_count_);
SendAndResetCrashFrequencySample(user_crashes_weekly_count_);
SendAndResetCrashFrequencySample(kernel_crashes_weekly_count_);
SendAndResetCrashFrequencySample(unclean_shutdowns_weekly_count_);
}
}
void MetricsCollector::HandleUpdateStatsTimeout() {
UpdateStats(TimeTicks::Now(), Time::Now());
base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&MetricsCollector::HandleUpdateStatsTimeout,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
}

View File

@ -1,284 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_METRICS_COLLECTOR_H_
#define METRICS_METRICS_COLLECTOR_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/memory/weak_ptr.h>
#include <base/time/time.h>
#include <brillo/binder_watcher.h>
#include <brillo/daemons/daemon.h>
#include <libweaved/command.h>
#include <libweaved/service.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include "collectors/averaged_statistics_collector.h"
#include "collectors/cpu_usage_collector.h"
#include "collectors/disk_usage_collector.h"
#include "metrics/metrics_library.h"
#include "persistent_integer.h"
using chromeos_metrics::PersistentInteger;
using std::unique_ptr;
class MetricsCollector : public brillo::Daemon {
public:
MetricsCollector();
~MetricsCollector();
// Initializes metrics class variables.
void Init(bool testing,
MetricsLibraryInterface* metrics_lib,
const std::string& diskstats_path,
const base::FilePath& private_metrics_directory,
const base::FilePath& shared_metrics_directory);
// Initializes the daemon.
int OnInit() override;
// Does all the work.
int Run() override;
// Returns the active time since boot (uptime minus sleep time) in seconds.
static double GetActiveTime();
// Updates the active use time and logs time between user-space
// process crashes. Called via MetricsCollectorServiceTrampoline.
void ProcessUserCrash();
protected:
// Used also by the unit tests.
static const char kComprDataSizeName[];
static const char kOrigDataSizeName[];
static const char kZeroPagesName[];
private:
friend class MetricsCollectorTest;
FRIEND_TEST(MetricsCollectorTest, CheckSystemCrash);
FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoCurrent);
FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoLast);
FRIEND_TEST(MetricsCollectorTest, GetHistogramPath);
FRIEND_TEST(MetricsCollectorTest, IsNewEpoch);
FRIEND_TEST(MetricsCollectorTest, MessageFilter);
FRIEND_TEST(MetricsCollectorTest, ProcessKernelCrash);
FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo);
FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo2);
FRIEND_TEST(MetricsCollectorTest, ProcessUncleanShutdown);
FRIEND_TEST(MetricsCollectorTest, ProcessUserCrash);
FRIEND_TEST(MetricsCollectorTest, ReportCrashesDailyFrequency);
FRIEND_TEST(MetricsCollectorTest, ReportKernelCrashInterval);
FRIEND_TEST(MetricsCollectorTest, ReportUncleanShutdownInterval);
FRIEND_TEST(MetricsCollectorTest, ReportUserCrashInterval);
FRIEND_TEST(MetricsCollectorTest, SendSample);
FRIEND_TEST(MetricsCollectorTest, SendZramMetrics);
// Type of scale to use for meminfo histograms. For most of them we use
// percent of total RAM, but for some we use absolute numbers, usually in
// megabytes, on a log scale from 0 to 4000, and 0 to 8000 for compressed
// swap (since it can be larger than total RAM).
enum MeminfoOp {
kMeminfoOp_HistPercent = 0,
kMeminfoOp_HistLog,
kMeminfoOp_SwapTotal,
kMeminfoOp_SwapFree,
};
// Record for retrieving and reporting values from /proc/meminfo.
struct MeminfoRecord {
const char* name; // print name
const char* match; // string to match in output of /proc/meminfo
MeminfoOp op; // histogram scale selector, or other operator
int value; // value from /proc/meminfo
};
// Enables metrics reporting.
void OnEnableMetrics(std::unique_ptr<weaved::Command> command);
// Disables metrics reporting.
void OnDisableMetrics(std::unique_ptr<weaved::Command> command);
// Updates the weave device state.
void UpdateWeaveState();
// Updates the active use time and logs time between kernel crashes.
void ProcessKernelCrash();
// Updates the active use time and logs time between unclean shutdowns.
void ProcessUncleanShutdown();
// Checks if a kernel crash has been detected and returns true if
// so. The method assumes that a kernel crash has happened if
// |crash_file| exists. It removes the file immediately if it
// exists, so it must not be called more than once.
bool CheckSystemCrash(const std::string& crash_file);
// Sends a regular (exponential) histogram sample to Chrome for
// transport to UMA. See MetricsLibrary::SendToUMA in
// metrics_library.h for a description of the arguments.
void SendSample(const std::string& name, int sample,
int min, int max, int nbuckets);
// Sends a linear histogram sample to Chrome for transport to UMA. See
// MetricsLibrary::SendToUMA in metrics_library.h for a description of the
// arguments.
void SendLinearSample(const std::string& name, int sample,
int max, int nbuckets);
// Sends various cumulative kernel crash-related stats, for instance the
// total number of kernel crashes since the last version update.
void SendKernelCrashesCumulativeCountStats();
// Sends a sample representing the number of seconds of active use
// for a 24-hour period and reset |use|.
void SendAndResetDailyUseSample(const unique_ptr<PersistentInteger>& use);
// Sends a sample representing a time interval between two crashes of the
// same type and reset |interval|.
void SendAndResetCrashIntervalSample(
const unique_ptr<PersistentInteger>& interval);
// Sends a sample representing a frequency of crashes of some type and reset
// |frequency|.
void SendAndResetCrashFrequencySample(
const unique_ptr<PersistentInteger>& frequency);
// Initializes vm and disk stats reporting.
void StatsReporterInit();
// Schedules meminfo collection callback.
void ScheduleMeminfoCallback(int wait);
// Reports memory statistics. Reschedules callback on success.
void MeminfoCallback(base::TimeDelta wait);
// Parses content of /proc/meminfo and sends fields of interest to UMA.
// Returns false on errors. |meminfo_raw| contains the content of
// /proc/meminfo.
bool ProcessMeminfo(const std::string& meminfo_raw);
// Parses meminfo data from |meminfo_raw|. |fields| is a vector containing
// the fields of interest. The order of the fields must be the same in which
// /proc/meminfo prints them. The result of parsing fields[i] is placed in
// fields[i].value.
bool FillMeminfo(const std::string& meminfo_raw,
std::vector<MeminfoRecord>* fields);
// Schedule a memory use callback in |interval| seconds.
void ScheduleMemuseCallback(double interval);
// Calls MemuseCallbackWork, and possibly schedules next callback, if enough
// active time has passed. Otherwise reschedules itself to simulate active
// time callbacks (i.e. wall clock time minus sleep time).
void MemuseCallback();
// Reads /proc/meminfo and sends total anonymous memory usage to UMA.
bool MemuseCallbackWork();
// Parses meminfo data and sends it to UMA.
bool ProcessMemuse(const std::string& meminfo_raw);
// Reads the current OS version from /etc/lsb-release and hashes it
// to a unsigned 32-bit int.
uint32_t GetOsVersionHash();
// Updates stats, additionally sending them to UMA if enough time has elapsed
// since the last report.
void UpdateStats(base::TimeTicks now_ticks, base::Time now_wall_time);
// Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
void HandleUpdateStatsTimeout();
// Reports zram statistics.
bool ReportZram(const base::FilePath& zram_dir);
// Reads a string from a file and converts it to uint64_t.
static bool ReadFileToUint64(const base::FilePath& path, uint64_t* value);
// Callback invoked when a connection to weaved's service is established
// over Binder interface.
void OnWeaveServiceConnected(const std::weak_ptr<weaved::Service>& service);
// VARIABLES
// Test mode.
bool testing_;
// Publicly readable metrics directory.
base::FilePath shared_metrics_directory_;
// The metrics library handle.
MetricsLibraryInterface* metrics_lib_;
// The last time that UpdateStats() was called.
base::TimeTicks last_update_stats_time_;
// End time of current memuse stat collection interval.
double memuse_final_time_;
// Selects the wait time for the next memory use callback.
unsigned int memuse_interval_index_;
// Used internally by GetIncrementalCpuUse() to return the CPU utilization
// between calls.
base::TimeDelta latest_cpu_use_microseconds_;
// Persistent values and accumulators for crash statistics.
unique_ptr<PersistentInteger> daily_cycle_;
unique_ptr<PersistentInteger> weekly_cycle_;
unique_ptr<PersistentInteger> version_cycle_;
// Active use accumulated in a day.
unique_ptr<PersistentInteger> daily_active_use_;
// Active use accumulated since the latest version update.
unique_ptr<PersistentInteger> version_cumulative_active_use_;
// The CPU time accumulator. This contains the CPU time, in milliseconds,
// used by the system since the most recent OS version update.
unique_ptr<PersistentInteger> version_cumulative_cpu_use_;
unique_ptr<PersistentInteger> user_crash_interval_;
unique_ptr<PersistentInteger> kernel_crash_interval_;
unique_ptr<PersistentInteger> unclean_shutdown_interval_;
unique_ptr<PersistentInteger> any_crashes_daily_count_;
unique_ptr<PersistentInteger> any_crashes_weekly_count_;
unique_ptr<PersistentInteger> user_crashes_daily_count_;
unique_ptr<PersistentInteger> user_crashes_weekly_count_;
unique_ptr<PersistentInteger> kernel_crashes_daily_count_;
unique_ptr<PersistentInteger> kernel_crashes_weekly_count_;
unique_ptr<PersistentInteger> kernel_crashes_version_count_;
unique_ptr<PersistentInteger> unclean_shutdowns_daily_count_;
unique_ptr<PersistentInteger> unclean_shutdowns_weekly_count_;
unique_ptr<CpuUsageCollector> cpu_usage_collector_;
unique_ptr<DiskUsageCollector> disk_usage_collector_;
unique_ptr<AveragedStatisticsCollector> averaged_stats_collector_;
unique_ptr<weaved::Service::Subscription> weave_service_subscription_;
std::weak_ptr<weaved::Service> service_;
base::WeakPtrFactory<MetricsCollector> weak_ptr_factory_{this};
};
#endif // METRICS_METRICS_COLLECTOR_H_

View File

@ -1,4 +0,0 @@
service metricscollector /system/bin/metrics_collector --foreground --logtosyslog
class late_start
user metrics_coll
group metrics_coll

View File

@ -1,98 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <base/at_exit.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include <rootdev.h>
#include "constants.h"
#include "metrics_collector.h"
// Returns the path to the disk stats in the sysfs. Returns the null string if
// it cannot find the disk stats file.
static
const std::string MetricsMainDiskStatsPath() {
char dev_path_cstr[PATH_MAX];
std::string dev_prefix = "/dev/block/";
std::string dev_path;
int ret = rootdev(dev_path_cstr, sizeof(dev_path_cstr), true, true);
if (ret != 0) {
LOG(WARNING) << "error " << ret << " determining root device";
return "";
}
dev_path = dev_path_cstr;
// Check that rootdev begins with "/dev/block/".
if (!base::StartsWith(dev_path, dev_prefix,
base::CompareCase::INSENSITIVE_ASCII)) {
LOG(WARNING) << "unexpected root device " << dev_path;
return "";
}
return "/sys/class/block/" + dev_path.substr(dev_prefix.length()) + "/stat";
}
int main(int argc, char** argv) {
DEFINE_bool(foreground, false, "Don't daemonize");
DEFINE_string(private_directory, metrics::kMetricsCollectorDirectory,
"Path to the private directory used by metrics_collector "
"(testing only)");
DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory,
"Path to the shared metrics directory, used by "
"metrics_collector, metricsd and all metrics clients "
"(testing only)");
DEFINE_bool(logtostderr, false, "Log to standard error");
DEFINE_bool(logtosyslog, false, "Log to syslog");
brillo::FlagHelper::Init(argc, argv, "Chromium OS Metrics Daemon");
int logging_location = (FLAGS_foreground ? brillo::kLogToStderr
: brillo::kLogToSyslog);
if (FLAGS_logtosyslog)
logging_location = brillo::kLogToSyslog;
if (FLAGS_logtostderr)
logging_location = brillo::kLogToStderr;
// Also log to stderr when not running as daemon.
brillo::InitLog(logging_location | brillo::kLogHeader);
if (FLAGS_logtostderr && FLAGS_logtosyslog) {
LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set";
return 1;
}
if (!FLAGS_foreground && daemon(0, 0) != 0) {
return errno;
}
MetricsLibrary metrics_lib;
metrics_lib.InitWithNoCaching();
MetricsCollector daemon;
daemon.Init(false,
&metrics_lib,
MetricsMainDiskStatsPath(),
base::FilePath(FLAGS_private_directory),
base::FilePath(FLAGS_shared_directory));
daemon.Run();
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Client interface to IMetricsCollectorService.
#include "metrics/metrics_collector_service_client.h"
#include <base/logging.h>
#include <binder/IServiceManager.h>
#include <utils/String16.h>
#include "android/brillo/metrics/IMetricsCollectorService.h"
namespace {
const char kMetricsCollectorServiceName[] =
"android.brillo.metrics.IMetricsCollectorService";
}
bool MetricsCollectorServiceClient::Init() {
const android::String16 name(kMetricsCollectorServiceName);
metrics_collector_service_ = android::interface_cast<
android::brillo::metrics::IMetricsCollectorService>(
android::defaultServiceManager()->checkService(name));
if (metrics_collector_service_ == nullptr)
LOG(ERROR) << "Unable to lookup service " << kMetricsCollectorServiceName;
return metrics_collector_service_ != nullptr;
}
bool MetricsCollectorServiceClient::notifyUserCrash() {
if (metrics_collector_service_ == nullptr)
return false;
metrics_collector_service_->notifyUserCrash();
return true;
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "metrics_collector_service_impl.h"
#include <binder/IServiceManager.h>
#include <binder/Status.h>
#include <utils/Errors.h>
#include "metrics_collector.h"
using namespace android;
BnMetricsCollectorServiceImpl::BnMetricsCollectorServiceImpl(
MetricsCollector* metrics_collector)
: metrics_collector_(metrics_collector) {
}
android::binder::Status BnMetricsCollectorServiceImpl::notifyUserCrash() {
metrics_collector_->ProcessUserCrash();
return android::binder::Status::ok();
}

View File

@ -1,48 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
#define METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
// metrics_collector binder service implementation. Constructed by
// MetricsCollector.
#include "android/brillo/metrics/BnMetricsCollectorService.h"
#include <binder/Status.h>
class MetricsCollector;
class BnMetricsCollectorServiceImpl
: public android::brillo::metrics::BnMetricsCollectorService {
public:
// Passed a this pointer from the MetricsCollector object that constructs us.
explicit BnMetricsCollectorServiceImpl(
MetricsCollector* metrics_collector_service);
virtual ~BnMetricsCollectorServiceImpl() = default;
// Called by crash_reporter to report a userspace crash event. We relay
// this to MetricsCollector.
android::binder::Status notifyUserCrash();
private:
// MetricsCollector object that constructs us, we use this to call back
// to it.
MetricsCollector* metrics_collector_;
};
#endif // METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_

View File

@ -1,170 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <vector>
#include <base/at_exit.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/flag_helper.h>
#include <gtest/gtest.h>
#include "constants.h"
#include "metrics_collector.h"
#include "metrics/metrics_library_mock.h"
#include "persistent_integer_mock.h"
using base::FilePath;
using base::TimeDelta;
using std::string;
using std::vector;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::Return;
using ::testing::StrictMock;
using chromeos_metrics::PersistentIntegerMock;
class MetricsCollectorTest : public testing::Test {
protected:
virtual void SetUp() {
brillo::FlagHelper::Init(0, nullptr, "");
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
base::FilePath private_dir = temp_dir_.path().Append("private");
base::FilePath shared_dir = temp_dir_.path().Append("shared");
EXPECT_TRUE(base::CreateDirectory(private_dir));
EXPECT_TRUE(base::CreateDirectory(shared_dir));
daemon_.Init(true, &metrics_lib_, "", private_dir, shared_dir);
}
// Adds a metrics library mock expectation that the specified metric
// will be generated.
void ExpectSample(const std::string& name, int sample) {
EXPECT_CALL(metrics_lib_, SendToUMA(name, sample, _, _, _))
.Times(1)
.WillOnce(Return(true))
.RetiresOnSaturation();
}
// Creates or overwrites the file in |path| so that it contains the printable
// representation of |value|.
void CreateUint64ValueFile(const base::FilePath& path, uint64_t value) {
std::string value_string = base::Uint64ToString(value);
ASSERT_EQ(value_string.length(),
base::WriteFile(path, value_string.c_str(),
value_string.length()));
}
// The MetricsCollector under test.
MetricsCollector daemon_;
// Temporary directory used for tests.
base::ScopedTempDir temp_dir_;
// Mocks. They are strict mock so that all unexpected
// calls are marked as failures.
StrictMock<MetricsLibraryMock> metrics_lib_;
};
TEST_F(MetricsCollectorTest, SendSample) {
ExpectSample("Dummy.Metric", 3);
daemon_.SendSample("Dummy.Metric", /* sample */ 3,
/* min */ 1, /* max */ 100, /* buckets */ 50);
}
TEST_F(MetricsCollectorTest, ProcessMeminfo) {
string meminfo =
"MemTotal: 2000000 kB\nMemFree: 500000 kB\n"
"Buffers: 1000000 kB\nCached: 213652 kB\n"
"SwapCached: 0 kB\nActive: 133400 kB\n"
"Inactive: 183396 kB\nActive(anon): 92984 kB\n"
"Inactive(anon): 58860 kB\nActive(file): 40416 kB\n"
"Inactive(file): 124536 kB\nUnevictable: 0 kB\n"
"Mlocked: 0 kB\nSwapTotal: 0 kB\n"
"SwapFree: 0 kB\nDirty: 40 kB\n"
"Writeback: 0 kB\nAnonPages: 92652 kB\n"
"Mapped: 59716 kB\nShmem: 59196 kB\n"
"Slab: 16656 kB\nSReclaimable: 6132 kB\n"
"SUnreclaim: 10524 kB\nKernelStack: 1648 kB\n"
"PageTables: 2780 kB\nNFS_Unstable: 0 kB\n"
"Bounce: 0 kB\nWritebackTmp: 0 kB\n"
"CommitLimit: 970656 kB\nCommitted_AS: 1260528 kB\n"
"VmallocTotal: 122880 kB\nVmallocUsed: 12144 kB\n"
"VmallocChunk: 103824 kB\nDirectMap4k: 9636 kB\n"
"DirectMap2M: 1955840 kB\n";
// All enum calls must report percents.
EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100)).Times(AtLeast(1));
// Check that MemFree is correctly computed at 25%.
EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMemFree", 25, 100))
.Times(AtLeast(1));
// Check that we call SendToUma at least once (log histogram).
EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
.Times(AtLeast(1));
// Make sure we don't report fields not in the list.
EXPECT_CALL(metrics_lib_, SendToUMA("Platform.MeminfoMlocked", _, _, _, _))
.Times(0);
EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMlocked", _, _))
.Times(0);
EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
}
TEST_F(MetricsCollectorTest, ProcessMeminfo2) {
string meminfo = "MemTotal: 2000000 kB\nMemFree: 1000000 kB\n";
// Not enough fields.
EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
}
TEST_F(MetricsCollectorTest, SendZramMetrics) {
EXPECT_TRUE(daemon_.testing_);
// |compr_data_size| is the size in bytes of compressed data.
const uint64_t compr_data_size = 50 * 1000 * 1000;
// The constant '3' is a realistic but random choice.
// |orig_data_size| does not include zero pages.
const uint64_t orig_data_size = compr_data_size * 3;
const uint64_t page_size = 4096;
const uint64_t zero_pages = 10 * 1000 * 1000 / page_size;
CreateUint64ValueFile(
temp_dir_.path().Append(MetricsCollector::kComprDataSizeName),
compr_data_size);
CreateUint64ValueFile(
temp_dir_.path().Append(MetricsCollector::kOrigDataSizeName),
orig_data_size);
CreateUint64ValueFile(
temp_dir_.path().Append(MetricsCollector::kZeroPagesName), zero_pages);
const uint64_t real_orig_size = orig_data_size + zero_pages * page_size;
const uint64_t zero_ratio_percent =
zero_pages * page_size * 100 / real_orig_size;
// Ratio samples are in percents.
const uint64_t actual_ratio_sample = real_orig_size * 100 / compr_data_size;
EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _));
EXPECT_CALL(metrics_lib_,
SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _));
EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _));
EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _));
EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _));
EXPECT_TRUE(daemon_.ReportZram(temp_dir_.path()));
}

View File

@ -1,226 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "metrics/metrics_library.h"
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <binder/IServiceManager.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <utils/String16.h>
#include <cstdio>
#include <cstring>
#include "android/brillo/metrics/IMetricsd.h"
#include "constants.h"
static const char kCrosEventHistogramName[] = "Platform.CrOSEvent";
static const int kCrosEventHistogramMax = 100;
static const char kMetricsServiceName[] = "android.brillo.metrics.IMetricsd";
/* Add new cros events here.
*
* The index of the event is sent in the message, so please do not
* reorder the names.
*/
static const char *kCrosEventNames[] = {
"ModemManagerCommandSendFailure", // 0
"HwWatchdogReboot", // 1
"Cras.NoCodecsFoundAtBoot", // 2
"Chaps.DatabaseCorrupted", // 3
"Chaps.DatabaseRepairFailure", // 4
"Chaps.DatabaseCreateFailure", // 5
"Attestation.OriginSpecificExhausted", // 6
"SpringPowerSupply.Original.High", // 7
"SpringPowerSupply.Other.High", // 8
"SpringPowerSupply.Original.Low", // 9
"SpringPowerSupply.ChargerIdle", // 10
"TPM.NonZeroDictionaryAttackCounter", // 11
"TPM.EarlyResetDuringCommand", // 12
};
using android::binder::Status;
using android::brillo::metrics::IMetricsd;
using android::String16;
MetricsLibrary::MetricsLibrary() {}
MetricsLibrary::~MetricsLibrary() {}
// We take buffer and buffer_size as parameters in order to simplify testing
// of various alignments of the |device_name| with |buffer_size|.
bool MetricsLibrary::IsDeviceMounted(const char* device_name,
const char* mounts_file,
char* buffer,
int buffer_size,
bool* result) {
if (buffer == nullptr || buffer_size < 1)
return false;
int mounts_fd = open(mounts_file, O_RDONLY);
if (mounts_fd < 0)
return false;
// match_offset describes:
// -1 -- not beginning of line
// 0..strlen(device_name)-1 -- this offset in device_name is next to match
// strlen(device_name) -- matched full name, just need a space.
int match_offset = 0;
bool match = false;
while (!match) {
int read_size = read(mounts_fd, buffer, buffer_size);
if (read_size <= 0) {
if (errno == -EINTR)
continue;
break;
}
for (int i = 0; i < read_size; ++i) {
if (buffer[i] == '\n') {
match_offset = 0;
continue;
}
if (match_offset < 0) {
continue;
}
if (device_name[match_offset] == '\0') {
if (buffer[i] == ' ') {
match = true;
break;
}
match_offset = -1;
continue;
}
if (buffer[i] == device_name[match_offset]) {
++match_offset;
} else {
match_offset = -1;
}
}
}
close(mounts_fd);
*result = match;
return true;
}
bool MetricsLibrary::IsGuestMode() {
char buffer[256];
bool result = false;
if (!IsDeviceMounted("guestfs",
"/proc/mounts",
buffer,
sizeof(buffer),
&result)) {
return false;
}
return result && (access("/var/run/state/logged-in", F_OK) == 0);
}
bool MetricsLibrary::CheckService() {
if (metricsd_proxy_.get() &&
android::IInterface::asBinder(metricsd_proxy_)->isBinderAlive())
return true;
const String16 name(kMetricsServiceName);
metricsd_proxy_ = android::interface_cast<IMetricsd>(
android::defaultServiceManager()->checkService(name));
return metricsd_proxy_.get();
}
bool MetricsLibrary::AreMetricsEnabled() {
static struct stat stat_buffer;
time_t this_check_time = time(nullptr);
if (!use_caching_ || this_check_time != cached_enabled_time_) {
cached_enabled_time_ = this_check_time;
cached_enabled_ = stat(consent_file_.value().data(), &stat_buffer) >= 0;
}
return cached_enabled_;
}
void MetricsLibrary::Init() {
base::FilePath dir = base::FilePath(metrics::kSharedMetricsDirectory);
consent_file_ = dir.Append(metrics::kConsentFileName);
cached_enabled_ = false;
cached_enabled_time_ = 0;
use_caching_ = true;
}
void MetricsLibrary::InitWithNoCaching() {
Init();
use_caching_ = false;
}
void MetricsLibrary::InitForTest(const base::FilePath& metrics_directory) {
consent_file_ = metrics_directory.Append(metrics::kConsentFileName);
cached_enabled_ = false;
cached_enabled_time_ = 0;
use_caching_ = true;
}
bool MetricsLibrary::SendToUMA(
const std::string& name, int sample, int min, int max, int nbuckets) {
return CheckService() &&
metricsd_proxy_->recordHistogram(String16(name.c_str()), sample, min,
max, nbuckets)
.isOk();
}
bool MetricsLibrary::SendEnumToUMA(const std::string& name,
int sample,
int max) {
return CheckService() &&
metricsd_proxy_->recordLinearHistogram(String16(name.c_str()), sample,
max)
.isOk();
}
bool MetricsLibrary::SendBoolToUMA(const std::string& name, bool sample) {
return CheckService() &&
metricsd_proxy_->recordLinearHistogram(String16(name.c_str()),
sample ? 1 : 0, 2)
.isOk();
}
bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
return CheckService() &&
metricsd_proxy_->recordSparseHistogram(String16(name.c_str()), sample)
.isOk();
}
bool MetricsLibrary::SendCrashToUMA(const char* crash_kind) {
return CheckService() &&
metricsd_proxy_->recordCrash(String16(crash_kind)).isOk();
}
bool MetricsLibrary::SendCrosEventToUMA(const std::string& event) {
for (size_t i = 0; i < arraysize(kCrosEventNames); i++) {
if (strcmp(event.c_str(), kCrosEventNames[i]) == 0) {
return SendEnumToUMA(kCrosEventHistogramName, i, kCrosEventHistogramMax);
}
}
return false;
}
bool MetricsLibrary::GetHistogramsDump(std::string* dump) {
android::String16 temp_dump;
if (!CheckService() ||
!metricsd_proxy_->getHistogramsDump(&temp_dump).isOk()) {
return false;
}
*dump = android::String8(temp_dump).string();
return true;
}

View File

@ -1,107 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "metrics/c_metrics_library.h"
#include "metrics/metrics_library.h"
class MetricsLibraryTest : public testing::Test {
protected:
virtual void SetUp() {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
lib_.InitForTest(temp_dir_.path());
// Defeat metrics enabled caching between tests.
lib_.cached_enabled_time_ = 0;
}
void SetMetricsConsent(bool enabled) {
if (enabled) {
ASSERT_EQ(base::WriteFile(lib_.consent_file_, "", 0), 0);
} else {
ASSERT_TRUE(base::DeleteFile(lib_.consent_file_, false));
}
}
void VerifyEnabledCacheHit(bool to_value);
void VerifyEnabledCacheEviction(bool to_value);
MetricsLibrary lib_;
base::ScopedTempDir temp_dir_;
};
TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) {
SetMetricsConsent(false);
EXPECT_FALSE(lib_.AreMetricsEnabled());
}
TEST_F(MetricsLibraryTest, AreMetricsEnabledTrue) {
SetMetricsConsent(true);
EXPECT_TRUE(lib_.AreMetricsEnabled());
}
void MetricsLibraryTest::VerifyEnabledCacheHit(bool to_value) {
// We might step from one second to the next one time, but not 100
// times in a row.
for (int i = 0; i < 100; ++i) {
lib_.cached_enabled_time_ = 0;
SetMetricsConsent(to_value);
lib_.AreMetricsEnabled();
// If we check the metrics status twice in a row, we use the cached value
// the second time.
SetMetricsConsent(!to_value);
if (lib_.AreMetricsEnabled() == to_value)
return;
}
ADD_FAILURE() << "Did not see evidence of caching";
}
void MetricsLibraryTest::VerifyEnabledCacheEviction(bool to_value) {
lib_.cached_enabled_time_ = 0;
SetMetricsConsent(!to_value);
ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
SetMetricsConsent(to_value);
// Sleep one second (or cheat to be faster) and check that we are not using
// the cached value.
--lib_.cached_enabled_time_;
ASSERT_EQ(to_value, lib_.AreMetricsEnabled());
}
TEST_F(MetricsLibraryTest, AreMetricsEnabledCaching) {
VerifyEnabledCacheHit(false);
VerifyEnabledCacheHit(true);
VerifyEnabledCacheEviction(false);
VerifyEnabledCacheEviction(true);
}
TEST_F(MetricsLibraryTest, AreMetricsEnabledNoCaching) {
// disable caching.
lib_.use_caching_ = false;
// Checking the consent repeatedly should return the right result.
for (int i=0; i<100; ++i) {
SetMetricsConsent(true);
ASSERT_EQ(true, lib_.AreMetricsEnabled());
SetMetricsConsent(false);
ASSERT_EQ(false, lib_.AreMetricsEnabled());
}
}

View File

@ -1,9 +0,0 @@
on post-fs-data
mkdir /data/misc/metrics 0750 metrics_coll system
mkdir /data/misc/metricsd 0700 metricsd metricsd
mkdir /data/misc/metrics_collector 0700 metrics_coll metrics_coll
service metricsd /system/bin/metricsd --foreground --logtosyslog
class late_start
user metricsd
group system inet

View File

@ -1,83 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <base/command_line.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include "constants.h"
#include "uploader/metricsd_service_runner.h"
#include "uploader/upload_service.h"
int main(int argc, char** argv) {
DEFINE_bool(foreground, false, "Don't daemonize");
// Upload the metrics once and exit. (used for testing)
DEFINE_bool(uploader_test, false, "run the uploader once and exit");
// Upload Service flags.
DEFINE_int32(upload_interval_secs, 1800,
"Interval at which metricsd uploads the metrics.");
DEFINE_int32(disk_persistence_interval_secs, 300,
"Interval at which metricsd saves the aggregated metrics to "
"disk to avoid losing them if metricsd stops in between "
"two uploads.");
DEFINE_string(server, metrics::kMetricsServer,
"Server to upload the metrics to.");
DEFINE_string(private_directory, metrics::kMetricsdDirectory,
"Path to the private directory used by metricsd "
"(testing only)");
DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory,
"Path to the shared metrics directory, used by "
"metrics_collector, metricsd and all metrics clients "
"(testing only)");
DEFINE_bool(logtostderr, false, "Log to standard error");
DEFINE_bool(logtosyslog, false, "Log to syslog");
brillo::FlagHelper::Init(argc, argv, "Brillo metrics daemon.");
int logging_location =
(FLAGS_foreground ? brillo::kLogToStderr : brillo::kLogToSyslog);
if (FLAGS_logtosyslog)
logging_location = brillo::kLogToSyslog;
if (FLAGS_logtostderr)
logging_location = brillo::kLogToStderr;
// Also log to stderr when not running as daemon.
brillo::InitLog(logging_location | brillo::kLogHeader);
if (FLAGS_logtostderr && FLAGS_logtosyslog) {
LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set";
return 1;
}
if (!FLAGS_foreground && daemon(0, 0) != 0) {
return errno;
}
UploadService upload_service(
FLAGS_server, base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs),
base::TimeDelta::FromSeconds(FLAGS_disk_persistence_interval_secs),
base::FilePath(FLAGS_private_directory),
base::FilePath(FLAGS_shared_directory));
return upload_service.Run();
}

View File

@ -1,96 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "persistent_integer.h"
#include <fcntl.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include "constants.h"
namespace chromeos_metrics {
PersistentInteger::PersistentInteger(const std::string& name,
const base::FilePath& directory)
: value_(0),
version_(kVersion),
name_(name),
backing_file_path_(directory.Append(name_)),
synced_(false) {}
PersistentInteger::~PersistentInteger() {}
void PersistentInteger::Set(int64_t value) {
value_ = value;
Write();
}
int64_t PersistentInteger::Get() {
// If not synced, then read. If the read fails, it's a good idea to write.
if (!synced_ && !Read())
Write();
return value_;
}
int64_t PersistentInteger::GetAndClear() {
int64_t v = Get();
Set(0);
return v;
}
void PersistentInteger::Add(int64_t x) {
Set(Get() + x);
}
void PersistentInteger::Write() {
int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(),
O_WRONLY | O_CREAT | O_TRUNC,
S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH));
PCHECK(fd >= 0) << "cannot open " << backing_file_path_.value()
<< " for writing";
PCHECK((HANDLE_EINTR(write(fd, &version_, sizeof(version_))) ==
sizeof(version_)) &&
(HANDLE_EINTR(write(fd, &value_, sizeof(value_))) ==
sizeof(value_)))
<< "cannot write to " << backing_file_path_.value();
close(fd);
synced_ = true;
}
bool PersistentInteger::Read() {
int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(), O_RDONLY));
if (fd < 0) {
PLOG(WARNING) << "cannot open " << backing_file_path_.value()
<< " for reading";
return false;
}
int32_t version;
int64_t value;
bool read_succeeded = false;
if (HANDLE_EINTR(read(fd, &version, sizeof(version))) == sizeof(version) &&
version == version_ &&
HANDLE_EINTR(read(fd, &value, sizeof(value))) == sizeof(value)) {
value_ = value;
read_succeeded = true;
synced_ = true;
}
close(fd);
return read_succeeded;
}
} // namespace chromeos_metrics

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_PERSISTENT_INTEGER_H_
#define METRICS_PERSISTENT_INTEGER_H_
#include <stdint.h>
#include <string>
#include <base/files/file_path.h>
namespace chromeos_metrics {
// PersistentIntegers is a named 64-bit integer value backed by a file.
// The in-memory value acts as a write-through cache of the file value.
// If the backing file doesn't exist or has bad content, the value is 0.
class PersistentInteger {
public:
PersistentInteger(const std::string& name, const base::FilePath& directory);
// Virtual only because of mock.
virtual ~PersistentInteger();
// Sets the value. This writes through to the backing file.
void Set(int64_t v);
// Gets the value. May sync from backing file first.
int64_t Get();
// Returns the name of the object.
std::string Name() { return name_; }
// Convenience function for Get() followed by Set(0).
int64_t GetAndClear();
// Convenience function for v = Get, Set(v + x).
// Virtual only because of mock.
virtual void Add(int64_t x);
private:
static const int kVersion = 1001;
// Writes |value_| to the backing file, creating it if necessary.
void Write();
// Reads the value from the backing file, stores it in |value_|, and returns
// true if the backing file is valid. Returns false otherwise, and creates
// a valid backing file as a side effect.
bool Read();
int64_t value_;
int32_t version_;
std::string name_;
base::FilePath backing_file_path_;
bool synced_;
};
} // namespace chromeos_metrics
#endif // METRICS_PERSISTENT_INTEGER_H_

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_PERSISTENT_INTEGER_MOCK_H_
#define METRICS_PERSISTENT_INTEGER_MOCK_H_
#include <string>
#include <gmock/gmock.h>
#include "persistent_integer.h"
namespace chromeos_metrics {
class PersistentIntegerMock : public PersistentInteger {
public:
explicit PersistentIntegerMock(const std::string& name,
const base::FilePath& directory)
: PersistentInteger(name, directory) {}
MOCK_METHOD1(Add, void(int64_t count));
};
} // namespace chromeos_metrics
#endif // METRICS_PERSISTENT_INTEGER_MOCK_H_

View File

@ -1,65 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include <base/compiler_specific.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <gtest/gtest.h>
#include "persistent_integer.h"
const char kBackingFileName[] = "1.pibakf";
using chromeos_metrics::PersistentInteger;
class PersistentIntegerTest : public testing::Test {
void SetUp() override {
// Set testing mode.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
protected:
base::ScopedTempDir temp_dir_;
};
TEST_F(PersistentIntegerTest, BasicChecks) {
std::unique_ptr<PersistentInteger> pi(
new PersistentInteger(kBackingFileName, temp_dir_.path()));
// Test initialization.
EXPECT_EQ(0, pi->Get());
EXPECT_EQ(kBackingFileName, pi->Name()); // boring
// Test set and add.
pi->Set(2);
pi->Add(3);
EXPECT_EQ(5, pi->Get());
// Test persistence.
pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path()));
EXPECT_EQ(5, pi->Get());
// Test GetAndClear.
EXPECT_EQ(5, pi->GetAndClear());
EXPECT_EQ(pi->Get(), 0);
// Another persistence test.
pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path()));
EXPECT_EQ(0, pi->Get());
}

View File

@ -1,118 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "metrics/timer.h"
#include <string>
#include "metrics/metrics_library.h"
namespace chromeos_metrics {
base::TimeTicks ClockWrapper::GetCurrentTime() const {
return base::TimeTicks::Now();
}
Timer::Timer()
: timer_state_(kTimerStopped),
clock_wrapper_(new ClockWrapper()) {}
bool Timer::Start() {
elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero.
start_time_ = clock_wrapper_->GetCurrentTime();
timer_state_ = kTimerRunning;
return true;
}
bool Timer::Stop() {
if (timer_state_ == kTimerStopped)
return false;
if (timer_state_ == kTimerRunning)
elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
timer_state_ = kTimerStopped;
return true;
}
bool Timer::Pause() {
switch (timer_state_) {
case kTimerStopped:
if (!Start())
return false;
timer_state_ = kTimerPaused;
return true;
case kTimerRunning:
timer_state_ = kTimerPaused;
elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
return true;
default:
return false;
}
}
bool Timer::Resume() {
switch (timer_state_) {
case kTimerStopped:
return Start();
case kTimerPaused:
start_time_ = clock_wrapper_->GetCurrentTime();
timer_state_ = kTimerRunning;
return true;
default:
return false;
}
}
bool Timer::Reset() {
elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero.
timer_state_ = kTimerStopped;
return true;
}
bool Timer::HasStarted() const {
return timer_state_ != kTimerStopped;
}
bool Timer::GetElapsedTime(base::TimeDelta* elapsed_time) const {
if (start_time_.is_null() || !elapsed_time)
return false;
*elapsed_time = elapsed_time_;
if (timer_state_ == kTimerRunning) {
*elapsed_time += clock_wrapper_->GetCurrentTime() - start_time_;
}
return true;
}
// static
MetricsLibraryInterface* TimerReporter::metrics_lib_ = nullptr;
TimerReporter::TimerReporter(const std::string& histogram_name, int min,
int max, int num_buckets)
: histogram_name_(histogram_name),
min_(min),
max_(max),
num_buckets_(num_buckets) {}
bool TimerReporter::ReportMilliseconds() const {
base::TimeDelta elapsed_time;
if (!metrics_lib_ || !GetElapsedTime(&elapsed_time)) return false;
return metrics_lib_->SendToUMA(histogram_name_,
elapsed_time.InMilliseconds(),
min_,
max_,
num_buckets_);
}
} // namespace chromeos_metrics

View File

@ -1,464 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <memory>
#include "metrics/metrics_library_mock.h"
#include "metrics/timer.h"
#include "metrics/timer_mock.h"
using ::testing::_;
using ::testing::Return;
namespace chromeos_metrics {
namespace {
const int64_t kStime1MSec = 1400;
const int64_t kEtime1MSec = 3000;
const int64_t kDelta1MSec = 1600;
const int64_t kStime2MSec = 4200;
const int64_t kEtime2MSec = 5000;
const int64_t kDelta2MSec = 800;
const int64_t kStime3MSec = 6600;
const int64_t kEtime3MSec = 6800;
const int64_t kDelta3MSec = 200;
} // namespace
class TimerTest : public testing::Test {
public:
TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {}
protected:
virtual void SetUp() {
EXPECT_EQ(Timer::kTimerStopped, timer_.timer_state_);
stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
stime2 += base::TimeDelta::FromMilliseconds(kStime2MSec);
etime2 += base::TimeDelta::FromMilliseconds(kEtime2MSec);
stime3 += base::TimeDelta::FromMilliseconds(kStime3MSec);
etime3 += base::TimeDelta::FromMilliseconds(kEtime3MSec);
}
virtual void TearDown() {}
Timer timer_;
std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_;
base::TimeTicks stime, etime, stime2, etime2, stime3, etime3;
};
TEST_F(TimerTest, StartStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_FALSE(timer_.HasStarted());
}
TEST_F(TimerTest, ReStart) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
timer_.Start();
base::TimeTicks buffer = timer_.start_time_;
timer_.Start();
ASSERT_FALSE(timer_.start_time_ == buffer);
}
TEST_F(TimerTest, Reset) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
timer_.Start();
ASSERT_TRUE(timer_.Reset());
ASSERT_FALSE(timer_.HasStarted());
}
TEST_F(TimerTest, SeparatedTimers) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime))
.WillOnce(Return(stime2))
.WillOnce(Return(etime2));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime2);
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
ASSERT_FALSE(timer_.HasStarted());
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, InvalidStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_FALSE(timer_.Stop());
// Now we try it again, but after a valid start/stop.
timer_.Start();
timer_.Stop();
base::TimeDelta elapsed_time = timer_.elapsed_time_;
ASSERT_FALSE(timer_.Stop());
ASSERT_TRUE(elapsed_time == timer_.elapsed_time_);
}
TEST_F(TimerTest, InvalidElapsedTime) {
base::TimeDelta elapsed_time;
ASSERT_FALSE(timer_.GetElapsedTime(&elapsed_time));
}
TEST_F(TimerTest, PauseStartStopResume) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(stime2))
.WillOnce(Return(etime2))
.WillOnce(Return(stime3))
.WillOnce(Return(etime3));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Pause()); // Starts timer paused.
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Start()); // Restarts timer.
ASSERT_TRUE(timer_.start_time_ == stime2);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
ASSERT_FALSE(timer_.HasStarted());
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(kDelta3MSec, elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, ResumeStartStopPause) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(stime2))
.WillOnce(Return(etime2))
.WillOnce(Return(stime3));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime2);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
ASSERT_FALSE(timer_.HasStarted());
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(0, elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, StartResumeStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_FALSE(timer_.Resume());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
ASSERT_FALSE(timer_.HasStarted());
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, StartPauseStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
ASSERT_FALSE(timer_.HasStarted());
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, StartPauseResumeStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime))
.WillOnce(Return(stime2))
.WillOnce(Return(etime2));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
ASSERT_FALSE(timer_.HasStarted());
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, PauseStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
ASSERT_FALSE(timer_.HasStarted());
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, PauseResumeStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(stime2))
.WillOnce(Return(etime2));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
ASSERT_FALSE(timer_.HasStarted());
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, StartPauseResumePauseStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime))
.WillOnce(Return(stime2))
.WillOnce(Return(stime3))
.WillOnce(Return(etime3));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.HasStarted());
// Make sure GetElapsedTime works while we're running.
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(kDelta1MSec + kStime3MSec - kStime2MSec,
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
kDelta1MSec + kEtime3MSec - kStime2MSec);
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
kDelta1MSec + kEtime3MSec - kStime2MSec);
ASSERT_FALSE(timer_.HasStarted());
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
TEST_F(TimerTest, StartPauseResumePauseResumeStop) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime))
.WillOnce(Return(stime2))
.WillOnce(Return(etime2))
.WillOnce(Return(stime3))
.WillOnce(Return(etime3));
timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
ASSERT_TRUE(timer_.Start());
ASSERT_TRUE(timer_.start_time_ == stime);
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
base::TimeDelta elapsed_time;
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Pause());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
ASSERT_TRUE(timer_.Resume());
ASSERT_TRUE(timer_.HasStarted());
ASSERT_TRUE(timer_.Stop());
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
kDelta1MSec + kDelta2MSec + kDelta3MSec);
ASSERT_FALSE(timer_.HasStarted());
ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
elapsed_time.InMilliseconds());
}
static const char kMetricName[] = "test-timer";
static const int kMinSample = 0;
static const int kMaxSample = 120 * 1E6;
static const int kNumBuckets = 50;
class TimerReporterTest : public testing::Test {
public:
TimerReporterTest() : timer_reporter_(kMetricName, kMinSample, kMaxSample,
kNumBuckets),
clock_wrapper_mock_(new ClockWrapperMock()) {}
protected:
virtual void SetUp() {
timer_reporter_.set_metrics_lib(&lib_);
EXPECT_EQ(timer_reporter_.histogram_name_, kMetricName);
EXPECT_EQ(timer_reporter_.min_, kMinSample);
EXPECT_EQ(timer_reporter_.max_, kMaxSample);
EXPECT_EQ(timer_reporter_.num_buckets_, kNumBuckets);
stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
}
virtual void TearDown() {
timer_reporter_.set_metrics_lib(nullptr);
}
TimerReporter timer_reporter_;
MetricsLibraryMock lib_;
std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_;
base::TimeTicks stime, etime;
};
TEST_F(TimerReporterTest, StartStopReport) {
EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
.WillOnce(Return(stime))
.WillOnce(Return(etime));
timer_reporter_.clock_wrapper_ = std::move(clock_wrapper_mock_);
EXPECT_CALL(lib_, SendToUMA(kMetricName, kDelta1MSec, kMinSample, kMaxSample,
kNumBuckets)).WillOnce(Return(true));
ASSERT_TRUE(timer_reporter_.Start());
ASSERT_TRUE(timer_reporter_.Stop());
ASSERT_TRUE(timer_reporter_.ReportMilliseconds());
}
TEST_F(TimerReporterTest, InvalidReport) {
ASSERT_FALSE(timer_reporter_.ReportMilliseconds());
}
} // namespace chromeos_metrics
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,98 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "uploader/bn_metricsd_impl.h"
#include <base/metrics/histogram.h>
#include <base/metrics/sparse_histogram.h>
#include <base/metrics/statistics_recorder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/String8.h>
using android::binder::Status;
using android::String16;
static const char16_t kCrashTypeKernel[] = u"kernel";
static const char16_t kCrashTypeUncleanShutdown[] = u"uncleanshutdown";
static const char16_t kCrashTypeUser[] = u"user";
BnMetricsdImpl::BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters)
: counters_(counters) {
CHECK(counters_) << "Invalid counters argument to constructor";
}
Status BnMetricsdImpl::recordHistogram(
const String16& name, int sample, int min, int max, int nbuckets) {
base::HistogramBase* histogram = base::Histogram::FactoryGet(
android::String8(name).string(), min, max, nbuckets,
base::Histogram::kUmaTargetedHistogramFlag);
// |histogram| may be null if a client reports two contradicting histograms
// with the same name but different limits.
// FactoryGet will print a useful message if that is the case.
if (histogram) {
histogram->Add(sample);
}
return Status::ok();
}
Status BnMetricsdImpl::recordLinearHistogram(const String16& name,
int sample,
int max) {
base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
android::String8(name).string(), 1, max, max + 1,
base::Histogram::kUmaTargetedHistogramFlag);
// |histogram| may be null if a client reports two contradicting histograms
// with the same name but different limits.
// FactoryGet will print a useful message if that is the case.
if (histogram) {
histogram->Add(sample);
}
return Status::ok();
}
Status BnMetricsdImpl::recordSparseHistogram(const String16& name, int sample) {
base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
android::String8(name).string(),
base::Histogram::kUmaTargetedHistogramFlag);
// |histogram| may be null if a client reports two contradicting histograms
// with the same name but different limits.
// FactoryGet will print a useful message if that is the case.
if (histogram) {
histogram->Add(sample);
}
return Status::ok();
}
Status BnMetricsdImpl::recordCrash(const String16& type) {
if (type == kCrashTypeUser) {
counters_->IncrementUserCrashCount();
} else if (type == kCrashTypeKernel) {
counters_->IncrementKernelCrashCount();
} else if (type == kCrashTypeUncleanShutdown) {
counters_->IncrementUncleanShutdownCount();
} else {
LOG(ERROR) << "Unknown crash type received: " << type;
}
return Status::ok();
}
Status BnMetricsdImpl::getHistogramsDump(String16* dump) {
std::string str_dump;
base::StatisticsRecorder::WriteGraph(std::string(), &str_dump);
*dump = String16(str_dump.c_str());
return Status::ok();
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
#define METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
#include "android/brillo/metrics/BnMetricsd.h"
#include "uploader/crash_counters.h"
class BnMetricsdImpl : public android::brillo::metrics::BnMetricsd {
public:
explicit BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters);
virtual ~BnMetricsdImpl() = default;
// Records a histogram.
android::binder::Status recordHistogram(const android::String16& name,
int sample,
int min,
int max,
int nbuckets) override;
// Records a linear histogram.
android::binder::Status recordLinearHistogram(const android::String16& name,
int sample,
int max) override;
// Records a sparse histogram.
android::binder::Status recordSparseHistogram(const android::String16& name,
int sample) override;
// Records a crash.
android::binder::Status recordCrash(const android::String16& type) override;
// Returns a dump of the histograms aggregated in memory.
android::binder::Status getHistogramsDump(android::String16* dump) override;
private:
std::shared_ptr<CrashCounters> counters_;
};
#endif // METRICSD_UPLOADER_BN_METRICSD_IMPL_H_

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "uploader/crash_counters.h"
CrashCounters::CrashCounters()
: kernel_crashes_(0), unclean_shutdowns_(0), user_crashes_(0) {}
void CrashCounters::IncrementKernelCrashCount() {
kernel_crashes_++;
}
unsigned int CrashCounters::GetAndResetKernelCrashCount() {
return kernel_crashes_.exchange(0);
}
void CrashCounters::IncrementUncleanShutdownCount() {
unclean_shutdowns_++;
}
unsigned int CrashCounters::GetAndResetUncleanShutdownCount() {
return unclean_shutdowns_.exchange(0);
}
void CrashCounters::IncrementUserCrashCount() {
user_crashes_++;
}
unsigned int CrashCounters::GetAndResetUserCrashCount() {
return user_crashes_.exchange(0);
}

View File

@ -1,45 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_UPLOADER_CRASH_COUNTERS_H_
#define METRICSD_UPLOADER_CRASH_COUNTERS_H_
#include <atomic>
// This class is used to keep track of the crash counters.
// An instance of it will be used by both the binder thread (to increment the
// counters) and the uploader thread (to gather and reset the counters).
// As such, the internal counters are atomic uints to allow concurrent access.
class CrashCounters {
public:
CrashCounters();
void IncrementKernelCrashCount();
unsigned int GetAndResetKernelCrashCount();
void IncrementUserCrashCount();
unsigned int GetAndResetUserCrashCount();
void IncrementUncleanShutdownCount();
unsigned int GetAndResetUncleanShutdownCount();
private:
std::atomic_uint kernel_crashes_;
std::atomic_uint unclean_shutdowns_;
std::atomic_uint user_crashes_;
};
#endif // METRICSD_UPLOADER_CRASH_COUNTERS_H_

View File

@ -1,51 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "uploader/metrics_hashes.h"
#include "base/logging.h"
#include "base/md5.h"
#include "base/sys_byteorder.h"
namespace metrics {
namespace {
// Converts the 8-byte prefix of an MD5 hash into a uint64 value.
inline uint64_t HashToUInt64(const std::string& hash) {
uint64_t value;
DCHECK_GE(hash.size(), sizeof(value));
memcpy(&value, hash.data(), sizeof(value));
return base::HostToNet64(value);
}
} // namespace
uint64_t HashMetricName(const std::string& name) {
// Create an MD5 hash of the given |name|, represented as a byte buffer
// encoded as an std::string.
base::MD5Context context;
base::MD5Init(&context);
base::MD5Update(&context, name);
base::MD5Digest digest;
base::MD5Final(&digest, &context);
std::string hash_str(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
return HashToUInt64(hash_str);
}
} // namespace metrics

View File

@ -1,30 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICS_UPLOADER_METRICS_HASHES_H_
#define METRICS_UPLOADER_METRICS_HASHES_H_
#include <string>
namespace metrics {
// Computes a uint64 hash of a given string based on its MD5 hash. Suitable for
// metric names.
uint64_t HashMetricName(const std::string& name);
} // namespace metrics
#endif // METRICS_UPLOADER_METRICS_HASHES_H_

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "uploader/metrics_hashes.h"
#include <base/format_macros.h>
#include <base/macros.h>
#include <base/strings/stringprintf.h>
#include <gtest/gtest.h>
namespace metrics {
// Make sure our ID hashes are the same as what we see on the server side.
TEST(MetricsUtilTest, HashMetricName) {
static const struct {
std::string input;
std::string output;
} cases[] = {
{"Back", "0x0557fa923dcee4d0"},
{"Forward", "0x67d2f6740a8eaebf"},
{"NewTab", "0x290eb683f96572f1"},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
uint64_t hash = HashMetricName(cases[i].input);
std::string hash_hex = base::StringPrintf("0x%016" PRIx64, hash);
EXPECT_EQ(cases[i].output, hash_hex);
}
}
} // namespace metrics

View File

@ -1,90 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "uploader/metrics_log.h"
#include <string>
#include <base/files/file_util.h>
#include "uploader/proto/system_profile.pb.h"
#include "uploader/system_profile_setter.h"
// We use default values for the MetricsLogBase constructor as the setter will
// override them.
MetricsLog::MetricsLog()
: MetricsLogBase("", 0, metrics::MetricsLogBase::ONGOING_LOG, "") {
}
bool MetricsLog::LoadFromFile(const base::FilePath& saved_log) {
std::string encoded_log;
if (!base::ReadFileToString(saved_log, &encoded_log)) {
LOG(ERROR) << "Failed to read the metrics log backup from "
<< saved_log.value();
return false;
}
if (!uma_proto()->ParseFromString(encoded_log)) {
LOG(ERROR) << "Failed to parse log from " << saved_log.value()
<< ", deleting the log";
base::DeleteFile(saved_log, false);
uma_proto()->Clear();
return false;
}
VLOG(1) << uma_proto()->histogram_event_size() << " histograms loaded from "
<< saved_log.value();
return true;
}
bool MetricsLog::SaveToFile(const base::FilePath& path) {
std::string encoded_log;
GetEncodedLog(&encoded_log);
if (static_cast<int>(encoded_log.size()) !=
base::WriteFile(path, encoded_log.data(), encoded_log.size())) {
LOG(ERROR) << "Failed to persist the current log to " << path.value();
return false;
}
return true;
}
void MetricsLog::IncrementUserCrashCount(unsigned int count) {
metrics::SystemProfileProto::Stability* stability(
uma_proto()->mutable_system_profile()->mutable_stability());
int current = stability->other_user_crash_count();
stability->set_other_user_crash_count(current + count);
}
void MetricsLog::IncrementKernelCrashCount(unsigned int count) {
metrics::SystemProfileProto::Stability* stability(
uma_proto()->mutable_system_profile()->mutable_stability());
int current = stability->kernel_crash_count();
stability->set_kernel_crash_count(current + count);
}
void MetricsLog::IncrementUncleanShutdownCount(unsigned int count) {
metrics::SystemProfileProto::Stability* stability(
uma_proto()->mutable_system_profile()->mutable_stability());
int current = stability->unclean_system_shutdown_count();
stability->set_unclean_system_shutdown_count(current + count);
}
bool MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
CHECK(profile_setter);
return profile_setter->Populate(uma_proto());
}

View File

@ -1,67 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef METRICSD_UPLOADER_METRICS_LOG_H_
#define METRICSD_UPLOADER_METRICS_LOG_H_
#include <string>
#include <base/files/file_path.h>
#include <base/macros.h>
#include "uploader/metrics_log_base.h"
// This file defines a set of user experience metrics data recorded by
// the MetricsService. This is the unit of data that is sent to the server.
class SystemProfileSetter;
// This class provides base functionality for logging metrics data.
class MetricsLog : public metrics::MetricsLogBase {
public:
// The constructor doesn't set any metadata. The metadata is only set by a
// SystemProfileSetter.
MetricsLog();
// Increment the crash counters in the protobuf.
// These methods don't have to be thread safe as metrics logs are only
// accessed by the uploader thread.
void IncrementUserCrashCount(unsigned int count);
void IncrementKernelCrashCount(unsigned int count);
void IncrementUncleanShutdownCount(unsigned int count);
// Populate the system profile with system information using setter.
bool PopulateSystemProfile(SystemProfileSetter* setter);
// Load the log from |path|.
bool LoadFromFile(const base::FilePath& path);
// Save this log to |path|.
bool SaveToFile(const base::FilePath& path);
private:
friend class UploadServiceTest;
FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts);
FRIEND_TEST(UploadServiceTest, LogKernelCrash);
FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
FRIEND_TEST(UploadServiceTest, LogUserCrash);
FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
DISALLOW_COPY_AND_ASSIGN(MetricsLog);
};
#endif // METRICSD_UPLOADER_METRICS_LOG_H_

View File

@ -1,154 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "uploader/metrics_log_base.h"
#include <memory>
#include "base/build_time.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "uploader/metrics_hashes.h"
#include "uploader/proto/histogram_event.pb.h"
#include "uploader/proto/system_profile.pb.h"
#include "uploader/proto/user_action_event.pb.h"
using base::Histogram;
using base::HistogramBase;
using base::HistogramSamples;
using base::SampleCountIterator;
using base::Time;
using base::TimeDelta;
using metrics::HistogramEventProto;
using metrics::SystemProfileProto;
using metrics::UserActionEventProto;
namespace metrics {
namespace {
// Any id less than 16 bytes is considered to be a testing id.
bool IsTestingID(const std::string& id) {
return id.size() < 16;
}
} // namespace
MetricsLogBase::MetricsLogBase(const std::string& client_id,
int session_id,
LogType log_type,
const std::string& version_string)
: num_events_(0),
locked_(false),
log_type_(log_type) {
DCHECK_NE(NO_LOG, log_type);
if (IsTestingID(client_id))
uma_proto_.set_client_id(0);
else
uma_proto_.set_client_id(Hash(client_id));
uma_proto_.set_session_id(session_id);
uma_proto_.mutable_system_profile()->set_build_timestamp(GetBuildTime());
uma_proto_.mutable_system_profile()->set_app_version(version_string);
}
MetricsLogBase::~MetricsLogBase() {}
// static
uint64_t MetricsLogBase::Hash(const std::string& value) {
uint64_t hash = metrics::HashMetricName(value);
// The following log is VERY helpful when folks add some named histogram into
// the code, but forgot to update the descriptive list of histograms. When
// that happens, all we get to see (server side) is a hash of the histogram
// name. We can then use this logging to find out what histogram name was
// being hashed to a given MD5 value by just running the version of Chromium
// in question with --enable-logging.
VLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
return hash;
}
// static
int64_t MetricsLogBase::GetBuildTime() {
static int64_t integral_build_time = 0;
if (!integral_build_time) {
Time time = base::GetBuildTime();
integral_build_time = static_cast<int64_t>(time.ToTimeT());
}
return integral_build_time;
}
// static
int64_t MetricsLogBase::GetCurrentTime() {
return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
}
void MetricsLogBase::CloseLog() {
DCHECK(!locked_);
locked_ = true;
}
void MetricsLogBase::GetEncodedLog(std::string* encoded_log) {
DCHECK(locked_);
uma_proto_.SerializeToString(encoded_log);
}
void MetricsLogBase::RecordUserAction(const std::string& key) {
DCHECK(!locked_);
UserActionEventProto* user_action = uma_proto_.add_user_action_event();
user_action->set_name_hash(Hash(key));
user_action->set_time(GetCurrentTime());
++num_events_;
}
void MetricsLogBase::RecordHistogramDelta(const std::string& histogram_name,
const HistogramSamples& snapshot) {
DCHECK(!locked_);
DCHECK_NE(0, snapshot.TotalCount());
// We will ignore the MAX_INT/infinite value in the last element of range[].
HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event();
histogram_proto->set_name_hash(Hash(histogram_name));
histogram_proto->set_sum(snapshot.sum());
for (std::unique_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done();
it->Next()) {
HistogramBase::Sample min;
HistogramBase::Sample max;
HistogramBase::Count count;
it->Get(&min, &max, &count);
HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
bucket->set_min(min);
bucket->set_max(max);
bucket->set_count(count);
}
// Omit fields to save space (see rules in histogram_event.proto comments).
for (int i = 0; i < histogram_proto->bucket_size(); ++i) {
HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i);
if (i + 1 < histogram_proto->bucket_size() &&
bucket->max() == histogram_proto->bucket(i + 1).min()) {
bucket->clear_max();
} else if (bucket->max() == bucket->min() + 1) {
bucket->clear_min();
}
}
}
} // namespace metrics

View File

@ -1,122 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file defines a set of user experience metrics data recorded by
// the MetricsService. This is the unit of data that is sent to the server.
#ifndef METRICS_UPLOADER_METRICS_LOG_BASE_H_
#define METRICS_UPLOADER_METRICS_LOG_BASE_H_
#include <string>
#include "base/macros.h"
#include "base/metrics/histogram.h"
#include "base/time/time.h"
#include "uploader/proto/chrome_user_metrics_extension.pb.h"
namespace base {
class HistogramSamples;
} // namespace base
namespace metrics {
// This class provides base functionality for logging metrics data.
class MetricsLogBase {
public:
// TODO(asvitkine): Remove the NO_LOG value.
enum LogType {
INITIAL_STABILITY_LOG, // The initial log containing stability stats.
ONGOING_LOG, // Subsequent logs in a session.
NO_LOG, // Placeholder value for when there is no log.
};
// Creates a new metrics log of the specified type.
// client_id is the identifier for this profile on this installation
// session_id is an integer that's incremented on each application launch
MetricsLogBase(const std::string& client_id,
int session_id,
LogType log_type,
const std::string& version_string);
virtual ~MetricsLogBase();
// Computes the MD5 hash of the given string, and returns the first 8 bytes of
// the hash.
static uint64_t Hash(const std::string& value);
// Get the GMT buildtime for the current binary, expressed in seconds since
// January 1, 1970 GMT.
// The value is used to identify when a new build is run, so that previous
// reliability stats, from other builds, can be abandoned.
static int64_t GetBuildTime();
// Convenience function to return the current time at a resolution in seconds.
// This wraps base::TimeTicks, and hence provides an abstract time that is
// always incrementing for use in measuring time durations.
static int64_t GetCurrentTime();
// Records a user-initiated action.
void RecordUserAction(const std::string& key);
// Record any changes in a given histogram for transmission.
void RecordHistogramDelta(const std::string& histogram_name,
const base::HistogramSamples& snapshot);
// Stop writing to this record and generate the encoded representation.
// None of the Record* methods can be called after this is called.
void CloseLog();
// Fills |encoded_log| with the serialized protobuf representation of the
// record. Must only be called after CloseLog() has been called.
void GetEncodedLog(std::string* encoded_log);
int num_events() { return num_events_; }
void set_hardware_class(const std::string& hardware_class) {
uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
hardware_class);
}
LogType log_type() const { return log_type_; }
protected:
bool locked() const { return locked_; }
metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
const metrics::ChromeUserMetricsExtension* uma_proto() const {
return &uma_proto_;
}
// TODO(isherman): Remove this once the XML pipeline is outta here.
int num_events_; // the number of events recorded in this log
private:
// locked_ is true when record has been packed up for sending, and should
// no longer be written to. It is only used for sanity checking and is
// not a real lock.
bool locked_;
// The type of the log, i.e. initial or ongoing.
const LogType log_type_;
// Stores the protocol buffer representation for this log.
metrics::ChromeUserMetricsExtension uma_proto_;
DISALLOW_COPY_AND_ASSIGN(MetricsLogBase);
};
} // namespace metrics
#endif // METRICS_UPLOADER_METRICS_LOG_BASE_H_

Some files were not shown because too many files have changed in this diff Show More