blob: 64d27feb81d41306a2581dedf529203e64e32019 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Report any risky CLs using CL-Scanner."""
from __future__ import print_function
import requests
from chromite.lib import timeout_util
from chromite.lib import cros_logging as logging
TIMEOUT = 3 * 60
CL_SCANNER_API = (
'https://chromiumos-cl-scanner.appspot.com/external/risk/build/%d')
# Include all CLs with at least 10% risk.
_MINIMUM_RISK_THRESHOLD = 0.10
# Include CLs at least 80% as risky as the top risky CL
_CLOSE_TO_TOP_RATIO = 0.80
_EXTERNAL_CL_LINK = 'https://crrev.com/c/%s'
def GetCLRiskReport(build_id):
"""Returns a string reporting the riskiest CLs.
Args:
build_id: The master build id in cidb.
Returns:
A dictionary mapping "CL=risk%" strings to the CLs' URLs.
"""
try:
risks = _GetCLRisks(build_id)
except requests.exceptions.HTTPError:
logging.exception('Encountered an error reaching CL-Scanner.')
return {'(encountered exception reaching CL-Scanner)': ''}
except timeout_util.TimeoutError:
logging.exception('Timed out reaching CL-Scanner.')
return {'(timeout reaching CL-Scanner)': ''}
logging.info('CL-Scanner risks: %s', _PrettyPrintCLRisks(risks))
if not risks:
return {}
# TODO(phobbs) this will need to be changed to handle internal CLs.
top_risky = _TopRisky(risks)
return {_PrettyPrintCLRisk(cl, risk): _EXTERNAL_CL_LINK % cl
for cl, risk in top_risky.iteritems()}
@timeout_util.TimeoutDecorator(TIMEOUT)
def _GetCLRisks(build_id):
"""Gets the CL risks given a cidb build_id.
Args:
build_id: The master build id in cidb.
Returns:
A mapping of CL numbers to risks
"""
response = requests.get(CL_SCANNER_API % build_id)
response.raise_for_status()
return response.json().get('cls')
def _TopRisky(risks):
"""Returns the top risky commits.
Ars:
risks: A dictionary mapping CL numbers to bad CL probability.
"""
top_key = max(risks, key=risks.get)
above_threshold = {
cl: risk for cl, risk in risks.iteritems()
if risk > _MINIMUM_RISK_THRESHOLD}
close_to_top = {
cl: risk for cl, risk in risks.iteritems()
if risk >= risks[top_key] * _CLOSE_TO_TOP_RATIO
}
above_threshold.update(close_to_top)
return above_threshold
def _PrettyPrintCLRisks(risks):
"""Pretty print a risk map as a string.
Args:
risks: The risks to print.
"""
return ', '.join([
'%s = %s' % (k, v)
for k, v in risks.iteritems()])
def _PrettyPrintCLRisk(cl, risk):
"""Pretty print a cl, risk pair as a string.
Args:
cl: The Gerrit CL number.
risk: The risk as a percent.
"""
return 'Bad CL risk for %s = %s' % (cl, _Percent(risk))
def _Percent(risk):
"""Converts a float to a percent.
Args:
risk: A probability.
Returns:
A string percent.
"""
return '{:0.1f}%'.format(risk * 100)