Merge "Remove crash_reporter and metricsd"
This commit is contained in:
commit
07572a92b9
|
@ -1 +0,0 @@
|
|||
crash
|
|
@ -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"
|
|
@ -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)
|
|
@ -1,2 +0,0 @@
|
|||
set noparent
|
||||
vapier@chromium.org
|
|
@ -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.
|
|
@ -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+
|
|
@ -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(×tamp, &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));
|
||||
}
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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 "$@"
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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, ×tamp, 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,
|
||||
×tamp,
|
||||
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;
|
||||
}
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
|
@ -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_
|
|
@ -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"
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
|
@ -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 "$@"
|
|
@ -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();
|
||||
}
|
|
@ -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 "";
|
||||
}
|
|
@ -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_
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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());
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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));
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
../../../build/tools/brillo-clang-format
|
|
@ -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)
|
|
@ -1,3 +0,0 @@
|
|||
semenzato@chromium.org
|
||||
derat@chromium.org
|
||||
bsimonnet@chromium.org
|
|
@ -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.
|
|
@ -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']
|
||||
},
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
|
@ -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_);
|
||||
}
|
|
@ -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_
|
|
@ -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_
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"_metrics": {
|
||||
"commands": {
|
||||
"enableAnalyticsReporting": {
|
||||
"minimalRole": "manager",
|
||||
"parameters": {}
|
||||
},
|
||||
"disableAnalyticsReporting": {
|
||||
"minimalRole": "manager",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"analyticsReportingState": {
|
||||
"type": "string",
|
||||
"enum": [ "enabled", "disabled" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
'variables': {
|
||||
'libbase_ver': 369476,
|
||||
},
|
||||
'includes': [
|
||||
'libmetrics.gypi',
|
||||
],
|
||||
}
|
|
@ -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': ['.'],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
bslot=@BSLOT@
|
||||
|
||||
Name: libmetrics
|
||||
Description: Chrome OS metrics library
|
||||
Version: ${bslot}
|
||||
Requires.private: libchrome-${bslot}
|
||||
Libs: -lmetrics-${bslot}
|
|
@ -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': ['.'],
|
||||
},
|
||||
],
|
||||
}],
|
||||
]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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_
|
|
@ -1,4 +0,0 @@
|
|||
service metricscollector /system/bin/metrics_collector --foreground --logtosyslog
|
||||
class late_start
|
||||
user metrics_coll
|
||||
group metrics_coll
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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_
|
|
@ -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()));
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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_
|
|
@ -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);
|
||||
}
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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_
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue