| // Copyright 2020 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/pb/metadata_builder.h" |
| |
| #include <chrono> |
| |
| #include "google/protobuf/repeated_field.h" |
| #include "src/lib/util/clock.h" |
| #include "src/lib/util/consistent_proto_store.h" |
| #include "src/lib/util/datetime_util.h" |
| #include "src/logging.h" |
| #include "src/pb/common.pb.h" |
| #include "src/pb/system_data_history.pb.h" |
| #include "src/registry/metric_definition.pb.h" |
| #include "src/registry/report_definition.pb.h" |
| #include "src/registry/window_size.pb.h" |
| #include "src/system_data/system_data.h" |
| |
| namespace cobalt { |
| |
| MetadataBuilder::MetadataBuilder(system_data::SystemDataInterface* system_data, |
| util::ValidatedClockInterface* validated_clock, |
| std::string system_profile_cache_dir, util::FileSystem* fs) |
| : system_data_(system_data), validated_clock_(validated_clock) { |
| if (system_data_) { |
| system_data_->OnChange([this]() { SnapshotSystemData(); }); |
| } |
| if (!validated_clock_) { |
| owned_clock_ = |
| std::make_unique<util::AlwaysAccurateClock>(std::make_unique<util::SystemClock>()); |
| validated_clock_ = owned_clock_.get(); |
| } |
| if (!system_profile_cache_dir.empty()) { |
| history_store_ = |
| std::make_unique<util::ConsistentProtoStore>(std::move(system_profile_cache_dir), fs); |
| history_store_->Read(&*system_profile_history_.lock()); |
| } else { |
| LOG(WARNING) << "No system_profile_cache_dir provided to MetadataBuilder. The system profile " |
| "cache will not be persisted to disk"; |
| } |
| } |
| |
| namespace { |
| |
| template <class InputIterator> |
| std::optional<const SystemProfile*> FindProfile( |
| InputIterator first, InputIterator last, |
| const std::function<bool(const SystemProfileSnapshot&)>& comparator) { |
| while (first != last) { |
| if (comparator(*first)) { |
| return &first->system_profile(); |
| } |
| |
| ++first; |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::function<bool(const SystemProfileSnapshot&)> TZCompare( |
| ReportDefinition::ReportType report_type, MetricDefinition::TimeZonePolicy time_zone, |
| const std::function<bool(uint32_t)>& day_cmp, const std::function<bool(uint32_t)>& hour_cmp) { |
| return [report_type, time_zone, hour_cmp, day_cmp](const SystemProfileSnapshot& snapshot) { |
| switch (report_type) { |
| case ReportDefinition::FLEETWIDE_OCCURRENCE_COUNTS: |
| case ReportDefinition::HOURLY_VALUE_NUMERIC_STATS: |
| case ReportDefinition::HOURLY_VALUE_HISTOGRAMS: |
| case ReportDefinition::STRING_COUNTS: |
| switch (time_zone) { |
| case MetricDefinition::LOCAL: |
| return hour_cmp(snapshot.hour_id_local()); |
| case MetricDefinition::UTC: |
| default: |
| return hour_cmp(snapshot.hour_id_utc()); |
| } |
| default: |
| switch (time_zone) { |
| case MetricDefinition::LOCAL: |
| return day_cmp(snapshot.day_index_local()); |
| case MetricDefinition::UTC: |
| default: |
| return day_cmp(snapshot.day_index_utc()); |
| } |
| } |
| }; |
| } |
| |
| std::optional<const SystemProfile*> FindProfileForSelectFirst( |
| const SystemProfileHistory& system_profile_history, ReportDefinition::ReportType report_type, |
| MetricDefinition::TimeZonePolicy time_zone, uint32_t day_index, uint32_t hour_id) { |
| // Find the first system profile whose hour_id or day_index equals the expected one. |
| std::optional<const SystemProfile*> profile = FindProfile( |
| system_profile_history.snapshots().begin(), system_profile_history.snapshots().end(), |
| TZCompare( |
| report_type, time_zone, |
| [day_index](uint32_t other_day_index) { return other_day_index == day_index; }, |
| [hour_id](uint32_t other_hour_id) { return other_hour_id == hour_id; })); |
| |
| if (!profile) { |
| // Otherwise, find the *last* system profile whose hour_id or day_index is less than the |
| // expected one. |
| profile = FindProfile( |
| system_profile_history.snapshots().rbegin(), system_profile_history.snapshots().rend(), |
| TZCompare( |
| report_type, time_zone, |
| [day_index](uint32_t other_day_index) { return other_day_index < day_index; }, |
| [hour_id](uint32_t other_hour_id) { return other_hour_id < hour_id; })); |
| } |
| |
| return profile; |
| } |
| |
| std::optional<const SystemProfile*> FindProfileForSelectLast( |
| const SystemProfileHistory& system_profile_history, ReportDefinition::ReportType report_type, |
| MetricDefinition::TimeZonePolicy time_zone, uint32_t day_index, uint32_t hour_id) { |
| // Find the last system profile whose hour_id or day_index is less than or equal to the |
| // expected value. |
| return FindProfile( |
| system_profile_history.snapshots().rbegin(), system_profile_history.snapshots().rend(), |
| TZCompare( |
| report_type, time_zone, |
| [day_index](uint32_t other_day_index) { return other_day_index <= day_index; }, |
| [hour_id](uint32_t other_hour_id) { return other_hour_id <= hour_id; })); |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<ObservationMetadata> MetadataBuilder::Build(const logger::MetricRef& metric, |
| const ReportDefinition& report, |
| uint32_t day_index, |
| uint32_t hour_id) const { |
| auto metadata = std::make_unique<ObservationMetadata>(); |
| |
| metadata->set_customer_id(metric.project().customer_id()); |
| metadata->set_project_id(metric.project().project_id()); |
| metadata->set_metric_id(metric.metric_id()); |
| metadata->set_report_id(report.id()); |
| metadata->set_day_index(day_index); |
| |
| std::optional<const SystemProfile*> profile = std::nullopt; |
| |
| switch (report.system_profile_selection()) { |
| case SystemProfileSelectionPolicy::SELECT_FIRST: { |
| auto selected_day_index = day_index; |
| switch (report.local_aggregation_period()) { |
| case WindowSize::UNSET: |
| break; |
| default: |
| selected_day_index -= report.local_aggregation_period() - 1; |
| break; |
| } |
| profile = FindProfileForSelectFirst( |
| *system_profile_history_.const_lock(), report.report_type(), |
| metric.metric_definition()->time_zone_policy(), selected_day_index, hour_id); |
| |
| } break; |
| case SystemProfileSelectionPolicy::SELECT_LAST: |
| case SystemProfileSelectionPolicy::SELECT_DEFAULT: |
| default: |
| profile = FindProfileForSelectLast( |
| *system_profile_history_.const_lock(), report.report_type(), |
| metric.metric_definition()->time_zone_policy(), day_index, hour_id); |
| break; |
| } |
| |
| if (profile) { |
| FilteredSystemProfile(report, *profile.value(), metadata->mutable_system_profile()); |
| } else { |
| // No snapshots made (problem?) |
| FilteredSystemProfile(report, metadata->mutable_system_profile()); |
| } |
| |
| return metadata; |
| } // namespace cobalt |
| |
| void MetadataBuilder::FilteredSystemProfile(const ReportDefinition& report, |
| SystemProfile* profile_out) const { |
| if (system_data_) { |
| FilteredSystemProfile(report, system_data_->system_profile(), profile_out); |
| } |
| } |
| |
| void MetadataBuilder::FilteredSystemProfile(const ReportDefinition& report, |
| const SystemProfile& profile_in, |
| SystemProfile* profile_out) { |
| for (const auto& field : report.system_profile_field()) { |
| switch (field) { |
| case SystemProfileField::OS: |
| profile_out->set_os(profile_in.os()); |
| break; |
| case SystemProfileField::ARCH: |
| profile_out->set_arch(profile_in.arch()); |
| break; |
| case SystemProfileField::BOARD_NAME: |
| profile_out->set_board_name(profile_in.board_name()); |
| break; |
| case SystemProfileField::PRODUCT_NAME: |
| profile_out->set_product_name(profile_in.product_name()); |
| break; |
| case SystemProfileField::SYSTEM_VERSION: |
| profile_out->set_system_version(profile_in.system_version()); |
| break; |
| case SystemProfileField::CHANNEL: |
| profile_out->set_channel(profile_in.channel()); |
| break; |
| case SystemProfileField::REALM: |
| profile_out->set_realm(profile_in.realm()); |
| break; |
| } |
| } |
| } |
| |
| void MetadataBuilder::SnapshotSystemData() { |
| if (!system_data_) { |
| // If there is no valid system_data_, we can do nothing. |
| return; |
| } |
| |
| std::optional<std::chrono::system_clock::time_point> now = validated_clock_->now(); |
| if (!now) { |
| // The system clock is not accurate, we will wait for this method to be called again by |
| // `CobaltService::SystemClockIsAccurate` |
| return; |
| } |
| |
| auto history = system_profile_history_.lock(); |
| if (history->snapshots_size() > 0 && history->snapshots().rbegin()->SerializeAsString() == |
| system_data_->system_profile().SerializeAsString()) { |
| // The most recent snapshot is the same as the current system_profile. No need to snapshot |
| // again. |
| return; |
| } |
| |
| SystemProfileSnapshot* snapshot = history->add_snapshots(); |
| snapshot->mutable_system_profile()->CopyFrom(system_data_->system_profile()); |
| |
| util::TimeInfo utc = util::TimeInfo::FromTimePoint(*now, MetricDefinition::UTC); |
| util::TimeInfo local = util::TimeInfo::FromTimePoint(*now, MetricDefinition::LOCAL); |
| |
| snapshot->set_day_index_utc(utc.day_index); |
| snapshot->set_day_index_local(local.day_index); |
| |
| snapshot->set_hour_id_utc(utc.hour_id); |
| snapshot->set_hour_id_local(local.hour_id); |
| |
| snapshot->set_timestamp(util::ToUnixSeconds(*now)); |
| |
| WriteHistory(std::move(history)); |
| } |
| |
| void MetadataBuilder::WriteHistory( |
| util::ProtectedFields<SystemProfileHistory>::LockedFieldsPtr history) { |
| if (history_store_) { |
| history_store_->Write(*history); |
| } |
| } |
| |
| void MetadataBuilder::CleanupBefore(std::chrono::hours hours_ago) { |
| std::optional<std::chrono::system_clock::time_point> now = validated_clock_->now(); |
| if (!now) { |
| // Without an accurate clock, cleanup is impossible. |
| return; |
| } |
| |
| std::chrono::system_clock::time_point delete_before = *now - hours_ago; |
| uint32_t delete_before_timestamp = util::ToUnixSeconds(delete_before); |
| |
| google::protobuf::RepeatedPtrField<SystemProfileSnapshot> kept_snapshots; |
| auto history = system_profile_history_.lock(); |
| for (auto& snapshot : *history->mutable_snapshots()) { |
| if (snapshot.timestamp() >= delete_before_timestamp) { |
| kept_snapshots.Add()->Swap(&snapshot); |
| } |
| } |
| |
| history->mutable_snapshots()->Swap(&kept_snapshots); |
| |
| WriteHistory(std::move(history)); |
| } |
| |
| } // namespace cobalt |