ceph/teuthology/coverage.py

212 lines
6.7 KiB
Python
Raw Normal View History

from contextlib import closing
import logging
import os
import shutil
import subprocess
import MySQLdb
import yaml
import teuthology
from teuthology.misc import read_config
log = logging.getLogger(__name__)
2012-10-24 23:10:56 +00:00
"""
The coverage database can be created in mysql with:
CREATE TABLE `coverage` (
`run_id` bigint(20) NOT NULL AUTO_INCREMENT,
`rev` char(40) NOT NULL,
`test` varchar(255) NOT NULL,
`suite` varchar(255) NOT NULL,
`lines` int(10) unsigned NOT NULL,
`line_cov` float unsigned NOT NULL,
`functions` int(10) unsigned NOT NULL,
`function_cov` float unsigned NOT NULL,
`branches` int(10) unsigned NOT NULL,
`branch_cov` float unsigned NOT NULL,
`run_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`run_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
"""
def connect_to_db(ctx):
db = MySQLdb.connect(
host=ctx.teuthology_config['coverage_db_host'],
user=ctx.teuthology_config['coverage_db_user'],
db=ctx.teuthology_config['coverage_db_name'],
passwd=ctx.teuthology_config['coverage_db_password'],
)
db.autocommit(False)
return db
def store_coverage(ctx, test_coverage, rev, suite):
with closing(connect_to_db(ctx)) as db:
rows = []
for test, coverage in test_coverage.iteritems():
flattened_cov = [item for sublist in coverage for item in sublist]
rows.append([rev, test, suite] + flattened_cov)
log.debug('inserting rows into db: %s', str(rows))
try:
cursor = db.cursor()
cursor.executemany(
'INSERT INTO `coverage`'
' (`rev`, `test`, `suite`, `lines`, `line_cov`, `functions`,'
' `function_cov`, `branches`, `branch_cov`)'
' VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)',
rows)
except Exception:
log.exception('error updating database')
db.rollback()
raise
else:
db.commit()
log.info('added coverage to database')
finally:
cursor.close()
def read_coverage(output):
log.debug('reading coverage from output: %s', output)
coverage = [None, None, None]
prefixes = [' lines......: ', ' functions..: ', ' branches...: ']
for line in reversed(output.splitlines()):
for i, prefix in enumerate(prefixes):
if line.startswith(prefix):
if '%' in line:
cov_num = int(line[line.find('(') + 1:line.find(' of')])
cov_percent = float(line[len(prefix):line.find('%')])
coverage[i] = (cov_num, cov_percent)
else:
# may have no data for e.g. branches on the initial run
coverage[i] = (None, None)
break
if None not in coverage:
break
return coverage
def main(args):
if args.verbose:
teuthology.log.setLevel(logging.DEBUG)
log = logging.getLogger(__name__)
read_config(args)
handler = logging.FileHandler(
filename=os.path.join(args.test_dir, 'coverage.log'),
)
formatter = logging.Formatter(
fmt='%(asctime)s.%(msecs)03d %(levelname)s:%(message)s',
datefmt='%Y-%m-%dT%H:%M:%S',
)
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
try:
analyze(args)
except Exception:
log.exception('error generating coverage')
raise
def analyze(args):
tests = [
f for f in sorted(os.listdir(args.test_dir))
if not f.startswith('.')
and os.path.isdir(os.path.join(args.test_dir, f))
and os.path.exists(os.path.join(args.test_dir, f, 'summary.yaml'))
and os.path.exists(os.path.join(args.test_dir, f, 'ceph-sha1'))]
test_summaries = {}
for test in tests:
summary = {}
with file(os.path.join(args.test_dir, test, 'summary.yaml')) as f:
g = yaml.safe_load_all(f)
for new in g:
summary.update(new)
if summary['flavor'] != 'gcov':
log.debug('Skipping %s, since it does not include coverage', test)
continue
test_summaries[test] = summary
assert len(test_summaries) > 0
suite = os.path.basename(args.test_dir)
# only run cov-init once.
# this only works if all tests were run against the same version.
if not args.skip_init:
log.info('initializing coverage data...')
subprocess.check_call(
args=[
os.path.join(args.cov_tools_dir, 'cov-init.sh'),
os.path.join(args.test_dir, tests[0]),
args.lcov_output,
os.path.join(
args.teuthology_config['ceph_build_output_dir'],
'{suite}.tgz'.format(suite=suite),
),
])
shutil.copy(
os.path.join(args.lcov_output, 'base.lcov'),
os.path.join(args.lcov_output, 'total.lcov')
)
test_coverage = {}
for test, summary in test_summaries.iteritems():
lcov_file = '{name}.lcov'.format(name=test)
log.info('analyzing coverage for %s', test)
proc = subprocess.Popen(
args=[
os.path.join(args.cov_tools_dir, 'cov-analyze.sh'),
'-t', os.path.join(args.test_dir, test),
'-d', args.lcov_output,
'-o', test,
],
stdout=subprocess.PIPE,
)
output, _ = proc.communicate()
desc = summary.get('description', test)
test_coverage[desc] = read_coverage(output)
log.info('adding %s data to total', test)
proc = subprocess.Popen(
args=[
'lcov',
'-a', os.path.join(args.lcov_output, lcov_file),
'-a', os.path.join(args.lcov_output, 'total.lcov'),
'-o', os.path.join(args.lcov_output, 'total_tmp.lcov'),
],
stdout=subprocess.PIPE,
)
output, _ = proc.communicate()
os.rename(
os.path.join(args.lcov_output, 'total_tmp.lcov'),
os.path.join(args.lcov_output, 'total.lcov')
)
coverage = read_coverage(output)
test_coverage['total for {suite}'.format(suite=suite)] = coverage
log.debug('total coverage is %s', str(coverage))
if args.html_output:
subprocess.check_call(
args=[
'genhtml',
'-s',
'-o', os.path.join(args.html_output, 'total'),
'-t', 'Total for {suite}'.format(suite=suite),
'--',
os.path.join(args.lcov_output, 'total.lcov'),
])
store_coverage(args, test_coverage, summary['ceph-sha1'], suite)