blob: c238c375510fdd81023c91c19d171cd2549d968a [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 <fcntl.h>
#include <fuchsia/cobalt/cpp/fidl.h>
#include <fuchsia/scheduler/cpp/fidl.h>
#include <fuchsia/sysinfo/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/syslog/cpp/macros.h>
#include <lib/trace-provider/provider.h>
#include <lib/zx/channel.h>
#include <lib/zx/clock.h>
#include <stdlib.h>
#include <zircon/boot/image.h>
#include <zircon/types.h>
#include <zircon/utc.h>
#include <chrono>
#include <fstream>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#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 kTestOnlyFakeClockFlagName = "test_only_use_fake_clock";
// 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();
}
std::string ReadBuildInfo(std::string value) {
std::ifstream file("/config/build-info/" + value);
if (file.is_open()) {
std::stringstream buffer;
buffer << file.rdbuf();
auto val = buffer.str();
if (val == "") {
return "<" + value + " not specified>";
}
return val;
} else {
return "<" + value + " read failed>";
}
}
// Replaces the UTC clock installed in the process' namespace with a fake one
// that is always started. 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);
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 = 1024 * 1024; // 1 MiB
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;
}
}
bool use_fake_clock = command_line.HasOption(kTestOnlyFakeClockFlagName);
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
<< ", event_aggregator_backfill_days=" << event_aggregator_backfill_days
<< ", start_event_aggregator_worker=" << start_event_aggregator_worker
<< ", test_only_use_fake_clock=" << use_fake_clock << ".";
if (use_fake_clock) {
FX_LOGS(WARNING) << "Using a fake clock. This should only be enabled in tests.";
ReplaceRuntimeClock();
}
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
// Lower the priority of the main thread.
fuchsia::scheduler::ProfileProviderSyncPtr provider;
context->svc()->Connect<fuchsia::scheduler::ProfileProvider>(provider.NewRequest());
zx_status_t fidl_status;
zx::profile profile;
auto status =
provider->GetProfile(0 /* LOWEST_PRIORITY */, "src/cobalt/bin/app", &fidl_status, &profile);
if (status == ZX_OK) {
FX_CHECK(fidl_status == ZX_OK) << "Recieved error while acquiring profile";
FX_LOGS(INFO) << "Fetched profile!";
status = zx_object_set_profile(zx_thread_self(), profile.get(), 0);
FX_CHECK(status == ZX_OK) << "Unable to set current thread priority";
FX_LOGS(INFO) << "Profile aplied to current thread";
} else {
// If we fail with ZX_ERR_PEER_CLOSED, then the ProfileProvider is down, and we should continue
// anyway.
FX_CHECK(status == ZX_ERR_PEER_CLOSED)
<< "Unable to connect to ProfileProvider, received unexpected error (" << status << ")";
FX_LOGS(INFO) << "Unable to lower current thread provider. ProfileProvider is down.";
}
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::CobaltApp app = cobalt::CobaltApp::CreateCobaltApp(
std::move(context), loop.dispatcher(), upload_schedule, event_aggregator_backfill_days,
start_event_aggregator_worker, use_memory_observation_store, max_bytes_per_observation_store,
ReadBuildInfo("product"), boardname, ReadBuildInfo("version"));
loop.Run();
return 0;
}