Home | History | Annotate | Download | only in handlers
      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