blob: 2d92f39943c27395d8c48ee579747f1b47552b87 [file] [log] [blame]
Lalit Maganti21160e82018-10-16 09:40:29 +01001#!/usr/bin/env python
2# Copyright (C) 2018 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
16import argparse
Lalit Maganti467a8ef2019-12-05 15:54:20 +000017import datetime
Lalit Maganti21160e82018-10-16 09:40:29 +010018import difflib
19import glob
Lalit Magantidfe69ca2018-10-30 12:18:23 +000020import importlib
Lalit Maganti46a0a532019-07-15 15:09:22 +010021import json
Lalit Maganti21160e82018-10-16 09:40:29 +010022import os
Lalit Maganti4be82e12019-12-05 15:33:09 +000023import re
Lalit Maganti21160e82018-10-16 09:40:29 +010024import subprocess
25import sys
Lalit Magantidfe69ca2018-10-30 12:18:23 +000026import tempfile
Lalit Maganti21160e82018-10-16 09:40:29 +010027
Lalit Magantic8d1d2c2019-08-12 16:10:45 +010028from itertools import chain
Lalit Magantida1c1d02019-05-08 11:55:50 +010029from google.protobuf import descriptor, descriptor_pb2, message_factory
30from google.protobuf import reflection, text_format
Lalit Maganti21160e82018-10-16 09:40:29 +010031
Lalit Magantida1c1d02019-05-08 11:55:50 +010032ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
33
Primiano Tucci834fdc72019-10-04 11:33:44 +010034
Lalit Maganti467a8ef2019-12-05 15:54:20 +000035class Test(object):
36
37 def __init__(self, trace_fname, query_fname_or_metric, expected_fname):
38 self.trace_fname = trace_fname
39 self.query_fname_or_metric = query_fname_or_metric
40 self.expected_fname = expected_fname
41
42
Lalit Magantic8d1d2c2019-08-12 16:10:45 +010043class PerfResult(object):
Primiano Tucci834fdc72019-10-04 11:33:44 +010044
Lalit Maganti467a8ef2019-12-05 15:54:20 +000045 def __init__(self, trace_name, query_or_metric, ingest_time_ns_str,
46 real_time_ns_str):
Lalit Magantic8d1d2c2019-08-12 16:10:45 +010047 self.trace_name = trace_name
48 self.query_or_metric = query_or_metric
Lalit Maganti467a8ef2019-12-05 15:54:20 +000049 self.ingest_time_ns = int(ingest_time_ns_str)
50 self.real_time_ns = int(real_time_ns_str)
Lalit Magantic8d1d2c2019-08-12 16:10:45 +010051
Primiano Tucci834fdc72019-10-04 11:33:44 +010052
Ioannis Ilkos3c6a9142019-08-14 00:39:27 +010053def create_message_factory(descriptor_file_path, proto_type):
54 with open(descriptor_file_path, 'rb') as descriptor_file:
55 descriptor_content = descriptor_file.read()
Lalit Magantida1c1d02019-05-08 11:55:50 +010056
57 file_desc_set_pb2 = descriptor_pb2.FileDescriptorSet()
Ioannis Ilkos3c6a9142019-08-14 00:39:27 +010058 file_desc_set_pb2.MergeFromString(descriptor_content)
Lalit Magantida1c1d02019-05-08 11:55:50 +010059
60 desc_by_path = {}
61 for f_desc_pb2 in file_desc_set_pb2.file:
62 f_desc_pb2_encode = f_desc_pb2.SerializeToString()
63 f_desc = descriptor.FileDescriptor(
64 name=f_desc_pb2.name,
65 package=f_desc_pb2.package,
66 serialized_pb=f_desc_pb2_encode)
67
68 for desc in f_desc.message_types_by_name.values():
69 desc_by_path[desc.full_name] = desc
70
Primiano Tucci834fdc72019-10-04 11:33:44 +010071 return message_factory.MessageFactory().GetPrototype(desc_by_path[proto_type])
72
Ioannis Ilkos3c6a9142019-08-14 00:39:27 +010073
74def create_trace_message_factory(trace_descriptor_path):
Primiano Tucci834fdc72019-10-04 11:33:44 +010075 return create_message_factory(trace_descriptor_path, 'perfetto.protos.Trace')
76
Ioannis Ilkos3c6a9142019-08-14 00:39:27 +010077
78def create_metrics_message_factory(metrics_descriptor_path):
79 return create_message_factory(metrics_descriptor_path,
80 'perfetto.protos.TraceMetrics')
81
Primiano Tucci834fdc72019-10-04 11:33:44 +010082
Ioannis Ilkos3c6a9142019-08-14 00:39:27 +010083def serialize_text_proto_to_file(proto_descriptor_path, text_proto_path,
84 output_file):
85 trace_message_factory = create_trace_message_factory(proto_descriptor_path)
86 proto = trace_message_factory()
87 with open(text_proto_path, 'r') as text_proto_file:
88 text_format.Merge(text_proto_file.read(), proto)
89 output_file.write(proto.SerializeToString())
90 output_file.flush()
Lalit Magantida1c1d02019-05-08 11:55:50 +010091
Primiano Tucci834fdc72019-10-04 11:33:44 +010092
Lalit Magantida1c1d02019-05-08 11:55:50 +010093def write_diff(expected, actual):
94 expected_lines = expected.splitlines(True)
95 actual_lines = actual.splitlines(True)
Primiano Tucci834fdc72019-10-04 11:33:44 +010096 diff = difflib.unified_diff(
97 expected_lines, actual_lines, fromfile='expected', tofile='actual')
Lalit Magantida1c1d02019-05-08 11:55:50 +010098 for line in diff:
99 sys.stderr.write(line)
100
Primiano Tucci834fdc72019-10-04 11:33:44 +0100101
Lalit Maganti04a450a2019-07-03 16:04:19 +0100102class TestResult(object):
Primiano Tucci834fdc72019-10-04 11:33:44 +0100103
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000104 def __init__(self, test_type, input_name, trace, cmd, expected, actual,
105 stderr):
Lalit Maganti04a450a2019-07-03 16:04:19 +0100106 self.test_type = test_type
Lalit Maganti637589a2019-07-04 17:25:29 +0100107 self.input_name = input_name
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000108 self.trace = trace
Lalit Maganti04a450a2019-07-03 16:04:19 +0100109 self.cmd = cmd
110 self.expected = expected
111 self.actual = actual
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000112 self.stderr = stderr
Lalit Maganti04a450a2019-07-03 16:04:19 +0100113
Primiano Tucci834fdc72019-10-04 11:33:44 +0100114
Lalit Maganti04a450a2019-07-03 16:04:19 +0100115def run_metrics_test(trace_processor_path, gen_trace_path, metric,
Lalit Maganti75869e62019-07-12 16:55:38 +0100116 expected_path, perf_path, metrics_message_factory):
Lalit Maganti46a0a532019-07-15 15:09:22 +0100117 with open(expected_path, 'r') as expected_file:
Lalit Magantida1c1d02019-05-08 11:55:50 +0100118 expected = expected_file.read()
119
Lalit Maganti25863f72019-08-28 22:14:25 +0100120 json_output = os.path.basename(expected_path).endswith('.json.out')
Lalit Maganti58e24922019-05-30 18:56:14 +0100121 cmd = [
Primiano Tucci834fdc72019-10-04 11:33:44 +0100122 trace_processor_path,
123 '--run-metrics',
124 metric,
125 '--metrics-output=%s' % ('json' if json_output else 'binary'),
126 gen_trace_path,
127 '--perf-file',
128 perf_path,
Lalit Maganti58e24922019-05-30 18:56:14 +0100129 ]
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000130 tp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
131 (stdout, stderr) = tp.communicate()
Lalit Magantida1c1d02019-05-08 11:55:50 +0100132
Lalit Maganti25863f72019-08-28 22:14:25 +0100133 if json_output:
134 expected_text = expected
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000135 actual_text = stdout
Lalit Maganti25863f72019-08-28 22:14:25 +0100136 else:
137 # Expected will be in text proto format and we'll need to parse it to a real
138 # proto.
139 expected_message = metrics_message_factory()
140 text_format.Merge(expected, expected_message)
Lalit Magantida1c1d02019-05-08 11:55:50 +0100141
Lalit Maganti25863f72019-08-28 22:14:25 +0100142 # Actual will be the raw bytes of the proto and we'll need to parse it into
143 # a message.
144 actual_message = metrics_message_factory()
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000145 actual_message.ParseFromString(stdout)
Lalit Magantida1c1d02019-05-08 11:55:50 +0100146
Lalit Maganti25863f72019-08-28 22:14:25 +0100147 # Convert both back to text format.
148 expected_text = text_format.MessageToString(expected_message)
149 actual_text = text_format.MessageToString(actual_message)
Lalit Maganti770167a2019-05-08 14:32:50 +0100150
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000151 return TestResult('metric', metric, gen_trace_path, cmd, expected_text,
152 actual_text, stderr)
Lalit Magantida1c1d02019-05-08 11:55:50 +0100153
Primiano Tucci834fdc72019-10-04 11:33:44 +0100154
155def run_query_test(trace_processor_path, gen_trace_path, query_path,
156 expected_path, perf_path):
Lalit Maganti46a0a532019-07-15 15:09:22 +0100157 with open(expected_path, 'r') as expected_file:
Lalit Magantida1c1d02019-05-08 11:55:50 +0100158 expected = expected_file.read()
159
Lalit Maganti75869e62019-07-12 16:55:38 +0100160 cmd = [
Primiano Tucci834fdc72019-10-04 11:33:44 +0100161 trace_processor_path,
162 '-q',
163 query_path,
164 gen_trace_path,
165 '--perf-file',
166 perf_path,
Lalit Maganti75869e62019-07-12 16:55:38 +0100167 ]
Lalit Magantida1c1d02019-05-08 11:55:50 +0100168
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000169 tp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
170 (stdout, stderr) = tp.communicate()
171 return TestResult('query', query_path, gen_trace_path, cmd, expected, stdout,
172 stderr)
173
174
175def run_all_tests(args, test_dir, index_dir, trace_descriptor_path,
176 metrics_message_factory, tests):
177 perf_data = []
178 test_failure = 0
179 for test in tests:
180 trace_path = os.path.abspath(os.path.join(index_dir, test.trace_fname))
181 expected_path = os.path.abspath(
182 os.path.join(index_dir, test.expected_fname))
183 if not os.path.exists(trace_path):
184 sys.stderr.write('Trace file not found {}\n'.format(trace_path))
185 test_failure += 1
186 continue
187 elif not os.path.exists(expected_path):
188 sys.stderr.write('Expected file not found {}\n'.format(expected_path))
189 test_failure += 1
190 continue
191
192 if trace_path.endswith('.py'):
193 gen_trace_file = tempfile.NamedTemporaryFile()
194 python_cmd = ['python', trace_path, trace_descriptor_path]
195 subprocess.check_call(python_cmd, stdout=gen_trace_file)
196 gen_trace_path = os.path.realpath(gen_trace_file.name)
197 elif trace_path.endswith('.textproto'):
198 gen_trace_file = tempfile.NamedTemporaryFile()
199 serialize_text_proto_to_file(trace_descriptor_path, trace_path,
200 gen_trace_file)
201 gen_trace_path = os.path.realpath(gen_trace_file.name)
202 else:
203 gen_trace_file = None
204 gen_trace_path = trace_path
205
206 with tempfile.NamedTemporaryFile() as tmp_perf_file:
207 sys.stderr.write('[ RUN ] {} {}\n'.format(
208 os.path.basename(test.query_fname_or_metric),
209 os.path.basename(trace_path)))
210
211 tmp_perf_path = tmp_perf_file.name
212 if args.test_type == 'queries':
213 query_path = os.path.abspath(
214 os.path.join(index_dir, test.query_fname_or_metric))
215 if not os.path.exists(query_path):
216 print('Query file not found {}'.format(query_path))
217 test_failure += 1
218 continue
219
220 result = run_query_test(args.trace_processor, gen_trace_path,
221 query_path, expected_path, tmp_perf_path)
222 elif args.test_type == 'metrics':
223 result = run_metrics_test(args.trace_processor, gen_trace_path,
224 test.query_fname_or_metric, expected_path,
225 tmp_perf_path, metrics_message_factory)
226 else:
227 assert False
228
229 perf_lines = tmp_perf_file.readlines()
230
231 if gen_trace_file:
232 gen_trace_file.close()
233
234 if result.expected == result.actual:
235 assert len(perf_lines) == 1
236 perf_numbers = perf_lines[0].split(',')
237
238 trace_shortpath = os.path.relpath(trace_path, test_dir)
239
240 assert len(perf_numbers) == 2
241 perf_result = PerfResult(trace_shortpath, test.query_fname_or_metric,
242 perf_numbers[0], perf_numbers[1])
243 perf_data.append(perf_result)
244
245 sys.stderr.write(
246 '[ OK ] {} {} (ingest: {} ms, query: {} ms)\n'.format(
247 os.path.basename(test.query_fname_or_metric),
248 os.path.basename(trace_path),
249 perf_result.ingest_time_ns / 1000000,
250 perf_result.real_time_ns / 1000000))
251 else:
252 sys.stderr.write(result.stderr)
253
254 sys.stderr.write(
255 'Expected did not match actual for trace {} and {} {}\n'.format(
256 trace_path, result.test_type, result.input_name))
257 sys.stderr.write('Expected file: {}\n'.format(expected_path))
258 sys.stderr.write('Command line: {}\n'.format(' '.join(result.cmd)))
259
260 write_diff(result.expected, result.actual)
261
262 sys.stderr.write('[ FAIL ] {} {}\n'.format(
263 os.path.basename(test.query_fname_or_metric),
264 os.path.basename(trace_path)))
265
266 test_failure += 1
267
268 return test_failure, perf_data
Lalit Magantidfe69ca2018-10-30 12:18:23 +0000269
Primiano Tucci834fdc72019-10-04 11:33:44 +0100270
Lalit Maganti21160e82018-10-16 09:40:29 +0100271def main():
272 parser = argparse.ArgumentParser()
Lalit Maganti46a0a532019-07-15 15:09:22 +0100273 parser.add_argument('--test-type', type=str, default='queries')
Lalit Magantida1c1d02019-05-08 11:55:50 +0100274 parser.add_argument('--trace-descriptor', type=str)
275 parser.add_argument('--metrics-descriptor', type=str)
Lalit Maganti75869e62019-07-12 16:55:38 +0100276 parser.add_argument('--perf-file', type=str)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100277 parser.add_argument(
Lalit Maganti4be82e12019-12-05 15:33:09 +0000278 '--query-metric-filter',
279 default='.*',
280 type=str,
281 help=
282 'Filter the name of query files or metrics to diff test (regex syntax)')
283 parser.add_argument(
284 '--trace-filter',
285 default='.*',
286 type=str,
287 help='Filter the name of trace files to diff test (regex syntax)')
288 parser.add_argument(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100289 'trace_processor', type=str, help='location of trace processor binary')
Lalit Maganti21160e82018-10-16 09:40:29 +0100290 args = parser.parse_args()
291
Lalit Maganti46a0a532019-07-15 15:09:22 +0100292 test_dir = os.path.join(ROOT_DIR, 'test')
Lalit Magantida1c1d02019-05-08 11:55:50 +0100293 if args.test_type == 'queries':
Lalit Maganti46a0a532019-07-15 15:09:22 +0100294 index = os.path.join(test_dir, 'trace_processor', 'index')
Lalit Magantida1c1d02019-05-08 11:55:50 +0100295 elif args.test_type == 'metrics':
Lalit Maganti46a0a532019-07-15 15:09:22 +0100296 index = os.path.join(test_dir, 'metrics', 'index')
Lalit Magantida1c1d02019-05-08 11:55:50 +0100297 else:
Lalit Maganti46a0a532019-07-15 15:09:22 +0100298 print('Unknown test type {}. Supported: queries, metircs'.format(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100299 args.test_type))
Lalit Magantida1c1d02019-05-08 11:55:50 +0100300 return 1
301
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000302 index_dir = os.path.dirname(index)
Lalit Magantida1c1d02019-05-08 11:55:50 +0100303 with open(index, 'r') as file:
Lalit Maganti21160e82018-10-16 09:40:29 +0100304 index_lines = file.readlines()
305
Lalit Magantidfe69ca2018-10-30 12:18:23 +0000306 if args.trace_descriptor:
307 trace_descriptor_path = args.trace_descriptor
308 else:
309 out_path = os.path.dirname(args.trace_processor)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100310 trace_protos_path = os.path.join(out_path, 'gen', 'protos', 'perfetto',
311 'trace')
Lalit Maganti46a0a532019-07-15 15:09:22 +0100312 trace_descriptor_path = os.path.join(trace_protos_path, 'trace.descriptor')
Lalit Magantidfe69ca2018-10-30 12:18:23 +0000313
Lalit Magantida1c1d02019-05-08 11:55:50 +0100314 if args.metrics_descriptor:
315 metrics_descriptor_path = args.metrics_descriptor
316 else:
317 out_path = os.path.dirname(args.trace_processor)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100318 metrics_protos_path = os.path.join(out_path, 'gen', 'protos', 'perfetto',
319 'metrics')
Lalit Magantida1c1d02019-05-08 11:55:50 +0100320 metrics_descriptor_path = os.path.join(metrics_protos_path,
Lalit Maganti46a0a532019-07-15 15:09:22 +0100321 'metrics.descriptor')
Lalit Magantida1c1d02019-05-08 11:55:50 +0100322
323 metrics_message_factory = create_metrics_message_factory(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100324 metrics_descriptor_path)
Lalit Magantida1c1d02019-05-08 11:55:50 +0100325
Lalit Maganti4be82e12019-12-05 15:33:09 +0000326 query_metric_pattern = re.compile(args.query_metric_filter)
327 trace_pattern = re.compile(args.trace_filter)
328
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000329 tests = []
Lalit Maganti21160e82018-10-16 09:40:29 +0100330 for line in index_lines:
Lalit Maganticc674222019-01-23 17:54:00 +0000331 stripped = line.strip()
332 if stripped.startswith('#'):
333 continue
Lalit Maganti6cf6edb2019-02-07 11:09:47 +0000334 elif not stripped:
335 continue
Lalit Maganticc674222019-01-23 17:54:00 +0000336
Lalit Magantida1c1d02019-05-08 11:55:50 +0100337 [trace_fname, query_fname_or_metric, expected_fname] = stripped.split(' ')
Lalit Maganti4be82e12019-12-05 15:33:09 +0000338 if not query_metric_pattern.match(os.path.basename(query_fname_or_metric)):
339 continue
340
341 if not trace_pattern.match(os.path.basename(trace_fname)):
342 continue
343
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000344 tests.append(Test(trace_fname, query_fname_or_metric, expected_fname))
Lalit Maganti21160e82018-10-16 09:40:29 +0100345
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000346 sys.stderr.write('[==========] Running {} tests.\n'.format(len(tests)))
Lalit Maganti21160e82018-10-16 09:40:29 +0100347
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000348 test_run_start = datetime.datetime.now()
349 test_failure, perf_data = run_all_tests(args, test_dir, index_dir,
350 trace_descriptor_path,
351 metrics_message_factory, tests)
352 test_run_end = datetime.datetime.now()
Hector Dearmanf1f51a32018-10-22 11:09:41 +0100353
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000354 sys.stderr.write('[==========] {} tests ran. ({} ms total)\n'.format(
355 len(tests), int((test_run_end - test_run_start).total_seconds() * 1000)))
356 sys.stderr.write('[ PASSED ] {} tests.\n'.format(len(tests) - test_failure))
Lalit Maganti04a450a2019-07-03 16:04:19 +0100357
Lalit Maganti21160e82018-10-16 09:40:29 +0100358 if test_failure == 0:
Lalit Maganti75869e62019-07-12 16:55:38 +0100359 if args.perf_file:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100360 metrics = [[{
361 'metric': 'tp_perf_test_ingest_time',
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000362 'value': float(perf_args.ingest_time_ns) / 1.0e9,
Primiano Tucci834fdc72019-10-04 11:33:44 +0100363 'unit': 's',
364 'tags': {
365 'test_name':
366 '{}-{}'.format(perf_args.trace_name,
367 perf_args.query_or_metric),
368 'test_type':
369 args.test_type,
Lalit Magantic8d1d2c2019-08-12 16:10:45 +0100370 },
Primiano Tucci834fdc72019-10-04 11:33:44 +0100371 'labels': {},
372 },
373 {
374 'metric': 'perf_test_real_time',
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000375 'value': float(perf_args.real_time_ns) / 1.0e9,
Primiano Tucci834fdc72019-10-04 11:33:44 +0100376 'unit': 's',
377 'tags': {
378 'test_name':
379 '{}-{}'.format(perf_args.trace_name,
380 perf_args.query_or_metric),
381 'test_type':
382 args.test_type,
383 },
384 'labels': {},
385 }] for perf_args in sorted(perf_data)]
386 output_data = {'metrics': list(chain.from_iterable(metrics))}
Lalit Maganti75869e62019-07-12 16:55:38 +0100387 with open(args.perf_file, 'w+') as perf_file:
Lalit Maganti46a0a532019-07-15 15:09:22 +0100388 perf_file.write(json.dumps(output_data, indent=2))
Lalit Maganti21160e82018-10-16 09:40:29 +0100389 return 0
390 else:
Lalit Maganti467a8ef2019-12-05 15:54:20 +0000391 sys.stderr.write('[ FAILED ] {} tests.\n'.format(test_failure))
Lalit Maganti21160e82018-10-16 09:40:29 +0100392 return 1
393
Primiano Tucci834fdc72019-10-04 11:33:44 +0100394
Lalit Maganti21160e82018-10-16 09:40:29 +0100395if __name__ == '__main__':
396 sys.exit(main())