1# Copyright (c) 2005 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Nathan Binkert
28
29from orderdict import orderdict
29import output
30
31class FileData(dict):
32 def __init__(self, filename):
33 self.filename = filename
34 fd = file(filename)
35 current = []
36 for line in fd:
37 line = line.strip()
38 if line.startswith('>>>'):
39 current = []
40 self[line[3:]] = current
41 else:
42 current.append(line)
43 fd.close()
44
45class RunData(dict):
46 def __init__(self, filename):
47 self.filename = filename
48
49 def __getattribute__(self, attr):
50 if attr == 'total':
51 total = 0.0
52 for value in self.itervalues():
53 total += value
54 return total
55
56 if attr == 'filedata':
57 return FileData(self.filename)
58
59 if attr == 'maxsymlen':
60 return max([ len(sym) for sym in self.iterkeys() ])
61
62 return super(RunData, self).__getattribute__(attr)
63
64 def display(self, output=None, limit=None, maxsymlen=None):
65 if not output:
66 import sys
67 output = sys.stdout
68 elif isinstance(output, str):
69 output = file(output, 'w')
70
71 total = float(self.total)
72
73 # swap (string,count) order so we can sort on count
74 symbols = [ (count,name) for name,count in self.iteritems() ]
75 symbols.sort(reverse=True)
76 if limit is not None:
77 symbols = symbols[:limit]
78
79 if not maxsymlen:
80 maxsymlen = self.maxsymlen
81
82 symbolf = "%-" + str(maxsymlen + 1) + "s %.2f%%"
83 for number,name in symbols:
84 print >>output, symbolf % (name, 100.0 * (float(number) / total))
85
86class PCData(RunData):
87 def __init__(self, filename=None, categorize=None, showidle=True):
88 super(PCData, self).__init__(self, filename)
89
90 filedata = self.filedata['PC data']
91 for line in filedata:
92 (symbol, count) = line.split()
93 if symbol == "0x0":
94 continue
95 count = int(count)
96
97 if categorize is not None:
98 category = categorize(symbol)
99 if category is None:
100 category = 'other'
101 elif category == 'idle' and not showidle:
102 continue
103
104 self[category] = count
105
106class FuncNode(object):
107 def __new__(cls, filedata=None):
108 if filedata is None:
109 return super(FuncNode, cls).__new__(cls)
110
111 nodes = {}
112 for line in filedata['function data']:
113 data = line.split(' ')
114 node_id = long(data[0], 16)
115 node = FuncNode()
116 node.symbol = data[1]
117 if node.symbol == '':
118 node.symbol = 'unknown'
119 node.count = long(data[2])
120 node.children = [ long(child, 16) for child in data[3:] ]
121 nodes[node_id] = node
122
123 for node in nodes.itervalues():
124 children = []
125 for cid in node.children:
126 child = nodes[cid]
127 children.append(child)
128 child.parent = node
129 node.children = tuple(children)
130 if not nodes:
131 print filedata.filename
132 print nodes
133 return nodes[0]
134
135 def total(self):
136 total = self.count
137 for child in self.children:
138 total += child.total()
139
140 return total
141
142 def aggregate(self, dict, categorize, incategory):
143 category = None
144 if categorize:
145 category = categorize(self.symbol)
146
147 total = self.count
148 for child in self.children:
149 total += child.aggregate(dict, categorize, category or incategory)
150
151 if category:
152 dict[category] = dict.get(category, 0) + total
153 return 0
154 elif not incategory:
155 dict[self.symbol] = dict.get(self.symbol, 0) + total
156
157 return total
158
159 def dump(self):
160 kids = [ child.symbol for child in self.children]
161 print '%s %d <%s>' % (self.symbol, self.count, ', '.join(kids))
162 for child in self.children:
163 child.dump()
164
165 def _dot(self, dot, threshold, categorize, total):
166 from pydot import Dot, Edge, Node
167 self.dot_node = None
168
169 value = self.total() * 100.0 / total
170 if value < threshold:
171 return
172 if categorize:
173 category = categorize(self.symbol)
174 if category and category != 'other':
175 return
176 label = '%s %.2f%%' % (self.symbol, value)
177 self.dot_node = Node(self, label=label)
178 dot.add_node(self.dot_node)
179
180 for child in self.children:
181 child._dot(dot, threshold, categorize, total)
182 if child.dot_node is not None:
183 dot.add_edge(Edge(self, child))
184
185 def _cleandot(self):
186 for child in self.children:
187 child._cleandot()
188 self.dot_node = None
189 del self.__dict__['dot_node']
190
191 def dot(self, dot, threshold=0.1, categorize=None):
192 self._dot(dot, threshold, categorize, self.total())
193 self._cleandot()
194
195class FuncData(RunData):
196 def __init__(self, filename, categorize=None):
197 super(FuncData, self).__init__(filename)
198 tree = self.tree
199 tree.aggregate(self, categorize, incategory=False)
200 self.total = tree.total()
201
202 def __getattribute__(self, attr):
203 if attr == 'tree':
204 return FuncNode(self.filedata)
205 return super(FuncData, self).__getattribute__(attr)
206
207 def displayx(self, output=None, maxcount=None):
208 if output is None:
209 import sys
210 output = sys.stdout
211
212 items = [ (val,key) for key,val in self.iteritems() ]
213 items.sort(reverse=True)
214 for val,key in items:
215 if maxcount is not None:
216 if maxcount == 0:
217 return
218 maxcount -= 1
219
220 percent = val * 100.0 / self.total
221 print >>output, '%-30s %8s' % (key, '%3.2f%%' % percent)
222
223class Profile(object):
224 # This list controls the order of values in stacked bar data output
225 default_categories = [ 'interrupt',
226 'driver',
227 'stack',
228 'buffer',
229 'copy',
230 'syscall',
231 'user',
232 'other',
233 'idle']
234
235 def __init__(self, datatype, categorize=None):
236 categories = Profile.default_categories
237
238 self.datatype = datatype
239 self.categorize = categorize
240 self.data = {}
241 self.categories = categories[:]
242 self.rcategories = categories[:]
243 self.rcategories.reverse()
244 self.cpu = 0
245
246 # Read in files
247 def inputdir(self, directory):
248 import os, os.path, re
249 from os.path import expanduser, join as joinpath
250
251 directory = expanduser(directory)
252 label_ex = re.compile(r'profile\.(.*).dat')
253 for root,dirs,files in os.walk(directory):
254 for name in files:
255 match = label_ex.match(name)
256 if not match:
257 continue
258
259 filename = joinpath(root, name)
260 prefix = os.path.commonprefix([root, directory])
261 dirname = root[len(prefix)+1:]
262 data = self.datatype(filename, self.categorize)
263 self.setdata(dirname, match.group(1), data)
264
265 def setdata(self, run, cpu, data):
266 if run not in self.data:
267 self.data[run] = {}
268
269 if cpu in self.data[run]:
270 raise AttributeError, \
271 'data already stored for run %s and cpu %s' % (run, cpu)
272
273 self.data[run][cpu] = data
274
275 def getdata(self, run, cpu):
276 try:
277 return self.data[run][cpu]
278 except KeyError:
279 print run, cpu
280 return None
281
282 def alldata(self):
283 for run,cpus in self.data.iteritems():
284 for cpu,data in cpus.iteritems():
285 yield run,cpu,data
286
287 def get(self, job, stat, system=None):
288 if system is None and hasattr('system', job):
289 system = job.system
290
291 if system is None:
292 raise AttributeError, 'The job must have a system set'
293
294 cpu = '%s.run%d' % (system, self.cpu)
295
296 data = self.getdata(str(job), cpu)
297 if not data:
298 return None
299
300 values = []
301 for category in self.categories:
302 val = float(data.get(category, 0.0))
303 if val < 0.0:
304 raise ValueError, 'value is %f' % val
305 values.append(val)
306 total = sum(values)
307 return [ v / total * 100.0 for v in values ]
308
309 def dump(self):
310 for run,cpu,data in self.alldata():
311 print 'run %s, cpu %s' % (run, cpu)
312 data.dump()
313 print
314
315 def write_dot(self, threshold, jobfile=None, jobs=None):
316 import pydot
317
318 if jobs is None:
319 jobs = [ job for job in jobfile.jobs() ]
320
321 for job in jobs:
322 cpu = '%s.run%d' % (job.system, self.cpu)
323 symbols = self.getdata(job.name, cpu)
324 if not symbols:
325 continue
326
327 dot = pydot.Dot()
328 symbols.tree.dot(dot, threshold=threshold)
329 dot.write(symbols.filename[:-3] + 'dot')
330
331 def write_txt(self, jobfile=None, jobs=None, limit=None):
332 if jobs is None:
333 jobs = [ job for job in jobfile.jobs() ]
334
335 for job in jobs:
336 cpu = '%s.run%d' % (job.system, self.cpu)
337 symbols = self.getdata(job.name, cpu)
338 if not symbols:
339 continue
340
341 output = file(symbols.filename[:-3] + 'txt', 'w')
342 symbols.display(output, limit)
343
344 def display(self, jobfile=None, jobs=None, limit=None):
345 if jobs is None:
346 jobs = [ job for job in jobfile.jobs() ]
347
348 maxsymlen = 0
349
350 thejobs = []
351 for job in jobs:
352 cpu = '%s.run%d' % (job.system, self.cpu)
353 symbols = self.getdata(job.name, cpu)
354 if symbols:
355 thejobs.append(job)
356 maxsymlen = max(maxsymlen, symbols.maxsymlen)
357
358 for job in thejobs:
359 cpu = '%s.run%d' % (job.system, self.cpu)
360 symbols = self.getdata(job.name, cpu)
361 print job.name
362 symbols.display(limit=limit, maxsymlen=maxsymlen)
363 print
364
365
366from categories import func_categorize, pc_categorize
367class PCProfile(Profile):
368 def __init__(self, categorize=pc_categorize):
369 super(PCProfile, self).__init__(PCData, categorize)
370
371
372class FuncProfile(Profile):
373 def __init__(self, categorize=func_categorize):
374 super(FuncProfile, self).__init__(FuncData, categorize)
375
376def usage(exitcode = None):
377 print '''\
378Usage: %s [-bc] [-g <dir>] [-j <jobfile>] [-n <num>]
379
380 -c groups symbols into categories
381 -b dumps data for bar charts
382 -d generate dot output
383 -g <d> draw graphs and send output to <d>
384 -j <jobfile> specify a different jobfile (default is Test.py)
385 -n <n> selects number of top symbols to print (default 5)
386''' % sys.argv[0]
387
388 if exitcode is not None:
389 sys.exit(exitcode)
390
391if __name__ == '__main__':
392 import getopt, re, sys
393 from os.path import expanduser
394 from output import StatOutput
395
396 # default option values
397 numsyms = 10
398 graph = None
399 cpus = [ 0 ]
400 categorize = False
401 showidle = True
402 funcdata = True
403 jobfilename = 'Test.py'
404 dodot = False
405 dotfile = None
406 textout = False
407 threshold = 0.01
408 inputfile = None
409
410 try:
411 opts, args = getopt.getopt(sys.argv[1:], 'C:cdD:f:g:ij:n:pT:t')
412 except getopt.GetoptError:
413 usage(2)
414
415 for o,a in opts:
416 if o == '-C':
417 cpus = [ int(x) for x in a.split(',') ]
418 elif o == '-c':
419 categorize = True
420 elif o == '-D':
421 dotfile = a
422 elif o == '-d':
423 dodot = True
424 elif o == '-f':
425 inputfile = expanduser(a)
426 elif o == '-g':
427 graph = a
428 elif o == '-i':
429 showidle = False
430 elif o == '-j':
431 jobfilename = a
432 elif o == '-n':
433 numsyms = int(a)
434 elif o == '-p':
435 funcdata = False
436 elif o == '-T':
437 threshold = float(a)
438 elif o == '-t':
439 textout = True
440
441 if args:
442 print "'%s'" % args, len(args)
443 usage(1)
444
445 if inputfile:
446 catfunc = None
447 if categorize:
448 catfunc = func_categorize
449 data = FuncData(inputfile, categorize=catfunc)
450
451 if dodot:
452 import pydot
453 dot = pydot.Dot()
454 data.tree.dot(dot, threshold=threshold)
455 #dot.orientation = 'landscape'
456 #dot.ranksep='equally'
457 #dot.rank='samerank'
458 dot.write(dotfile, format='png')
459 else:
460 data.display(limit=numsyms)
461
462 else:
463 from jobfile import JobFile
464 jobfile = JobFile(jobfilename)
465
466 if funcdata:
467 profile = FuncProfile()
468 else:
469 profile = PCProfile()
470
471 if not categorize:
472 profile.categorize = None
473 profile.inputdir(jobfile.rootdir)
474
475 if graph:
476 for cpu in cpus:
477 profile.cpu = cpu
478 if funcdata:
479 name = 'funcstacks%d' % cpu
480 else:
481 name = 'stacks%d' % cpu
482 output = StatOutput(jobfile, info=profile)
483 output.xlabel = 'System Configuration'
484 output.ylabel = '% CPU utilization'
485 output.stat = name
486 output.graph(name, graph)
487
488 if dodot:
489 for cpu in cpus:
490 profile.cpu = cpu
491 profile.write_dot(jobfile=jobfile, threshold=threshold)
492
493 if textout:
494 for cpu in cpus:
495 profile.cpu = cpu
496 profile.write_txt(jobfile=jobfile)
497
498 if not graph and not textout and not dodot:
499 for cpu in cpus:
500 if not categorize:
501 profile.categorize = None
502 profile.cpu = cpu
503 profile.display(jobfile=jobfile, limit=numsyms)