blob: cdfe7f061dd23060ecb2337e7c9854f97f6d2f3b [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/feedback_agent/attachments/kernel_log_ptr.h"
#include <zircon/syscalls/log.h>
#include "src/developer/feedback/utils/cobalt_metrics.h"
#include "src/developer/feedback/utils/promise.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/syslog/cpp/logger.h"
namespace feedback {
fit::promise<fuchsia::mem::Buffer> CollectKernelLog(async_dispatcher_t* dispatcher,
std::shared_ptr<sys::ServiceDirectory> services,
zx::duration timeout,
Cobalt* cobalt) {
std::unique_ptr<BootLog> boot_log =
std::make_unique<BootLog>(dispatcher, services, cobalt);
// We must store the promise in a variable due to the fact that the order of evaluation of
// function parameters is undefined.
auto logs = boot_log->GetLog(timeout);
return ExtendArgsLifetimeBeyondPromise(/*promise=*/std::move(logs), /*args=*/std::move(boot_log));
}
BootLog::BootLog(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
Cobalt* cobalt)
: dispatcher_(dispatcher), services_(services), cobalt_(cobalt) {}
fit::promise<fuchsia::mem::Buffer> BootLog::GetLog(const zx::duration timeout) {
FXL_CHECK(!has_called_get_log_) << "GetLog() is not intended to be called twice";
has_called_get_log_ = true;
log_ptr_ = services_->Connect<fuchsia::boot::ReadOnlyLog>();
// fit::promise does not have the notion of a timeout. So we post a delayed task that will call
// the completer after the timeout and return an error.
//
// We wrap the delayed task in a CancelableClosure so we can cancel it when the fit::bridge is
// completed another way.
//
// It is safe to pass "this" to the fit::function as the callback won't be callable when the
// CancelableClosure goes out of scope, which is before "this".
done_after_timeout_.Reset([this] {
if (!done_.completer) {
return;
}
FX_LOGS(ERROR) << "Kernel log get timed out";
cobalt_->LogOccurrence(TimedOutData::kKernelLog);
done_.completer.complete_error();
});
const zx_status_t post_status = async::PostDelayedTask(
dispatcher_, [cb = done_after_timeout_.callback()] { cb(); }, timeout);
if (post_status != ZX_OK) {
FX_PLOGS(ERROR, post_status) << "Failed to post delayed cancellation task";
return fit::make_result_promise<fuchsia::mem::Buffer>(fit::error());
}
log_ptr_.set_error_handler([this](zx_status_t status) {
if (!done_.completer) {
return;
}
FX_PLOGS(ERROR, status) << "Lost connection to fuchsia.boot.ReadOnlyLog";
done_.completer.complete_error();
});
log_ptr_->Get([this](zx::debuglog log) {
if (!done_.completer) {
return;
}
// zx_log_record_t has a flexible array member, so we need to allocate the buffer explicitly.
char buf[ZX_LOG_RECORD_MAX + 1];
zx_log_record_t* record = reinterpret_cast<zx_log_record_t*>(buf);
std::string kernel_log;
while (log.read(/*options=*/0, /*buffer=*/record,
/*buffer_size=*/ZX_LOG_RECORD_MAX) > 0) {
if (record->datalen && (record->data[record->datalen - 1] == '\n')) {
record->datalen--;
}
record->data[record->datalen] = 0;
kernel_log += fxl::StringPrintf("[%05d.%03d] %05" PRIu64 ".%05" PRIu64 "> %s\n",
static_cast<int>(record->timestamp / 1000000000ULL),
static_cast<int>((record->timestamp / 1000000ULL) % 1000ULL),
record->pid, record->tid, record->data);
}
if (kernel_log.empty()) {
FX_LOGS(ERROR) << "Empty kernel log";
done_.completer.complete_error();
return;
}
fsl::SizedVmo vmo;
if (!fsl::VmoFromString(kernel_log, &vmo)) {
FX_LOGS(ERROR) << "Failed to convert kernel log string to vmo";
done_.completer.complete_error();
return;
}
done_.completer.complete_ok(std::move(vmo).ToTransport());
});
return done_.consumer.promise_or(fit::error())
.then([this](fit::result<fuchsia::mem::Buffer>& result) {
done_after_timeout_.Cancel();
return std::move(result);
});
}
} // namespace feedback