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
5import json
6import time
7
8from google.appengine.api import urlfetch
9import webapp2
10
11from base import bigquery
12from base import constants
13from common import query_filter
14
15
16class 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
34def _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
87def _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