1*8fb009dcSAndroid Build Coastguard Worker#!/usr/bin/python 2*8fb009dcSAndroid Build Coastguard Worker# Copyright (c) 2016, Google Inc. 3*8fb009dcSAndroid Build Coastguard Worker# 4*8fb009dcSAndroid Build Coastguard Worker# Permission to use, copy, modify, and/or distribute this software for any 5*8fb009dcSAndroid Build Coastguard Worker# purpose with or without fee is hereby granted, provided that the above 6*8fb009dcSAndroid Build Coastguard Worker# copyright notice and this permission notice appear in all copies. 7*8fb009dcSAndroid Build Coastguard Worker# 8*8fb009dcSAndroid Build Coastguard Worker# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9*8fb009dcSAndroid Build Coastguard Worker# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10*8fb009dcSAndroid Build Coastguard Worker# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 11*8fb009dcSAndroid Build Coastguard Worker# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12*8fb009dcSAndroid Build Coastguard Worker# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 13*8fb009dcSAndroid Build Coastguard Worker# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 14*8fb009dcSAndroid Build Coastguard Worker# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15*8fb009dcSAndroid Build Coastguard Workerimport os 16*8fb009dcSAndroid Build Coastguard Workerimport os.path 17*8fb009dcSAndroid Build Coastguard Workerimport subprocess 18*8fb009dcSAndroid Build Coastguard Workerimport sys 19*8fb009dcSAndroid Build Coastguard Worker 20*8fb009dcSAndroid Build Coastguard Worker# The LCOV output format for each source file is: 21*8fb009dcSAndroid Build Coastguard Worker# 22*8fb009dcSAndroid Build Coastguard Worker# SF:<filename> 23*8fb009dcSAndroid Build Coastguard Worker# DA:<line>,<execution count> 24*8fb009dcSAndroid Build Coastguard Worker# ... 25*8fb009dcSAndroid Build Coastguard Worker# end_of_record 26*8fb009dcSAndroid Build Coastguard Worker# 27*8fb009dcSAndroid Build Coastguard Worker# The <execution count> can either be 0 for an unexecuted instruction or a 28*8fb009dcSAndroid Build Coastguard Worker# value representing the number of executions. The DA line should be omitted 29*8fb009dcSAndroid Build Coastguard Worker# for lines not representing an instruction. 30*8fb009dcSAndroid Build Coastguard Worker 31*8fb009dcSAndroid Build Coastguard WorkerSECTION_SEPERATOR = '-' * 80 32*8fb009dcSAndroid Build Coastguard Worker 33*8fb009dcSAndroid Build Coastguard Workerdef is_asm(l): 34*8fb009dcSAndroid Build Coastguard Worker """Returns whether a line should be considered to be an instruction.""" 35*8fb009dcSAndroid Build Coastguard Worker l = l.strip() 36*8fb009dcSAndroid Build Coastguard Worker # Empty lines 37*8fb009dcSAndroid Build Coastguard Worker if l == '': 38*8fb009dcSAndroid Build Coastguard Worker return False 39*8fb009dcSAndroid Build Coastguard Worker # Comments 40*8fb009dcSAndroid Build Coastguard Worker if l.startswith('#'): 41*8fb009dcSAndroid Build Coastguard Worker return False 42*8fb009dcSAndroid Build Coastguard Worker # Assembly Macros 43*8fb009dcSAndroid Build Coastguard Worker if l.startswith('.'): 44*8fb009dcSAndroid Build Coastguard Worker return False 45*8fb009dcSAndroid Build Coastguard Worker # Label 46*8fb009dcSAndroid Build Coastguard Worker if l.endswith(':'): 47*8fb009dcSAndroid Build Coastguard Worker return False 48*8fb009dcSAndroid Build Coastguard Worker return True 49*8fb009dcSAndroid Build Coastguard Worker 50*8fb009dcSAndroid Build Coastguard Workerdef merge(callgrind_files, srcs): 51*8fb009dcSAndroid Build Coastguard Worker """Calls callgrind_annotate over the set of callgrind output 52*8fb009dcSAndroid Build Coastguard Worker |callgrind_files| using the sources |srcs| and merges the results 53*8fb009dcSAndroid Build Coastguard Worker together.""" 54*8fb009dcSAndroid Build Coastguard Worker out = '' 55*8fb009dcSAndroid Build Coastguard Worker for file in callgrind_files: 56*8fb009dcSAndroid Build Coastguard Worker data = subprocess.check_output(['callgrind_annotate', file] + srcs) 57*8fb009dcSAndroid Build Coastguard Worker out += '%s\n%s\n' % (data, SECTION_SEPERATOR) 58*8fb009dcSAndroid Build Coastguard Worker return out 59*8fb009dcSAndroid Build Coastguard Worker 60*8fb009dcSAndroid Build Coastguard Workerdef parse(filename, data, current): 61*8fb009dcSAndroid Build Coastguard Worker """Parses an annotated execution flow |data| from callgrind_annotate for 62*8fb009dcSAndroid Build Coastguard Worker source |filename| and updates the current execution counts from |current|.""" 63*8fb009dcSAndroid Build Coastguard Worker with open(filename) as f: 64*8fb009dcSAndroid Build Coastguard Worker source = f.read().split('\n') 65*8fb009dcSAndroid Build Coastguard Worker 66*8fb009dcSAndroid Build Coastguard Worker out = current 67*8fb009dcSAndroid Build Coastguard Worker if out == None: 68*8fb009dcSAndroid Build Coastguard Worker out = [0 if is_asm(l) else None for l in source] 69*8fb009dcSAndroid Build Coastguard Worker 70*8fb009dcSAndroid Build Coastguard Worker # Lines are of the following formats: 71*8fb009dcSAndroid Build Coastguard Worker # -- line: Indicates that analysis continues from a different place. 72*8fb009dcSAndroid Build Coastguard Worker # Ir : Indicates the start of a file. 73*8fb009dcSAndroid Build Coastguard Worker # => : Indicates a call/jump in the control flow. 74*8fb009dcSAndroid Build Coastguard Worker # <Count> <Code>: Indicates that the line has been executed that many times. 75*8fb009dcSAndroid Build Coastguard Worker line = None 76*8fb009dcSAndroid Build Coastguard Worker for l in data: 77*8fb009dcSAndroid Build Coastguard Worker l = l.strip() + ' ' 78*8fb009dcSAndroid Build Coastguard Worker if l.startswith('-- line'): 79*8fb009dcSAndroid Build Coastguard Worker line = int(l.split(' ')[2]) - 1 80*8fb009dcSAndroid Build Coastguard Worker elif l.strip() == 'Ir': 81*8fb009dcSAndroid Build Coastguard Worker line = 0 82*8fb009dcSAndroid Build Coastguard Worker elif line != None and l.strip() and '=>' not in l and 'unidentified lines' not in l: 83*8fb009dcSAndroid Build Coastguard Worker count = l.split(' ')[0].replace(',', '').replace('.', '0') 84*8fb009dcSAndroid Build Coastguard Worker instruction = l.split(' ', 1)[1].strip() 85*8fb009dcSAndroid Build Coastguard Worker if count != '0' or is_asm(instruction): 86*8fb009dcSAndroid Build Coastguard Worker if out[line] == None: 87*8fb009dcSAndroid Build Coastguard Worker out[line] = 0 88*8fb009dcSAndroid Build Coastguard Worker out[line] += int(count) 89*8fb009dcSAndroid Build Coastguard Worker line += 1 90*8fb009dcSAndroid Build Coastguard Worker 91*8fb009dcSAndroid Build Coastguard Worker return out 92*8fb009dcSAndroid Build Coastguard Worker 93*8fb009dcSAndroid Build Coastguard Worker 94*8fb009dcSAndroid Build Coastguard Workerdef generate(data): 95*8fb009dcSAndroid Build Coastguard Worker """Parses the merged callgrind_annotate output |data| and generates execution 96*8fb009dcSAndroid Build Coastguard Worker counts for all annotated files.""" 97*8fb009dcSAndroid Build Coastguard Worker out = {} 98*8fb009dcSAndroid Build Coastguard Worker data = [p.strip() for p in data.split(SECTION_SEPERATOR)] 99*8fb009dcSAndroid Build Coastguard Worker 100*8fb009dcSAndroid Build Coastguard Worker 101*8fb009dcSAndroid Build Coastguard Worker # Most sections are ignored, but a section with: 102*8fb009dcSAndroid Build Coastguard Worker # User-annotated source: <file> 103*8fb009dcSAndroid Build Coastguard Worker # precedes a listing of execution count for that <file>. 104*8fb009dcSAndroid Build Coastguard Worker for i in range(len(data)): 105*8fb009dcSAndroid Build Coastguard Worker if 'User-annotated source' in data[i] and i < len(data) - 1: 106*8fb009dcSAndroid Build Coastguard Worker filename = data[i].split(':', 1)[1].strip() 107*8fb009dcSAndroid Build Coastguard Worker res = data[i + 1] 108*8fb009dcSAndroid Build Coastguard Worker if filename not in out: 109*8fb009dcSAndroid Build Coastguard Worker out[filename] = None 110*8fb009dcSAndroid Build Coastguard Worker if 'No information' in res: 111*8fb009dcSAndroid Build Coastguard Worker res = [] 112*8fb009dcSAndroid Build Coastguard Worker else: 113*8fb009dcSAndroid Build Coastguard Worker res = res.split('\n') 114*8fb009dcSAndroid Build Coastguard Worker out[filename] = parse(filename, res, out[filename]) 115*8fb009dcSAndroid Build Coastguard Worker return out 116*8fb009dcSAndroid Build Coastguard Worker 117*8fb009dcSAndroid Build Coastguard Workerdef output(data): 118*8fb009dcSAndroid Build Coastguard Worker """Takes a dictionary |data| of filenames and execution counts and generates 119*8fb009dcSAndroid Build Coastguard Worker a LCOV coverage output.""" 120*8fb009dcSAndroid Build Coastguard Worker out = '' 121*8fb009dcSAndroid Build Coastguard Worker for filename, counts in data.iteritems(): 122*8fb009dcSAndroid Build Coastguard Worker out += 'SF:%s\n' % (os.path.abspath(filename)) 123*8fb009dcSAndroid Build Coastguard Worker for line, count in enumerate(counts): 124*8fb009dcSAndroid Build Coastguard Worker if count != None: 125*8fb009dcSAndroid Build Coastguard Worker out += 'DA:%d,%s\n' % (line + 1, count) 126*8fb009dcSAndroid Build Coastguard Worker out += 'end_of_record\n' 127*8fb009dcSAndroid Build Coastguard Worker return out 128*8fb009dcSAndroid Build Coastguard Worker 129*8fb009dcSAndroid Build Coastguard Workerif __name__ == '__main__': 130*8fb009dcSAndroid Build Coastguard Worker if len(sys.argv) != 3: 131*8fb009dcSAndroid Build Coastguard Worker print '%s <Callgrind Folder> <Build Folder>' % (__file__) 132*8fb009dcSAndroid Build Coastguard Worker sys.exit() 133*8fb009dcSAndroid Build Coastguard Worker 134*8fb009dcSAndroid Build Coastguard Worker cg_folder = sys.argv[1] 135*8fb009dcSAndroid Build Coastguard Worker build_folder = sys.argv[2] 136*8fb009dcSAndroid Build Coastguard Worker 137*8fb009dcSAndroid Build Coastguard Worker cg_files = [] 138*8fb009dcSAndroid Build Coastguard Worker for (cwd, _, files) in os.walk(cg_folder): 139*8fb009dcSAndroid Build Coastguard Worker for f in files: 140*8fb009dcSAndroid Build Coastguard Worker if f.startswith('callgrind.out'): 141*8fb009dcSAndroid Build Coastguard Worker cg_files.append(os.path.abspath(os.path.join(cwd, f))) 142*8fb009dcSAndroid Build Coastguard Worker 143*8fb009dcSAndroid Build Coastguard Worker srcs = [] 144*8fb009dcSAndroid Build Coastguard Worker for (cwd, _, files) in os.walk(build_folder): 145*8fb009dcSAndroid Build Coastguard Worker for f in files: 146*8fb009dcSAndroid Build Coastguard Worker fn = os.path.join(cwd, f) 147*8fb009dcSAndroid Build Coastguard Worker if fn.endswith('.S'): 148*8fb009dcSAndroid Build Coastguard Worker srcs.append(fn) 149*8fb009dcSAndroid Build Coastguard Worker 150*8fb009dcSAndroid Build Coastguard Worker annotated = merge(cg_files, srcs) 151*8fb009dcSAndroid Build Coastguard Worker lcov = generate(annotated) 152*8fb009dcSAndroid Build Coastguard Worker print output(lcov) 153