1 # Copyright 2015 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import json 6 import time 7 8 from google.appengine.api import urlfetch 9 import webapp2 10 11 from base import bigquery 12 from base import constants 13 from common import query_filter 14 15 16 class Query(webapp2.RequestHandler): 17 18 def get(self): 19 urlfetch.set_default_fetch_deadline(60) 20 21 try: 22 filters = query_filter.Filters(self.request) 23 except ValueError as e: 24 self.response.headers['Content-Type'] = 'application/json' 25 self.response.out.write({'error': str(e)}) 26 return 27 query_results = _QueryEvents(bigquery.BigQuery(), **filters) 28 trace_events = list(_ConvertQueryEventsToTraceEvents(query_results)) 29 30 self.response.headers['Content-Type'] = 'application/json' 31 self.response.out.write(json.dumps(trace_events, separators=(',', ':'))) 32 33 34 def _QueryEvents(bq, **filters): 35 start_time = filters.get( 36 'start_time', time.time() - constants.DEFAULT_HISTORY_DURATION_SECONDS) 37 query_start_time_us = int(start_time * 1000000) 38 39 end_time = filters.get('end_time', time.time()) 40 query_end_time_us = int(end_time * 1000000) 41 42 fields = ( 43 'name', 44 'GREATEST(INTEGER(start_time), %d) AS start_time_us' % 45 query_start_time_us, 46 'LEAST(INTEGER(end_time), %d) AS end_time_us' % query_end_time_us, 47 'builder', 48 'configuration', 49 'hostname', 50 'status', 51 'url', 52 ) 53 54 tables = (constants.BUILDS_TABLE, constants.CURRENT_BUILDS_TABLE) 55 tables = ['[%s.%s]' % (constants.DATASET, table) for table in tables] 56 # TODO(dtu): Taking a snapshot of the table should reduce the cost of the 57 # query, but some trace events appear to be missing and not sure why. 58 #tables = ['[%s.%s@%d-]' % (constants.DATASET, table, query_start_time_ms) 59 #for table in tables] 60 61 conditions = [] 62 conditions.append('NOT LOWER(name) CONTAINS "trigger"') 63 conditions.append('end_time - start_time >= 1000000') 64 conditions.append('end_time > %d' % query_start_time_us) 65 conditions.append('start_time < %d' % query_end_time_us) 66 for filter_name, filter_values in filters.iteritems(): 67 if not isinstance(filter_values, list): 68 continue 69 70 if isinstance(filter_values[0], int): 71 filter_values = map(str, filter_values) 72 elif isinstance(filter_values[0], basestring): 73 # QueryFilter handles string validation. Assume no quotes in string. 74 filter_values = ['"%s"' % v for v in filter_values] 75 else: 76 raise NotImplementedError() 77 78 conditions.append('%s IN (%s)' % (filter_name, ','.join(filter_values))) 79 80 query = ('SELECT %s ' % ','.join(fields) + 81 'FROM %s ' % ','.join(tables) + 82 'WHERE %s ' % ' AND '.join(conditions) + 83 'ORDER BY builder, start_time') 84 return bq.QuerySync(query) 85 86 87 def _ConvertQueryEventsToTraceEvents(events): 88 for row in events: 89 event_start_time_us = int(row['f'][1]['v']) 90 event_end_time_us = int(row['f'][2]['v']) 91 92 status = row['f'][6]['v'] 93 if status: 94 status = int(status) 95 # TODO: Use constants from update/common/buildbot/__init__.py. 96 if status == 0: 97 color_name = 'cq_build_passed' 98 elif status == 1: 99 color_name = 'cq_build_warning' 100 elif status == 2: 101 color_name = 'cq_build_failed' 102 elif status == 4: 103 color_name = 'cq_build_exception' 104 elif status == 5: 105 color_name = 'cq_build_abandoned' 106 else: 107 color_name = 'cq_build_running' 108 109 yield { 110 'name': row['f'][0]['v'], 111 'pid': row['f'][4]['v'], 112 'tid': '%s [%s]' % (row['f'][3]['v'], row['f'][5]['v']), 113 'ph': 'X', 114 'ts': event_start_time_us, 115 'dur': event_end_time_us - event_start_time_us, 116 'cname': color_name, 117 'args': { 118 'url': row['f'][7]['v'], 119 }, 120 } 121