blob: 6375d02a6488a01213f397187b7624a7473dd93f [file] [log] [blame]
// Copyright 2017 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 <fuchsia/buildinfo/cpp/fidl.h>
#include <fuchsia/sysinfo/cpp/fidl.h>
#include <fuchsia/time/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/inspect/component/cpp/component.h>
#include <lib/scheduler/role.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace-provider/provider.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zx/channel.h>
#include <lib/zx/clock.h>
#include <stdlib.h>
#include <zircon/processargs.h>
#include <zircon/types.h>
#include <zircon/utc.h>
#include <chrono>
#include <fstream>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include "lib/fidl/cpp/interface_request.h"
#include "src/cobalt/bin/app/cobalt_app.h"
#include "src/lib/files/file.h"
#include "src/lib/fxl/command_line.h"
#include "src/lib/fxl/log_settings_command_line.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/split_string.h"
#include "src/public/cobalt_config.h"
// Command-line flags
// Used to override kScheduleIntervalDefault;
constexpr std::string_view kScheduleIntervalSecondsFlagName = "schedule_interval_seconds";
constexpr std::string_view kInitialIntervalSecondsFlagName = "initial_interval_seconds";
// Used to override kMinIntervalDefault;
constexpr std::string_view kMinIntervalSecondsFlagName = "min_interval_seconds";
// Used to override kUploadJitterDefault;
constexpr std::string_view kUploadJitterFlagName = "upload_jitter";
// Used to override kEventAggregatorBackfillDaysDefault
constexpr std::string_view kEventAggregatorBackfillDaysFlagName = "event_aggregator_backfill_days";
// Used to override kStartEventAggregatorWorkerDefault
constexpr std::string_view kStartEventAggregatorWorkerFlagName = "start_event_aggregator_worker";
constexpr std::string_view kUseMemoryObservationStore = "use_memory_observation_store";
constexpr std::string_view kMaxBytesTotalFlagName = "max_bytes_per_observation_store";
constexpr std::string_view kPerProjectReservedBytes = "per_project_reserved_bytes";
constexpr std::string_view kTotalCapacityBytes = "local_storage_max_bytes_total";
constexpr std::string_view kTestOnlyFakeClockFlagName = "test_only_use_fake_clock";
constexpr std::string_view kRequireLifecycleService = "require_lifecycle_service";
constexpr std::string_view kTestDontBackfillEmptyReports = "test_dont_backfill_empty_reports";
// We want to only upload every hour. This is the interval that will be
// approached by the uploader.
const std::chrono::hours kScheduleIntervalDefault(1);
// We start uploading every minute and exponentially back off until we reach 1
// hour.
const std::chrono::minutes kInitialIntervalDefault(1);
// We send Observations to the Shuffler more frequently than kScheduleInterval
// under some circumstances, namely, if there is memory pressure or if we
// are explicitly asked to do so via the RequestSendSoon() method. This value
// is a safety parameter. We do not make two attempts within a period of this
// specified length.
const std::chrono::seconds kMinIntervalDefault(10);
// Offset uploads by at most 20% of current interval.
float kUploadJitterDefault(.2);
// The EventAggregator looks back 2 days, in addition to the previous day, to
// make sure that all locally aggregated observations have been generated.
const size_t kEventAggregatorBackfillDaysDefault(2);
// We normally start the EventAggregator's worker thread after constructing the
// EventAggregator.
constexpr bool kStartEventAggregatorWorkerDefault(true);
// ReadBoardName returns the board name of the currently running device.
//
// At the time of this writing, this will either be 'pc' for x86 devices, or an
// appropriate board name for ARM devices (sherlock, qemu).
//
// This uses the fuchsia-sysinfo fidl service to read the board_name field out
// of the ZBI. This string will never exceed a length of 32.
//
// If the reading of the board name fails for any reason, this will return "".
std::string ReadBoardName(const std::shared_ptr<sys::ServiceDirectory>& services) {
fuchsia::sysinfo::SysInfoSyncPtr sysinfo;
services->Connect<fuchsia::sysinfo::SysInfo>(sysinfo.NewRequest());
// Read the board name out of the ZBI.
zx_status_t status;
fidl::StringPtr board_name;
zx_status_t fidl_status = sysinfo->GetBoardName(&status, &board_name);
if (fidl_status != ZX_OK || status != ZX_OK) {
return "";
}
return board_name.value();
}
// Replaces the UTC clock installed in the process' namespace with a fake one
// that is always "logging quality." This is intended to simulate a synchronized clock in
// test environments without network and should not be used outside of tests.
void ReplaceRuntimeClock() {
zx::clock current_clock(zx_utc_reference_get());
zx::time start_time;
current_clock.read(start_time.get_address());
zx::clock replacement;
zx_clock_create_args_v1_t clock_args{.backstop_time = start_time.get()};
zx::clock::create(0u, &clock_args, &replacement);
zx::clock::update_args update_args;
update_args.set_value(start_time);
replacement.update(update_args);
if (const zx_status_t status =
replacement.signal(/*clear_mask=*/0,
/*set_mask=*/fuchsia::time::SIGNAL_UTC_CLOCK_LOGGING_QUALITY);
status != ZX_OK) {
FX_PLOGS(FATAL, status) << "Failed to achieve logging quality clock";
}
zx_utc_reference_swap(replacement.release(), current_clock.reset_and_get_address());
}
int main(int argc, const char** argv) {
// Parse the flags.
const auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
fxl::SetLogSettingsFromCommandLine(command_line, {"cobalt", "fidl_service"});
// Parse the schedule_interval_seconds flag.
std::chrono::seconds schedule_interval =
std::chrono::duration_cast<std::chrono::seconds>(kScheduleIntervalDefault);
std::chrono::seconds initial_interval =
std::chrono::duration_cast<std::chrono::seconds>(kInitialIntervalDefault);
std::string flag_value;
if (command_line.GetOptionValue(kScheduleIntervalSecondsFlagName, &flag_value)) {
int num_seconds = std::stoi(flag_value);
if (num_seconds > 0) {
schedule_interval = std::chrono::seconds(num_seconds);
// Set initial_interval, it can still be overridden by a flag.
initial_interval = std::chrono::seconds(num_seconds);
}
}
// Parse the initial_interval_seconds flag.
flag_value.clear();
if (command_line.GetOptionValue(kInitialIntervalSecondsFlagName, &flag_value)) {
int num_seconds = std::stoi(flag_value);
if (num_seconds > 0) {
initial_interval = std::chrono::seconds(num_seconds);
}
}
// Parse the min_interval_seconds flag.
std::chrono::seconds min_interval =
std::chrono::duration_cast<std::chrono::seconds>(kMinIntervalDefault);
flag_value.clear();
if (command_line.GetOptionValue(kMinIntervalSecondsFlagName, &flag_value)) {
int num_seconds = std::stoi(flag_value);
// We allow min_interval = 0.
if (num_seconds >= 0) {
min_interval = std::chrono::seconds(num_seconds);
}
}
// Parse the upload_jitter flag.
float upload_jitter = kUploadJitterDefault;
flag_value.clear();
if (command_line.GetOptionValue(kUploadJitterFlagName, &flag_value)) {
float jitter_percentage = std::stof(flag_value);
// We allow 0 <= jitter < 1.
if (jitter_percentage >= 0 && jitter_percentage < 1) {
upload_jitter = jitter_percentage;
}
}
// Parse the event_aggregator_backfill_days flag.
size_t event_aggregator_backfill_days = kEventAggregatorBackfillDaysDefault;
flag_value.clear();
if (command_line.GetOptionValue(kEventAggregatorBackfillDaysFlagName, &flag_value)) {
int num_days = std::stoi(flag_value);
// We allow num_days = 0.
if (num_days >= 0) {
event_aggregator_backfill_days = num_days;
}
}
// Parse the start_event_aggregator_worker flag.
bool start_event_aggregator_worker = kStartEventAggregatorWorkerDefault;
flag_value.clear();
if (command_line.GetOptionValue(kStartEventAggregatorWorkerFlagName, &flag_value)) {
if (flag_value == "true") {
start_event_aggregator_worker = true;
} else if (flag_value == "false") {
start_event_aggregator_worker = false;
}
}
bool use_memory_observation_store = command_line.HasOption(kUseMemoryObservationStore);
// Parse the max_bytes_per_observation_store
size_t max_bytes_per_observation_store = size_t{210} * 1024; // 210 KiB (~20% of 1MiB)
flag_value.clear();
if (command_line.GetOptionValue(kMaxBytesTotalFlagName, &flag_value)) {
int num_bytes = std::stoi(flag_value);
if (num_bytes > 0) {
max_bytes_per_observation_store = num_bytes;
}
}
// Parse StorageQuotas
cobalt::StorageQuotas storage_quotas = {
.per_project_reserved_bytes = 1024, // 1KiB per project (enough for 714 projects)
.total_capacity_bytes = int64_t{714} * 1024 // 714KiB for local aggregation (~70% of 1MiB)
};
flag_value.clear();
if (command_line.GetOptionValue(kPerProjectReservedBytes, &flag_value)) {
int num_bytes = std::stoi(flag_value);
if (num_bytes > 0) {
storage_quotas.per_project_reserved_bytes = num_bytes;
}
}
flag_value.clear();
if (command_line.GetOptionValue(kTotalCapacityBytes, &flag_value)) {
int num_bytes = std::stoi(flag_value);
if (num_bytes > 0) {
storage_quotas.total_capacity_bytes = num_bytes;
}
}
bool use_fake_clock = command_line.HasOption(kTestOnlyFakeClockFlagName);
bool test_dont_backfill_empty_reports = command_line.HasOption(kTestDontBackfillEmptyReports);
FX_LOGS(INFO) << "Cobalt is starting with the following parameters: " << "schedule_interval="
<< schedule_interval.count() << " seconds, min_interval=" << min_interval.count()
<< " seconds, initial_interval=" << initial_interval.count()
<< " seconds, upload_jitter=" << (upload_jitter * 100) << "%"
<< ", max_bytes_per_observation_store=" << max_bytes_per_observation_store
<< ", storage_quotas.per_project_reserved_bytes="
<< storage_quotas.per_project_reserved_bytes
<< ", storage_quotas.total_capacity_bytes=" << storage_quotas.total_capacity_bytes
<< ", event_aggregator_backfill_days=" << event_aggregator_backfill_days
<< ", start_event_aggregator_worker=" << start_event_aggregator_worker
<< ", test_only_use_fake_clock=" << use_fake_clock
<< ", test_dont_backfill_empty_reports=" << test_dont_backfill_empty_reports << ".";
if (use_fake_clock) {
FX_LOGS(WARNING) << "Using a fake clock. This should only be enabled in tests.";
ReplaceRuntimeClock();
}
if (test_dont_backfill_empty_reports) {
FX_LOGS(WARNING) << "Not backfilling empty reports. This should only be enabled in tests.";
}
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
inspect::ComponentInspector inspector(loop.dispatcher(), {});
inspector.Health().StartingUp();
// Lower the priority of the main thread.
zx_status_t status = fuchsia_scheduler::SetRoleForThisThread("fuchsia.cobalt.main");
if (status != ZX_OK) {
FX_LOGS(INFO) << "Unable to set scheduler role.";
} else {
FX_LOGS(INFO) << "Scheduler role aplied to current thread";
}
std::string product = "<product read failed>";
std::string version = "<version read failed>";
{
fuchsia::buildinfo::ProviderSyncPtr buildinfo_provider;
context->svc()->Connect<fuchsia::buildinfo::Provider>(buildinfo_provider.NewRequest());
fuchsia::buildinfo::BuildInfo build_info;
status = buildinfo_provider->GetBuildInfo(&build_info);
if (status == ZX_OK) {
if (!build_info.has_product_config() || build_info.product_config().empty()) {
product = "<product not specified>";
} else {
product = build_info.product_config();
}
if (!build_info.has_version() || build_info.version().empty()) {
version = "<version not specified>";
} else {
version = build_info.version();
}
}
}
bool require_lifecycle_service = command_line.HasOption(kRequireLifecycleService);
fidl::InterfaceRequest<fuchsia::process::lifecycle::Lifecycle> lifecycle_handle;
// Fetch the provided fuchsia::process::lifecycle::Lifecycle service handle.
zx::channel lifecycle_request(zx_take_startup_handle(PA_LIFECYCLE));
if (lifecycle_request.is_valid()) {
lifecycle_handle.set_channel(std::move(lifecycle_request));
} else {
FX_LOGS(ERROR) << "Startup Error: Received invalid lifecycle handle. Cobalt will not be able "
"to listen for lifecycle events and shut down gracefully.";
if (require_lifecycle_service) {
FX_LOGS(FATAL) << "Lifecycle service is required. Exiting";
}
}
auto boardname = ReadBoardName(context->svc());
trace::TraceProviderWithFdio trace_provider(loop.dispatcher(), "cobalt_fidl_provider");
cobalt::UploadScheduleConfig upload_schedule = {
.target_interval = schedule_interval,
.min_interval = min_interval,
.initial_interval = initial_interval,
.jitter = upload_jitter,
};
cobalt::lib::statusor::StatusOr<std::unique_ptr<cobalt::CobaltApp>> app =
cobalt::CobaltApp::CreateCobaltApp(
std::move(context), loop.dispatcher(), std::move(lifecycle_handle),
[loop = &loop]() { loop->Quit(); }, inspector.root().CreateChild("cobalt_app"),
upload_schedule, event_aggregator_backfill_days, start_event_aggregator_worker,
test_dont_backfill_empty_reports, use_memory_observation_store,
max_bytes_per_observation_store, storage_quotas, product, boardname, version);
if (!app.ok()) {
FX_LOGS(FATAL) << "Failed to construct the cobalt app: " << app.status();
}
inspector.Health().Ok();
loop.Run();
FX_LOGS(INFO) << "Cobalt will now shut down.";
return 0;
}