stab-crashes/generate-data.py

390 строки
15 KiB
Python

# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import sys
import argparse
import json
import six
import functools
from datetime import (datetime, timedelta)
import os
import shutil
import hashlib
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
import libmozdata.socorro as socorro
import libmozdata.utils as utils
from libmozdata.redash import Redash
from libmozdata.connection import (Connection, Query)
from libmozdata.bugzilla import Bugzilla
import libmozdata.versions
import gfx_critical_errors
v = libmozdata.versions.get(base=True)
# http://bugs.python.org/issue7980
datetime.strptime('', '')
def __trend_handler(default_trend, json, data):
for facets in json['facets']['histogram_date']:
d = utils.as_utc(datetime.strptime(facets['term'], '%Y-%m-%dT00:00:00+00:00'))
s = facets['facets']['signature']
for signature in s:
count = signature['count']
sgn = signature['term']
if sgn in data:
data[sgn][d] = count
else:
trend = default_trend.copy()
trend[d] = count
data[sgn] = trend
def __bug_handler(json, data):
for bug in json['bugs']:
data.append(bug)
def get(channel, date, product='Firefox', duration=11, tc_limit=50, crash_type='all', startup=False):
"""Get crashes info
Args:
channel (str): the channel
date (str): the final date
product (Optional[str]): the product
duration (Optional[int]): the duration to retrieve the data
tc_limit (Optional[int]): the number of topcrashes to load
crash_type (Optional[str]): 'all' (default) or 'browser' or 'content' or 'plugin'
Returns:
dict: contains all the info relative to the crashes
"""
channel = channel.lower()
version = v[channel]
sys.stdout.write('Getting version information from Socorro...')
sys.stdout.flush()
versions_info = socorro.ProductVersions.get_version_info(version, channel=channel, product=product)
versions = versions_info.keys()
platforms = socorro.Platforms.get_cached_all()
sys.stdout.write('\n')
sys.stdout.flush()
if crash_type and isinstance(crash_type, six.string_types):
crash_type = [crash_type]
throttle = set(map(lambda p: p[1], versions_info.values()))
if len(throttle) == 1:
throttle = throttle.pop()
else:
return
_date = utils.get_date_ymd(date)
start_date = utils.get_date_str(_date - timedelta(duration - 1))
end_date = utils.get_date_str(_date)
# First, we get the ADI
sys.stdout.write('Getting ADI from Socorro...')
sys.stdout.flush()
adi = socorro.ADI.get(version=versions, product=product, end_date=end_date, duration=duration, platforms=platforms)
adi = [adi[key] for key in sorted(adi.keys(), reverse=True)]
sys.stdout.write('\n')
sys.stdout.flush()
# get the khours
sys.stdout.write('Getting khours from Re:dash...')
sys.stdout.flush()
khours = Redash.get_khours(utils.get_date_ymd(start_date), utils.get_date_ymd(end_date), channel, versions, product)
khours = [khours[key] for key in sorted(khours.keys(), reverse=True)]
sys.stdout.write('\n')
sys.stdout.flush()
overall_crashes_by_day = []
signatures = {}
def signature_handler(json):
for signature in json['facets']['signature']:
signatures[signature['term']] = [signature['count'], 0, 0, 0, 0, 0]
for platform in signature['facets']['platform']:
if platform['term'] == 'Linux':
signatures[signature['term']][3] = platform['count']
elif platform['term'] == 'Windows NT':
signatures[signature['term']][1] = platform['count']
elif platform['term'] == 'Mac OS X':
signatures[signature['term']][2] = platform['count']
# XXX: Remove this when all versions will have the StartupCrash annotation.
if version >= 51:
for startup_crash in signature['facets']['startup_crash']:
if startup_crash['term'] in ['1', 'T']:
signatures[signature['term']][4] += startup_crash['count']
else:
for uptime in signature['facets']['histogram_uptime']:
if uptime['term'] == 0:
signatures[signature['term']][4] = uptime['count']
break
signatures[signature['term']][5] = signature['facets']['cardinality_install_time']['value']
for facets in json['facets']['histogram_date']:
overall_crashes_by_day.insert(0, facets['count'])
params = {
'product': product,
'version': versions,
'date': socorro.SuperSearch.get_search_date(start_date, end_date),
'release_channel': channel,
'_aggs.signature': ['platform', '_histogram.uptime', '_cardinality.install_time', 'startup_crash'],
'_results_number': 0,
'_facets_size': tc_limit,
'_histogram.date': ['product'],
'_histogram_interval': 1,
'_histogram_interval.uptime': 60,
}
if startup:
# XXX: Remove this when all versions will have the StartupCrash annotation.
if version >= 51:
params['startup_crash'] = True
else:
params['uptime'] = '<=60'
sys.stdout.write('Getting top signatures from Socorro...')
sys.stdout.flush()
socorro.SuperSearch(params=params, handler=signature_handler).wait()
sys.stdout.write('\n')
sys.stdout.flush()
bug_flags = ['resolution', 'id', 'last_change_time', 'cf_tracking_firefox' + str(version)]
for i in range(int(version), int(v['nightly']) + 1):
bug_flags.append('cf_status_firefox' + str(i))
# TODO: too many requests... should be improved with chunks
bugs = {}
# TODO: Use regexp, when the Bugzilla bug that prevents them from working will be fixed.
base = {
'j_top': 'OR',
'o1': 'substring',
'f1': 'cf_crash_signature',
'v1': None,
'o2': 'substring',
'f2': 'cf_crash_signature',
'v2': None,
'o3': 'substring',
'f3': 'cf_crash_signature',
'v3': None,
'o4': 'substring',
'f4': 'cf_crash_signature',
'v4': None,
'include_fields': bug_flags
}
queries = []
for sgn in signatures.keys():
cparams = base.copy()
cparams['v1'] = '[@' + sgn + ']'
cparams['v2'] = '[@ ' + sgn + ' ]'
cparams['v3'] = '[@ ' + sgn + ']'
cparams['v4'] = '[@' + sgn + ' ]'
bugs[sgn] = []
queries.append(Query(Bugzilla.API_URL, cparams, __bug_handler, bugs[sgn]))
res_bugs = Bugzilla(queries=queries)
# we have stats by signature in self.signatures
# for each signature get the number of crashes on the last X days
# so get the signature trend
trends = {}
default_trend = {}
for i in range(duration):
default_trend[_date - timedelta(i)] = 0
base = {'product': product,
'version': versions,
'signature': None,
'date': socorro.SuperSearch.get_search_date(start_date, end_date),
'release_channel': channel,
'_results_number': 0,
'_histogram.date': ['signature'],
'_histogram_interval': 1}
queries = []
for sgns in Connection.chunks(list(map(lambda sgn: '=' + sgn, signatures.keys())), 10):
sgn_group = []
for sgn in sgns:
if sum(len(s) for s in sgn_group) >= 1000:
cparams = base.copy()
cparams['signature'] = sgn_group
queries.append(Query(socorro.SuperSearch.URL, cparams, functools.partial(__trend_handler, default_trend), trends))
sgn_group = []
sgn_group.append(sgn)
if len(sgn_group) > 0:
cparams = base.copy()
cparams['signature'] = sgn_group
queries.append(Query(socorro.SuperSearch.URL, cparams, functools.partial(__trend_handler, default_trend), trends))
sys.stdout.write('Getting trends for top signatures from Socorro...')
sys.stdout.flush()
socorro.SuperSearch(queries=queries).wait()
sys.stdout.write('\n')
sys.stdout.flush()
for sgn, trend in trends.items():
signatures[sgn] = (signatures[sgn], [trend[key] for key in sorted(trend.keys(), reverse=True)])
_signatures = {}
# order self.signatures by crash count
l = sorted(signatures.items(), key=lambda x: x[1][0][0], reverse=True)
i = 1
for s in l:
_signatures[s[0]] = i # top crash rank
i += 1
sys.stdout.write('Getting bugs linked to the top signatures from Bugzilla...')
sys.stdout.flush()
res_bugs.wait()
sys.stdout.write('\n')
sys.stdout.flush()
# TODO: In the first query to get the bugs, also get dupe_of and avoid the first query
# in follow_dup (so modify follow_dup to accept both a bug ID or a bug object).
queries = []
for sgn in signatures.keys():
duplicate_ids = [bug['id'] for bug in bugs[sgn] if bug['resolution'] == 'DUPLICATE']
# Remove bugs resolved as DUPLICATE from the list of bugs associated to the signature.
bugs[sgn] = [bug for bug in bugs[sgn] if bug['id'] not in duplicate_ids]
# Find duplicates for bugs resolved as DUPLICATE.
duplicates = {k: v for k, v in Bugzilla.follow_dup(duplicate_ids).items() if v is not None}
duplicate_targets = [bug_id for bug_id in duplicates.values() if int(bug_id) not in [bug['id'] for bug in bugs[sgn]]]
if len(duplicate_targets) == 0:
continue
# Get info about bugs that the DUPLICATE bugs have been duped to.
params = {
'id': ','.join(duplicate_targets),
'include_fields': bug_flags,
}
queries.append(Query(Bugzilla.API_URL, params, __bug_handler, bugs[sgn]))
sys.stdout.write('Resolving duplicate bugs to the bugs they\'ve been duplicated to...')
sys.stdout.flush()
Bugzilla(queries=queries).wait()
sys.stdout.write('\n')
sys.stdout.flush()
for sgn, stats in signatures.items():
# stats is 2-uple: ([count, win_count, mac_count, linux_count, startup_count], trend)
startup_percent = float(stats[0][4]) / float(stats[0][0])
_signatures[sgn] = {'tc_rank': _signatures[sgn],
'crash_count': stats[0][0],
'estimated_user_count': stats[0][5],
'startup_percent': startup_percent,
'crash_by_day': stats[1],
'bugs': bugs[sgn]}
return {
'start_date': start_date,
'end_date': end_date,
'versions': list(versions),
'adi': adi,
'khours': khours,
'crash_by_day': overall_crashes_by_day,
'signatures': _signatures,
'throttle': float(throttle)
}
def get_with_retries(url, params=None, headers=None):
retries = Retry(total=16, backoff_factor=1, status_forcelist=[429])
s = requests.Session()
s.mount('https://analysis-output.telemetry.mozilla.org', HTTPAdapter(max_retries=retries))
return s.get(url, params=params, headers=headers)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Track')
parser.add_argument('-c', '--channels', action='store', nargs='+', default=['release', 'beta', 'aurora', 'nightly', 'esr'], help='the channels')
parser.add_argument('-d', '--date', action='store', default='yesterday', help='the end date')
parser.add_argument('-D', '--duration', action='store', default=11, help='the duration')
parser.add_argument('-t', '--tclimit', action='store', default=200, help='number of top crashes to retrieve')
args = parser.parse_args()
try:
shutil.rmtree('dist')
except:
pass
os.mkdir('dist')
os.mkdir('dist/images')
for channel in args.channels:
for startup in [False, True]:
print('Getting top-' + str(args.tclimit) + (' startup ' if startup else ' ') + 'crashes for the \'' + channel + '\' channel')
stats = get(channel, args.date, duration=int(args.duration), tc_limit=int(args.tclimit), startup=startup)
print('\n\n')
with open('dist/' + channel + ('-startup' if startup else '') + '.json', 'w') as f:
json.dump(stats, f, allow_nan=False)
with open('dist/graphics_critical_errors.json', 'w') as f:
json.dump(gfx_critical_errors.analyze_gfx_critical_errors(), f)
files = [
'index.html',
'correlations.js',
'dashboard.html', 'dashboard.js', 'style.css',
'exclamation_mark.svg', 'question_mark.svg', 'rocket_fly.png', 'spin.svg',
'correlations.html', 'correlations_page.js',
'missing_uplifts.html', 'missing_uplifts.js', 'all_missing_uplifts.html', 'all_missing_uplifts.js',
'compare-betas.html', 'compare-betas.js', 'scomp.html', 'scomp.css', 'scomp.js',
'beta-stability-pushlog.html', 'beta-stability-pushlog.js',
'graphics_critical_errors.html', 'graphics_critical_errors.js',
'buildid_changeset.html', 'buildid_changeset.js',
'addon_related_signatures.html', 'addon_related_signatures.js',
'supergraph.html', 'supergraph.js',
'rerank.html', 'rerank.js',
'common_landings.html', 'common_landings.js',
] + ['images/' + image for image in os.listdir('images')]
for f in files:
shutil.copyfile(f, 'dist/' + f)
for product in ['firefox']:
base_url = 'https://analysis-output.telemetry.mozilla.org/top-signatures-correlations/data/' if product == 'firefox' else 'https://analysis-output.telemetry.mozilla.org/top-fennec-signatures-correlations/data/'
# Check that correlations were generated.
r = get_with_retries(base_url + 'all.json.gz')
if r.status_code != 200:
print(product + ' correlations weren\'t generated.')
print(r.text)
raise Exception(r)
generation_date = datetime.strptime(r.headers['last-modified'], '%a, %d %b %Y %H:%M:%S %Z').date()
if datetime.utcnow().date() - timedelta(1) > generation_date:
print(product + ' correlations weren\'t generated yesterday, they were last generated on ' + str(generation_date) + '.')
raise Exception(r)
for channel in ['release', 'beta', 'aurora', 'nightly']:
# Check that the OOM | small correlations contain "moz_crash_reason" as a sanity check (mainly for https://issues.apache.org/jira/browse/SPARK-16664).
r = get_with_retries(base_url + channel + '/' + hashlib.sha1('OOM | small'.encode('utf-8')).hexdigest() + '.json.gz')
if r.status_code != 200:
print('Failure downloading ' + product + ' ' + channel + 'correlations for "OOM | small".')
print(r.text)
raise Exception(r)
if not any(any(key in result['item'].keys() for key in ['CPU Info', 'reason', 'moz_crash_reason', 'platform_version']) for result in r.json()['results']):
print(product + ' ' + channel + ' correlations failing "OOM | small" sanity check.')
print(r.json())
raise Exception(r)