blob: 48b8f572dc92d68b021c1bf9b70ce0818ff0418c [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 "garnet/bin/cobalt/app/legacy_logger_impl.h"
#include "garnet/bin/cobalt/app/utils.h"
#include <type_traits>
namespace cobalt {
using cobalt::TimerManager;
using cobalt::TimerVal;
using fuchsia::cobalt::Status;
namespace {
// Returns a tuple of the names of the three MetricParts used to report a
// Metric with at most one int/float part, one string part and one index part.
// The 0th item will be the name of the int/float part, the 1st item will be the
// name of the string part for the component name, and the 2nd item will be the
// name of the index part that is for the event type index. If the metric is not
// found or the MetricParts do not fit the expected types, a tuple with empty
// strings will be returned.
std::tuple<std::string, std::string, std::string> ThreePartMetricPartNames(
const Metric* metric) {
if (!metric || metric->parts_size() > 3) {
return std::make_tuple("", "", "");
}
std::string number_part, component_name_part, index_part;
for (auto pair : metric->parts()) {
switch (pair.second.data_type()) {
case MetricPart::INT:
case MetricPart::DOUBLE: {
if (!number_part.empty()) {
return std::make_tuple("", "", "");
}
number_part = pair.first;
break;
}
case MetricPart::STRING: {
if (!component_name_part.empty()) {
return std::make_tuple("", "", "");
}
component_name_part = pair.first;
break;
}
case MetricPart::INDEX: {
if (!index_part.empty()) {
return std::make_tuple("", "", "");
}
index_part = pair.first;
break;
}
default:
return std::make_tuple("", "", "");
}
}
return std::make_tuple(number_part, component_name_part, index_part);
}
} // namespace
LegacyLoggerImpl::LegacyLoggerImpl(
std::unique_ptr<encoder::ProjectContext> project_context,
encoder::ClientSecret client_secret,
encoder::ObservationStore* observation_store,
util::EncryptedMessageMaker* encrypt_to_analyzer,
encoder::ShippingManager* shipping_manager,
const encoder::SystemData* system_data, TimerManager* timer_manager)
: encoder_(std::move(project_context), std::move(client_secret),
system_data),
observation_store_(observation_store),
encrypt_to_analyzer_(encrypt_to_analyzer),
shipping_manager_(shipping_manager),
timer_manager_(timer_manager) {}
template <class ValueType, class CB>
void LegacyLoggerImpl::LogThreePartMetric(const std::string& value_part_name,
uint32_t metric_id,
uint32_t event_code,
fidl::StringPtr component,
ValueType value, CB callback,
bool value_part_required) {
const Metric* metric = encoder_.GetMetric(metric_id);
if (!metric) {
FXL_LOG(ERROR) << "There is no metric with ID = " << metric_id << ".";
callback(Status::INVALID_ARGUMENTS);
return;
}
const std::string& metric_name = metric->name();
auto encodings = encoder_.DefaultEncodingsForMetric(metric_id);
std::string value_part, component_name_part, index_part;
std::tie(value_part, component_name_part, index_part) =
ThreePartMetricPartNames(encoder_.GetMetric(metric_id));
cobalt::encoder::Encoder::Value new_value;
// LogElapsedTime, LogFrameRate and LogMemoryUsage can be logged to a metric
// with just a single part while LogEventCount cannot (the user should use
// LogEvent instead)
if (encodings.size() == 1 && value_part_required) {
if (event_code != 0 || !component->empty()) {
FXL_LOG(ERROR) << "Metric " << metric_name
<< " is a single part metric so only " << value_part_name
<< " must be provided "
"(event_code must be 0 and component must be empty).";
callback(Status::INVALID_ARGUMENTS);
return;
}
if (std::is_same<ValueType, int64_t>::value) {
new_value.AddIntPart(encodings.begin()->second, "", value);
} else if (std::is_same<ValueType, float>::value) {
new_value.AddDoublePart(encodings.begin()->second, "", value);
}
} else if (encodings.size() == 2 || encodings.size() == 3) {
if (!value_part.empty()) {
if (std::is_same<ValueType, int64_t>::value) {
new_value.AddIntPart(encodings[value_part], value_part, value);
} else if (std::is_same<ValueType, float>::value) {
new_value.AddDoublePart(encodings[value_part], value_part, value);
}
} else if (value_part_required) {
FXL_LOG(ERROR) << "Metric " << metric_name
<< " must have a numeric part to be a valid "
<< value_part_name << " metric.";
callback(Status::INVALID_ARGUMENTS);
return;
}
if (!component_name_part.empty()) {
new_value.AddStringPart(encodings[component_name_part],
component_name_part, component);
} else if (component_name_part.empty() && !component->empty()) {
FXL_LOG(ERROR) << "Metric " << metric_name
<< " is a two part metric with no string part so "
"component must be empty";
callback(Status::INVALID_ARGUMENTS);
return;
}
if (!index_part.empty()) {
new_value.AddIndexPart(encodings[index_part], index_part, event_code);
} else if (index_part.empty() && event_code != 0) {
FXL_LOG(ERROR) << "Metric " << metric_name
<< " is a two part metric with no index part so "
"event_code must be 0";
callback(Status::INVALID_ARGUMENTS);
return;
}
} else {
FXL_LOG(ERROR) << "Metric " << metric_name << " is not a valid "
<< value_part_name << " metric.";
callback(Status::INVALID_ARGUMENTS);
return;
}
auto result = encoder_.Encode(metric_id, new_value);
AddEncodedObservation(&result, std::move(callback));
}
// Duplicated from cobalt_encoder_impl.cc
template <class CB>
void LegacyLoggerImpl::AddEncodedObservation(
cobalt::encoder::Encoder::Result* result, CB callback) {
switch (result->status) {
case cobalt::encoder::Encoder::kOK:
break;
case cobalt::encoder::Encoder::kInsufficientBuildLevel:
FXL_LOG(WARNING)
<< "Cobalt metric reporting attempt with insufficient build level";
callback(Status::OK);
return;
case cobalt::encoder::Encoder::kInvalidArguments:
callback(Status::INVALID_ARGUMENTS);
return;
case cobalt::encoder::Encoder::kInvalidConfig:
case cobalt::encoder::Encoder::kEncodingFailed:
callback(Status::INTERNAL_ERROR);
FXL_LOG(WARNING) << "Cobalt internal error: " << result->status;
return;
}
auto message = std::make_unique<EncryptedMessage>();
if (!encrypt_to_analyzer_->Encrypt(*(result->observation), message.get())) {
FXL_LOG(WARNING)
<< "Cobalt internal error. Unable to encrypt observations.";
callback(Status::INTERNAL_ERROR);
}
// AddEncryptedObservation returns a StatusOr<ObservationStore::StoreStatus>.
auto add_result = observation_store_->AddEncryptedObservation(
std::move(message), std::move(result->metadata));
// Unpack the inner StoreStatus and convert it to a cobalt Status.
Status status = ToCobaltStatus(add_result);
shipping_manager_->NotifyObservationsAdded();
callback(status);
}
uint32_t LegacyLoggerImpl::GetSinglePartMetricEncoding(uint32_t metric_id) {
const Metric* metric = encoder_.GetMetric(metric_id);
if (!metric) {
FXL_LOG(ERROR) << "There is no metric with ID = " << metric_id << ".";
return 0;
}
const std::string& metric_name = metric->name();
auto encodings = encoder_.DefaultEncodingsForMetric(metric_id);
if (encodings.size() != 1) {
FXL_LOG(ERROR) << "Expected Metric " << metric_name
<< " to only have a single part.";
return 0;
}
return encodings.begin()->second;
}
void LegacyLoggerImpl::LogEvent(
uint32_t metric_id, uint32_t event_code,
fuchsia::cobalt::LoggerBase::LogEventCallback callback) {
uint32_t encoding_id = GetSinglePartMetricEncoding(metric_id);
if (encoding_id == 0) {
callback(Status::INVALID_ARGUMENTS);
return;
}
auto result = encoder_.EncodeIndex(metric_id, encoding_id, event_code);
AddEncodedObservation(&result, std::move(callback));
}
void LegacyLoggerImpl::LogEventCount(
uint32_t metric_id, uint32_t event_code, std::string component,
int64_t period_duration_micros, int64_t count,
fuchsia::cobalt::LoggerBase::LogEventCountCallback callback) {
const Metric* metric = encoder_.GetMetric(metric_id);
if (!metric) {
FXL_LOG(ERROR) << "There is no metric with ID = " << metric_id << ".";
return;
}
const std::string& metric_name = metric->name();
if (period_duration_micros != 0) {
FXL_LOG(ERROR) << "The parameter |period_duration_micros| in the "
"method LogEventCount is unsupported in the current "
"version of Cobalt. Pass the value 0 for now. Metric="
<< metric_name;
callback(Status::INVALID_ARGUMENTS);
return;
}
LogThreePartMetric("event count", metric_id, event_code, component, count,
std::move(callback), false);
}
void LegacyLoggerImpl::LogElapsedTime(
uint32_t metric_id, uint32_t event_code, std::string component,
int64_t elapsed_micros,
fuchsia::cobalt::LoggerBase::LogElapsedTimeCallback callback) {
LogThreePartMetric("elapsed time", metric_id, event_code, component,
elapsed_micros, std::move(callback), true);
}
void LegacyLoggerImpl::LogFrameRate(
uint32_t metric_id, uint32_t event_code, std::string component,
float fps, fuchsia::cobalt::LoggerBase::LogFrameRateCallback callback) {
LogThreePartMetric("frame rate", metric_id, event_code, component, fps,
std::move(callback), true);
}
void LegacyLoggerImpl::LogMemoryUsage(
uint32_t metric_id, uint32_t event_code, std::string component,
int64_t bytes,
fuchsia::cobalt::LoggerBase::LogMemoryUsageCallback callback) {
LogThreePartMetric("memory usage", metric_id, event_code, component, bytes,
std::move(callback), true);
}
void LegacyLoggerImpl::LogString(
uint32_t metric_id, std::string s,
fuchsia::cobalt::LoggerBase::LogStringCallback callback) {
uint32_t encoding_id = GetSinglePartMetricEncoding(metric_id);
if (encoding_id == 0) {
callback(Status::INVALID_ARGUMENTS);
return;
}
auto result = encoder_.EncodeString(metric_id, encoding_id, s);
AddEncodedObservation(&result, std::move(callback));
}
template <class CB>
void LegacyLoggerImpl::AddTimerObservationIfReady(
std::unique_ptr<TimerVal> timer_val_ptr, CB callback) {
if (!TimerManager::isReady(timer_val_ptr)) {
// TimerManager has not received both StartTimer and EndTimer calls. Return
// OK status and wait for the other call.
callback(Status::OK);
return;
}
auto result = encoder_.EncodeInt(
timer_val_ptr->metric_id, timer_val_ptr->encoding_id,
timer_val_ptr->end_timestamp - timer_val_ptr->start_timestamp);
AddEncodedObservation(&result, std::move(callback));
}
void LegacyLoggerImpl::StartTimer(
uint32_t metric_id, uint32_t event_code, std::string component,
std::string timer_id, uint64_t timestamp, uint32_t timeout_s,
fuchsia::cobalt::LoggerBase::StartTimerCallback callback) {
if (event_code != 0 || !component.empty()) {
FXL_LOG(ERROR) << "event_code and component are not currently "
"consumed. Pass in 0 and empty string respectively.";
callback(Status::INVALID_ARGUMENTS);
}
std::unique_ptr<TimerVal> timer_val_ptr;
uint32_t encoding_id = GetSinglePartMetricEncoding(metric_id);
if (encoding_id == 0) {
callback(Status::INVALID_ARGUMENTS);
return;
}
auto status = timer_manager_->GetTimerValWithStart(
metric_id, 0, "", encoding_id, timer_id, timestamp, timeout_s,
&timer_val_ptr);
if (status != Status::OK) {
callback(status);
return;
}
AddTimerObservationIfReady(std::move(timer_val_ptr), std::move(callback));
}
void LegacyLoggerImpl::EndTimer(
std::string timer_id, uint64_t timestamp, uint32_t timeout_s,
fuchsia::cobalt::LoggerBase::EndTimerCallback callback) {
std::unique_ptr<TimerVal> timer_val_ptr;
auto status = timer_manager_->GetTimerValWithEnd(timer_id, timestamp,
timeout_s, &timer_val_ptr);
if (status != Status::OK) {
callback(status);
return;
}
AddTimerObservationIfReady(std::move(timer_val_ptr), std::move(callback));
}
void LegacyLoggerImpl::LogIntHistogram(
uint32_t metric_id, uint32_t event_code, std::string component,
std::vector<fuchsia::cobalt::HistogramBucket> histogram,
fuchsia::cobalt::Logger::LogIntHistogramCallback callback) {
const Metric* metric = encoder_.GetMetric(metric_id);
if (!metric) {
FXL_LOG(ERROR) << "There is no metric with ID = " << metric_id << ".";
return;
}
const std::string& metric_name = metric->name();
if (event_code != 0) {
FXL_LOG(ERROR) << "The parameter |event_code| in the method "
"LogIntHistogram is unsupported in the current version "
"of Cobalt. Pass in the value 0 for now. Metric="
<< metric_name;
callback(Status::INVALID_ARGUMENTS);
return;
}
if (!component.empty()) {
FXL_LOG(ERROR) << "The parameter |component| in the method LogIntHistogram "
"is unsupported in the current version of Cobalt. Pass "
"in an empty string for now. Metric="
<< metric_name;
callback(Status::INVALID_ARGUMENTS);
return;
}
uint32_t encoding_id = GetSinglePartMetricEncoding(metric_id);
if (encoding_id == 0) {
callback(Status::INVALID_ARGUMENTS);
return;
}
std::map<uint32_t, uint64_t> histogram_map;
for (auto it : histogram) {
histogram_map[it.index] = it.count;
}
auto result = encoder_.EncodeIntBucketDistribution(metric_id, encoding_id,
histogram_map);
AddEncodedObservation(&result, std::move(callback));
}
void LegacyLoggerImpl::LogIntHistogram(
uint32_t metric_id, uint32_t event_code, std::string component,
std::vector<uint32_t> bucket_indices,
std::vector<uint64_t> bucket_counts,
fuchsia::cobalt::LoggerSimple::LogIntHistogramCallback callback) {
const Metric* metric = encoder_.GetMetric(metric_id);
if (!metric) {
FXL_LOG(ERROR) << "There is no metric with ID = " << metric_id << ".";
return;
}
const std::string& metric_name = metric->name();
if (event_code != 0) {
FXL_LOG(ERROR) << "The parameter |event_code| in the method "
"LogIntHistogram is unsupported in the current version "
"of Cobalt. Pass in the value 0 for now. Metric="
<< metric_name;
callback(Status::INVALID_ARGUMENTS);
return;
}
if (!component.empty()) {
FXL_LOG(ERROR) << "The parameter |component| in the method LogIntHistogram "
"is unsupported in the current version of Cobalt. Pass "
"in an empty string for now. Metric="
<< metric_name;
callback(Status::INVALID_ARGUMENTS);
return;
}
uint32_t encoding_id = GetSinglePartMetricEncoding(metric_id);
if (encoding_id == 0) {
callback(Status::INVALID_ARGUMENTS);
return;
}
if (bucket_indices.size() != bucket_counts.size()) {
FXL_LOG(ERROR) << "[" << metric_id
<< "]: bucket_indices.size() != bucket_counts.size().";
callback(Status::INVALID_ARGUMENTS);
return;
}
std::map<uint32_t, uint64_t> histogram_map;
for (auto i = 0; i < bucket_indices.size(); i++) {
histogram_map[bucket_indices.at(i)] = bucket_counts.at(i);
}
auto result = encoder_.EncodeIntBucketDistribution(metric_id, encoding_id,
histogram_map);
AddEncodedObservation(&result, std::move(callback));
}
void LegacyLoggerImpl::LogCustomEvent(
uint32_t metric_id,
std::vector<fuchsia::cobalt::CustomEventValue> event_values,
fuchsia::cobalt::Logger::LogCustomEventCallback callback) {
auto encodings = encoder_.DefaultEncodingsForMetric(metric_id);
cobalt::encoder::Encoder::Value value;
for (const auto& event_val : event_values) {
switch (event_val.value.Which()) {
case fuchsia::cobalt::Value::Tag::kStringValue: {
value.AddStringPart(encodings[event_val.dimension_name],
event_val.dimension_name,
event_val.value.string_value());
break;
}
case fuchsia::cobalt::Value::Tag::kIntValue: {
value.AddIntPart(encodings[event_val.dimension_name],
event_val.dimension_name, event_val.value.int_value());
break;
}
case fuchsia::cobalt::Value::Tag::kDoubleValue: {
value.AddDoublePart(encodings[event_val.dimension_name],
event_val.dimension_name,
event_val.value.double_value());
break;
}
case fuchsia::cobalt::Value::Tag::kIndexValue: {
value.AddIndexPart(encodings[event_val.dimension_name],
event_val.dimension_name,
event_val.value.index_value());
break;
}
default:
callback(Status::INVALID_ARGUMENTS);
FXL_LOG(ERROR)
<< "Cobalt: Unrecognized value type for observation part "
<< event_val.dimension_name;
return;
}
}
auto result = encoder_.Encode(metric_id, value);
AddEncodedObservation(&result, std::move(callback));
}
} // namespace cobalt