blob: 705a53eede41c9c5ff1bf20ea702b2ab1338af52 [file] [log] [blame]
// Copyright 2018 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/logger/event_loggers.h"
#include <memory>
#include <string>
#include <google/protobuf/repeated_field.h>
#include "src/lib/util/datetime_util.h"
#include "src/lib/util/status_builder.h"
#include "src/logger/event_record.h"
#include "src/pb/event.pb.h"
#include "src/pb/observation.pb.h"
#include "src/public/lib/status.h"
#include "src/public/lib/statusor/status_macros.h"
#include "src/registry/metric_definition.pb.h"
#include "src/registry/report_definition.pb.h"
#include "src/tracing.h"
namespace cobalt::logger::internal {
using ::google::protobuf::RepeatedField;
constexpr char TRACE_PREFIX[] = "[COBALT_EVENT_TRACE] ";
std::unique_ptr<EventLogger> EventLogger::Create(
MetricDefinition::MetricType metric_type,
local_aggregation::LocalAggregation& local_aggregation,
const ObservationWriter& observation_writer,
const system_data::SystemDataInterface& system_data,
util::CivilTimeConverterInterface& civil_time_converter) {
switch (metric_type) {
case MetricDefinition::OCCURRENCE: {
return std::make_unique<internal::OccurrenceEventLogger>(
local_aggregation, observation_writer, system_data, civil_time_converter);
}
case MetricDefinition::INTEGER: {
return std::make_unique<internal::IntegerEventLogger>(local_aggregation, observation_writer,
system_data, civil_time_converter);
}
case MetricDefinition::INTEGER_HISTOGRAM: {
return std::make_unique<internal::IntegerHistogramEventLogger>(
local_aggregation, observation_writer, system_data, civil_time_converter);
}
case MetricDefinition::STRING: {
return std::make_unique<internal::StringEventLogger>(local_aggregation, observation_writer,
system_data, civil_time_converter);
}
default: {
LOG(ERROR) << "Failed to process a metric type of " << metric_type;
return nullptr;
}
}
}
std::string EventLogger::TraceEvent(const EventRecord& event_record) {
if (!event_record.metric()->meta_data().also_log_locally()) {
return "";
}
Event* event = event_record.event();
std::stringstream ss;
ss << "Day index: " << event->day_index() << std::endl;
if (event->has_occurrence_event()) {
const OccurrenceEvent& e = event->occurrence_event();
ss << "OccurrenceEvent:" << std::endl;
ss << "EventCodes:";
for (uint32_t code : e.event_code()) {
ss << " " << code;
}
ss << ", Count: " << e.count() << std::endl;
} else if (event->has_integer_event()) {
const IntegerEvent& e = event->integer_event();
ss << "IntegerEvent:" << std::endl;
ss << "EventCodes:";
for (uint32_t code : e.event_code()) {
ss << " " << code;
}
ss << ", Value: " << e.value() << std::endl;
} else if (event->has_integer_histogram_event()) {
const IntegerHistogramEvent& e = event->integer_histogram_event();
ss << "IntegerHistogramEvent:" << std::endl;
ss << "EventCodes:";
for (uint32_t code : e.event_code()) {
ss << " " << code;
}
for (const HistogramBucket& bucket : e.buckets()) {
ss << "| " << bucket.index() << " = " << bucket.count() << std::endl;
}
} else if (event->has_string_event()) {
const StringEvent& e = event->string_event();
ss << "StringEvent:" << std::endl;
ss << "EventCodes:";
for (uint32_t code : e.event_code()) {
ss << " " << code;
}
ss << ", String: " << e.string_value() << std::endl;
}
return ss.str();
}
void EventLogger::TraceLogFailure(const Status& status, const EventRecord& event_record,
const std::string& trace, const ReportDefinition& report) {
if (!event_record.metric()->meta_data().also_log_locally()) {
return;
}
LOG(INFO) << TRACE_PREFIX << "("
<< event_record.project_context()->RefMetric(event_record.metric()).FullyQualifiedName()
<< "): Error (" << status << ")" << std::endl
<< trace << "While trying to send report: " << report.report_name() << std::endl;
}
void EventLogger::TraceLogSuccess(const EventRecord& event_record, const std::string& trace) {
if (!event_record.metric()->meta_data().also_log_locally()) {
return;
}
LOG(INFO) << TRACE_PREFIX << "("
<< event_record.project_context()->RefMetric(event_record.metric()).FullyQualifiedName()
<< "):" << std::endl
<< trace;
}
Status EventLogger::Log(std::unique_ptr<EventRecord> event_record,
const std::chrono::system_clock::time_point& event_timestamp) {
TRACE_DURATION("cobalt_core", "EventLogger::Log", "metric_id", event_record->metric()->id());
CB_RETURN_IF_ERROR(FinalizeEvent(event_record.get(), event_timestamp));
if (system_data_.release_stage() > event_record->metric()->meta_data().max_release_stage()) {
// Quietly ignore this metric.
LOG_FIRST_N(INFO, 10) << "Not logging metric `"
<< event_record->project_context()->FullMetricName(
*event_record->metric())
<< "` because its max_release_stage ("
<< event_record->metric()->meta_data().max_release_stage()
<< ") is lower than the device's current release_stage: "
<< system_data_.release_stage();
return Status::OkStatus();
}
if (event_record->metric()->reports_size() == 0) {
VLOG(1) << "Warning: An event was logged for a metric with no reports "
"defined: "
<< event_record->project_context()->FullMetricName(*event_record->metric());
}
// Store the trace before attempting to log the event. This way, if parts of
// the event are moved out of the object, the resulting trace will still have
// useful information.
auto trace = TraceEvent(*event_record);
Status status = local_aggregation().AddEvent(*event_record, event_timestamp);
if (!status.ok()) {
TraceLogFailure(status, *event_record, trace, event_record->metric()->reports(0));
return status;
}
TraceLogSuccess(*event_record, trace);
return Status::OkStatus();
}
Status EventLogger::PrepareAndValidateEvent(uint32_t metric_id,
MetricDefinition::MetricType expected_type,
EventRecord* event_record) {
if (event_record->metric() == nullptr) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT, "Metric with ID `")
.AppendMsg(metric_id)
.AppendMsg("` was not found in the project.")
.WithContexts(*event_record)
.LogError()
.Build();
}
if (event_record->metric()->metric_type() != expected_type) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT, "Metric is of incorrect type")
.WithContexts(*event_record)
.WithContext("expected_type", expected_type)
.WithContext("found_type", event_record->metric()->metric_type())
.LogError()
.Build();
}
return ValidateEvent(*event_record);
}
Status EventLogger::FinalizeEvent(EventRecord* event_record,
const std::chrono::system_clock::time_point& event_timestamp) {
// Compute the day_index and hour ID in the appropriate time zone for the metric's TimeZonePolicy.
CB_ASSIGN_OR_RETURN(util::TimeInfo event_time,
util::TimeInfo::FromTimePoint(event_timestamp, civil_time_converter_,
*event_record->metric()));
event_record->event()->set_day_index(event_time.day_index);
event_record->event()->set_hour_id(event_time.hour_id);
return Status::OkStatus();
}
Status EventLogger::ValidateEvent(const EventRecord& /*event_record*/) {
return Status::OkStatus();
}
Status EventLogger::ValidateEventCodes(const MetricDefinition& metric,
const RepeatedField<uint32_t>& event_codes,
const std::string& full_metric_name) {
// When new dimensions are added to a metric, they can only be appended, not deleted or inserted.
// Because of this, and because metric definitions may change before the matching code does, we
// want to accept events where fewer than the expected number of event_codes have been provided.
if (event_codes.size() > metric.metric_dimensions_size()) {
return util::StatusBuilder(
StatusCode::INVALID_ARGUMENT,
"The number of event codes given is more than the number of metric dimensions")
.WithContext("event_codes.size()", event_codes.size())
.WithContext("metric_dimensions", metric.metric_dimensions_size())
.WithContext("Metric", full_metric_name)
.LogError()
.Build();
}
for (int i = 0; i < event_codes.size(); i++) {
const auto& dim = metric.metric_dimensions(i);
auto code = event_codes.Get(i);
// This verifies the two possible validation modes for a metric_dimension.
//
// 1. If it has a max_event_code, then all we do is verify that the supplied
// code is <= that value
//
// 2. If no max_event_code is specified, we verify that the supplied code
// maps to one of the values in the event_code map.
if (dim.max_event_code() > 0) {
if (code > dim.max_event_code()) {
return util::StatusBuilder(
StatusCode::INVALID_ARGUMENT,
"The given event_code exceeds the max_event_code for that dimension")
.WithContext("Dimension", i)
.WithContext("given event_code", code)
.WithContext("max_event_code", dim.max_event_code())
.WithContext("Metric", full_metric_name)
.LogError()
.Build();
}
} else {
bool valid = false;
for (const auto& event_code : dim.event_codes()) {
if (event_code.first == code) {
valid = true;
break;
}
}
if (!valid) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT)
.AppendMsg(
"The given event_code is not a valid event code for that dimension. You "
"must either define this event code in the metric_dimension, or set the "
"max_event_code >= ")
.AppendMsg(code)
.WithContext("Dimension", i)
.WithContext("given event_code", code)
.WithContext("max_event_code", dim.max_event_code())
.WithContext("Metric", full_metric_name)
.LogError()
.Build();
}
}
}
return Status::OkStatus();
}
///////////// OccurrenceEventLogger method implementations //////////////////////////
Status OccurrenceEventLogger::ValidateEvent(const EventRecord& event_record) {
if (!event_record.event()->has_occurrence_event()) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided event is invalid, expected it to contain an event "
"type of OccurredEvent")
.WithContexts(event_record, event_record.event()->type_case())
.LogError()
.Build();
}
if (event_record.metric() == nullptr) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided event is missing a metric")
.LogError()
.Build();
}
const MetricDefinition& metric = *(event_record.metric());
return ValidateEventCodes(metric, event_record.event()->occurrence_event().event_code(),
event_record.project_context()->FullMetricName(metric));
}
///////////// IntegerEventLogger method implementations //////////////////////////
Status IntegerEventLogger::ValidateEvent(const EventRecord& event_record) {
if (!event_record.event()->has_integer_event()) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided event is invalid, expected it to contain an event "
"type of IntegerEvent")
.WithContexts(event_record.event()->type_case(), event_record)
.LogError()
.Build();
}
if (event_record.metric() == nullptr) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided event is missing a metric")
.LogError()
.Build();
}
const MetricDefinition& metric = *(event_record.metric());
return ValidateEventCodes(metric, event_record.event()->integer_event().event_code(),
event_record.project_context()->FullMetricName(metric));
}
///////////// IntegerHistogramEventLogger method implementations //////////////////////////
Status IntegerHistogramEventLogger::ValidateEvent(const EventRecord& event_record) {
if (!event_record.event()->has_integer_histogram_event()) {
return util::StatusBuilder(
StatusCode::INVALID_ARGUMENT,
"The provided event is invalid, expected it to contain an event type of "
"IntegerHistogramEvent")
.WithContexts(event_record.event()->type_case(), event_record)
.LogError()
.Build();
}
const IntegerHistogramEvent& integer_histogram_event =
event_record.event()->integer_histogram_event();
if (event_record.metric() == nullptr) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided event is missing a metric")
.LogError()
.Build();
}
const MetricDefinition& metric = *(event_record.metric());
auto status = ValidateEventCodes(metric, integer_histogram_event.event_code(),
event_record.project_context()->FullMetricName(metric));
if (!status.ok()) {
return status;
}
if (!metric.has_int_buckets()) {
return util::StatusBuilder(
StatusCode::FAILED_PRECONDITION,
"Invalid Cobalt config: Metric does not have an |int_buckets| field set.")
.WithContext("Metric", event_record.project_context()->FullMetricName(metric))
.LogError()
.Build();
}
const IntegerBuckets& int_buckets = metric.int_buckets();
uint32_t num_valid_buckets;
switch (int_buckets.buckets_case()) {
case IntegerBuckets::kExponential:
num_valid_buckets = int_buckets.exponential().num_buckets();
break;
case IntegerBuckets::kLinear:
num_valid_buckets = int_buckets.linear().num_buckets();
break;
case IntegerBuckets::BUCKETS_NOT_SET: {
return util::StatusBuilder(
StatusCode::FAILED_PRECONDITION,
"Invalid Cobalt config: Metric has an invalid |int_buckets| field. Either "
"exponential or linear buckets must be specified.")
.WithContext("Metric", event_record.project_context()->FullMetricName(metric))
.LogError()
.Build();
}
}
// In addition to the specified num_buckets, there are the underflow and
// overflow buckets.
num_valid_buckets += 2;
int num_provided_buckets = integer_histogram_event.buckets_size();
for (int i = 0; i < num_provided_buckets; ++i) {
if (integer_histogram_event.buckets(i).index() >= num_valid_buckets) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided histogram is invalid. The index value is out of "
"bounds for the Metric")
.WithContext("Metric", event_record.project_context()->FullMetricName(metric))
.WithContext("index_value", integer_histogram_event.buckets(i).index())
.WithContext("position", i)
.LogError()
.Build();
}
}
return Status::OkStatus();
}
///////////// StringEventLogger method implementations //////////////////////////
Status StringEventLogger::ValidateEvent(const EventRecord& event_record) {
if (!event_record.event()->has_string_event()) {
return util::StatusBuilder(
StatusCode::INVALID_ARGUMENT,
"The provided event is invalid, expected it to contain an event type of StringEvent")
.WithContexts(event_record.event()->type_case(), event_record)
.LogError()
.Build();
}
if (event_record.metric() == nullptr) {
return util::StatusBuilder(StatusCode::INVALID_ARGUMENT,
"The provided event is missing a metric")
.LogError()
.Build();
}
const MetricDefinition& metric = *(event_record.metric());
return ValidateEventCodes(metric, event_record.event()->string_event().event_code(),
event_record.project_context()->FullMetricName(metric));
}
} // namespace cobalt::logger::internal