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"""URL endpoints to show bisect stats."""
6
7import datetime
8import json
9
10from dashboard import layered_cache
11from dashboard import request_handler
12from dashboard import utils
13
14_BISECT_STATS_CACHE_KEY = 'bisect_stats'
15_NUM_POINTS_TO_DISPLAY = 52
16_BISECT_STAT_SERIES_NAME = ['win', 'linux', 'mac', 'android']
17
18
19class BisectStatsHandler(request_handler.RequestHandler):
20  """URL endpoint to get stats about bisect runs."""
21
22  def get(self):
23    """Renders the UI with charts."""
24    bisect_stats = layered_cache.GetExternal(_BISECT_STATS_CACHE_KEY)
25    if not bisect_stats:
26      bisect_stats = {
27          'failed': [],
28          'completed': []
29      }
30
31    series_data = {
32        'failed': bisect_stats['failed'],
33        'completed': bisect_stats['completed']
34    }
35
36    total_series_data = {
37        'failed': self._GetTotalBisectRunSeries(bisect_stats['failed']),
38        'completed': self._GetTotalBisectRunSeries(bisect_stats['completed'])
39    }
40
41    self.RenderHtml('bisect_stats.html', {
42        'series_data': json.dumps(series_data),
43        'total_series_data': json.dumps(total_series_data),
44    })
45
46  def _GetTotalBisectRunSeries(self, series_map):
47    """Sums up failed and completed bisect run series.
48
49    Args:
50      series_map: Dictionary of series names to list of data series.
51
52    Returns:
53      A list of data series.
54    """
55    cropped_series_list = []
56    for key in series_map:
57      series = series_map[key]
58      cropped_series_list.append(series[len(series) - _NUM_POINTS_TO_DISPLAY:])
59
60    # Sum up series.
61    series_map = {}
62    for series in cropped_series_list:
63      for x_value, y_value in series:
64        if x_value not in series_map:
65          series_map[x_value] = y_value
66        else:
67          series_map[x_value] += y_value
68
69    result_list = []
70    for key in sorted(series_map):
71      result_list.append([key, series_map[key]])
72    return result_list
73
74
75def UpdateBisectStats(bot_name, status):
76  """Updates bisect run stat by bot name and status.
77
78  Bisect stats stored in a layered_cache entity have the form below.  Each
79  tick is one week and count is the sum of failed or completed bisect runs.
80
81  {
82    'failed': {
83      bot_name: [[week_timestamp, count], [week_timestamp, count]],
84    },
85    'completed': {
86      bot_name: [[week_timestamp, count], [week_timestamp, count]],
87    }
88  }
89
90  Args:
91    bot_name: Name of the bisect bot.
92    status: Bisect status.  Either 'failed' or 'completed'.
93  """
94  # TODO(chrisphan): Add stats for staled bisect.
95  if status not in ['failed', 'completed']:
96    return
97  series_name = _GetSeriesNameFromBotName(bot_name)
98  week_timestamp = _GetLastMondayTimestamp()
99
100  bisect_stats = layered_cache.GetExternal(_BISECT_STATS_CACHE_KEY)
101  if not bisect_stats:
102    bisect_stats = {
103        'failed': {},
104        'completed': {},
105    }
106
107  series_map = bisect_stats[status]
108  if series_name not in series_map:
109    series_map[series_name] = [[week_timestamp, 1]]
110  else:
111    series = series_map[series_name]
112    if week_timestamp == series[-1][0]:
113      series[-1][1] += 1
114    else:
115      series.append([week_timestamp, 1])
116
117  layered_cache.SetExternal(_BISECT_STATS_CACHE_KEY, bisect_stats)
118
119
120def _GetLastMondayTimestamp():
121  """Gets timestamp of 00:00 last Monday in milliseconds as an integer."""
122  today = datetime.date.today()
123  monday = today - datetime.timedelta(days=today.weekday())
124  return utils.TimestampMilliseconds(monday)
125
126
127def _GetSeriesNameFromBotName(bot_name):
128  for series_name in _BISECT_STAT_SERIES_NAME:
129    if series_name in bot_name:
130      return series_name
131  return 'other'
132