Add meta files to crash directory, enabling OS version at crash time.
Adding meta files also: 1) ensures atomically added crash reports 2) allows us to remove orphaned crash report payload files (such as core files) 3) gives us better control over the number of reports in crash directory While we're here, also made these minor changes 1) send board (x86-generic, x86-mario, etc) to crash server 2) send hwclass to crash server 3) Only record crash reports when metrics are enabled. 4) No longer allow crash reporting to staging server. BUG=6100,5805,5624,6865 TEST=unit tests plus UserCrash,CrashSender,KernelCrash autotests Change-Id: Ieea9bdc8e0680b379c65b91cc56ca0611dd0f31c Review URL: http://codereview.chromium.org/3436029
This commit is contained in:
parent
563d08ba7d
commit
ee849c5ef4
9 changed files with 359 additions and 126 deletions
|
|
@ -8,12 +8,15 @@
|
|||
#include <pwd.h> // For struct passwd.
|
||||
#include <sys/types.h> // for mode_t.
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "base/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/string_util.h"
|
||||
#include "crash-reporter/system_logging.h"
|
||||
|
||||
static const char kDefaultUserName[] = "chronos";
|
||||
static const char kLsbRelease[] = "/etc/lsb-release";
|
||||
static const char kSystemCrashPath[] = "/var/spool/crash";
|
||||
static const char kUserCrashPath[] = "/home/chronos/user/crash";
|
||||
|
||||
|
|
@ -55,13 +58,23 @@ void CrashCollector::Initialize(
|
|||
logger_ = logger;
|
||||
}
|
||||
|
||||
std::string CrashCollector::Sanitize(const std::string &name) {
|
||||
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",
|
||||
exec_name.c_str(),
|
||||
sanitized_exec_name.c_str(),
|
||||
tm.tm_year + 1900,
|
||||
tm.tm_mon + 1,
|
||||
tm.tm_mday,
|
||||
|
|
@ -173,22 +186,27 @@ bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
|
|||
}
|
||||
struct dirent ent_buf;
|
||||
struct dirent* ent;
|
||||
int count_non_core = 0;
|
||||
int count_core = 0;
|
||||
bool full = false;
|
||||
std::set<std::string> basenames;
|
||||
while (readdir_r(dir, &ent_buf, &ent) == 0 && ent != NULL) {
|
||||
if ((strcmp(ent->d_name, ".") == 0) ||
|
||||
(strcmp(ent->d_name, "..") == 0))
|
||||
continue;
|
||||
|
||||
if (strcmp(ent->d_name + strlen(ent->d_name) - 5, ".core") == 0) {
|
||||
++count_core;
|
||||
} else {
|
||||
++count_non_core;
|
||||
}
|
||||
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 (count_core >= kMaxCrashDirectorySize ||
|
||||
count_non_core >= kMaxCrashDirectorySize) {
|
||||
if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
|
||||
logger_->LogWarning(
|
||||
"Crash directory %s already full with %d pending reports",
|
||||
crash_directory.value().c_str(),
|
||||
|
|
@ -200,3 +218,53 @@ bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
|
|||
closedir(dir);
|
||||
return !full;
|
||||
}
|
||||
|
||||
bool CrashCollector::ReadKeyValueFile(
|
||||
const FilePath &path,
|
||||
const char separator,
|
||||
std::map<std::string, std::string> *dictionary) {
|
||||
std::string contents;
|
||||
if (!file_util::ReadFileToString(path, &contents)) {
|
||||
return false;
|
||||
}
|
||||
typedef std::vector<std::string> StringVector;
|
||||
StringVector lines;
|
||||
SplitString(contents, '\n', &lines);
|
||||
bool any_errors = false;
|
||||
for (StringVector::iterator line = lines.begin(); line != lines.end();
|
||||
++line) {
|
||||
// Allow empty strings.
|
||||
if (line->empty())
|
||||
continue;
|
||||
StringVector sides;
|
||||
SplitString(*line, separator, &sides);
|
||||
if (sides.size() != 2) {
|
||||
any_errors = true;
|
||||
continue;
|
||||
}
|
||||
dictionary->insert(std::pair<std::string, std::string>(sides[0], sides[1]));
|
||||
}
|
||||
return !any_errors;
|
||||
}
|
||||
|
||||
void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
|
||||
const std::string &exec_name) {
|
||||
std::map<std::string, std::string> contents;
|
||||
if (!ReadKeyValueFile(FilePath(std::string(kLsbRelease)), '=', &contents)) {
|
||||
logger_->LogError("Problem parsing %s", kLsbRelease);
|
||||
// Even though there was some failure, take as much as we could read.
|
||||
}
|
||||
std::string version("unknown");
|
||||
std::map<std::string, std::string>::iterator i;
|
||||
if ((i = contents.find("CHROMEOS_RELEASE_VERSION")) != contents.end()) {
|
||||
version = i->second;
|
||||
}
|
||||
std::string meta_data = StringPrintf("exec_name=%s\n"
|
||||
"ver=%s\n"
|
||||
"done=1\n",
|
||||
exec_name.c_str(),
|
||||
version.c_str());
|
||||
if (!file_util::WriteFile(meta_path, meta_data.c_str(), meta_data.size())) {
|
||||
logger_->LogError("Unable to write %s", meta_path.value().c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
#ifndef _CRASH_REPORTER_CRASH_COLLECTOR_H_
|
||||
#define _CRASH_REPORTER_CRASH_COLLECTOR_H_
|
||||
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest_prod.h" // for FRIEND_TEST
|
||||
|
||||
class FilePath;
|
||||
|
|
@ -32,15 +34,22 @@ class CrashCollector {
|
|||
|
||||
protected:
|
||||
friend class CrashCollectorTest;
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityOverCore);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityOverNonCore);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityCorrectBasename);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityStrangeNames);
|
||||
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityUsual);
|
||||
FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfo);
|
||||
FRIEND_TEST(CrashCollectorTest, FormatDumpBasename);
|
||||
FRIEND_TEST(CrashCollectorTest, Initialize);
|
||||
FRIEND_TEST(CrashCollectorTest, ReadKeyValueFile);
|
||||
FRIEND_TEST(CrashCollectorTest, Sanitize);
|
||||
|
||||
// Set maximum enqueued crashes in a crash directory.
|
||||
static const int kMaxCrashDirectorySize;
|
||||
|
||||
// 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 char *forced_directory) {
|
||||
|
|
@ -72,6 +81,16 @@ class CrashCollector {
|
|||
// crash.
|
||||
bool CheckHasCapacity(const FilePath &crash_directory);
|
||||
|
||||
// Read the given file of form [<key><separator><value>\n...] and return
|
||||
// a map of its contents.
|
||||
bool ReadKeyValueFile(const FilePath &file,
|
||||
char separator,
|
||||
std::map<std::string, std::string> *dictionary);
|
||||
|
||||
// Write a file of metadata about crash.
|
||||
void WriteCrashMetaData(const FilePath &meta_path,
|
||||
const std::string &exec_name);
|
||||
|
||||
CountCrashFunction count_crash_function_;
|
||||
IsFeedbackAllowedFunction is_feedback_allowed_function_;
|
||||
SystemLogging *logger_;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@ TEST_F(CrashCollectorTest, Initialize) {
|
|||
ASSERT_TRUE(&logging_ == collector_.logger_);
|
||||
}
|
||||
|
||||
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, GetCrashDirectoryInfo) {
|
||||
FilePath path;
|
||||
const int kRootUid = 0;
|
||||
|
|
@ -118,41 +128,96 @@ bool CrashCollectorTest::CheckHasCapacity() {
|
|||
return has_capacity;
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityOverNonCore) {
|
||||
// Test up to kMaxCrashDirectorySize-1 non-core files can be added.
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityUsual) {
|
||||
// Test kMaxCrashDirectorySize - 1 non-meta files can be added.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf("file%d", i)), "", 0);
|
||||
}
|
||||
|
||||
// Test an additional kMaxCrashDirectorySize - 1 core files fit.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf("file%d.core", i)),
|
||||
"", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
// Test an additional kMaxCrashDirectorySize non-core files don't fit.
|
||||
// Test an additional kMaxCrashDirectorySize - 1 meta files fit.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
file_util::WriteFile(test_dir_.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) {
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf("overage%d", i)), "", 0);
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf("overage%d.meta", i)),
|
||||
"", 0);
|
||||
EXPECT_FALSE(CheckHasCapacity());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityOverCore) {
|
||||
// Set up kMaxCrashDirectorySize - 1 core files.
|
||||
TEST_F(CrashCollectorTest, CheckHasCapacityCorrectBasename) {
|
||||
// Test kMaxCrashDirectorySize - 1 files can be added.
|
||||
for (int i = 0; i < CrashCollector::kMaxCrashDirectorySize - 1; ++i) {
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf("file%d.core", i)),
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf("file.%d.core", i)),
|
||||
"", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
|
||||
// Test an additional core file does not fit.
|
||||
file_util::WriteFile(test_dir_.Append("overage.core"), "", 0);
|
||||
file_util::WriteFile(test_dir_.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) {
|
||||
file_util::WriteFile(test_dir_.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) {
|
||||
file_util::WriteFile(test_dir_.Append(StringPrintf(".file%d", i)), "", 0);
|
||||
EXPECT_TRUE(CheckHasCapacity());
|
||||
}
|
||||
file_util::WriteFile(test_dir_.Append("normal.meta"), "", 0);
|
||||
EXPECT_FALSE(CheckHasCapacity());
|
||||
}
|
||||
|
||||
TEST_F(CrashCollectorTest, ReadKeyValueFile) {
|
||||
const char *contents = ("a=b\n"
|
||||
"\n"
|
||||
" c=d \n");
|
||||
FilePath path(test_dir_.Append("keyval"));
|
||||
std::map<std::string, std::string> dictionary;
|
||||
std::map<std::string, std::string>::iterator i;
|
||||
|
||||
file_util::WriteFile(path, contents, strlen(contents));
|
||||
|
||||
EXPECT_TRUE(collector_.ReadKeyValueFile(path, '=', &dictionary));
|
||||
i = dictionary.find("a");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "b");
|
||||
i = dictionary.find("c");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "d");
|
||||
|
||||
dictionary.clear();
|
||||
|
||||
contents = ("a=b c d\n"
|
||||
"e\n"
|
||||
" f g = h\n"
|
||||
"i=j\n"
|
||||
"=k\n"
|
||||
"l=\n");
|
||||
file_util::WriteFile(path, contents, strlen(contents));
|
||||
|
||||
EXPECT_FALSE(collector_.ReadKeyValueFile(path, '=', &dictionary));
|
||||
i = dictionary.find("a");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "b c d");
|
||||
i = dictionary.find("e");
|
||||
EXPECT_TRUE(i == dictionary.end());
|
||||
i = dictionary.find("f g");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "h");
|
||||
i = dictionary.find("i");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "j");
|
||||
i = dictionary.find("");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "k");
|
||||
i = dictionary.find("l");
|
||||
EXPECT_TRUE(i != dictionary.end() && i->second == "");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
|
|
|||
|
|
@ -43,9 +43,7 @@ static MetricsLibrary s_metrics_lib;
|
|||
static SystemLoggingImpl s_system_log;
|
||||
|
||||
static bool IsFeedbackAllowed() {
|
||||
// Once crosbug.com/5814 is fixed, call the is opted in function
|
||||
// here.
|
||||
return true;
|
||||
return s_metrics_lib.AreMetricsEnabled();
|
||||
}
|
||||
|
||||
static bool TouchFile(const FilePath &file_path) {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,15 @@ CONSENT_ID="/home/chronos/Consent To Send Stats"
|
|||
# Path to find which is required for computing the crash rate.
|
||||
FIND="/usr/bin/find"
|
||||
|
||||
# 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"
|
||||
|
||||
# Maximum crashes to send per day.
|
||||
MAX_CRASH_RATE=32
|
||||
MAX_CRASH_RATE=${MAX_CRASH_RATE:-32}
|
||||
|
||||
# File whose existence mocks crash sending. If empty we pretend the
|
||||
# crash sending was successful, otherwise unsuccessful.
|
||||
|
|
@ -32,9 +39,6 @@ MOCK_CRASH_SENDING="/tmp/mock-crash-sending"
|
|||
# Must be stateful to enable testing kernel crashes.
|
||||
PAUSE_CRASH_SENDING="/var/lib/crash_sender_paused"
|
||||
|
||||
# URL to send non-official build crash reports to.
|
||||
REPORT_UPLOAD_STAGING_URL="http://clients2.google.com/cr/staging_report"
|
||||
|
||||
# URL to send official build crash reports to.
|
||||
REPORT_UPLOAD_PROD_URL="http://clients2.google.com/cr/report"
|
||||
|
||||
|
|
@ -89,11 +93,8 @@ check_not_already_running() {
|
|||
exit 1
|
||||
}
|
||||
|
||||
get_version() {
|
||||
grep ^CHROMEOS_RELEASE_VERSION /etc/lsb-release | cut -d = -f 2-
|
||||
}
|
||||
|
||||
is_official() {
|
||||
[ ${FORCE_OFFICIAL} -ne 0 ] && return 0
|
||||
grep ^CHROMEOS_RELEASE_DESCRIPTION /etc/lsb-release | grep -q Official
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +120,8 @@ is_on_3g() {
|
|||
check_rate() {
|
||||
mkdir -p ${TIMESTAMPS_DIR}
|
||||
# Only consider minidumps written in the past 24 hours by removing all older.
|
||||
${FIND} "${TIMESTAMPS_DIR}" -mindepth 1 -mmin +$((24 * 60)) -exec rm '{}' ';'
|
||||
${FIND} "${TIMESTAMPS_DIR}" -mindepth 1 -mmin +$((24 * 60)) \
|
||||
-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
|
||||
|
|
@ -132,37 +134,81 @@ check_rate() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# Return if $1 is a .core file
|
||||
get_kind() {
|
||||
local kind="${1##*.}"
|
||||
if [ "${kind}" = "dmp" ]; then
|
||||
kind="minidump"
|
||||
fi
|
||||
echo ${kind}
|
||||
# Gets the base part of a crash report file, such as
|
||||
# name.01234.5678.9012 from name.01234.5678.9012.meta
|
||||
get_base() {
|
||||
echo "${1%.*}"
|
||||
}
|
||||
|
||||
get_exec_name() {
|
||||
local filename=$(basename "$1")
|
||||
echo "${filename%%.*}"
|
||||
# Return which kind of report the given metadata file relates to
|
||||
get_kind() {
|
||||
# There should never be a report with both a dmp and kcrash file.
|
||||
# If that were to happen we arbitrarily consider this a minidump
|
||||
# report and effectively ignore the kcrash.
|
||||
local base="$(get_base "$1")"
|
||||
if [ -r "${base}.dmp" ]; then
|
||||
echo "minidump"
|
||||
return
|
||||
fi
|
||||
if [ -r "${base}.kcrash" ]; then
|
||||
echo "kcrash"
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
get_key_value() {
|
||||
if ! grep -q "$2=" "$1"; then
|
||||
echo "undefined"
|
||||
return
|
||||
fi
|
||||
grep "$2=" "$1" | cut -d = -f 2-
|
||||
}
|
||||
|
||||
# Returns true if mock is enabled.
|
||||
is_mock() {
|
||||
[ -f "${MOCK_CRASH_SENDING}" ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Return the board name.
|
||||
get_board() {
|
||||
echo $(get_key_value "/etc/lsb-release" "CHROMEOS_RELEASE_BOARD")
|
||||
}
|
||||
|
||||
# Return the hardware class or "unknown".
|
||||
get_hardware_class() {
|
||||
if [ -r "${HWCLASS_PATH}" ]; then
|
||||
cat "${HWCLASS_PATH}"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
send_crash() {
|
||||
local report_path="$1"
|
||||
local kind=$(get_kind "${report_path}")
|
||||
local exec_name=$(get_exec_name "${report_path}")
|
||||
local meta_path="$1"
|
||||
local kind="$(get_kind "${meta_path}")"
|
||||
local exec_name="$(get_key_value "${meta_path}" "exec_name")"
|
||||
local sleep_time=$(generate_uniform_random $SECONDS_SEND_SPREAD)
|
||||
local url="${REPORT_UPLOAD_STAGING_URL}"
|
||||
if is_official; then
|
||||
url="${REPORT_UPLOAD_PROD_URL}"
|
||||
fi
|
||||
local url="${REPORT_UPLOAD_PROD_URL}"
|
||||
local chromeos_version="$(get_key_value "${meta_path}" "ver")"
|
||||
local board="$(get_board)"
|
||||
local hwclass="$(get_hardware_class)"
|
||||
local payload_extension="${kind}"
|
||||
[ "${kind}" = "minidump" ] && payload_extension="dmp"
|
||||
local report_payload="$(get_base "${meta_path}").${payload_extension}"
|
||||
lecho "Sending crash:"
|
||||
lecho " Scheduled to send in ${sleep_time}s"
|
||||
lecho " Report: ${report_path} (${kind})"
|
||||
lecho " URL: ${url}"
|
||||
lecho " Product: ${CHROMEOS_PRODUCT}"
|
||||
lecho " Metadata: ${meta_path} (${kind})"
|
||||
lecho " Payload: ${report_payload}"
|
||||
lecho " Version: ${chromeos_version}"
|
||||
if is_mock; then
|
||||
lecho " Product: ${CHROMEOS_PRODUCT}"
|
||||
lecho " URL: ${url}"
|
||||
lecho " Board: ${board}"
|
||||
lecho " HWClass: ${hwclass}"
|
||||
fi
|
||||
lecho " Exec name: ${exec_name}"
|
||||
if [ -f "${MOCK_CRASH_SENDING}" ]; then
|
||||
if is_mock; then
|
||||
local mock_in=$(cat "${MOCK_CRASH_SENDING}")
|
||||
if [ "${mock_in}" = "" ]; then
|
||||
lecho "Mocking successful send"
|
||||
|
|
@ -185,7 +231,9 @@ send_crash() {
|
|||
curl "${url}" \
|
||||
-F "prod=${CHROMEOS_PRODUCT}" \
|
||||
-F "ver=${chromeos_version}" \
|
||||
-F "upload_file_${kind}=@${report_path}" \
|
||||
-F "upload_file_${kind}=@${report_payload}" \
|
||||
-F "board=${board}" \
|
||||
-F "hwclass=${hwclass}" \
|
||||
-F "exec_name=${exec_name}" \
|
||||
-F "guid=<${CONSENT_ID}" -o "${report_id}" 2>"${curl_stderr}"
|
||||
curl_result=$?
|
||||
|
|
@ -202,53 +250,98 @@ send_crash() {
|
|||
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.
|
||||
send_crashes() {
|
||||
local dir="$1"
|
||||
lecho "Considering crashes in ${dir}"
|
||||
|
||||
# Cycle through minidumps, most recent first. That way if we're about
|
||||
# to exceed the daily rate, we send the most recent minidumps.
|
||||
if [ ! -d "${dir}" ]; then
|
||||
return
|
||||
fi
|
||||
for file in $(ls -1t "${dir}"); do
|
||||
local report_path="${dir}/${file}"
|
||||
lecho "Considering file ${report_path}"
|
||||
local kind=$(get_kind "${report_path}")
|
||||
|
||||
if [ "${kind}" = "core" ]; then
|
||||
lecho "Ignoring core file."
|
||||
continue
|
||||
elif [ "${kind}" != "minidump" ] && [ "${kind}" != "kcrash" ]; then
|
||||
lecho "Unknown report kind: ${kind}. Removing report."
|
||||
rm -f "${report_path}"
|
||||
# Consider any old files which still have no corresponding meta file
|
||||
# as orphaned, and remove them.
|
||||
for old_file in $(${FIND} "${dir}" -mindepth 1 \
|
||||
-mmin +$((24 * 60)) -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, if any exist.
|
||||
for meta_path in $(ls -1t "${dir}"/*.meta 2>/dev/null); do
|
||||
lecho "Considering metadata ${meta_path}."
|
||||
local kind=$(get_kind "${meta_path}")
|
||||
|
||||
if [ "${kind}" != "minidump" ] && [ "${kind}" != "kcrash" ]; then
|
||||
lecho "Unknown report kind. Removing report."
|
||||
remove_report "${meta_path}"
|
||||
continue
|
||||
fi
|
||||
if ! check_rate; then
|
||||
lecho "Sending ${report_path} would exceed rate. Leaving for later."
|
||||
return 0
|
||||
fi
|
||||
local chromeos_version=$(get_version)
|
||||
|
||||
if is_feedback_disabled; then
|
||||
lecho "Uploading is disabled. Removing crash."
|
||||
rm "${report_path}"
|
||||
elif is_on_3g; then
|
||||
lecho "Not sending crash report while on 3G, saving for later."
|
||||
elif send_crash "${report_path}"; then
|
||||
# Send was successful, now remove
|
||||
lecho "Successfully sent crash ${report_path} and removing"
|
||||
rm "${report_path}"
|
||||
else
|
||||
lecho "Problem sending ${report_path}, not removing"
|
||||
remove_report "${meta_path}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! is_mock && ! is_official; then
|
||||
lecho "Not an official OS version. Removing crash."
|
||||
remove_report "${meta_path}"
|
||||
continue
|
||||
fi
|
||||
|
||||
if is_on_3g; then
|
||||
lecho "Not sending crash reports while on 3G, saving for later."
|
||||
return 0
|
||||
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}") -mmin +$((24 * 60)) -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
|
||||
|
||||
if ! check_rate; then
|
||||
lecho "Sending ${meta_path} would exceed rate. Leaving for later."
|
||||
return 0
|
||||
fi
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
main() {
|
||||
trap cleanup EXIT INT TERM
|
||||
if [ -e "${PAUSE_CRASH_SENDING}" ]; then
|
||||
lecho "Exiting early due to ${PAUSE_CRASH_SENDING}"
|
||||
lecho "Exiting early due to ${PAUSE_CRASH_SENDING}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -66,17 +66,6 @@ bool KernelCollector::ClearPreservedDump() {
|
|||
return true;
|
||||
}
|
||||
|
||||
FilePath KernelCollector::GetKernelCrashPath(
|
||||
const FilePath &root_crash_path,
|
||||
time_t timestamp) {
|
||||
std::string dump_basename =
|
||||
FormatDumpBasename(kKernelExecName,
|
||||
timestamp,
|
||||
kKernelPid);
|
||||
return root_crash_path.Append(
|
||||
StringPrintf("%s.kcrash", dump_basename.c_str()));
|
||||
}
|
||||
|
||||
bool KernelCollector::Collect() {
|
||||
std::string kernel_dump;
|
||||
FilePath root_crash_directory;
|
||||
|
|
@ -86,6 +75,8 @@ bool KernelCollector::Collect() {
|
|||
if (kernel_dump.empty()) {
|
||||
return false;
|
||||
}
|
||||
logger_->LogInfo("Received prior crash notification from kernel");
|
||||
|
||||
if (is_feedback_allowed_function_()) {
|
||||
count_crash_function_();
|
||||
|
||||
|
|
@ -94,8 +85,13 @@ bool KernelCollector::Collect() {
|
|||
return true;
|
||||
}
|
||||
|
||||
FilePath kernel_crash_path = GetKernelCrashPath(root_crash_directory,
|
||||
time(NULL));
|
||||
std::string dump_basename =
|
||||
FormatDumpBasename(kKernelExecName,
|
||||
time(NULL),
|
||||
kKernelPid);
|
||||
FilePath kernel_crash_path = root_crash_directory.Append(
|
||||
StringPrintf("%s.kcrash", dump_basename.c_str()));
|
||||
|
||||
if (file_util::WriteFile(kernel_crash_path,
|
||||
kernel_dump.data(),
|
||||
kernel_dump.length()) !=
|
||||
|
|
@ -105,6 +101,11 @@ bool KernelCollector::Collect() {
|
|||
return true;
|
||||
}
|
||||
|
||||
WriteCrashMetaData(
|
||||
root_crash_directory.Append(
|
||||
StringPrintf("%s.meta", dump_basename.c_str())),
|
||||
kKernelExecName);
|
||||
|
||||
logger_->LogInfo("Collected kernel crash diagnostics into %s",
|
||||
kernel_crash_path.value().c_str());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -37,14 +37,11 @@ class KernelCollector : public CrashCollector {
|
|||
private:
|
||||
friend class KernelCollectorTest;
|
||||
FRIEND_TEST(KernelCollectorTest, ClearPreservedDump);
|
||||
FRIEND_TEST(KernelCollectorTest, GetKernelCrashPath);
|
||||
FRIEND_TEST(KernelCollectorTest, LoadPreservedDump);
|
||||
FRIEND_TEST(KernelCollectorTest, CollectOK);
|
||||
|
||||
bool LoadPreservedDump(std::string *contents);
|
||||
bool ClearPreservedDump();
|
||||
FilePath GetKernelCrashPath(const FilePath &root_crash_path,
|
||||
time_t timestamp);
|
||||
|
||||
bool is_enabled_;
|
||||
FilePath preserved_dump_path_;
|
||||
|
|
|
|||
|
|
@ -95,21 +95,6 @@ TEST_F(KernelCollectorTest, ClearPreservedDump) {
|
|||
ASSERT_EQ(KernelCollector::kClearingSequence, dump);
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, GetKernelCrashPath) {
|
||||
FilePath root("/var/spool/crash");
|
||||
struct tm tm = {0};
|
||||
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;
|
||||
FilePath path = collector_.GetKernelCrashPath(root, mktime(&tm));
|
||||
ASSERT_EQ("/var/spool/crash/kernel.20100523.135015.0.kcrash",
|
||||
path.value());
|
||||
}
|
||||
|
||||
TEST_F(KernelCollectorTest, CollectPreservedFileMissing) {
|
||||
ASSERT_FALSE(collector_.Collect());
|
||||
ASSERT_NE(logging_.log().find("Unable to read test/kcrash"),
|
||||
|
|
|
|||
|
|
@ -275,6 +275,11 @@ bool UserCollector::GenerateDiagnostics(pid_t pid,
|
|||
conversion_result = false;
|
||||
}
|
||||
|
||||
WriteCrashMetaData(
|
||||
crash_path.Append(
|
||||
StringPrintf("%s.meta", dump_basename.c_str())),
|
||||
exec_name);
|
||||
|
||||
if (conversion_result) {
|
||||
logger_->LogInfo("Stored minidump to %s", minidump_path.value().c_str());
|
||||
}
|
||||
|
|
@ -298,10 +303,12 @@ bool UserCollector::HandleCrash(int signal, int pid, const char *force_exec) {
|
|||
// failing by indicating an unknown name.
|
||||
exec = "unknown";
|
||||
}
|
||||
logger_->LogWarning("Received crash notification for %s[%d] sig %d",
|
||||
exec.c_str(), pid, signal);
|
||||
bool feedback = is_feedback_allowed_function_();
|
||||
logger_->LogWarning("Received crash notification for %s[%d] sig %d (%s)",
|
||||
exec.c_str(), pid, signal,
|
||||
feedback ? "handling" : "ignoring");
|
||||
|
||||
if (is_feedback_allowed_function_()) {
|
||||
if (feedback) {
|
||||
count_crash_function_();
|
||||
|
||||
if (generate_diagnostics_) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue