blob: f55a43227558c8b578d70a7405ca585bba5cb8b6 [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/forensics/utils/cobalt/logger.h"
#include <lib/async/cpp/task.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <string>
#include "src/cobalt/bin/utils/status_utils.h"
#include "src/developer/forensics/utils/cobalt/metrics.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace forensics {
namespace cobalt {
namespace {
using async::PostDelayedTask;
using ::cobalt::StatusToString;
using fuchsia::cobalt::LoggerFactory;
using fuchsia::cobalt::Status;
using fxl::StringPrintf;
constexpr uint32_t kMaxPendingEvents = 500u;
uint64_t CurrentTimeUSecs(const std::unique_ptr<timekeeper::Clock>& clock) {
return zx::nsec(clock->Now().get()).to_usecs();
}
} // namespace
Logger::Logger(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
std::unique_ptr<timekeeper::Clock> clock)
: dispatcher_(dispatcher),
services_(services),
clock_(std::move(clock)),
logger_reconnection_backoff_(/*initial_delay=*/zx::msec(100), /*retry_factor=*/2u,
/*max_delay=*/zx::hour(1)) {
logger_.set_error_handler([this](zx_status_t status) {
FX_PLOGS(WARNING, status) << "Lost connection with fuchsia.cobalt.Logger";
RetryConnectingToLogger();
});
auto logger_request = logger_.NewRequest();
ConnectToLogger(std::move(logger_request));
}
void Logger::Shutdown() {
shut_down_ = true;
pending_events_.clear();
timer_starts_usecs_.clear();
reconnect_task_.Cancel();
logger_factory_.Unbind();
logger_.Unbind();
}
void Logger::ConnectToLogger(::fidl::InterfaceRequest<fuchsia::cobalt::Logger> logger_request) {
// Connect to the LoggerFactory.
logger_factory_ = services_->Connect<LoggerFactory>();
logger_factory_.set_error_handler([](zx_status_t status) {
FX_PLOGS(WARNING, status) << "Lost connection with fuchsia.cobalt.LoggerFactory";
});
// We don't need a long standing connection to the LoggerFactory so we unbind afer setting up the
// Logger.
logger_factory_->CreateLoggerFromProjectId(
kProjectId, std::move(logger_request), [this](Status status) {
logger_factory_.Unbind();
if (status == Status::OK) {
logger_reconnection_backoff_.Reset();
} else if (status == Status::SHUT_DOWN) {
FX_LOGS(INFO) << "Stopping logging Cobalt events";
logger_.Unbind();
} else {
FX_LOGS(WARNING) << "Failed to set up Logger: " << StatusToString(status);
logger_.Unbind();
RetryConnectingToLogger();
}
});
}
void Logger::RetryConnectingToLogger() {
if (logger_) {
return;
}
// Bind |logger_| and immediately send the events that were not acknowledged by the server on the
// previous connection.
auto logger_request = logger_.NewRequest();
SendAllPendingEvents();
reconnect_task_.Reset([this, request = std::move(logger_request)]() mutable {
ConnectToLogger(std::move(request));
});
PostDelayedTask(
dispatcher_, [reconnect = reconnect_task_.callback()]() { reconnect(); },
logger_reconnection_backoff_.GetNext());
}
void Logger::LogEvent(Event event) {
FX_CHECK(!shut_down_);
if (pending_events_.size() >= kMaxPendingEvents) {
FX_LOGS(INFO) << StringPrintf("Dropping Cobalt event %s - too many pending events (%lu)",
event.ToString().c_str(), pending_events_.size());
return;
}
const uint64_t event_id = next_event_id_++;
pending_events_.insert(std::make_pair(event_id, std::move(event)));
SendEvent(event_id);
}
uint64_t Logger::StartTimer() {
FX_CHECK(!shut_down_);
const uint64_t timer_id = next_event_id_++;
timer_starts_usecs_.insert(std::make_pair(timer_id, CurrentTimeUSecs(clock_)));
return timer_id;
}
void Logger::SendEvent(uint64_t event_id) {
if (!logger_) {
return;
}
if (pending_events_.find(event_id) == pending_events_.end()) {
return;
}
Event& event = pending_events_.at(event_id);
auto cb = [this, event_id, &event](Status status) {
if (status != Status::OK) {
FX_LOGS(INFO) << StringPrintf("Cobalt logging error: status %s, event %s",
StatusToString(status).c_str(), event.ToString().c_str());
}
// We don't retry events that have been acknowledged by the server, regardless of the return
// status.
pending_events_.erase(event_id);
};
switch (event.type) {
case EventType::kOccurrence:
logger_->LogEvent(event.metric_id, event.dimensions[0], std::move(cb));
break;
case EventType::kCount:
logger_->LogEventCount(event.metric_id, event.dimensions[0], /*component=*/"",
/*period_duration_micros=*/0u, event.count, std::move(cb));
break;
case EventType::kTimeElapsed:
logger_->LogElapsedTime(event.metric_id, event.dimensions[0], /*component=*/"",
/*elapsed_micros=*/event.usecs_elapsed, std::move(cb));
break;
case EventType::kMultidimensionalEvent:
fuchsia::cobalt::CobaltEvent cobalt_event;
cobalt_event.metric_id = event.metric_id;
cobalt_event.event_codes = event.dimensions;
cobalt_event.payload = fuchsia::cobalt::EventPayload::WithEventCount(
fuchsia::cobalt::CountEvent{.count = static_cast<int64_t>(event.count)});
logger_->LogCobaltEvent(std::move(cobalt_event), std::move(cb));
break;
}
}
void Logger::SendAllPendingEvents() {
for (const auto& [event_id, _] : pending_events_) {
SendEvent(event_id);
}
}
uint64_t Logger::GetTimerDurationUSecs(uint64_t timer_id) const {
FX_CHECK(timer_starts_usecs_.find(timer_id) != timer_starts_usecs_.end());
return CurrentTimeUSecs(clock_) - timer_starts_usecs_.at(timer_id);
}
} // namespace cobalt
} // namespace forensics