blob: a80815e49850156140f7e14aac921e6449b49226 [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/encoder.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <google/protobuf/repeated_field.h>
#include <gtest/gtest.h>
#include "src/lib/util/testing/test_with_files.h"
#include "src/local_aggregation/aggregation_utils.h"
#include "src/logger/project_context.h"
#include "src/logger/project_context_factory.h"
#include "src/logger/status.h"
#include "src/logger/test_registries/encoder_test_registry.cb.h"
#include "src/logging.h"
#include "src/pb/common.pb.h"
#include "src/pb/metadata_builder.h"
#include "src/pb/observation.pb.h"
#include "src/pb/observation_batch.pb.h"
#include "src/registry/packed_event_codes.h"
#include "src/system_data/fake_system_data.h"
#include "third_party/abseil-cpp/absl/strings/escaping.h"
namespace cobalt {
using google::protobuf::RepeatedPtrField;
using local_aggregation::MakeDayWindow;
using local_aggregation::MakeHourWindow;
using system_data::ClientSecret;
using system_data::FakeSystemData;
using system_data::SystemDataInterface;
namespace logger {
namespace {
namespace registry = testing::encoder_test_registry;
constexpr uint32_t kCustomerId = 1;
constexpr uint32_t kProjectId = 1;
constexpr uint32_t kDayIndex = 111;
constexpr uint32_t kAggregationDays = 7;
constexpr uint32_t kValue = 314159;
bool PopulateCobaltRegistry(CobaltRegistry* cobalt_registry) {
std::string cobalt_registry_bytes;
if (!absl::Base64Unescape(registry::kCobaltRegistryBase64, &cobalt_registry_bytes)) {
return false;
}
return cobalt_registry->ParseFromString(cobalt_registry_bytes);
}
HistogramPtr NewHistogram(std::vector<uint32_t> indices, std::vector<uint32_t> counts) {
CHECK(indices.size() == counts.size());
HistogramPtr histogram = std::make_unique<RepeatedPtrField<HistogramBucket>>();
for (auto i = 0u; i < indices.size(); i++) {
auto* bucket = histogram->Add();
bucket->set_index(indices[i]);
bucket->set_count(counts[i]);
}
return histogram;
}
EventValuesPtr NewCustomEvent(std::vector<std::string> dimension_names,
std::vector<CustomDimensionValue> values) {
CHECK(dimension_names.size() == values.size());
EventValuesPtr custom_event =
std::make_unique<google::protobuf::Map<std::string, CustomDimensionValue>>();
for (auto i = 0u; i < values.size(); i++) {
(*custom_event)[dimension_names[i]] = values[i];
}
return custom_event;
}
void CheckSystemProfile(const Encoder::Result& result, SystemProfile::OS expected_os,
SystemProfile::ARCH expected_arch, const std::string& expected_board_name,
const std::string& expected_product, const std::string& expected_channel,
const std::string& expected_realm) {
EXPECT_TRUE(result.metadata->has_system_profile());
EXPECT_EQ(expected_os, result.metadata->system_profile().os());
EXPECT_EQ(expected_arch, result.metadata->system_profile().arch());
EXPECT_EQ(expected_board_name, result.metadata->system_profile().board_name());
EXPECT_EQ(expected_product, result.metadata->system_profile().product_name());
EXPECT_EQ(expected_channel, result.metadata->system_profile().channel());
EXPECT_EQ(expected_realm, result.metadata->system_profile().realm());
}
void CheckResult(const Encoder::Result& result, uint32_t expected_metric_id,
uint32_t expected_report_id, uint32_t expected_day_index) {
EXPECT_EQ(kOK, result.status);
EXPECT_EQ(kCustomerId, result.metadata->customer_id());
EXPECT_EQ(kProjectId, result.metadata->project_id());
EXPECT_EQ(expected_metric_id, result.metadata->metric_id());
EXPECT_EQ(expected_report_id, result.metadata->report_id());
EXPECT_EQ(expected_day_index, result.metadata->day_index());
}
} // namespace
class EncoderTest : public util::testing::TestWithFiles {
protected:
void SetUp() override {
MakeTestFolder();
auto cobalt_registry = std::make_unique<CobaltRegistry>();
ASSERT_TRUE(PopulateCobaltRegistry(cobalt_registry.get()));
ProjectContextFactory project_context_factory(std::move(cobalt_registry));
ASSERT_TRUE(project_context_factory.is_single_project());
project_context_ = project_context_factory.TakeSingleProjectContext();
system_data_ = std::make_unique<FakeSystemData>();
metadata_builder_ =
std::make_unique<MetadataBuilder>(system_data_.get(), system_data_cache_path(), fs());
encoder_ =
std::make_unique<Encoder>(ClientSecret::GenerateNewSecret(), metadata_builder_.get());
}
std::pair<const MetricDefinition*, const ReportDefinition*> GetMetricAndReport(
const std::string& metric_name, const std::string& report_name) {
const auto* metric = project_context_->GetMetric(metric_name);
CHECK(metric) << "No such metric: " << metric_name;
const ReportDefinition* report = nullptr;
for (const auto& rept : metric->reports()) {
if (rept.report_name() == report_name) {
report = &rept;
break;
}
}
CHECK(report) << "No such report: " << report_name;
return {metric, report};
}
std::unique_ptr<MetadataBuilder> metadata_builder_;
std::unique_ptr<Encoder> encoder_;
std::unique_ptr<ProjectContext> project_context_;
private:
std::unique_ptr<SystemDataInterface> system_data_;
};
TEST_F(EncoderTest, EncodeBasicRapporObservation) {
const char kMetricName[] = "ErrorOccurred";
const char kReportName[] = "ErrorCountsByType";
const uint32_t kExpectedMetricId = 1;
auto pair = GetMetricAndReport(kMetricName, kReportName);
const uint32_t day_index = 111;
{
const uint32_t value_index = 9;
const uint32_t num_categories = 8;
// This should fail with kInvalidArguments because 9 > 8.
auto result =
encoder_->EncodeBasicRapporObservation(project_context_->RefMetric(pair.first), pair.second,
day_index, value_index, num_categories);
EXPECT_EQ(kInvalidArguments, result.status);
}
{
// This should fail with kInvalidConfig because num_categories is too large.
const uint32_t value_index = 9;
const uint32_t num_categories = 999999;
auto result =
encoder_->EncodeBasicRapporObservation(project_context_->RefMetric(pair.first), pair.second,
day_index, value_index, num_categories);
EXPECT_EQ(kInvalidConfig, result.status);
}
{
// If we use the wrong report, it won't have local_privacy_noise_level
// set and we should get InvalidConfig
const uint32_t num_categories = 128;
const uint32_t value_index = 10;
pair = GetMetricAndReport("ReadCacheHits", "ReadCacheHitCounts");
auto result =
encoder_->EncodeBasicRapporObservation(project_context_->RefMetric(pair.first), pair.second,
day_index, value_index, num_categories);
EXPECT_EQ(kInvalidConfig, result.status);
// Finally we pass all valid parameters and the operation should succeed.
pair = GetMetricAndReport(kMetricName, kReportName);
result =
encoder_->EncodeBasicRapporObservation(project_context_->RefMetric(pair.first), pair.second,
day_index, value_index, num_categories);
CheckResult(result, kExpectedMetricId, registry::kErrorOccurredErrorCountsByTypeReportId,
day_index);
CheckSystemProfile(result, SystemProfile::UNKNOWN_OS, SystemProfile::UNKNOWN_ARCH,
"Testing Board", "Testing Product", "Testing Channel", "Testing Realm");
ASSERT_TRUE(result.observation->has_basic_rappor());
EXPECT_FALSE(result.observation->basic_rappor().data().empty());
}
}
TEST_F(EncoderTest, EncodeIntegerEventObservation) {
const char kMetricName[] = "ReadCacheHits";
const char kReportName[] = "ReadCacheHitCounts";
const uint32_t kExpectedMetricId = 2;
const char kComponent[] = "My Component";
const uint32_t kEventCode = 9;
google::protobuf::RepeatedField<uint32_t> event_codes;
*event_codes.Add() = kEventCode;
auto pair = GetMetricAndReport(kMetricName, kReportName);
auto result =
encoder_->EncodeIntegerEventObservation(project_context_->RefMetric(pair.first), pair.second,
kDayIndex, event_codes, kComponent, kValue);
CheckResult(result, kExpectedMetricId, registry::kReadCacheHitsReadCacheHitCountsReportId,
kDayIndex);
// In the SystemProfile only the OS should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::UNKNOWN_ARCH, "", "", "", "");
ASSERT_TRUE(result.observation->has_numeric_event());
const IntegerEventObservation& obs = result.observation->numeric_event();
EXPECT_EQ(kEventCode, obs.event_code());
EXPECT_EQ(obs.component_name_hash().size(), 32u);
EXPECT_EQ(kValue, obs.value());
}
TEST_F(EncoderTest, MultipleEventCodes) {
const char kMetricName[] = "MultiEventCodeTest";
const char kReportName[] = "MultiEventCodeCounts";
const uint32_t kExpectedMetricId = 11;
const char kComponent[] = "My Component";
const uint32_t kEventCode1 = 7;
const uint32_t kEventCode2 = 5;
google::protobuf::RepeatedField<uint32_t> event_codes;
*event_codes.Add() = kEventCode1;
*event_codes.Add() = kEventCode2;
auto pair = GetMetricAndReport(kMetricName, kReportName);
auto result =
encoder_->EncodeIntegerEventObservation(project_context_->RefMetric(pair.first), pair.second,
kDayIndex, event_codes, kComponent, kValue);
CheckResult(result, kExpectedMetricId, registry::kMultiEventCodeTestMultiEventCodeCountsReportId,
kDayIndex);
// In the SystemProfile only the OS should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::UNKNOWN_ARCH, "", "", "", "");
ASSERT_TRUE(result.observation->has_numeric_event());
const IntegerEventObservation& obs = result.observation->numeric_event();
auto codes = config::UnpackEventCodes(obs.event_code());
EXPECT_EQ(kEventCode1, codes[0]);
EXPECT_EQ(kEventCode2, codes[1]);
EXPECT_EQ(obs.component_name_hash().size(), 32u);
EXPECT_EQ(kValue, obs.value());
}
TEST_F(EncoderTest, EncodeHistogramObservation) {
const char kMetricName[] = "FileSystemWriteTimes";
const char kReportName[] = "FileSystemWriteTimes_Histogram";
const uint32_t kExpectedMetricId = 6;
const std::string kComponent;
const uint32_t kEventCode = 9;
google::protobuf::RepeatedField<uint32_t> event_codes;
*event_codes.Add() = kEventCode;
const std::vector<uint32_t> indices = {0, 1, 2};
const std::vector<uint32_t> counts = {100, 200, 300};
auto histogram = NewHistogram(indices, counts);
auto pair = GetMetricAndReport(kMetricName, kReportName);
auto result = encoder_->EncodeHistogramObservation(project_context_->RefMetric(pair.first),
pair.second, kDayIndex, event_codes,
kComponent, std::move(histogram));
CheckResult(result, kExpectedMetricId,
registry::kFileSystemWriteTimesFileSystemWriteTimesHistogramReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result.observation->has_histogram());
const HistogramObservation& obs = result.observation->histogram();
EXPECT_EQ(kEventCode, obs.event_code());
EXPECT_TRUE(obs.component_name_hash().empty());
EXPECT_EQ(static_cast<size_t>(obs.buckets_size()), indices.size());
for (auto i = 0u; i < indices.size(); i++) {
const auto& bucket = obs.buckets(i);
EXPECT_EQ(bucket.index(), indices[i]);
EXPECT_EQ(bucket.count(), counts[i]);
}
}
TEST_F(EncoderTest, EncodeCustomObservation) {
const char kMetricName[] = "ModuleInstalls";
const char kReportName[] = "ModuleInstalls_DetailedData";
const uint32_t kExpectedMetricId = 8;
CustomDimensionValue module_value, number_value;
module_value.set_string_value("gmail");
number_value.set_int_value(3);
std::vector<std::string> dimension_names = {"module", "number"};
std::vector<CustomDimensionValue> values = {module_value, number_value};
auto custom_event = NewCustomEvent(dimension_names, values);
auto pair = GetMetricAndReport(kMetricName, kReportName);
auto result = encoder_->EncodeCustomObservation(project_context_->RefMetric(pair.first),
pair.second, kDayIndex, std::move(custom_event));
CheckResult(result, kExpectedMetricId,
registry::kModuleInstallsModuleInstallsDetailedDataReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result.observation->has_custom());
const CustomObservation& obs = result.observation->custom();
for (auto i = 0u; i < values.size(); i++) {
auto obs_dimension = obs.values().at(dimension_names[i]);
EXPECT_EQ(obs_dimension.SerializeAsString(), values[i].SerializeAsString());
}
}
TEST_F(EncoderTest, EncodeSerializedCustomObservation) {
const char kMetricName[] = "ModuleInstalls";
const char kReportName[] = "ModuleInstalls_DetailedData";
const uint32_t kExpectedMetricId = 8;
std::string serialized_proto = "serialized proto";
auto serialized_proto_ptr = std::make_unique<std::string>(serialized_proto);
auto pair = GetMetricAndReport(kMetricName, kReportName);
auto result = encoder_->EncodeSerializedCustomObservation(project_context_->RefMetric(pair.first),
pair.second, kDayIndex,
std::move(serialized_proto_ptr));
CheckResult(result, kExpectedMetricId,
registry::kModuleInstallsModuleInstallsDetailedDataReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result.observation->has_custom());
const CustomObservation& obs = result.observation->custom();
EXPECT_EQ(serialized_proto, obs.serialized_proto());
}
TEST_F(EncoderTest, EncodeUniqueActivesObservation) {
const char kMetricName[] = "DeviceBoots";
const char kReportName[] = "DeviceBoots_UniqueDevices";
const uint32_t kExpectedMetricId = 9;
const uint32_t kEventCode = 0;
const int kAggregationHours = 1;
auto pair = GetMetricAndReport(kMetricName, kReportName);
// Encode a valid UniqueActivesObservation of activity for a 7-day window.
auto result_active = encoder_->EncodeUniqueActivesObservation(
project_context_->RefMetric(pair.first), pair.second, kDayIndex, kEventCode, true,
MakeDayWindow(kAggregationDays));
CheckResult(result_active, kExpectedMetricId,
registry::kDeviceBootsDeviceBootsUniqueDevicesReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result_active, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result_active.observation->has_unique_actives());
auto active_obs = result_active.observation->unique_actives();
ASSERT_EQ(OnDeviceAggregationWindow::kDays, active_obs.aggregation_window().units_case());
EXPECT_EQ(kAggregationDays, active_obs.aggregation_window().days());
EXPECT_EQ(kEventCode, active_obs.event_code());
ASSERT_TRUE(active_obs.has_basic_rappor_obs());
EXPECT_EQ(1u, active_obs.basic_rappor_obs().data().size());
// Encode a valid UniqueActivesObservation of inactivity for a 1-hour window.
auto result_inactive = encoder_->EncodeUniqueActivesObservation(
project_context_->RefMetric(pair.first), pair.second, kDayIndex, kEventCode, false,
MakeHourWindow(kAggregationHours));
CheckResult(result_inactive, kExpectedMetricId,
registry::kDeviceBootsDeviceBootsUniqueDevicesReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result_inactive, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "",
"");
ASSERT_TRUE(result_inactive.observation->has_unique_actives());
auto inactive_obs = result_inactive.observation->unique_actives();
ASSERT_EQ(OnDeviceAggregationWindow::kHours, inactive_obs.aggregation_window().units_case());
EXPECT_EQ(kAggregationHours, inactive_obs.aggregation_window().hours());
EXPECT_EQ(kEventCode, inactive_obs.event_code());
ASSERT_TRUE(inactive_obs.has_basic_rappor_obs());
EXPECT_EQ(1u, inactive_obs.basic_rappor_obs().data().size());
}
TEST_F(EncoderTest, EncodePerDeviceNumericObservation) {
const char kMetricName[] = "ConnectionFailures";
const char kReportName[] = "ConnectionFailures_PerDeviceCount";
const uint32_t kExpectedMetricId = 10;
const char kComponent[] = "Some Component";
const uint32_t kEventCode = 0;
const int64_t kCount = 1728;
auto pair = GetMetricAndReport(kMetricName, kReportName);
google::protobuf::RepeatedField<uint32_t> event_codes;
*event_codes.Add() = kEventCode;
auto result = encoder_->EncodePerDeviceNumericObservation(
project_context_->RefMetric(pair.first), pair.second, kDayIndex, kComponent, event_codes,
kCount, MakeDayWindow(kAggregationDays));
CheckResult(result, kExpectedMetricId,
registry::kConnectionFailuresConnectionFailuresPerDeviceCountReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result.observation->has_per_device_numeric());
const auto& per_device_numeric_obs = result.observation->per_device_numeric();
ASSERT_EQ(OnDeviceAggregationWindow::kDays,
per_device_numeric_obs.aggregation_window().units_case());
EXPECT_EQ(kAggregationDays, per_device_numeric_obs.aggregation_window().days());
ASSERT_TRUE(per_device_numeric_obs.has_integer_event_obs());
const auto& integer_obs = per_device_numeric_obs.integer_event_obs();
EXPECT_EQ(kEventCode, integer_obs.event_code());
EXPECT_EQ(32u, integer_obs.component_name_hash().size());
EXPECT_EQ(kCount, integer_obs.value());
}
TEST_F(EncoderTest, EncodePerDeviceNumericHistogramObservation) {
const char kMetricName[] = "ConnectionFailures";
const char kReportName[] = "ConnectionFailures_PerDeviceHistogram";
const uint32_t kExpectedMetricId = 10;
const char kComponent[] = "Some Component";
const uint32_t kEventCode = 0;
const int64_t kCount = 41;
const int64_t kExpectedBucket = 5;
auto pair = GetMetricAndReport(kMetricName, kReportName);
google::protobuf::RepeatedField<uint32_t> event_codes;
*event_codes.Add() = kEventCode;
auto result = encoder_->EncodePerDeviceHistogramObservation(
project_context_->RefMetric(pair.first), pair.second, kDayIndex, kComponent, event_codes,
kCount, MakeDayWindow(kAggregationDays));
CheckResult(result, kExpectedMetricId,
registry::kConnectionFailuresConnectionFailuresPerDeviceHistogramReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result.observation->has_per_device_histogram());
auto per_device_histogram_obs = result.observation->per_device_histogram();
ASSERT_EQ(OnDeviceAggregationWindow::kDays,
per_device_histogram_obs.aggregation_window().units_case());
EXPECT_EQ(kAggregationDays, per_device_histogram_obs.aggregation_window().days());
ASSERT_TRUE(result.observation->per_device_histogram().has_histogram());
auto histogram = result.observation->per_device_histogram().histogram();
EXPECT_EQ(kEventCode, histogram.event_code());
EXPECT_EQ(32u, histogram.component_name_hash().size());
EXPECT_EQ(1, histogram.buckets_size());
EXPECT_EQ(kExpectedBucket, histogram.buckets(0).index());
EXPECT_EQ(1, histogram.buckets(0).count());
}
TEST_F(EncoderTest, EncodeReportParticipationObservation) {
const char kMetricName[] = "ConnectionFailures";
const char kReportName[] = "ConnectionFailures_PerDeviceCount";
const uint32_t kExpectedMetricId = 10;
auto pair = GetMetricAndReport(kMetricName, kReportName);
auto result = encoder_->EncodeReportParticipationObservation(
project_context_->RefMetric(pair.first), pair.second, kDayIndex);
CheckResult(result, kExpectedMetricId,
registry::kConnectionFailuresConnectionFailuresPerDeviceCountReportId, kDayIndex);
// In the SystemProfile only the OS and ARCH should be set.
CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
ASSERT_TRUE(result.observation->has_report_participation());
}
} // namespace logger
} // namespace cobalt