blob: 2363c59b2f79b39371ee5df9027f3e6a856faba2 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/feedback/boot_log_checker/reboot_log_handler.h"
#include <lib/async/cpp/task.h>
#include <lib/fit/result.h>
#include <lib/zx/time.h>
#include <zircon/types.h>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include "src/developer/feedback/utils/promise.h"
#include "src/lib/files/file.h"
#include "src/lib/fsl/vmo/file.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/syslog/cpp/logger.h"
namespace feedback {
fit::promise<void> HandleRebootLog(const std::string& filepath, async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services) {
auto handler = std::make_unique<internal::RebootLogHandler>(dispatcher, services);
// We must store the promise in a variable due to the fact that the order of evaluation of
// function parameters is undefined.
auto promise = handler->Handle(filepath);
return ExtendArgsLifetimeBeyondPromise(/*promise=*/std::move(promise),
/*args=*/std::move(handler));
}
namespace internal {
RebootLogHandler::RebootLogHandler(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services)
: dispatcher_(dispatcher), services_(services), cobalt_(dispatcher_, services_) {}
namespace {
void ExtractRebootReason(const std::string line, RebootReason* reboot_reason) {
struct StrToReason {
const char* str;
RebootReason reason;
};
constexpr std::array str_to_reason_map{
StrToReason{.str = "ZIRCON REBOOT REASON (KERNEL PANIC)",
.reason = RebootReason::kKernelPanic},
StrToReason{.str = "ZIRCON REBOOT REASON (OOM)", .reason = RebootReason::kOOM},
StrToReason{.str = "ZIRCON REBOOT REASON (SW WATCHDOG)",
.reason = RebootReason::kSoftwareWatchdog},
StrToReason{.str = "ZIRCON REBOOT REASON (HW WATCHDOG)",
.reason = RebootReason::kHardwareWatchdog},
StrToReason{.str = "ZIRCON REBOOT REASON (BROWNOUT)", .reason = RebootReason::kBrownout},
StrToReason{.str = "ZIRCON REBOOT REASON (UNKNOWN)", .reason = RebootReason::kUnknown},
};
for (const auto entry : str_to_reason_map) {
if (line == entry.str) {
*reboot_reason = entry.reason;
return;
}
}
FX_LOGS(ERROR) << "Failed to extract a reboot reason from first line of reboot log - defaulting "
"to kernel panic";
*reboot_reason = RebootReason::kKernelPanic;
}
void ExtractUptime(const std::string& third_line, const std::string& fourth_line,
std::optional<zx::duration>* uptime) {
if (third_line != "UPTIME (ms)") {
FX_LOGS(ERROR) << "Unexpected third line '" << third_line << "'";
*uptime = std::nullopt;
return;
}
*uptime = zx::msec(std::stoll(fourth_line));
}
bool ExtractRebootInfo(const std::string& reboot_log, RebootInfo* info) {
std::istringstream iss(reboot_log);
std::string first_line;
if (!std::getline(iss, first_line)) {
FX_LOGS(ERROR) << "Failed to read first line of reboot log";
return false;
}
// As we were able to read the first line of reboot log, we consider it a success from that point,
// even if we are unable to read the next couple of lines to get the uptime.
ExtractRebootReason(first_line, &(info->reboot_reason));
std::string second_line;
if (!std::getline(iss, second_line)) {
FX_LOGS(ERROR) << "Failed to read second line of reboot log";
return true;
}
if (!second_line.empty()) {
FX_LOGS(ERROR) << "Expected second line of reboot log to be empty, found '" << second_line
<< "'";
return true;
}
std::string third_line;
if (!std::getline(iss, third_line)) {
FX_LOGS(ERROR) << "Failed to read third line of reboot log";
return true;
}
std::string fourth_line;
if (!std::getline(iss, fourth_line)) {
FX_LOGS(ERROR) << "Failed to read fourth line of reboot log";
return true;
}
ExtractUptime(third_line, fourth_line, &(info->uptime));
return true;
}
std::string ProgramName(const RebootReason reboot_reason) {
switch (reboot_reason) {
case RebootReason::kKernelPanic:
return "kernel";
case RebootReason::kOOM:
return "oom";
case RebootReason::kBrownout:
case RebootReason::kHardwareWatchdog:
case RebootReason::kUnknown:
return "device";
case RebootReason::kClean:
case RebootReason::kCold:
case RebootReason::kSoftwareWatchdog:
return "system";
}
}
std::string Signature(const RebootReason reboot_reason) {
switch (reboot_reason) {
case RebootReason::kKernelPanic:
return "fuchsia-kernel-panic";
case RebootReason::kOOM:
return "fuchsia-oom";
case RebootReason::kSoftwareWatchdog:
return "fuchsia-sw-watchdog";
case RebootReason::kHardwareWatchdog:
return "fuchsia-hw-watchdog";
case RebootReason::kBrownout:
return "fuchsia-brownout";
case RebootReason::kUnknown:
return "fuchsia-reboot-unknown";
case RebootReason::kClean:
return "fuchsia-clean-reboot";
case RebootReason::kCold:
return "fuchsia-cold-boot";
}
}
} // namespace
fit::promise<void> RebootLogHandler::Handle(const std::string& filepath) {
FXL_CHECK(!has_called_handle_) << "Handle() is not intended to be called twice";
has_called_handle_ = true;
// We first check for the existence of the reboot log and attempt to parse it.
if (!files::IsFile(filepath)) {
FX_LOGS(INFO) << "No reboot log found";
return fit::make_ok_promise();
}
if (!fsl::VmoFromFilename(filepath, &reboot_log_)) {
FX_LOGS(ERROR) << "Error loading reboot log into VMO";
return fit::make_result_promise<void>(fit::error());
}
std::string reboot_log_str;
if (!fsl::StringFromVmo(reboot_log_, &reboot_log_str)) {
FX_LOGS(ERROR) << "Error parsing reboot log VMO as string";
return fit::make_result_promise<void>(fit::error());
}
FX_LOGS(INFO) << "Found reboot log:\n" << reboot_log_str;
RebootInfo info;
if (!ExtractRebootInfo(reboot_log_str, &info)) {
return fit::make_result_promise<void>(fit::error());
}
cobalt_.LogOccurrence(info.reboot_reason);
return WaitForNetworkToBeReachable().and_then(FileCrashReport(info));
}
fit::promise<void> RebootLogHandler::WaitForNetworkToBeReachable() {
connectivity_ = services_->Connect<fuchsia::net::Connectivity>();
connectivity_.set_error_handler([this](zx_status_t status) {
if (!network_reachable_.completer) {
return;
}
FX_PLOGS(ERROR, status) << "Lost connection to fuchsia.net.Connectivity";
network_reachable_.completer.complete_error();
});
connectivity_.events().OnNetworkReachable = [this](bool reachable) {
if (!reachable) {
return;
}
connectivity_.Unbind();
if (!network_reachable_.completer) {
return;
}
network_reachable_.completer.complete_ok();
};
return network_reachable_.consumer.promise_or(fit::error());
}
fit::promise<void> RebootLogHandler::FileCrashReport(const RebootInfo info) {
crash_reporter_ = services_->Connect<fuchsia::feedback::CrashReporter>();
crash_reporter_.set_error_handler([this](zx_status_t status) {
if (!crash_reporting_done_.completer) {
return;
}
FX_PLOGS(ERROR, status) << "Lost connection to fuchsia.feedback.CrashReporter";
crash_reporting_done_.completer.complete_error();
});
// Build the crash report attachments.
std::vector<fuchsia::feedback::Attachment> attachments;
fuchsia::feedback::Attachment attachment;
attachment.key = "reboot_crash_log";
attachment.value = std::move(reboot_log_).ToTransport();
attachments.push_back(std::move(attachment));
// Build the crash report.
fuchsia::feedback::GenericCrashReport generic_report;
generic_report.set_crash_signature(Signature(info.reboot_reason));
fuchsia::feedback::SpecificCrashReport specific_report;
specific_report.set_generic(std::move(generic_report));
fuchsia::feedback::CrashReport report;
report.set_program_name(ProgramName(info.reboot_reason));
if (info.uptime.has_value()) {
report.set_program_uptime(info.uptime.value().get());
}
report.set_specific_report(std::move(specific_report));
report.set_attachments(std::move(attachments));
// We file the crash report with a 30s delay so that memory_monitor has time to get picked up by
// the Inspect service and its data is included in the bugreport.zip generated by feedback_agent.
// The data is critical to debug OOM crash reports.
// TODO(fxb/46216): remove delay.
delayed_crash_reporting_.Reset([this, report = std::move(report)]() mutable {
crash_reporter_->File(std::move(report), [this](fit::result<void, zx_status_t> result) {
if (!crash_reporting_done_.completer) {
return;
}
if (result.is_error()) {
FX_PLOGS(ERROR, result.error())
<< "Failed to file a crash report for crash extracted from reboot log";
crash_reporting_done_.completer.complete_error();
} else {
crash_reporting_done_.completer.complete_ok();
}
});
});
if (const zx_status_t status = async::PostDelayedTask(
dispatcher_, [cb = delayed_crash_reporting_.callback()] { cb(); }, zx::sec(30));
status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed to post delayed task, no crash reporting";
crash_reporting_done_.completer.complete_error();
}
return crash_reporting_done_.consumer.promise_or(fit::error())
.then([this](fit::result<>& result) {
delayed_crash_reporting_.Cancel();
return std::move(result);
});
}
} // namespace internal
} // namespace feedback