| // 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; |
| } |