blob: 2e0272f70135569834eee43b3c7d9e4fd49ebf71 [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.
"""Provides the web interface for displaying an overview of alerts."""
import json
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
from dashboard.models import sheriff
from dashboard.models import stoppage_alert
_MAX_ANOMALIES_TO_COUNT = 5000
_MAX_ANOMALIES_TO_SHOW = 500
_MAX_STOPPAGE_ALERTS = 500
class AlertsHandler(request_handler.RequestHandler):
"""Shows an overview of recent anomalies for perf sheriffing."""
def get(self):
"""Renders the UI for listing alerts.
Request parameters:
sheriff: The name of a sheriff (optional).
triaged: Whether to include triaged alerts (i.e. with a bug ID).
improvements: Whether to include improvement anomalies.
Outputs:
A page displaying an overview table of all alerts.
"""
sheriff_name = self.request.get('sheriff', 'Chromium Perf Sheriff')
sheriff_key = ndb.Key('Sheriff', sheriff_name)
include_improvements = bool(self.request.get('improvements'))
include_triaged = bool(self.request.get('triaged'))
anomaly_keys = _FetchAnomalyKeys(
sheriff_key, include_improvements, include_triaged)
anomalies = ndb.get_multi(anomaly_keys[:_MAX_ANOMALIES_TO_SHOW])
stoppage_alerts = _FetchStoppageAlerts(sheriff_key, include_triaged)
self.RenderHtml('alerts.html', {
'anomaly_list': json.dumps(AnomalyDicts(anomalies)),
'stoppage_alert_list': json.dumps(StoppageAlertDicts(stoppage_alerts)),
'have_anomalies': bool(anomalies),
'have_stoppage_alerts': bool(stoppage_alerts),
'sheriff_list': json.dumps(_GetSheriffList()),
'num_anomalies': len(anomaly_keys),
})
def _FetchAnomalyKeys(sheriff_key, include_improvements, include_triaged):
"""Fetches the list of Anomaly keys that may be shown.
Args:
sheriff_key: The ndb.Key for the Sheriff to fetch alerts for.
include_improvements: Whether to include improvement Anomalies.
include_triaged: Whether to include Anomalies with a bug ID already set.
Returns:
A list of Anomaly keys, in reverse-chronological order.
"""
query = anomaly.Anomaly.query(
anomaly.Anomaly.sheriff == sheriff_key)
if not include_improvements:
query = query.filter(
anomaly.Anomaly.is_improvement == False)
if not include_triaged:
query = query.filter(
anomaly.Anomaly.bug_id == None)
query = query.filter(
anomaly.Anomaly.recovered == False)
query = query.order(-anomaly.Anomaly.timestamp)
return query.fetch(limit=_MAX_ANOMALIES_TO_COUNT, keys_only=True)
def _FetchStoppageAlerts(sheriff_key, include_triaged):
"""Fetches the list of Anomaly keys that may be shown.
Args:
sheriff_key: The ndb.Key for the Sheriff to fetch alerts for.
include_triaged: Whether to include alerts with a bug ID.
Returns:
A list of StoppageAlert entities, in reverse-chronological order.
"""
query = stoppage_alert.StoppageAlert.query(
stoppage_alert.StoppageAlert.sheriff == sheriff_key)
if not include_triaged:
query = query.filter(
stoppage_alert.StoppageAlert.bug_id == None)
query = query.filter(
stoppage_alert.StoppageAlert.recovered == False)
query = query.order(-stoppage_alert.StoppageAlert.timestamp)
return query.fetch(limit=_MAX_STOPPAGE_ALERTS)
def _GetSheriffList():
"""Returns a list of sheriff names for all sheriffs in the datastore."""
sheriff_keys = sheriff.Sheriff.query().fetch(keys_only=True)
return [key.string_id() for key in sheriff_keys]
def AnomalyDicts(anomalies):
"""Makes a list of dicts with properties of Anomaly entities."""
bisect_statuses = _GetBisectStatusDict(anomalies)
return [GetAnomalyDict(a, bisect_statuses.get(a.bug_id)) for a in anomalies]
def StoppageAlertDicts(stoppage_alerts):
"""Makes a list of dicts with properties of StoppageAlert entities."""
return [_GetStoppageAlertDict(a) for a in stoppage_alerts]
def GetAnomalyDict(anomaly_entity, bisect_status=None):
"""Returns a dictionary for an Anomaly which can be encoded as JSON.
Args:
anomaly_entity: An Anomaly entity.
bisect_status: String status of bisect run.
Returns:
A dictionary which is safe to be encoded as JSON.
"""
alert_dict = _AlertDict(anomaly_entity)
alert_dict.update({
'median_after_anomaly': anomaly_entity.median_after_anomaly,
'median_before_anomaly': anomaly_entity.median_before_anomaly,
'percent_changed': '%s' % anomaly_entity.GetDisplayPercentChanged(),
'improvement': anomaly_entity.is_improvement,
'bisect_status': bisect_status,
'recovered': anomaly_entity.recovered,
})
return alert_dict
def _GetStoppageAlertDict(stoppage_alert_entity):
"""Returns a dictionary of properties of a stoppage alert."""
alert_dict = _AlertDict(stoppage_alert_entity)
last_row_date = stoppage_alert_entity.last_row_date
if not last_row_date:
logging.error('No date for StoppageAlert:\n%s', stoppage_alert_entity)
alert_dict.update({
'mail_sent': stoppage_alert_entity.mail_sent,
'last_row_date': str(last_row_date.date()) if last_row_date else 'N/A',
'recovered': stoppage_alert_entity.recovered,
})
return alert_dict
def _AlertDict(alert_entity):
"""Returns a base dictionary with properties common to all alerts."""
test_path = utils.TestPath(alert_entity.test)
test_path_parts = test_path.split('/')
dashboard_link = email_template.GetReportPageLink(
test_path, rev=alert_entity.end_revision, add_protocol_and_host=False)
return {
'key': alert_entity.key.urlsafe(),
'group': alert_entity.group.urlsafe() if alert_entity.group else None,
'start_revision': alert_entity.start_revision,
'end_revision': alert_entity.end_revision,
'date': str(alert_entity.timestamp.date()),
'master': test_path_parts[0],
'bot': test_path_parts[1],
'testsuite': test_path_parts[2],
'test': '/'.join(test_path_parts[3:]),
'bug_id': alert_entity.bug_id,
'dashboard_link': dashboard_link,
}
def _GetBisectStatusDict(anomalies):
"""Returns a dictionary of bug ID to bisect status string."""
bug_id_list = {a.bug_id for a in anomalies if a.bug_id > 0}
bugs = ndb.get_multi(ndb.Key('Bug', b) for b in bug_id_list)
return {b.key.id(): b.latest_bisect_status for b in bugs if b}