| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import json |
| import time |
| |
| from google.appengine.api import urlfetch |
| import webapp2 |
| |
| from base import bigquery |
| from base import constants |
| from common import query_filter |
| |
| |
| class Query(webapp2.RequestHandler): |
| |
| def get(self): |
| urlfetch.set_default_fetch_deadline(60) |
| |
| try: |
| filters = query_filter.Filters(self.request) |
| except ValueError as e: |
| self.response.headers['Content-Type'] = 'application/json' |
| self.response.out.write({'error': str(e)}) |
| return |
| query_results = _QueryEvents(bigquery.BigQuery(), **filters) |
| trace_events = list(_ConvertQueryEventsToTraceEvents(query_results)) |
| |
| self.response.headers['Content-Type'] = 'application/json' |
| self.response.out.write(json.dumps(trace_events, separators=(',', ':'))) |
| |
| |
| def _QueryEvents(bq, **filters): |
| start_time = filters.get( |
| 'start_time', time.time() - constants.DEFAULT_HISTORY_DURATION_SECONDS) |
| query_start_time_ms = int(start_time * 1000) |
| query_start_time_us = int(start_time * 1000000) |
| |
| fields = ( |
| 'name', |
| 'GREATEST(INTEGER(start_time), %d) AS start_time_us' % |
| query_start_time_us, |
| 'INTEGER(end_time) AS end_time_us', |
| 'builder', |
| 'configuration', |
| 'hostname', |
| 'status', |
| 'url', |
| ) |
| |
| tables = (constants.BUILDS_TABLE, constants.CURRENT_BUILDS_TABLE) |
| tables = ['[%s.%s@%d-]' % (constants.DATASET, table, query_start_time_ms) |
| for table in tables] |
| |
| conditions = [] |
| conditions.append('NOT LOWER(name) CONTAINS "trigger"') |
| conditions.append('end_time - start_time >= 1000000') |
| conditions.append('end_time > %d' % query_start_time_us) |
| for filter_name, filter_values in filters.iteritems(): |
| if not isinstance(filter_values, list): |
| continue |
| |
| if isinstance(filter_values[0], int): |
| filter_values = map(str, filter_values) |
| elif isinstance(filter_values[0], basestring): |
| # QueryFilter handles string validation. Assume no quotes in string. |
| filter_values = ['"%s"' % v for v in filter_values] |
| else: |
| raise NotImplementedError() |
| |
| conditions.append('%s IN (%s)' % (filter_name, ','.join(filter_values))) |
| |
| query = ('SELECT %s ' % ','.join(fields) + |
| 'FROM %s ' % ','.join(tables) + |
| 'WHERE %s ' % ' AND '.join(conditions) + |
| 'ORDER BY builder, start_time') |
| return bq.QuerySync(query) |
| |
| |
| def _ConvertQueryEventsToTraceEvents(events): |
| for row in events: |
| event_start_time_us = int(row['f'][1]['v']) |
| event_end_time_us = int(row['f'][2]['v']) |
| |
| status = row['f'][6]['v'] |
| if status: |
| status = int(status) |
| # TODO: Use constants from update/common/buildbot/__init__.py. |
| if status == 0: |
| color_name = 'cq_build_passed' |
| elif status == 1: |
| color_name = 'cq_build_warning' |
| elif status == 2: |
| color_name = 'cq_build_failed' |
| elif status == 4: |
| color_name = 'cq_build_exception' |
| elif status == 5: |
| color_name = 'cq_build_abandoned' |
| else: |
| color_name = 'cq_build_running' |
| |
| yield { |
| 'name': row['f'][0]['v'], |
| 'pid': row['f'][4]['v'], |
| 'tid': '%s [%s]' % (row['f'][3]['v'], row['f'][5]['v']), |
| 'ph': 'X', |
| 'ts': event_start_time_us, |
| 'dur': event_end_time_us - event_start_time_us, |
| 'cname': color_name, |
| 'args': { |
| 'url': row['f'][7]['v'], |
| }, |
| } |