blob: 2eb186f1b7f2afec5eb7b9287cc2c9c02f1c6df0 [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/trace_processor/trace_summary/summary.h"
#include <cctype>
#include <cstdint>
#include <iterator>
#include <memory>
#include <string>
#include <vector>
#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "src/base/test/status_matchers.h"
#include "src/trace_processor/trace_summary/trace_summary.descriptor.h"
#include "src/trace_processor/util/descriptors.h"
#include "test/gtest_and_gmock.h"
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>
#endif
namespace perfetto::trace_processor::summary {
namespace {
using ::testing::HasSubstr;
MATCHER_P(EqualsIgnoringWhitespace, param, "equals ignoring whitespace") {
auto RemoveAllWhitespace = [](const std::string& input) {
std::string result;
result.reserve(input.length());
std::copy_if(input.begin(), input.end(), std::back_inserter(result),
[](char c) { return !std::isspace(c); });
return result;
};
return RemoveAllWhitespace(arg) == RemoveAllWhitespace(param);
}
MATCHER_P(HasSubstrIgnoringWhitespace,
param,
"has substring ignoring whitespace") {
auto RemoveAllWhitespace = [](const std::string& input) {
std::string result;
result.reserve(input.length());
std::copy_if(input.begin(), input.end(), std::back_inserter(result),
[](char c) { return !std::isspace(c); });
return result;
};
return RemoveAllWhitespace(arg).find(RemoveAllWhitespace(param)) !=
std::string::npos;
}
class TraceSummaryTest : public ::testing::Test {
protected:
void SetUp() override {
tp_ = TraceProcessor::CreateInstance(Config{});
tp_->NotifyEndOfFile();
pool_.AddFromFileDescriptorSet(kTraceSummaryDescriptor.data(),
kTraceSummaryDescriptor.size());
}
base::StatusOr<std::string> RunSummarize(const std::string& spec_str) {
TraceSummarySpecBytes spec;
spec.ptr = reinterpret_cast<const uint8_t*>(spec_str.data());
spec.size = spec_str.size();
spec.format = TraceSummarySpecBytes::Format::kTextProto;
std::vector<uint8_t> output;
TraceSummaryOutputSpec output_spec;
output_spec.format = TraceSummaryOutputSpec::Format::kTextProto;
base::Status status =
Summarize(tp_.get(), pool_, {}, {spec}, &output, output_spec);
if (!status.ok()) {
return status;
}
return std::string(output.begin(), output.end());
}
std::unique_ptr<TraceProcessor> tp_;
DescriptorPool pool_;
base::StatusOr<std::vector<uint8_t>> RunSummarizeBinary(
const std::string& spec_str,
const TraceSummaryOutputSpec& output_spec) {
TraceSummarySpecBytes spec;
spec.ptr = reinterpret_cast<const uint8_t*>(spec_str.data());
spec.size = spec_str.size();
spec.format = TraceSummarySpecBytes::Format::kTextProto;
std::vector<uint8_t> output;
base::Status status =
Summarize(tp_.get(), pool_, {}, {spec}, &output, output_spec);
if (!status.ok()) {
return status;
}
return output;
}
};
TEST_F(TraceSummaryTest, DuplicateDimensionsErrorIfUnique) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_spec {
id: "my_metric"
value: "value"
dimensions: "dim"
query {
sql {
sql: "SELECT 'a' as dim, 1.0 as value UNION ALL SELECT 'a' as dim, 2.0 as value"
column_names: "dim"
column_names: "value"
}
}
dimension_uniqueness: UNIQUE
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(
status_or_output.status().message(),
HasSubstr("Duplicate dimensions found for metric bundle 'my_metric'"));
}
TEST_F(TraceSummaryTest, DuplicateDimensionsNoErrorIfNotUnique) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_spec {
id: "my_metric"
value: "value"
dimensions: "dim"
query {
sql {
sql: "SELECT 'a' as dim, 1.0 as value UNION ALL SELECT 'a' as dim, 2.0 as value"
column_names: "dim"
column_names: "value"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok());
}
TEST_F(TraceSummaryTest, SingleTemplateSpec) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_template_spec {
id_prefix: "my_metric"
value_columns: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok());
EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_value\""));
}
TEST_F(TraceSummaryTest, MultiValueColumnTemplateSpec) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_template_spec {
id_prefix: "my_metric"
value_columns: "value_a"
value_columns: "value_b"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok());
EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_value_a\""));
EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_value_b\""));
}
TEST_F(TraceSummaryTest, MultiTemplateSpec) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_template_spec {
id_prefix: "my_metric_a"
value_columns: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
metric_template_spec {
id_prefix: "my_metric_b"
value_columns: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok());
EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_a_value\""));
EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_b_value\""));
}
TEST_F(TraceSummaryTest, EmptyIdPrefixTemplateSpec) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_template_spec {
value_columns: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(status_or_output.status().message(),
HasSubstr("Metric template with empty id_prefix field"));
}
TEST_F(TraceSummaryTest, DuplicateMetricIdFromTemplate) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_spec {
id: "my_metric_value"
value: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
metric_template_spec {
id_prefix: "my_metric"
value_columns: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(status_or_output.status().message(),
HasSubstr("Duplicate definitions for metric 'my_metric_value'"));
}
TEST_F(TraceSummaryTest, GroupedBasic) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value_a"
bundle_id: "group"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
metric_spec {
id: "metric_b"
value: "value_b"
bundle_id: "group"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message();
EXPECT_THAT(*status_or_output, EqualsIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "group"
specs {
id: "metric_a"
value: "value_a"
bundle_id: "group"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
specs {
id: "metric_b"
value: "value_b"
bundle_id: "group"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
row {
values { double_value: 1.000000 }
values { double_value: 2.000000 }
}
}
)"));
}
TEST_F(TraceSummaryTest, GroupedTemplateGroupingOrder) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_template_spec {
id_prefix: "my_metric"
value_columns: "value_a"
value_columns: "value_b"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok());
EXPECT_THAT(*status_or_output, EqualsIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "my_metric"
specs {
id: "my_metric_value_a"
value: "value_a"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
bundle_id: "my_metric"
}
specs {
id: "my_metric_value_b"
value: "value_b"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
bundle_id: "my_metric"
}
row {
values { double_value: 1.000000 }
values { double_value: 2.000000 }
}
}
)"));
}
TEST_F(TraceSummaryTest, GroupedDifferentDimensionsError) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value"
dimensions: "dim_a"
bundle_id: "group"
query {
sql {
sql: "SELECT 1.0 as value, 'a' as dim_a, 'b' as dim_b"
column_names: "value"
column_names: "dim_a"
column_names: "dim_b"
}
}
}
metric_spec {
id: "metric_b"
value: "value"
dimensions: "dim_b"
bundle_id: "group"
query {
sql {
sql: "SELECT 1.0 as value, 'a' as dim_a, 'b' as dim_b"
column_names: "value"
column_names: "dim_a"
column_names: "dim_b"
}
}
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(status_or_output.status().message(),
HasSubstr("has different dimensions than the first metric"));
}
TEST_F(TraceSummaryTest, GroupedMultipleGroups) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value"
bundle_id: "group_a"
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
metric_spec {
id: "metric_b"
value: "value"
bundle_id: "group_b"
query { sql { sql: "SELECT 2.0 as value" column_names: "value" } }
}
)");
ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message();
EXPECT_THAT(*status_or_output, HasSubstrIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "group_a"
specs {
id: "metric_a"
value: "value"
bundle_id: "group_a"
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
row { values { double_value: 1.000000 } }
}
)"));
EXPECT_THAT(*status_or_output, HasSubstrIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "group_b"
specs {
id: "metric_b"
value: "value"
bundle_id: "group_b"
query { sql { sql: "SELECT 2.0 as value" column_names: "value" } }
}
row { values { double_value: 2.000000 } }
}
)"));
}
TEST_F(TraceSummaryTest, GroupedNullValues) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "my_metric"
value: "value"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT NULL as dim, NULL as value"
column_names: "dim"
column_names: "value"
}
}
}
)");
ASSERT_TRUE(status_or_output.ok());
EXPECT_THAT(*status_or_output, EqualsIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "group"
specs {
id: "my_metric"
value: "value"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT NULL as dim, NULL as value"
column_names: "dim"
column_names: "value"
}
}
}
}
)"));
}
TEST_F(TraceSummaryTest, GroupedMixedGrouping) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value"
bundle_id: "group"
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
metric_spec {
id: "metric_b"
value: "value"
query { sql { sql: "SELECT 2.0 as value" column_names: "value" } }
}
)");
ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message();
EXPECT_THAT(*status_or_output, HasSubstrIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "group"
specs {
id: "metric_a"
value: "value"
bundle_id: "group"
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
row { values { double_value: 1.000000 } }
}
)"));
EXPECT_THAT(*status_or_output, HasSubstrIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "metric_b"
specs {
id: "metric_b"
value: "value"
query { sql { sql: "SELECT 2.0 as value" column_names: "value" } }
}
row { values { double_value: 2.000000 } }
}
)"));
}
TEST_F(TraceSummaryTest, GroupedQueryMismatchError) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value"
bundle_id: "group"
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
metric_spec {
id: "metric_b"
value: "value"
bundle_id: "group"
query { sql { sql: "SELECT 2.0 as value" column_names: "value" } }
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(status_or_output.status().message(),
HasSubstr("has different query than the first metric"));
}
TEST_F(TraceSummaryTest, GroupedDimensionUniquenessMismatchError) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value"
bundle_id: "group"
dimension_uniqueness: UNIQUE
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
metric_spec {
id: "metric_b"
value: "value"
bundle_id: "group"
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(
status_or_output.status().message(),
HasSubstr("has different dimension_uniqueness than the first metric"));
}
TEST_F(TraceSummaryTest, GroupedEmptyGroupId) {
base::StatusOr<std::string> status_or_output = RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value"
bundle_id: ""
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
)");
ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message();
EXPECT_THAT(*status_or_output, EqualsIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "metric_a"
specs {
id: "metric_a"
value: "value"
bundle_id: ""
query { sql { sql: "SELECT 1.0 as value" column_names: "value" } }
}
row { values { double_value: 1.000000 } }
}
)"));
}
TEST_F(TraceSummaryTest, GroupedTemplateDisabledGrouping) {
ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(
R"(
metric_template_spec {
id_prefix: "my_metric"
value_columns: "value_a"
value_columns: "value_b"
disable_auto_bundling: true
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
)"));
EXPECT_THAT(output, HasSubstrIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "my_metric_value_a"
specs {
id: "my_metric_value_a"
value: "value_a"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
row {
values { double_value: 1.000000 }
}
}
)"));
EXPECT_THAT(output, HasSubstrIgnoringWhitespace(R"(
metric_bundles {
bundle_id: "my_metric_value_b"
specs {
id: "my_metric_value_b"
value: "value_b"
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
row {
values { double_value: 2.000000 }
}
}
)"));
}
TEST_F(TraceSummaryTest, GroupedAllNullValuesAreSkipped) {
ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value_a"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT 'not_null' as dim, 1.0 as value_a, 2.0 as value_b UNION ALL SELECT 'all_null' as dim, NULL as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
metric_spec {
id: "metric_b"
value: "value_b"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT 'not_null' as dim, 1.0 as value_a, 2.0 as value_b UNION ALL SELECT 'all_null' as dim, NULL as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
)"));
EXPECT_THAT(output, EqualsIgnoringWhitespace(R"-(
metric_bundles {
bundle_id: "group"
specs {
id: "metric_a"
value: "value_a"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT \'not_null\' as dim, 1.0 as value_a, 2.0 as value_b UNION ALL SELECT \'all_null\' as dim, NULL as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
specs {
id: "metric_b"
value: "value_b"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT \'not_null\' as dim, 1.0 as value_a, 2.0 as value_b UNION ALL SELECT \'all_null\' as dim, NULL as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
row {
dimension { string_value: "not_null" }
values { double_value: 1.000000 }
values { double_value: 2.000000 }
}
}
)-"));
}
TEST_F(TraceSummaryTest, GroupedOneNullValueIsNotSkipped) {
ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value_a"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT 'one_null' as dim, 1.0 as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
metric_spec {
id: "metric_b"
value: "value_b"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT 'one_null' as dim, 1.0 as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
)"));
EXPECT_THAT(output, EqualsIgnoringWhitespace(R"-(
metric_bundles {
bundle_id: "group"
specs {
id: "metric_a"
value: "value_a"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT \'one_null\' as dim, 1.0 as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
specs {
id: "metric_b"
value: "value_b"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT \'one_null\' as dim, 1.0 as value_a, NULL as value_b"
column_names: "dim"
column_names: "value_a"
column_names: "value_b"
}
}
}
row {
dimension { string_value: "one_null" }
values { double_value: 1.000000 }
values { null_value {} }
}
}
)-"));
}
TEST_F(TraceSummaryTest, GroupedSingleNullValueIsSkipped) {
ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(
R"(
metric_spec {
id: "metric_a"
value: "value_a"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT 'one_null' as dim, NULL as value_a"
column_names: "dim"
column_names: "value_a"
}
}
}
)"));
EXPECT_THAT(output, EqualsIgnoringWhitespace(R"-(
metric_bundles {
bundle_id: "group"
specs {
id: "metric_a"
value: "value_a"
dimensions: "dim"
bundle_id: "group"
query {
sql {
sql: "SELECT \'one_null\' as dim, NULL as value_a"
column_names: "dim"
column_names: "value_a"
}
}
}
}
)-"));
}
TEST_F(TraceSummaryTest, TemplateSpecWithUnitAndPolarity) {
ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(R"(
metric_template_spec {
id_prefix: "my_metric"
value_column_specs: {
name: "value_a"
unit: BYTES
polarity: LOWER_IS_BETTER
}
value_column_specs: {
name: "value_b"
custom_unit: "widgets"
polarity: HIGHER_IS_BETTER
}
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
)"));
EXPECT_THAT(output, EqualsIgnoringWhitespace(R"-(
metric_bundles {
bundle_id: "my_metric"
specs {
id: "my_metric_value_a"
value: "value_a"
unit: BYTES
polarity: LOWER_IS_BETTER
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
bundle_id: "my_metric"
}
specs {
id: "my_metric_value_b"
value: "value_b"
custom_unit: "widgets"
polarity: HIGHER_IS_BETTER
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
bundle_id: "my_metric"
}
row {
values { double_value: 1.000000 }
values { double_value: 2.000000 }
}
}
)-"));
}
TEST_F(TraceSummaryTest, TemplateSpecWithValueColumnsAndSpecsError) {
base::StatusOr<std::string> status_or_output = RunSummarize(R"(
metric_template_spec {
id_prefix: "my_metric"
value_columns: "value_a"
value_column_specs: {
name: "value_b"
}
query {
sql {
sql: "SELECT 1.0 as value_a, 2.0 as value_b"
column_names: "value_a"
column_names: "value_b"
}
}
}
)");
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(status_or_output.status().message(),
HasSubstr("Metric template has both value_columns and "
"value_column_specs defined"));
}
TEST_F(TraceSummaryTest, InternedDimensionBundleBasic) {
ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(R"(
metric_template_spec {
id_prefix: "my_metric"
value_columns: "dur"
value_columns: "count"
dimensions_specs { name: "dim" type: STRING }
query {
sql {
sql: "SELECT 'a' as dim, 750.0 as dur, 3.0 as count UNION ALL SELECT 'b' as dim, 425.0 as dur, 4.0 as count"
column_names: "dim"
column_names: "dur"
column_names: "count"
}
}
interned_dimension_specs {
key_column_spec { name: "dim" type: STRING }
data_column_specs { name: "version" type: DOUBLE }
data_column_specs { name: "is_kernel" type: BOOLEAN }
query {
sql {
sql: "SELECT 'a' as dim, 1.0 as version, false as is_kernel UNION ALL SELECT 'b' as dim, 2.0 as version, true as is_kernel"
}
}
}
}
)"));
EXPECT_THAT(output, EqualsIgnoringWhitespace(R"-(
metric_bundles {
bundle_id: "my_metric"
specs {
id: "my_metric_dur"
value: "dur"
dimensions_specs {
name: "dim"
type: STRING
}
query {
sql {
sql: "SELECT \'a\' as dim, 750.0 as dur, 3.0 as count UNION ALL SELECT \'b\' as dim, 425.0 as dur, 4.0 as count"
column_names: "dim"
column_names: "dur"
column_names: "count"
}
}
bundle_id: "my_metric"
interned_dimension_specs {
key_column_spec {
name: "dim"
type: STRING
}
data_column_specs {
name: "version"
type: DOUBLE
}
data_column_specs {
name: "is_kernel"
type: BOOLEAN
}
query {
sql {
sql: "SELECT \'a\' as dim, 1.0 as version, false as is_kernel UNION ALL SELECT \'b\' as dim, 2.0 as version, true as is_kernel"
}
}
}
}
specs {
id: "my_metric_count"
value: "count"
dimensions_specs {
name: "dim"
type: STRING
}
query {
sql {
sql: "SELECT \'a\' as dim, 750.0 as dur, 3.0 as count UNION ALL SELECT \'b\' as dim, 425.0 as dur, 4.0 as count"
column_names: "dim"
column_names: "dur"
column_names: "count"
}
}
bundle_id: "my_metric"
interned_dimension_specs {
key_column_spec {
name: "dim"
type: STRING
}
data_column_specs {
name: "version"
type: DOUBLE
}
data_column_specs {
name: "is_kernel"
type: BOOLEAN
}
query {
sql {
sql: "SELECT \'a\' as dim, 1.0 as version, false as is_kernel UNION ALL SELECT \'b\' as dim, 2.0 as version, true as is_kernel"
}
}
}
}
row {
dimension {
string_value: "a"
}
values {
double_value: 750.000000
}
values {
double_value: 3.000000
}
}
row {
dimension {
string_value: "b"
}
values {
double_value: 425.000000
}
values {
double_value: 4.000000
}
}
interned_dimension_bundles {
interned_dimension_rows {
key_dimension_value {
string_value: "a"
}
interned_dimension_values {
double_value: 1.000000
}
interned_dimension_values {
bool_value: false
}
}
interned_dimension_rows {
key_dimension_value {
string_value: "b"
}
interned_dimension_values {
double_value: 2.000000
}
interned_dimension_values {
bool_value: true
}
}
}
}
)-"));
}
TEST_F(TraceSummaryTest, InternedDimensionBundleKeyColumnNotInDimensions) {
auto status = RunSummarize(R"(
metric_spec {
id: "my_metric"
value: "value"
dimensions_specs { name: "dim" type: STRING }
query {
sql {
sql: "SELECT 'a' as dim, 1.0 as value"
column_names: "dim"
column_names: "value"
}
}
interned_dimension_specs {
key_column_spec { name: "other" type: STRING }
query { sql { sql: "SELECT 'a' as other" } }
}
}
)");
ASSERT_FALSE(status.ok());
EXPECT_THAT(
status.status().message(),
HasSubstr("Key column 'other' in interned dimension bundle not found in "
"metric dimensions"));
}
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
TEST_F(TraceSummaryTest, OutputIsCompressed) {
TraceSummaryOutputSpec uncompressed_spec;
uncompressed_spec.format = TraceSummaryOutputSpec::Format::kBinaryProto;
uncompressed_spec.compression = TraceSummaryOutputSpec::Compression::kNone;
const char* kSpec = R"(
metric_spec {
id: "my_metric"
value: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
)";
ASSERT_OK_AND_ASSIGN(auto uncompressed_output,
RunSummarizeBinary(kSpec, uncompressed_spec));
TraceSummaryOutputSpec compressed_spec;
compressed_spec.format = TraceSummaryOutputSpec::Format::kBinaryProto;
compressed_spec.compression = TraceSummaryOutputSpec::Compression::kZlib;
ASSERT_OK_AND_ASSIGN(auto compressed_output,
RunSummarizeBinary(kSpec, compressed_spec));
ASSERT_GT(uncompressed_output.size(), 0u);
ASSERT_GT(compressed_output.size(), 0u);
ASSERT_LT(compressed_output.size(), uncompressed_output.size());
std::vector<uint8_t> decompressed_output(uncompressed_output.size());
uLongf decompressed_size = static_cast<uLongf>(decompressed_output.size());
int res = uncompress(decompressed_output.data(), &decompressed_size,
compressed_output.data(),
static_cast<uLongf>(compressed_output.size()));
ASSERT_EQ(res, Z_OK);
decompressed_output.resize(decompressed_size);
ASSERT_EQ(decompressed_output, uncompressed_output);
}
#else
TEST_F(TraceSummaryTest, OutputCompressionFailsWhenZlibDisabled) {
TraceSummaryOutputSpec compressed_spec;
compressed_spec.format = TraceSummaryOutputSpec::Format::kBinaryProto;
compressed_spec.compression = TraceSummaryOutputSpec::Compression::kZlib;
const char* kSpec = R"(
metric_spec {
id: "my_metric"
value: "value"
query {
sql {
sql: "SELECT 1.0 as value"
column_names: "value"
}
}
}
)";
base::StatusOr<std::vector<uint8_t>> status_or_output =
RunSummarizeBinary(kSpec, compressed_spec);
// Zlib compression is not supported on this platform, but was requested, so
// the function should fail.
ASSERT_FALSE(status_or_output.ok());
EXPECT_THAT(
status_or_output.status().message(),
HasSubstr(
"Zlib compression requested but is not supported on this platform."));
}
#endif
} // namespace
} // namespace perfetto::trace_processor::summary