1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/metrics/metrics.h"
18
19 #include <cstdint>
20 #include <optional>
21 #include <string>
22 #include <unordered_map>
23 #include <vector>
24
25 #include "perfetto/protozero/field.h"
26 #include "perfetto/protozero/proto_decoder.h"
27 #include "perfetto/protozero/proto_utils.h"
28 #include "perfetto/trace_processor/basic_types.h"
29 #include "protos/perfetto/common/descriptor.pbzero.h"
30 #include "src/base/test/status_matchers.h"
31 #include "src/trace_processor/util/descriptors.h"
32 #include "test/gtest_and_gmock.h"
33
34 #include "protos/perfetto/trace_processor/metrics_impl.pbzero.h"
35
36 namespace perfetto::trace_processor::metrics {
37 namespace {
38 using base::gtest_matchers::IsError;
39
40 using base::gtest_matchers::IsError;
41 using testing::IsEmpty;
42
RunTemplateReplace(const std::string & str,const std::unordered_map<std::string,std::string> & subs)43 std::string RunTemplateReplace(
44 const std::string& str,
45 const std::unordered_map<std::string, std::string>& subs) {
46 std::string out;
47 EXPECT_EQ(TemplateReplace(str, subs, &out), 0);
48 return out;
49 }
50
TEST(MetricsTest,TemplateReplace)51 TEST(MetricsTest, TemplateReplace) {
52 auto res = RunTemplateReplace("no templates here", {});
53 ASSERT_EQ(res, "no templates here");
54
55 res = RunTemplateReplace("{{justtemplate}}", {{"justtemplate", "result"}});
56 ASSERT_EQ(res, "result");
57
58 res = RunTemplateReplace("{{temp1}} {{temp2}}!",
59 {{"temp1", "hello"}, {"temp2", "world"}});
60 ASSERT_EQ(res, "hello world!");
61
62 std::string unused;
63 ASSERT_NE(TemplateReplace("{{missing}}", {{}}, &unused), 0);
64 }
65
66 class ProtoBuilderTest : public ::testing::Test {
67 protected:
68 template <bool repeated>
DecodeSingleFieldProto(const std::vector<uint8_t> & result_ser)69 protozero::TypedProtoDecoder<1, repeated> DecodeSingleFieldProto(
70 const std::vector<uint8_t>& result_ser) {
71 protos::pbzero::ProtoBuilderResult::Decoder result(result_ser.data(),
72 result_ser.size());
73 protos::pbzero::SingleBuilderResult::Decoder single(result.single());
74 protozero::ConstBytes proto_ser = single.protobuf();
75 return protozero::TypedProtoDecoder<1, repeated>(proto_ser.data,
76 proto_ser.size);
77 }
78 };
79
TEST_F(ProtoBuilderTest,AppendLong)80 TEST_F(ProtoBuilderTest, AppendLong) {
81 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
82
83 // Create the descriptor version of the following message:
84 // message TestProto {
85 // optional int64 int_value = 1;
86 // }
87 DescriptorPool pool;
88 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
89 ".perfetto.protos.TestProto",
90 ProtoDescriptor::Type::kMessage, std::nullopt);
91 descriptor.AddField(
92 FieldDescriptor("int_value", 1, FieldDescriptorProto::TYPE_INT64, "",
93 std::vector<uint8_t>(), std::nullopt, false, false));
94
95 ProtoBuilder builder(&pool, &descriptor);
96 ASSERT_OK(builder.AppendSqlValue("int_value", SqlValue::Long(12345)));
97
98 auto result_ser = builder.SerializeToProtoBuilderResult();
99 auto proto = DecodeSingleFieldProto<false>(result_ser);
100 const protozero::Field& int_field = proto.Get(1);
101 ASSERT_EQ(int_field.as_int64(), 12345);
102 }
103
TEST_F(ProtoBuilderTest,AppendDouble)104 TEST_F(ProtoBuilderTest, AppendDouble) {
105 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
106
107 // Create the descriptor version of the following message:
108 // message TestProto {
109 // optional double double_value = 1;
110 // }
111 DescriptorPool pool;
112 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
113 ".perfetto.protos.TestProto",
114 ProtoDescriptor::Type::kMessage, std::nullopt);
115 descriptor.AddField(
116 FieldDescriptor("double_value", 1, FieldDescriptorProto::TYPE_DOUBLE, "",
117 std::vector<uint8_t>(), std::nullopt, false, false));
118
119 ProtoBuilder builder(&pool, &descriptor);
120 ASSERT_OK(builder.AppendSqlValue("double_value", SqlValue::Double(1.2345)));
121
122 auto result_ser = builder.SerializeToProtoBuilderResult();
123 auto proto = DecodeSingleFieldProto<false>(result_ser);
124 const protozero::Field& db_field = proto.Get(1);
125 ASSERT_DOUBLE_EQ(db_field.as_double(), 1.2345);
126 }
127
TEST_F(ProtoBuilderTest,AppendString)128 TEST_F(ProtoBuilderTest, AppendString) {
129 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
130
131 // Create the descriptor version of the following message:
132 // message TestProto {
133 // optional string string_value = 1;
134 // }
135 DescriptorPool pool;
136 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
137 ".perfetto.protos.TestProto",
138 ProtoDescriptor::Type::kMessage, std::nullopt);
139 descriptor.AddField(
140 FieldDescriptor("string_value", 1, FieldDescriptorProto::TYPE_STRING, "",
141 std::vector<uint8_t>(), std::nullopt, false, false));
142
143 ProtoBuilder builder(&pool, &descriptor);
144 ASSERT_OK(
145 builder.AppendSqlValue("string_value", SqlValue::String("hello world!")));
146
147 auto result_ser = builder.SerializeToProtoBuilderResult();
148 auto proto = DecodeSingleFieldProto<false>(result_ser);
149 const protozero::Field& str_field = proto.Get(1);
150 ASSERT_EQ(str_field.as_std_string(), "hello world!");
151 }
152
TEST_F(ProtoBuilderTest,AppendNested)153 TEST_F(ProtoBuilderTest, AppendNested) {
154 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
155
156 // Create the descriptor version of the following message:
157 // message TestProto {
158 // message NestedProto {
159 // optional int64 nested_int_value = 1;
160 // }
161 // optional NestedProto nested_value = 1;
162 // }
163 DescriptorPool pool;
164 ProtoDescriptor nested("file.proto", ".perfetto.protos",
165 ".perfetto.protos.TestProto.NestedProto",
166 ProtoDescriptor::Type::kMessage, std::nullopt);
167 nested.AddField(
168 FieldDescriptor("nested_int_value", 1, FieldDescriptorProto::TYPE_INT64,
169 "", std::vector<uint8_t>(), std::nullopt, false, false));
170
171 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
172 ".perfetto.protos.TestProto",
173 ProtoDescriptor::Type::kMessage, std::nullopt);
174 auto field =
175 FieldDescriptor("nested_value", 1, FieldDescriptorProto::TYPE_MESSAGE,
176 ".perfetto.protos.TestProto.NestedProto",
177 std::vector<uint8_t>(), std::nullopt, false, false);
178 field.set_resolved_type_name(".perfetto.protos.TestProto.NestedProto");
179 descriptor.AddField(field);
180
181 ProtoBuilder nest_builder(&pool, &nested);
182 ASSERT_OK(
183 nest_builder.AppendSqlValue("nested_int_value", SqlValue::Long(789)));
184
185 auto nest_ser = nest_builder.SerializeToProtoBuilderResult();
186
187 ProtoBuilder builder(&pool, &descriptor);
188 ASSERT_OK(builder.AppendSqlValue(
189 "nested_value", SqlValue::Bytes(nest_ser.data(), nest_ser.size())));
190
191 auto result_ser = builder.SerializeToProtoBuilderResult();
192 auto proto = DecodeSingleFieldProto<false>(result_ser);
193 const protozero::Field& nest_field = proto.Get(1);
194 ASSERT_EQ(nest_field.type(),
195 protozero::proto_utils::ProtoWireType::kLengthDelimited);
196
197 protozero::ConstBytes nest_bytes = nest_field.as_bytes();
198 protozero::TypedProtoDecoder<1, false> nest(nest_bytes.data, nest_bytes.size);
199
200 const protozero::Field& nest_int_field = nest.Get(1);
201 ASSERT_EQ(nest_int_field.type(),
202 protozero::proto_utils::ProtoWireType::kVarInt);
203 ASSERT_EQ(nest_int_field.as_int64(), 789);
204 }
205
TEST_F(ProtoBuilderTest,AppendRepeatedEmpty)206 TEST_F(ProtoBuilderTest, AppendRepeatedEmpty) {
207 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
208
209 // Create the descriptor version of the following message:
210 // message TestProto {
211 // repeated int64 int_value = 1;
212 // }
213 DescriptorPool pool;
214 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
215 ".perfetto.protos.TestProto",
216 ProtoDescriptor::Type::kMessage, std::nullopt);
217 descriptor.AddField(
218 FieldDescriptor("rep_int_value", 1, FieldDescriptorProto::TYPE_INT64, "",
219 std::vector<uint8_t>(), std::nullopt, true, false));
220
221 ASSERT_THAT(RepeatedFieldBuilder().SerializeToProtoBuilderResult(),
222 IsEmpty());
223
224 ProtoBuilder builder(&pool, &descriptor);
225 ASSERT_OK(builder.AppendSqlValue("rep_int_value", SqlValue()));
226
227 auto proto =
228 DecodeSingleFieldProto<true>(builder.SerializeToProtoBuilderResult());
229 auto it = proto.GetRepeated<int64_t>(1);
230 ASSERT_FALSE(it);
231 }
232
TEST_F(ProtoBuilderTest,AppendRepeatedPrimitive)233 TEST_F(ProtoBuilderTest, AppendRepeatedPrimitive) {
234 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
235
236 // Create the descriptor version of the following message:
237 // message TestProto {
238 // repeated int64 int_value = 1;
239 // }
240 DescriptorPool pool;
241 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
242 ".perfetto.protos.TestProto",
243 ProtoDescriptor::Type::kMessage, std::nullopt);
244 descriptor.AddField(
245 FieldDescriptor("rep_int_value", 1, FieldDescriptorProto::TYPE_INT64, "",
246 std::vector<uint8_t>(), std::nullopt, true, false));
247
248 RepeatedFieldBuilder rep_builder;
249 rep_builder.AddSqlValue(SqlValue::Long(1234));
250 rep_builder.AddSqlValue(SqlValue::Long(5678));
251
252 std::vector<uint8_t> rep_ser = rep_builder.SerializeToProtoBuilderResult();
253
254 ProtoBuilder builder(&pool, &descriptor);
255 ASSERT_OK(builder.AppendSqlValue(
256 "rep_int_value", SqlValue::Bytes(rep_ser.data(), rep_ser.size())));
257
258 auto proto =
259 DecodeSingleFieldProto<true>(builder.SerializeToProtoBuilderResult());
260 auto it = proto.GetRepeated<int64_t>(1);
261 ASSERT_EQ(*it, 1234);
262 ASSERT_EQ(*++it, 5678);
263 ASSERT_FALSE(++it);
264 }
265
TEST_F(ProtoBuilderTest,AppendEnums)266 TEST_F(ProtoBuilderTest, AppendEnums) {
267 using FieldDescriptorProto = protos::pbzero::FieldDescriptorProto;
268
269 // Create the descriptor version of the following enum and message:
270 // enum TestEnum {
271 // FIRST = 1,
272 // SECOND = 2,
273 // THIRD = 3
274 // }
275 // message TestMessage {
276 // optional TestEnum enum_value = 1;
277 // }
278 DescriptorPool pool;
279 ProtoDescriptor enum_descriptor("file.proto", ".perfetto.protos",
280 ".perfetto.protos.TestEnum",
281 ProtoDescriptor::Type::kEnum, std::nullopt);
282 enum_descriptor.AddEnumValue(1, "FIRST");
283 enum_descriptor.AddEnumValue(2, "SECOND");
284 enum_descriptor.AddEnumValue(3, "THIRD");
285 pool.AddProtoDescriptorForTesting(enum_descriptor);
286
287 ProtoDescriptor descriptor("file.proto", ".perfetto.protos",
288 ".perfetto.protos.TestMessage",
289 ProtoDescriptor::Type::kMessage, std::nullopt);
290 FieldDescriptor enum_field("enum_value", 1, FieldDescriptorProto::TYPE_ENUM,
291 ".perfetto.protos.TestEnum",
292 std::vector<uint8_t>(), std::nullopt, false,
293 false);
294 enum_field.set_resolved_type_name(".perfetto.protos.TestEnum");
295 descriptor.AddField(enum_field);
296 pool.AddProtoDescriptorForTesting(descriptor);
297
298 ProtoBuilder value_builder(&pool, &descriptor);
299 ASSERT_THAT(value_builder.AppendSqlValue("enum_value", SqlValue::Long(4)),
300 IsError());
301 ASSERT_OK(value_builder.AppendSqlValue("enum_value", SqlValue::Long(3)));
302 ASSERT_THAT(value_builder.AppendSqlValue("enum_value", SqlValue::Long(6)),
303 IsError());
304
305 auto value_proto = DecodeSingleFieldProto<false>(
306 value_builder.SerializeToProtoBuilderResult());
307 ASSERT_EQ(value_proto.Get(1).as_int32(), 3);
308
309 ProtoBuilder str_builder(&pool, &descriptor);
310 ASSERT_THAT(
311 str_builder.AppendSqlValue("enum_value", SqlValue::String("FOURTH")),
312 IsError());
313 ASSERT_OK(
314 str_builder.AppendSqlValue("enum_value", SqlValue::String("SECOND")));
315 ASSERT_THAT(
316 str_builder.AppendSqlValue("enum_value", SqlValue::String("OTHER")),
317 IsError());
318
319 auto str_proto = DecodeSingleFieldProto<false>(
320 str_builder.SerializeToProtoBuilderResult());
321 ASSERT_EQ(str_proto.Get(1).as_int32(), 2);
322 }
323
324 } // namespace
325 } // namespace perfetto::trace_processor::metrics
326