# 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. """URL endpoints to show bisect stats.""" import datetime import json from dashboard import layered_cache from dashboard import request_handler from dashboard import utils _BISECT_STATS_CACHE_KEY = 'bisect_stats' _NUM_POINTS_TO_DISPLAY = 52 _BISECT_STAT_SERIES_NAME = ['win', 'linux', 'mac', 'android'] class BisectStatsHandler(request_handler.RequestHandler): """URL endpoint to get stats about bisect runs.""" def get(self): """Renders the UI with charts.""" bisect_stats = layered_cache.GetExternal(_BISECT_STATS_CACHE_KEY) if not bisect_stats: bisect_stats = { 'failed': [], 'completed': [] } series_data = { 'failed': bisect_stats['failed'], 'completed': bisect_stats['completed'] } total_series_data = { 'failed': self._GetTotalBisectRunSeries(bisect_stats['failed']), 'completed': self._GetTotalBisectRunSeries(bisect_stats['completed']) } self.RenderHtml('bisect_stats.html', { 'series_data': json.dumps(series_data), 'total_series_data': json.dumps(total_series_data), }) def _GetTotalBisectRunSeries(self, series_map): """Sums up failed and completed bisect run series. Args: series_map: Dictionary of series names to list of data series. Returns: A list of data series. """ cropped_series_list = [] for key in series_map: series = series_map[key] cropped_series_list.append(series[len(series) - _NUM_POINTS_TO_DISPLAY:]) # Sum up series. series_map = {} for series in cropped_series_list: for x_value, y_value in series: if x_value not in series_map: series_map[x_value] = y_value else: series_map[x_value] += y_value result_list = [] for key in sorted(series_map): result_list.append([key, series_map[key]]) return result_list def UpdateBisectStats(bot_name, status): """Updates bisect run stat by bot name and status. Bisect stats stored in a layered_cache entity have the form below. Each tick is one week and count is the sum of failed or completed bisect runs. { 'failed': { bot_name: [[week_timestamp, count], [week_timestamp, count]], }, 'completed': { bot_name: [[week_timestamp, count], [week_timestamp, count]], } } Args: bot_name: Name of the bisect bot. status: Bisect status. Either 'failed' or 'completed'. """ # TODO(chrisphan): Add stats for staled bisect. if status not in ['failed', 'completed']: return series_name = _GetSeriesNameFromBotName(bot_name) week_timestamp = _GetLastMondayTimestamp() bisect_stats = layered_cache.GetExternal(_BISECT_STATS_CACHE_KEY) if not bisect_stats: bisect_stats = { 'failed': {}, 'completed': {}, } series_map = bisect_stats[status] if series_name not in series_map: series_map[series_name] = [[week_timestamp, 1]] else: series = series_map[series_name] if week_timestamp == series[-1][0]: series[-1][1] += 1 else: series.append([week_timestamp, 1]) layered_cache.SetExternal(_BISECT_STATS_CACHE_KEY, bisect_stats) def _GetLastMondayTimestamp(): """Gets timestamp of 00:00 last Monday in milliseconds as an integer.""" today = datetime.date.today() monday = today - datetime.timedelta(days=today.weekday()) return utils.TimestampMilliseconds(monday) def _GetSeriesNameFromBotName(bot_name): for series_name in _BISECT_STAT_SERIES_NAME: if series_name in bot_name: return series_name return 'other'