1#!/usr/bin/env python3 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 16import argparse 17import json 18import hashlib 19import sys 20 21from config import DB, PROJECT 22from common_utils import req, SCOPES 23''' 24Uploads the performance metrics of the Perfetto tests to StackDriver and 25Firebase. 26 27The expected format of the JSON is as follows: 28{ 29 metrics: [ 30 { 31 'metric': *metric name*, 32 'value': *metric value*, 33 'unit': *either s (seconds) or b (bytes)*, 34 'tags': { 35 *tag name*: *tag value*, 36 ... 37 }, 38 'labels': { 39 *label name*: *label value*, 40 ... 41 } 42 }, 43 ... 44 ] 45} 46''' 47 48STACKDRIVER_API = 'https://monitoring.googleapis.com/v3/projects/%s' % PROJECT 49SCOPES.append('https://www.googleapis.com/auth/firebase.database') 50SCOPES.append('https://www.googleapis.com/auth/userinfo.email') 51SCOPES.append('https://www.googleapis.com/auth/monitoring.write') 52 53 54def sha1(obj): 55 hasher = hashlib.sha1() 56 hasher.update( 57 json.dumps(obj, sort_keys=True, separators=(',', ':')).encode('utf-8')) 58 return hasher.hexdigest() 59 60 61def metric_list_to_hash_dict(raw_metrics): 62 metrics = {} 63 for metric in raw_metrics: 64 key = '%s-%s' % (metric['metric'], sha1(metric['tags'])) 65 metrics[key] = metric 66 return metrics 67 68 69def create_stackdriver_metrics(ts, metrics): 70 # Chunk up metrics into 100 element chunks to comply with Stackdriver's 71 # restrictions on the number of metrics in a request. 72 metrics_list = list(metrics.values()) 73 metric_chunks = [metrics_list[x:x + 100] for x in range(0, len(metrics), 100)] 74 desc_chunks = [] 75 76 for chunk in metric_chunks: 77 desc = {'timeSeries': []} 78 for metric in chunk: 79 metric_name = metric['metric'] 80 desc['timeSeries'] += [{ 81 'metric': { 82 'type': 83 'custom.googleapis.com/perfetto-ci/perf/%s' % metric_name, 84 'labels': 85 dict( 86 list(metric.get('tags', {}).items()) + 87 list(metric.get('labels', {}).items())), 88 }, 89 'resource': { 90 'type': 'global' 91 }, 92 'points': [{ 93 'interval': { 94 'endTime': ts 95 }, 96 'value': { 97 'doubleValue': str(metric['value']) 98 } 99 }] 100 }] 101 desc_chunks.append(desc) 102 return desc_chunks 103 104 105def main(): 106 parser = argparse.ArgumentParser() 107 parser.add_argument( 108 '--job-id', 109 type=str, 110 required=True, 111 help='The Perfetto CI job ID to tie this upload to') 112 parser.add_argument( 113 'metrics_file', type=str, help='File containing the metrics to upload') 114 args = parser.parse_args() 115 116 with open(args.metrics_file, 'r') as metrics_file: 117 raw_metrics = json.loads(metrics_file.read()) 118 119 job = req('GET', '%s/jobs/%s.json' % (DB, args.job_id)) 120 ts = job['time_started'] 121 122 metrics = metric_list_to_hash_dict(raw_metrics['metrics']) 123 req('PUT', '%s/perf/%s.json' % (DB, args.job_id), body=metrics) 124 125 # Only upload Stackdriver metrics for post-submit runs. 126 git_ref = job['env'].get('PERFETTO_TEST_GIT_REF') 127 if git_ref == 'refs/heads/master': 128 sd_metrics_chunks = create_stackdriver_metrics(ts, metrics) 129 for sd_metrics in sd_metrics_chunks: 130 req('POST', STACKDRIVER_API + '/timeSeries', body=sd_metrics) 131 132 return 0 133 134 135if __name__ == '__main__': 136 sys.exit(main()) 137