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