blob: 443179d01d5f460b3b36c4be8bc0fd6192632637 [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/fit/result.h>
#include <zircon/types.h>
#include <sstream>
#include <string>
#include <vector>
#include "src/developer/feedback/boot_log_checker/metrics_registry.cb.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,
std::shared_ptr<sys::ServiceDirectory> services) {
std::unique_ptr<RebootLogHandler> handler = std::make_unique<RebootLogHandler>(services);
// We move |handler| in a subsequent chained promise to guarantee its lifetime.
return handler->Handle(filepath).then(
[handler = std::move(handler)](fit::result<void>& result) { return std::move(result); });
}
RebootLogHandler::RebootLogHandler(std::shared_ptr<sys::ServiceDirectory> services)
: services_(services) {}
namespace {
bool ExtractCrashType(const std::string& reboot_log, CrashType* crash_type) {
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;
}
if (first_line == "ZIRCON KERNEL PANIC") {
*crash_type = CrashType::KERNEL_PANIC;
return true;
}
if (first_line == "ZIRCON OOM") {
*crash_type = CrashType::OOM;
return true;
}
FX_LOGS(ERROR) << "failed to extract a crash type from first line of reboot log - defaulting to "
"kernel panic";
*crash_type = CrashType::KERNEL_PANIC;
return true;
}
std::string ProgramName(const CrashType cause) {
switch (cause) {
case CrashType::KERNEL_PANIC:
return "kernel";
case CrashType::OOM:
return "oom";
}
}
std::string Signature(const CrashType cause) {
switch (cause) {
case CrashType::KERNEL_PANIC:
return "fuchsia-kernel-panic";
case CrashType::OOM:
return "fuchsia-oom";
}
}
std::string CobaltStatus(fuchsia::cobalt::Status status) {
switch (status) {
case fuchsia::cobalt::Status::OK:
return "OK";
case fuchsia::cobalt::Status::INVALID_ARGUMENTS:
return "INVALID_ARGUMENTS";
case fuchsia::cobalt::Status::EVENT_TOO_BIG:
return "EVENT_TOO_BIG";
case fuchsia::cobalt::Status::BUFFER_FULL:
return "BUFFER_FULL";
case fuchsia::cobalt::Status::INTERNAL_ERROR:
return "INTERNAL_ERROR";
}
}
} // 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;
CrashType crash_type;
if (!ExtractCrashType(reboot_log_str, &crash_type)) {
return fit::make_result_promise<void>(fit::error());
}
// We then wait for the network to be reachable before handing it off to the
// crash reporter.
return fit::join_promises(SendCobaltMetrics(crash_type),
WaitForNetworkToBeReachable().and_then(FileCrashReport(crash_type)))
.and_then([](std::tuple<fit::result<void>, fit::result<void>>& result) {
const auto& cobalt_result = std::get<0>(result);
if (cobalt_result.is_error()) {
return cobalt_result;
}
return std::get<1>(result);
}); // return error if either one in the tuple is error
}
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 CrashType crash_type) {
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 attachment.
fuchsia::feedback::Attachment attachment;
attachment.key = "reboot_crash_log";
attachment.value = std::move(reboot_log_).ToTransport();
std::vector<fuchsia::feedback::Attachment> attachments;
attachments.push_back(std::move(attachment));
// Build the crash report.
fuchsia::feedback::GenericCrashReport generic_report;
generic_report.set_crash_signature(Signature(crash_type));
fuchsia::feedback::SpecificCrashReport specific_report;
specific_report.set_generic(std::move(generic_report));
fuchsia::feedback::CrashReport report;
report.set_program_name(ProgramName(crash_type));
report.set_specific_report(std::move(specific_report));
report.set_attachments(std::move(attachments));
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();
}
});
return crash_reporting_done_.consumer.promise_or(fit::error());
}
fit::promise<void> RebootLogHandler::SendCobaltMetrics(CrashType crash_type) {
// Connect to the cobalt fidl service provided by the environment.
cobalt_logger_factory_ = services_->Connect<fuchsia::cobalt::LoggerFactory>();
cobalt_logger_factory_.set_error_handler([this](zx_status_t status) {
if (!cobalt_logging_done_.completer) {
return;
}
FX_PLOGS(ERROR, status) << "lost connection to Cobalt metrics logger factory";
cobalt_logging_done_.completer.complete_error();
});
// Create a Cobalt Logger. The project name is the one we specified in the
// Cobalt metrics registry. We specify that our release stage is DOGFOOD.
// This means we are not allowed to use any metrics declared as DEBUG
// or FISHFOOD.
static const char kProjectName[] = "feedback";
cobalt_logger_factory_->CreateLoggerFromProjectName(
kProjectName, fuchsia::cobalt::ReleaseStage::DOGFOOD, cobalt_logger_.NewRequest(),
[this, crash_type](fuchsia::cobalt::Status status) {
if (status != fuchsia::cobalt::Status::OK) {
FX_LOGS(ERROR) << "error getting feedback metrics logger: " << CobaltStatus(status);
cobalt_logging_done_.completer.complete_error();
return;
}
cobalt_logger_.set_error_handler([this](zx_status_t status) {
if (!cobalt_logging_done_.completer) {
return;
}
FX_PLOGS(ERROR, status) << "lost connection to feedback metrics logger";
cobalt_logging_done_.completer.complete_error();
});
cobalt_registry::RebootMetricDimensionReason reboot_reason =
crash_type == CrashType::KERNEL_PANIC
? cobalt_registry::RebootMetricDimensionReason::KernelPanic
: cobalt_registry::RebootMetricDimensionReason::Oom;
cobalt_logger_->LogEvent(cobalt_registry::kRebootMetricId, reboot_reason,
[this](fuchsia::cobalt::Status status) {
if (!cobalt_logging_done_.completer) {
return;
}
if (status != fuchsia::cobalt::Status::OK) {
FX_LOGS(ERROR) << "error sending feedback metrics: "
<< CobaltStatus(status);
cobalt_logging_done_.completer.complete_error();
return;
}
cobalt_logging_done_.completer.complete_ok();
});
});
return cobalt_logging_done_.consumer.promise_or(fit::error());
}
} // namespace feedback