blob: 5418269441d05ba3f01bd6b1a96b984d51a15942 [file] [log] [blame]
// 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;
case SystemProfileField::BUILD_TYPE:
profile_out->set_build_type(profile_in.build_type());
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