Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(229)

Unified Diff: experimental/soundwave/alert_analyzer.py

Issue 3014653002: [Soundwave] Add tool that displays alerts and related noise and bug data.
Patch Set: Fix error when metric is None when generating noise data Created 3 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: experimental/soundwave/alert_analyzer.py
diff --git a/experimental/soundwave/alert_analyzer.py b/experimental/soundwave/alert_analyzer.py
new file mode 100755
index 0000000000000000000000000000000000000000..569dab3969365bfef11545aeca01a1b3b1e09a09
--- /dev/null
+++ b/experimental/soundwave/alert_analyzer.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+# Copyright 2017 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.
+
+import argparse
+import httplib2
+import json
+import numpy
+from oauth2client import client
+from oauth2client import service_account # pylint: disable=no-name-in-module
+import time
+
+
+# TODO(rnephew): Integrate into catapult/experimental/benchmark_health_report.
+REQUEST_URL = 'https://chromeperf.appspot.com/api/'
+# pylint: disable=line-too-long
+HELP_SITE = 'https://developers.google.com/api-client-library/python/auth/service-accounts#creatinganaccount'
+
+OAUTH_CLIENT_ID = (
+ '62121018386-h08uiaftreu4dr3c4alh3l7mogskvb7i.apps.googleusercontent.com')
+OAUTH_CLIENT_SECRET = 'vc1fZfV1cZC6mgDSHV-KSPOz'
+SCOPES = 'https://www.googleapis.com/auth/userinfo.email'
+
+
+def AuthorizeAccount(args):
+ """A factory for authorized account credentials."""
+ if args.credentials:
+ try:
+ return AuthorizeAccountServiceAccount(args.credentials)
+ except Exception: # pylint: disable=broad-except
+ print ('Failure authenticating with service account. Falling back to user'
+ ' authentication.')
+ return AuthorizeAccountUserAccount()
+
+
+def AuthorizeAccountServiceAccount(json_key):
+ """Used to create a service account connection with the performance dashboard.
+
+ args:
+ json_key: Path to json file that contains credentials.
+ returns:
+ An object that can be used to communicate with the dashboard.
+ """
+ creds = service_account.ServiceAccountCredentials.from_json_keyfile_name(
+ json_key, [SCOPES])
+ return creds.authorize(httplib2.Http())
+
+
+def AuthorizeAccountUserAccount():
+ """Used to create an user account connection with the performance dashboard.
+
+ returns:
+ An object that can be used to communicate with the dashboard.
+ """
+ flow = client.OAuth2WebServerFlow(
+ OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, [SCOPES], approval_prompt='force')
+ flow.redirect_uri = client.OOB_CALLBACK_URN
+ print('Go to the followinhg link in your browser:\n'
+ ' %s\n' % flow.step1_get_authorize_url())
+ code = raw_input('Enter verification code: ').strip()
+ try:
+ creds = flow.step2_exchange(code)
+ return creds.authorize(httplib2.Http())
+ except client.FlowExchangeError:
+ print 'User authentication has failed.'
+ raise
+
+
+
+def MakeApiRequest(credentials, request, retry=True):
+ """Used to communicate with perf dashboard.
+
+ args:
+ credentials: Set of credentials generated by
+ request: String that contains POST request to dashboard.
+ returns:
+ Contents of the response from the dashboard.
+ """
+ print 'Making API request: %s' % request
+ resp, content = credentials.request(
+ REQUEST_URL + request,
+ method="POST",
+ headers={'Content-length': 0})
+ if resp['status'] != '200':
+ print ('Error detected while making api request. Returned: %s'
+ % (resp['status']))
+ if retry:
+ print 'Retrying command after 3 seconds...'
+ time.sleep(3)
+ return MakeApiRequest(credentials, request, retry=False)
+ return (resp, content)
+
+
+def _ProcessTimeseriesData(ts):
+ """Does noise processing of timeseries data.
+ args:
+ ts: Timeseries from dashboard.
+ returns:
+ Dict of noise metrics.
+ """
+ ts_values = [t[1] for t in ts]
+ mean = numpy.mean(ts_values)
+ std = numpy.std(ts_values)
+ return {
+ 'count': len(ts_values),
+ 'sum': sum(ts_values),
+ 'mean': mean,
+ 'variance': numpy.var(ts_values),
+ 'stdev': std,
+ 'cv': std / mean * 100 if mean else None,
+ }
+
+
+def GetBugData(creds, bug, cache):
+ """Returns data for given bug."""
+ try:
+ if not bug:
+ return {'bug': {'state': None, 'status': None, 'summary': None}}
+ if int(bug) == -1:
+ return {'bug': {'state': None, 'status': None, 'summary': 'Invalid'}}
+ if int(bug) == -2:
+ return {'bug': {'state': None, 'status': None, 'summary': 'Ignored'}}
+ r = 'bugs/%s' % bug
+ _, output = MakeApiRequest(creds, r)
+
+ if cache.get(bug):
+ print 'Returning cached data for bug %s' % bug
+ return cache[bug]
+ data = json.loads(output)
+ # Only care about date of comments, not connent.
+ data['bug']['comments'] = [a['published'] for a in data['bug']['comments']]
+ cache[bug] = data
+ return data
+ except Exception: # pylint: disable=broad-except
+ print 'Problem when collecting bug data for bug %s: %s' % (bug, output)
+ raise
+
+
+def GetAlertData(credentials, benchmark, days):
+ """Returns alerts for given benchmark."""
+ r = 'alerts/history/%s/?benchmark=%s' %(str(days), benchmark)
+ _, output = MakeApiRequest(credentials, r)
+ try:
+ data = json.loads(output)['anomalies']
+ return data
+ except:
+ print 'Problem getting alerts for benchmark %s: %s' % (benchmark, output)
+ raise
+
+
+def GetNoiseData(credentials, metric, days):
+ """Returns noise data for given metric."""
+ r = 'timeseries/%s?num_days=%s' % (metric, str(days))
+ if not metric:
+ return None
+ _, output = MakeApiRequest(credentials, r)
+ try:
+ data = json.loads(output)
+ if not data:
+ print 'No data found for metric %s in the last %s days.' % (metric, days)
+ return None
+ ts = data['timeseries'][1:] # First entry is book keeping.
+ return _ProcessTimeseriesData(ts)
+ except Exception:
+ print 'Problem getting timeseries for %s: %s' % (metric, output)
+ raise
+
+
+def Main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-b', '--benchmark', required=True,
+ help='Benchmark to pull data for.')
+ parser.add_argument('-d', '--days', required=False, default=30,
+ help='Number of days to collect data for. Default 30')
+ parser.add_argument('--credentials',
+ help=('Path to json credentials file. See %s for '
+ 'information about generating this.' % HELP_SITE))
+ parser.add_argument('--output-path', default='alert_analyzer.json',
+ help='Path to save file to. Default: alert_analyzer.json')
+ args = parser.parse_args()
+
+ credentials = AuthorizeAccount(args)
+ data = []
+
+ alerts = GetAlertData(credentials, args.benchmark, args.days)
+ bug_cache = {}
+ print '%s alerts found! Collecting data related to them...' % len(alerts)
+ for alert in alerts:
+ entry = {'alert': alert}
+ bug_id = alert.get('bug_id')
+ metric = '%s/%s/%s/%s' % (alert['master'], alert['bot'], alert['testsuite'],
+ alert['test'])
+
+ entry['noise'] = {
+ 'reg': GetNoiseData(credentials, metric, args.days),
+ 'ref': GetNoiseData(credentials, alert['ref_test'], args.days)
+ }
+ entry['bug'] = GetBugData(credentials, bug_id, bug_cache)['bug']
+
+ data.append(entry)
+
+ # Save at end.
+ with open(args.output_path, 'w') as fp:
+ print 'Saving data to %s.' % args.output_path
+ json.dump(data, fp, sort_keys=True, indent=2)
+
+
+if __name__ == '__main__':
+ Main()
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698