8 from xml.dom.minidom import parse as parseXML
9 from jinja2 import Environment, PackageLoader
15 __version__ = pkg_resources.get_distribution('hiprofile').version
17 __version__ = 'unknown'
20 """ Utility function: return the number in a 'count' element contained in
22 countnode = node.getElementsByTagName('count')
23 return int(countnode.item(0).firstChild.data)
25 def _threshold_list(list, fn, threshold):
26 """ Utility function: given a list and an ordering function, return the
27 elements of the list that are above the threshold.
29 If threshold is a percentage (ie, ends with '%'), then the items
30 that fn returns above that percentage of the total. If it's an absolute
31 number, then that number of items is returned"""
33 if threshold.endswith('%'):
34 percentage = float(threshold[0:-1]) / 100
35 total = reduce(int.__add__, map(fn, list))
36 list = [ x for x in list if fn(x) > (total * percentage) ]
37 list.sort(key = fn, reverse = True)
40 count = int(threshold)
41 list.sort(key = fn, reverse = True)
46 class Connection(object):
47 def __init__(self, host):
50 def execute(self, command, input = None):
53 command = ['ssh', self.host] + command
56 stdin = subprocess.PIPE
60 null_fd = open('/dev/null', 'w')
61 proc = subprocess.Popen(command, stdin = stdin,
62 stdout = subprocess.PIPE, stderr = null_fd)
65 proc.stdin.write(input)
69 class SymbolInstruction(object):
70 def __init__(self, addr, asm, source = None):
79 def set_samples(self, samples, total):
80 self.samples = samples
81 self.percentage = 100 * float(samples) / total
84 if not self.percentage:
87 if self.percentage * 40 > 0xc0:
90 return '#ff%02x00' % (0xc0 - self.percentage * 40)
93 # 13283 0.7260 :1031b6e0: stwu r1,-48(r1)
94 sampled_asm_re = re.compile( \
95 '^\s*(?P<samples>\d+)\s+\S+\s+:\s*(?P<addr>[0-9a-f]+):\s*(?P<asm>.*)\s*$')
97 # :1031b72c: addi r0,r9,4
98 unsampled_asm_re = re.compile( \
99 '^\s*:\s*(?P<addr>[0-9a-f]+):\s*(?P<asm>.*)\s*$')
101 # :AllocSetAlloc(MemoryContext context, Size size)
102 source_re = re.compile( \
103 '^\s*:(?P<source>.*)$')
105 class SymbolReference(object):
107 def __init__(self, id, name, module):
112 self.annotations = None
114 # annotation parsing buf
118 def module_name(self):
119 return self.module.split('/')[-1]
122 return 'symbol-%s.html' % self.id
124 def annotate(self, line):
127 sampled_match = sampled_asm_re.match(line)
129 match = sampled_match
131 unsampled_match = unsampled_asm_re.match(line)
133 match = unsampled_match
136 insn = SymbolInstruction(match.group('addr'), match.group('asm'),
139 insn.set_samples(int(match.group('samples')), self.count)
140 self.insns.append(insn)
144 match = source_re.match(line)
146 self.source_buf += match.group('source') + '\n'
149 def parse(report, node, module):
150 id = int(node.getAttribute('idref'))
151 ref = SymbolReference(id, report.symtab[id], module)
153 ref.count = _get_count(node)
156 class Binary(object):
158 def __init__(self, name, count):
163 self.id = b_id = b_id + 1
166 s = '%s: %d' % (self.name, self.count)
167 for ref in self.references:
172 return self.name.split('/')[-1]
175 return 'binary-%d.html' % self.id
177 def threshold(self, thresholds):
178 self.references = _threshold_list(self.references,
179 lambda r: r.count, thresholds['symbol'])
180 self.reference_dict = dict([ (r.name, r) for r in self.references ])
182 def annotate(self, report, conn, options):
183 fn_re = re.compile('^[0-9a-f]+\s+<[^>]+>: /\* (\S+) total:')
185 symbols = [ s for s in self.references if s.name != '(no symbols)' ]
190 command = [options.opannotate, '--source', '--assembly',
191 '--include-file=' + self.name,
192 '-i', ','.join([ s.name for s in symbols ])]
194 fd = conn.execute(command)
198 for line in fd.readlines():
199 match = fn_re.match(line)
202 symbol.annotate(line)
203 symname = match.group(1)
204 if symname in self.reference_dict:
205 symbol = self.reference_dict[symname]
209 symbol.annotate(line)
211 def parse_symbol(self, report, node, module = None):
215 ref = SymbolReference.parse(report, node, module)
216 ref.percentage = 100 * float(ref.count) / self.count
217 self.references.append(ref)
221 def parse(report, node):
222 name = node.getAttribute('name')
224 binary = Binary(name, _get_count(node))
226 for child_node in node.childNodes:
227 if child_node.nodeType != node.ELEMENT_NODE:
230 if child_node.nodeName == 'symbol':
231 binary.parse_symbol(report, child_node, None)
233 elif child_node.nodeName == 'module':
234 module_name = child_node.getAttribute('name')
235 for child_sym_node in child_node.getElementsByTagName('symbol'):
236 binary.parse_symbol(report, child_sym_node, module_name)
240 class Report(object):
241 def __init__(self, host, arch, cpu):
247 self.total_samples = 0
249 def add_binary(self, binary):
250 self.binaries.append(binary)
251 self.total_samples += binary.count
253 def threshold(self, thresholds):
254 self.binaries = _threshold_list(self.binaries,
255 lambda b: b.count, thresholds['binary'])
257 for binary in self.binaries:
258 binary.threshold(thresholds)
260 def annotate(self, conn, options):
261 for binary in self.binaries:
262 binary.annotate(self, conn, options)
265 def parse(doc, hostname):
266 node = doc.documentElement
268 cpu = '%s (%s MHz)' % (\
269 node.getAttribute('processor'),
270 node.getAttribute('mhz'))
272 report = Report(hostname, node.getAttribute('cputype'), cpu)
275 symtab_node = doc.getElementsByTagName('symboltable').item(0)
277 for node in symtab_node.childNodes:
278 if node.nodeType != node.ELEMENT_NODE:
280 report.symtab.insert(int(node.getAttribute('id')),
281 node.getAttribute('name'))
284 # parse each binary node
285 for node in doc.getElementsByTagName('binary'):
286 binary = Binary.parse(report, node)
287 report.add_binary(binary)
289 # calculate percentages
290 for binary in report.binaries:
291 binary.percentage = 100 * float(binary.count) / report.total_samples
296 def extract(connection, options):
297 fd = connection.execute([options.opreport, '--xml'])
301 hostname = connection.host
303 hostname = socket.gethostname()
305 return Report.parse(doc, hostname)
308 return self.machine + '\n' + '\n'.join(map(str, self.binaries))
310 def write_report(report, outdir):
314 # set up template engine
315 env = Environment(loader = PackageLoader(__name__, 'resources'),
318 for name in ['report', 'binary', 'symbol']:
319 templates[name] = env.get_template('%s.html' % name)
321 # copy required files over from resources
322 files = ['style.css', 'hiprofile.js', 'bar.png', 'jquery-1.3.1.min.js']
324 f = open(os.path.join(outdir, file), 'w')
325 f.write(pkg_resources.resource_string(__name__, 'resources/' + file))
328 reportfile = os.path.join(outdir, 'index.html')
329 templates['report'].stream(version = __version__,
330 report = report).dump(reportfile)
332 for binary in report.binaries:
333 binaryfile = os.path.join(outdir, binary.filename())
334 templates['binary'].stream(version = __version__,
339 for symbol in binary.references:
340 symbolfile = os.path.join(outdir, symbol.filename())
341 templates['symbol'].stream(version = __version__,
342 report = report, binary = binary,
343 symbol = symbol).dump(symbolfile)