blob: 459bf927ddc58f96f0a363a64475ba58cfcf4542 [file] [log] [blame]
# 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 endpoint for the main page which lists recent anomalies."""
import datetime
import logging
from google.appengine.ext import ndb
from dashboard import email_template
from dashboard import request_handler
from dashboard import utils
from dashboard.models import anomaly
_ANOMALY_FETCH_LIMIT = 1000
_DEFAULT_DAYS_TO_SHOW = 7
_DEFAULT_CHANGES_TO_SHOW = 10
_DEFAULT_SHERIFF_NAME = 'Chromium Perf Sheriff'
class MainHandler(request_handler.RequestHandler):
"""Displays the main overview page."""
def get(self):
"""Renders the UI for the main overview page.
Request parameters:
days: Number of days to show anomalies for (optional).
sheriff: Sheriff to show anomalies for (optional)
num_changes: The number of improvements/regressions to list.
Outputs:
A HTML page that shows recent regressions, improvements.
"""
days = int(self.request.get('days', _DEFAULT_DAYS_TO_SHOW))
num_changes = int(self.request.get('num_changes', _DEFAULT_CHANGES_TO_SHOW))
sheriff_name = self.request.get('sheriff', _DEFAULT_SHERIFF_NAME)
sheriff = ndb.Key('Sheriff', sheriff_name)
anomalies = _GetRecentAnomalies(days, sheriff)
top_improvements = _TopImprovements(anomalies, num_changes)
top_regressions = _TopRegressions(anomalies, num_changes)
tests = _GetKeyToTestDict(top_improvements + top_regressions)
template_dict = {
'num_days': days,
'num_changes': num_changes,
'sheriff_name': sheriff_name,
'improvements': _AnomalyInfoDicts(top_improvements, tests),
'regressions': _AnomalyInfoDicts(top_regressions, tests),
}
self.RenderHtml('main.html', template_dict)
def _GetRecentAnomalies(days, sheriff):
"""Fetches recent Anomalies from the datastore.
Args:
days: Number of days old of the oldest Anomalies to fetch.
sheriff: The ndb.Key of the Sheriff to fetch Anomalies for.
Returns:
A list of Anomaly entities sorted from large to small relative change.
"""
oldest_time = datetime.datetime.now() - datetime.timedelta(days=days)
anomalies_query = anomaly.Anomaly.query(
anomaly.Anomaly.timestamp > oldest_time,
anomaly.Anomaly.sheriff == sheriff)
anomalies = anomalies_query.fetch(limit=_ANOMALY_FETCH_LIMIT)
anomalies.sort(key=lambda a: abs(a.percent_changed), reverse=True)
# We only want to list alerts that aren't marked invalid or ignored.
anomalies = [a for a in anomalies if a.bug_id is None or a.bug_id > 0]
return anomalies
def _GetKeyToTestDict(anomalies):
"""Returns a map of Test keys to entities for the given anomalies."""
test_keys = {a.test for a in anomalies}
tests = utils.GetMulti(test_keys)
return {t.key: t for t in tests}
def _GetColorClass(percent_changed):
"""Returns a CSS class name for the anomaly, based on percent changed."""
if percent_changed > 50:
return 'over-50'
if percent_changed > 40:
return 'over-40'
if percent_changed > 30:
return 'over-30'
if percent_changed > 20:
return 'over-20'
if percent_changed > 10:
return 'over-10'
return 'under-10'
def _AnomalyInfoDicts(anomalies, tests):
"""Returns information info about the given anomalies.
Args:
anomalies: A list of anomalies.
tests: A dictionary mapping Test keys to Test entities.
Returns:
A list of dictionaries with information about the given anomalies.
"""
anomaly_list = []
for anomaly_entity in anomalies:
# TODO(qyearsley): Add test coverage. See catapult:#1346.
test = tests.get(anomaly_entity.test)
if not test:
logging.warning('No Test entity for key: %s.', anomaly_entity.test)
continue
subtest_path = '/'.join(test.test_path.split('/')[3:])
graph_link = email_template.GetGroupReportPageLink(anomaly_entity)
anomaly_list.append({
'key': anomaly_entity.key.urlsafe(),
'bug_id': anomaly_entity.bug_id or '',
'start_revision': anomaly_entity.start_revision,
'end_revision': anomaly_entity.end_revision,
'master': test.master_name,
'bot': test.bot_name,
'testsuite': test.suite_name,
'test': subtest_path,
'percent_changed': anomaly_entity.GetDisplayPercentChanged(),
'color_class': _GetColorClass(abs(anomaly_entity.percent_changed)),
'improvement': anomaly_entity.is_improvement,
'dashboard_link': graph_link,
})
return anomaly_list
def _TopImprovements(recent_anomalies, num_to_show):
"""Fills in the given template dictionary with top improvements.
Args:
recent_anomalies: A list of Anomaly entities sorted from large to small.
num_to_show: The number of improvements to return.
Returns:
A list of top improvement Anomaly entities, in decreasing order.
"""
improvements = [a for a in recent_anomalies if a.is_improvement]
return improvements[:num_to_show]
def _TopRegressions(recent_anomalies, num_to_show):
"""Fills in the given template dictionary with top regressions.
Args:
recent_anomalies: A list of Anomaly entities sorted from large to small.
num_to_show: The number of regressions to return.
Returns:
A list of top regression Anomaly entities, in decreasing order.
"""
regressions = [a for a in recent_anomalies if not a.is_improvement]
return regressions[:num_to_show]