mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[coverage] Move scripts to coverage directory
This commit is contained in:
204
utils/coverage/coverage-build-db
Executable file
204
utils/coverage/coverage-build-db
Executable file
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python
|
||||
# utils/coverage-build-db - Build sqlite3 database from profdata
|
||||
#
|
||||
# This source file is part of the Swift.org open source project
|
||||
#
|
||||
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
||||
# Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
#
|
||||
# See http://swift.org/LICENSE.txt for license information
|
||||
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
|
||||
import Queue
|
||||
import argparse
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
logging_format = '%(asctime)s %(levelname)s %(message)s'
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format=logging_format,
|
||||
filename='/tmp/%s.log' % os.path.basename(__file__),
|
||||
filemode='w')
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter(logging_format)
|
||||
console.setFormatter(formatter)
|
||||
logging.getLogger().addHandler(console)
|
||||
|
||||
|
||||
def parse_coverage_log(filepath):
|
||||
"""Return parsed coverage intervals from `llvm-profdata show` output at
|
||||
`filepath`"""
|
||||
logging.info('Parsing: %s', filepath)
|
||||
test = os.path.basename(os.path.dirname(filepath)).rsplit('.', 1)[0]
|
||||
with open(filepath) as f:
|
||||
# Initial parse
|
||||
parsed = []
|
||||
for section in [section.split("\n")
|
||||
for section in f.read().split("\n\n")]:
|
||||
if ".cpp:" in section[0] or ".h:" in section[0]:
|
||||
parsed_section = [section[0].split(":", 1) + [test]]
|
||||
for line in section[1:]:
|
||||
linedata = linedata_re.match(line).group(1, 2)
|
||||
parsed_section.append(linedata)
|
||||
parsed.append(parsed_section)
|
||||
# Build intervals
|
||||
parsed_interval = []
|
||||
for section in parsed:
|
||||
new_section = [section[0]]
|
||||
i = 1
|
||||
if len(section) > 2:
|
||||
while i < len(section)-1:
|
||||
while i < len(section)-1 and section[i][0] in ['0', None]:
|
||||
i += 1
|
||||
if i == len(section)-1:
|
||||
if section[i][0] not in ['0', None]:
|
||||
istart = int(section[i][1])
|
||||
iend = istart
|
||||
new_section.append((istart, iend))
|
||||
break
|
||||
istart = int(section[i][1])
|
||||
iend = istart # single line interval starting
|
||||
while i < len(section)-1 and \
|
||||
int(section[i+1][1]) == iend + 1 and \
|
||||
section[i+1][0] not in ['0', None]:
|
||||
iend += 1
|
||||
i += 1
|
||||
new_section.append((istart, iend))
|
||||
i += 1
|
||||
else:
|
||||
if section[i][0] not in ['0', None]:
|
||||
istart = int(section[i][1])
|
||||
iend = istart
|
||||
new_section.append((istart, iend))
|
||||
parsed_interval.append(new_section)
|
||||
return parsed_interval
|
||||
|
||||
|
||||
def worker(args):
|
||||
"""Parse coverage log at path `args[0]` and place in queue `args[1]`"""
|
||||
args[1].put(parse_coverage_log(args[0]))
|
||||
|
||||
manager = multiprocessing.Manager()
|
||||
result_queue = manager.Queue()
|
||||
worker_queue = []
|
||||
|
||||
linedata_re = re.compile(r"^\s*([^\s]+?)?\|\s*(\d+)\|")
|
||||
strings = dict()
|
||||
|
||||
pool = multiprocessing.Pool(3)
|
||||
|
||||
|
||||
def insert_coverage_section_into_db(db_cursor, section):
|
||||
"""Insert parsed intervals in `section` into database at `db_cursor`"""
|
||||
filename = section[0][0]
|
||||
function = section[0][1]
|
||||
test = section[0][2]
|
||||
logging.debug('Inserting section into database for file: %s', filename)
|
||||
if filename not in strings:
|
||||
db_cursor.execute("INSERT INTO strings (string) VALUES (?)",
|
||||
(filename,))
|
||||
strings[filename] = db_cursor.lastrowid
|
||||
if test not in strings:
|
||||
db_cursor.execute("INSERT INTO strings (string) VALUES (?)", (test,))
|
||||
strings[test] = db_cursor.lastrowid
|
||||
if function not in strings:
|
||||
db_cursor.execute("INSERT INTO strings (string) VALUES (?)",
|
||||
(function,))
|
||||
strings[function] = db_cursor.lastrowid
|
||||
for istart, iend in section[1:]:
|
||||
logging.debug('%s, %s, %s, %s, %s', function, filename, test, istart,
|
||||
iend)
|
||||
db_cursor.execute("INSERT INTO coverage VALUES (?,?,?,?,?)",
|
||||
(strings[function], strings[filename],
|
||||
strings[test], istart, iend))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Build sqlite3 database from profdata')
|
||||
parser.add_argument('root_directory',
|
||||
metavar='root-directory',
|
||||
help='a root directory to recursively search for '
|
||||
'coverage logs')
|
||||
parser.add_argument('--coverage-filename',
|
||||
help='a coverage log file name to search for '
|
||||
'(default: coverage.log.demangled)',
|
||||
metavar='NAME',
|
||||
default='coverage.log.demangled')
|
||||
parser.add_argument('--db-filepath',
|
||||
help='the filepath of the coverage db to build '
|
||||
'(default: coverage.db)',
|
||||
metavar='PATH',
|
||||
default='coverage.db')
|
||||
parser.add_argument('--log',
|
||||
help='the level of information to log (default: info)',
|
||||
metavar='LEVEL',
|
||||
default='info',
|
||||
choices=['info', 'debug', 'warning', 'error',
|
||||
'critical'])
|
||||
args = parser.parse_args()
|
||||
|
||||
console.setLevel(level=args.log.upper())
|
||||
logging.debug(args)
|
||||
|
||||
if not os.path.exists(args.root_directory):
|
||||
logging.critical('Directory not found: %s', args.root_directory)
|
||||
return 1
|
||||
|
||||
if os.path.exists(args.db_filepath):
|
||||
logging.info('Deleting existing database: %s', args.db_filepath)
|
||||
os.unlink(args.db_filepath)
|
||||
|
||||
logging.info('Creating database: %s', args.db_filepath)
|
||||
conn = sqlite3.connect(args.db_filepath)
|
||||
|
||||
try:
|
||||
logging.debug('Creating coverage table')
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE strings
|
||||
(id integer primary key autoincrement, string text)''')
|
||||
c.execute('''CREATE TABLE coverage
|
||||
(function references strings(id),
|
||||
src references strings(id),
|
||||
test references strings(id),
|
||||
istart integer, iend integer)''')
|
||||
conn.commit()
|
||||
|
||||
for root, folders, files in os.walk(args.root_directory):
|
||||
for f in files:
|
||||
if f == args.coverage_filename:
|
||||
filepath = os.path.join(root, f)
|
||||
logging.debug('Adding file to queue: %s', filepath)
|
||||
worker_queue.append((filepath, result_queue))
|
||||
|
||||
logging.info('Finished adding %s paths to queue', len(worker_queue))
|
||||
pool.map_async(worker, worker_queue)
|
||||
|
||||
while True:
|
||||
parsed = result_queue.get(timeout=60)
|
||||
logging.info('Inserting coverage data into db for %s function(s)',
|
||||
len(parsed))
|
||||
for section in parsed:
|
||||
insert_coverage_section_into_db(c, section)
|
||||
conn.commit()
|
||||
result_queue.task_done()
|
||||
except Queue.Empty:
|
||||
logging.info('Queue exhausted. Shutting down...')
|
||||
except KeyboardInterrupt:
|
||||
logging.debug('Caught KeyboardInterrupt. Shutting down...')
|
||||
finally:
|
||||
logging.debug('Closing database')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logging.debug('Terminating pool')
|
||||
pool.terminate()
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user