| /* | |
| * 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, TemplateSpecWithInnerQueryId) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "my_metric" | |
| value_columns: "value" | |
| query { | |
| inner_query_id: "shared_query" | |
| } | |
| } | |
| query { | |
| id: "shared_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, HasSubstr("id: \"my_metric_value\"")); | |
| } | |
| TEST_F(TraceSummaryTest, TemplateSpecWithInnerQueryIdAndGroupBy) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "my_metric" | |
| dimensions_specs { name: "category" type: STRING } | |
| value_columns: "total_value" | |
| query { | |
| inner_query_id: "shared_query" | |
| } | |
| } | |
| query { | |
| id: "shared_query" | |
| sql { | |
| sql: "SELECT 'cat1' as category, 1.0 as value UNION ALL SELECT 'cat1' as category, 2.0 as value UNION ALL SELECT 'cat2' as category, 3.0 as value" | |
| column_names: "category" | |
| column_names: "value" | |
| } | |
| group_by { | |
| column_names: "category" | |
| aggregates { | |
| column_name: "value" | |
| op: SUM | |
| result_column_name: "total_value" | |
| } | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_total_value\"")); | |
| } | |
| TEST_F(TraceSummaryTest, TemplateSpecWithInnerQueryIdTableSource) { | |
| // This test uses a table source in the shared query (like the slice table) | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "my_metric" | |
| dimensions_specs { name: "name" type: STRING } | |
| value_columns: "count" | |
| query { | |
| inner_query_id: "shared_query" | |
| } | |
| } | |
| query { | |
| id: "shared_query" | |
| table { | |
| table_name: "slice" | |
| } | |
| group_by { | |
| column_names: "name" | |
| aggregates { | |
| column_name: "id" | |
| op: COUNT | |
| result_column_name: "count" | |
| } | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"my_metric_count\"")); | |
| } | |
| // Test complex template spec with multiple inner_query_id references including | |
| // one in interned_dimension_specs. This mimics real-world heap graph metrics. | |
| TEST_F(TraceSummaryTest, TemplateSpecWithInternedDimensionsAndInnerQueryId) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "test_aggregation" | |
| dimensions_specs { | |
| name: "track_id" | |
| type: INT64 | |
| } | |
| dimensions_specs { | |
| name: "name" | |
| type: STRING | |
| } | |
| value_columns: "total_dur" | |
| value_columns: "slice_count" | |
| interned_dimension_specs { | |
| key_column_spec { | |
| name: "track_id" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "track_name" | |
| type: STRING | |
| } | |
| query { | |
| inner_query_id: "track_metadata_query" | |
| } | |
| } | |
| query { | |
| inner_query_id: "slice_aggregation_query" | |
| } | |
| } | |
| query { | |
| id: "slice_aggregation_query" | |
| table { | |
| table_name: "slice" | |
| } | |
| group_by { | |
| column_names: "track_id" | |
| column_names: "name" | |
| aggregates { | |
| column_name: "dur" | |
| op: SUM | |
| result_column_name: "total_dur" | |
| } | |
| aggregates { | |
| column_name: "id" | |
| op: COUNT | |
| result_column_name: "slice_count" | |
| } | |
| } | |
| } | |
| query { | |
| id: "track_metadata_query" | |
| table { | |
| table_name: "track" | |
| column_names: "id" | |
| column_names: "name" | |
| } | |
| select_columns { | |
| column_name_or_expression: "id" | |
| alias: "track_id" | |
| } | |
| select_columns { | |
| column_name_or_expression: "name" | |
| alias: "track_name" | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| EXPECT_THAT(*status_or_output, | |
| HasSubstr("id: \"test_aggregation_total_dur\"")); | |
| EXPECT_THAT(*status_or_output, | |
| HasSubstr("id: \"test_aggregation_slice_count\"")); | |
| } | |
| 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, InternedDimensionBundleUnusedKeysDropped) { | |
| ASSERT_OK_AND_ASSIGN(auto output, RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "my_metric" | |
| value_columns: "dur" | |
| dimensions_specs { name: "dim_1" type: INT64 } | |
| dimensions_specs { name: "dim_2" type: INT64 } | |
| query { | |
| sql { | |
| sql: "SELECT 123 as dim_1, 123 as dim_2, 750.0 as dur UNION ALL SELECT 456 as dim_1, 789 as dim_2, 850.0 as dur" | |
| column_names: "dim_1" | |
| column_names: "dim_2" | |
| column_names: "dur" | |
| } | |
| } | |
| interned_dimension_specs { | |
| key_column_spec { name: "dim_1" type: INT64 } | |
| data_column_specs { name: "version_1" type: INT64 } | |
| query { | |
| sql { | |
| sql: "SELECT 123 as dim_1, 100 as version_1 UNION ALL SELECT 456 as dim_1, 200 as version_1 UNION ALL SELECT 789 as dim_1, 300 as version_1" | |
| } | |
| } | |
| } | |
| interned_dimension_specs { | |
| key_column_spec { name: "dim_2" type: INT64 } | |
| data_column_specs { name: "version_2" type: INT64 } | |
| query { | |
| sql { | |
| sql: "SELECT 123 as dim_2, 1000 as version_2 UNION ALL SELECT 456 as dim_2, 2000 as version_2 UNION ALL SELECT 789 as dim_2, 3000 as version_2" | |
| } | |
| } | |
| } | |
| } | |
| )")); | |
| EXPECT_THAT(output, EqualsIgnoringWhitespace(R"-( | |
| metric_bundles { | |
| bundle_id: "my_metric" | |
| specs { | |
| id: "my_metric_dur" | |
| value: "dur" | |
| dimensions_specs { | |
| name: "dim_1" | |
| type: INT64 | |
| } | |
| dimensions_specs { | |
| name: "dim_2" | |
| type: INT64 | |
| } | |
| query { | |
| sql { | |
| sql: "SELECT 123 as dim_1, 123 as dim_2, 750.0 as dur UNION ALL SELECT 456 as dim_1, 789 as dim_2, 850.0 as dur" | |
| column_names: "dim_1" | |
| column_names: "dim_2" | |
| column_names: "dur" | |
| } | |
| } | |
| bundle_id: "my_metric" | |
| interned_dimension_specs { | |
| key_column_spec { | |
| name: "dim_1" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "version_1" | |
| type: INT64 | |
| } | |
| query { | |
| sql { | |
| sql: "SELECT 123 as dim_1, 100 as version_1 UNION ALL SELECT 456 as dim_1, 200 as version_1 UNION ALL SELECT 789 as dim_1, 300 as version_1" | |
| } | |
| } | |
| } | |
| interned_dimension_specs { | |
| key_column_spec { | |
| name: "dim_2" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "version_2" | |
| type: INT64 | |
| } | |
| query { | |
| sql { | |
| sql: "SELECT 123 as dim_2, 1000 as version_2 UNION ALL SELECT 456 as dim_2, 2000 as version_2 UNION ALL SELECT 789 as dim_2, 3000 as version_2" | |
| } | |
| } | |
| } | |
| } | |
| row { | |
| dimension { int64_value: 123 } | |
| dimension { int64_value: 123 } | |
| values { double_value: 750.000000 } | |
| } | |
| row { | |
| dimension { int64_value: 456 } | |
| dimension { int64_value: 789 } | |
| values { double_value: 850.000000 } | |
| } | |
| interned_dimension_bundles { | |
| interned_dimension_rows { | |
| key_dimension_value { int64_value: 123 } | |
| interned_dimension_values { int64_value: 100 } | |
| } | |
| interned_dimension_rows { | |
| key_dimension_value { int64_value: 456 } | |
| interned_dimension_values { int64_value: 200 } | |
| } | |
| } | |
| interned_dimension_bundles { | |
| interned_dimension_rows { | |
| key_dimension_value { int64_value: 123 } | |
| interned_dimension_values { int64_value: 1000 } | |
| } | |
| interned_dimension_rows { | |
| key_dimension_value { int64_value: 789 } | |
| interned_dimension_values { int64_value: 3000 } | |
| } | |
| } | |
| } | |
| )-")); | |
| } | |
| 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 | |
| // Test that sql.column_names works correctly with group_by transformations. | |
| // The column_names field describes what the SQL returns before transformations, | |
| // but group_by changes the output schema to group keys + aggregates. | |
| TEST_F(TraceSummaryTest, SqlColumnNamesWithGroupBy) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "test_metric" | |
| dimensions: "process_name" | |
| value_columns: "min_val" | |
| value_columns: "max_val" | |
| value_columns: "avg_val" | |
| query { | |
| sql { | |
| column_names: "process_name" | |
| column_names: "metric_val" | |
| column_names: "dur" | |
| sql: " | |
| SELECT 'systemui' as process_name, 100 as metric_val, 1000 as dur | |
| UNION ALL | |
| SELECT 'systemui' as process_name, 200 as metric_val, 2000 as dur | |
| UNION ALL | |
| SELECT 'launcher' as process_name, 150 as metric_val, 1500 as dur | |
| " | |
| } | |
| group_by { | |
| column_names: "process_name" | |
| aggregates { | |
| column_name: "metric_val" | |
| op: MIN | |
| result_column_name: "min_val" | |
| } | |
| aggregates { | |
| column_name: "metric_val" | |
| op: MAX | |
| result_column_name: "max_val" | |
| } | |
| aggregates { | |
| column_name: "metric_val" | |
| op: MEAN | |
| result_column_name: "avg_val" | |
| } | |
| } | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| // Verify we get metrics for both processes | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"test_metric_min_val\"")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"test_metric_max_val\"")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"test_metric_avg_val\"")); | |
| // Verify dimension values | |
| EXPECT_THAT(*status_or_output, HasSubstr("string_value: \"systemui\"")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("string_value: \"launcher\"")); | |
| // Verify aggregated values for systemui (min=100, max=200, avg=150) | |
| EXPECT_THAT(*status_or_output, HasSubstr("double_value: 100.000000")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("double_value: 200.000000")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("double_value: 150.000000")); | |
| } | |
| // Test that sql.column_names works correctly with select_columns | |
| // transformations. | |
| TEST_F(TraceSummaryTest, SqlColumnNamesWithSelectColumns) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_spec { | |
| id: "test_metric" | |
| value: "renamed_value" | |
| query { | |
| sql { | |
| column_names: "id" | |
| column_names: "name" | |
| column_names: "value" | |
| sql: "SELECT 1 as id, 'foo' as name, 42.0 as value" | |
| } | |
| select_columns { | |
| column_name_or_expression: "value" | |
| alias: "renamed_value" | |
| } | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"test_metric\"")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("double_value: 42")); | |
| } | |
| // Test that sql.column_names validation still works when there are no | |
| // transformations (only filters, which don't change the schema). | |
| TEST_F(TraceSummaryTest, SqlColumnNamesWithFiltersOnly) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_spec { | |
| id: "test_metric" | |
| value: "value" | |
| dimensions: "name" | |
| query { | |
| sql { | |
| column_names: "name" | |
| column_names: "value" | |
| sql: " | |
| SELECT 'a' as name, 10.0 as value | |
| UNION ALL | |
| SELECT 'b' as name, 20.0 as value | |
| UNION ALL | |
| SELECT 'c' as name, 30.0 as value | |
| " | |
| } | |
| filters { | |
| column_name: "value" | |
| op: GREATER_THAN | |
| double_rhs: 15.0 | |
| } | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| EXPECT_THAT(*status_or_output, HasSubstr("id: \"test_metric\"")); | |
| // Should only have b and c (values > 15) | |
| EXPECT_THAT(*status_or_output, HasSubstr("string_value: \"b\"")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("string_value: \"c\"")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("double_value: 20.000000")); | |
| EXPECT_THAT(*status_or_output, HasSubstr("double_value: 30.000000")); | |
| } | |
| // Test the exact heap_graph_class_aggregation spec with inner_query_id | |
| // references in both the main query and interned_dimension_specs. | |
| TEST_F(TraceSummaryTest, HeapGraphClassAggregationSpec) { | |
| base::StatusOr<std::string> status_or_output = RunSummarize(R"( | |
| metric_template_spec { | |
| id_prefix: "heap_graph_class_aggregation" | |
| dimensions_specs { | |
| name: "upid" | |
| type: INT64 | |
| } | |
| dimensions_specs { | |
| name: "type_name" | |
| type: STRING | |
| } | |
| dimensions_specs { | |
| name: "is_libcore_or_array" | |
| type: BOOLEAN | |
| } | |
| value_columns: "total_size_bytes" | |
| value_columns: "total_native_size_bytes" | |
| value_columns: "total_dominated_obj_count" | |
| value_columns: "total_dominated_size_bytes" | |
| value_columns: "total_reachable_obj_count" | |
| interned_dimension_specs { | |
| key_column_spec { | |
| name: "upid" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "pid" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "process_name" | |
| type: STRING | |
| } | |
| data_column_specs { | |
| name: "uid" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "user_id" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "package_name" | |
| type: STRING | |
| } | |
| data_column_specs { | |
| name: "version_code" | |
| type: INT64 | |
| } | |
| data_column_specs { | |
| name: "debuggable" | |
| type: BOOLEAN | |
| } | |
| data_column_specs { | |
| name: "is_kernel_task" | |
| type: BOOLEAN | |
| } | |
| query { | |
| inner_query_id: "process_metadata_query" | |
| } | |
| } | |
| query { | |
| inner_query_id: "heap_graph_class_aggregation_outer_query" | |
| } | |
| } | |
| query { | |
| id: "heap_graph_class_aggregation_outer_query" | |
| table { | |
| table_name: "android_heap_graph_class_aggregation" | |
| } | |
| referenced_modules: "android.memory.heap_graph.heap_graph_class_aggregation" | |
| group_by { | |
| column_names: "upid" | |
| column_names: "type_name" | |
| column_names: "is_libcore_or_array" | |
| aggregates { | |
| column_name: "size_bytes" | |
| op: SUM | |
| result_column_name: "total_size_bytes" | |
| } | |
| aggregates { | |
| column_name: "native_size_bytes" | |
| op: SUM | |
| result_column_name: "total_native_size_bytes" | |
| } | |
| aggregates { | |
| column_name: "dominated_obj_count" | |
| op: SUM | |
| result_column_name: "total_dominated_obj_count" | |
| } | |
| aggregates { | |
| column_name: "dominated_size_bytes" | |
| op: SUM | |
| result_column_name: "total_dominated_size_bytes" | |
| } | |
| aggregates { | |
| column_name: "reachable_obj_count" | |
| op: SUM | |
| result_column_name: "total_reachable_obj_count" | |
| } | |
| } | |
| } | |
| query { | |
| id: "process_metadata_query" | |
| referenced_modules: "android.process_metadata" | |
| table { | |
| table_name: "android_process_metadata" | |
| } | |
| } | |
| )"); | |
| ASSERT_TRUE(status_or_output.ok()) << status_or_output.status().message(); | |
| // Verify all the expected metric IDs are generated | |
| EXPECT_THAT( | |
| *status_or_output, | |
| HasSubstr("id: \"heap_graph_class_aggregation_total_size_bytes\"")); | |
| EXPECT_THAT( | |
| *status_or_output, | |
| HasSubstr( | |
| "id: \"heap_graph_class_aggregation_total_native_size_bytes\"")); | |
| EXPECT_THAT( | |
| *status_or_output, | |
| HasSubstr( | |
| "id: \"heap_graph_class_aggregation_total_dominated_obj_count\"")); | |
| EXPECT_THAT( | |
| *status_or_output, | |
| HasSubstr( | |
| "id: \"heap_graph_class_aggregation_total_dominated_size_bytes\"")); | |
| EXPECT_THAT( | |
| *status_or_output, | |
| HasSubstr( | |
| "id: \"heap_graph_class_aggregation_total_reachable_obj_count\"")); | |
| } | |
| } // namespace | |
| } // namespace perfetto::trace_processor::summary |