winlin

add gprof files and dot files

  1 +ccache是samba组织提供的加速编译过程的工具,
  2 +使用虚拟机编译可以考虑用这个工具,让编译过程飞快。
  3 +
  4 +链接:
  5 + http://ccache.samba.org/
  6 + http://samba.org/ftp/ccache/ccache-3.1.9.tar.xz
  7 + http://ccache.samba.org/manual.html
  8 +
  9 +安装方法:
  10 + bash build_ccache.sh
  11 +注意:要求以sudoer执行,要修改文件。
  1 +#!/bin/bash
  2 +
  3 +# check exists.
  4 +if [[ -d graphviz-2.18 ]]; then
  5 + echo "graphviz is ok";
  6 + exit 0;
  7 +fi
  8 +
  9 +# check sudoer.
  10 +sudo echo "ok" > /dev/null 2>&1;
  11 +ret=$?; if [[ 0 -ne ${ret} ]]; then echo "you must be sudoer"; exit 1; fi
  12 +
  13 +tar xf graphviz-2.18.tar.gz
  14 +cd graphviz-2.18 && ./configure && make && sudo make install
  15 +ret=$?; if [[ $ret -ne 0 ]]; then echo "build gprof2dot failed."; exit $ret; fi
  16 +
  17 +echo "we test in Centos6.0, it's ok"
  1 +#!/usr/bin/env python
  2 +#
  3 +# Copyright 2008-2009 Jose Fonseca
  4 +#
  5 +# This program is free software: you can redistribute it and/or modify it
  6 +# under the terms of the GNU Lesser General Public License as published
  7 +# by the Free Software Foundation, either version 3 of the License, or
  8 +# (at your option) any later version.
  9 +#
  10 +# This program is distributed in the hope that it will be useful,
  11 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13 +# GNU Lesser General Public License for more details.
  14 +#
  15 +# You should have received a copy of the GNU Lesser General Public License
  16 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  17 +#
  18 +
  19 +"""Generate a dot graph from the output of several profilers."""
  20 +
  21 +__author__ = "Jose Fonseca"
  22 +
  23 +__version__ = "1.0"
  24 +
  25 +
  26 +import sys
  27 +import math
  28 +import os.path
  29 +import re
  30 +import textwrap
  31 +import optparse
  32 +import xml.parsers.expat
  33 +
  34 +
  35 +try:
  36 + # Debugging helper module
  37 + import debug
  38 +except ImportError:
  39 + pass
  40 +
  41 +
  42 +def percentage(p):
  43 + return "%.02f%%" % (p*100.0,)
  44 +
  45 +def add(a, b):
  46 + return a + b
  47 +
  48 +def equal(a, b):
  49 + if a == b:
  50 + return a
  51 + else:
  52 + return None
  53 +
  54 +def fail(a, b):
  55 + assert False
  56 +
  57 +
  58 +tol = 2 ** -23
  59 +
  60 +def ratio(numerator, denominator):
  61 + try:
  62 + ratio = float(numerator)/float(denominator)
  63 + except ZeroDivisionError:
  64 + # 0/0 is undefined, but 1.0 yields more useful results
  65 + return 1.0
  66 + if ratio < 0.0:
  67 + if ratio < -tol:
  68 + sys.stderr.write('warning: negative ratio (%s/%s)\n' % (numerator, denominator))
  69 + return 0.0
  70 + if ratio > 1.0:
  71 + if ratio > 1.0 + tol:
  72 + sys.stderr.write('warning: ratio greater than one (%s/%s)\n' % (numerator, denominator))
  73 + return 1.0
  74 + return ratio
  75 +
  76 +
  77 +class UndefinedEvent(Exception):
  78 + """Raised when attempting to get an event which is undefined."""
  79 +
  80 + def __init__(self, event):
  81 + Exception.__init__(self)
  82 + self.event = event
  83 +
  84 + def __str__(self):
  85 + return 'unspecified event %s' % self.event.name
  86 +
  87 +
  88 +class Event(object):
  89 + """Describe a kind of event, and its basic operations."""
  90 +
  91 + def __init__(self, name, null, aggregator, formatter = str):
  92 + self.name = name
  93 + self._null = null
  94 + self._aggregator = aggregator
  95 + self._formatter = formatter
  96 +
  97 + def __eq__(self, other):
  98 + return self is other
  99 +
  100 + def __hash__(self):
  101 + return id(self)
  102 +
  103 + def null(self):
  104 + return self._null
  105 +
  106 + def aggregate(self, val1, val2):
  107 + """Aggregate two event values."""
  108 + assert val1 is not None
  109 + assert val2 is not None
  110 + return self._aggregator(val1, val2)
  111 +
  112 + def format(self, val):
  113 + """Format an event value."""
  114 + assert val is not None
  115 + return self._formatter(val)
  116 +
  117 +
  118 +MODULE = Event("Module", None, equal)
  119 +PROCESS = Event("Process", None, equal)
  120 +
  121 +CALLS = Event("Calls", 0, add)
  122 +SAMPLES = Event("Samples", 0, add)
  123 +SAMPLES2 = Event("Samples", 0, add)
  124 +
  125 +TIME = Event("Time", 0.0, add, lambda x: '(' + str(x) + ')')
  126 +TIME_RATIO = Event("Time ratio", 0.0, add, lambda x: '(' + percentage(x) + ')')
  127 +TOTAL_TIME = Event("Total time", 0.0, fail)
  128 +TOTAL_TIME_RATIO = Event("Total time ratio", 0.0, fail, percentage)
  129 +
  130 +CALL_RATIO = Event("Call ratio", 0.0, add, percentage)
  131 +
  132 +PRUNE_RATIO = Event("Prune ratio", 0.0, add, percentage)
  133 +
  134 +
  135 +class Object(object):
  136 + """Base class for all objects in profile which can store events."""
  137 +
  138 + def __init__(self, events=None):
  139 + if events is None:
  140 + self.events = {}
  141 + else:
  142 + self.events = events
  143 +
  144 + def __hash__(self):
  145 + return id(self)
  146 +
  147 + def __eq__(self, other):
  148 + return self is other
  149 +
  150 + def __contains__(self, event):
  151 + return event in self.events
  152 +
  153 + def __getitem__(self, event):
  154 + try:
  155 + return self.events[event]
  156 + except KeyError:
  157 + raise UndefinedEvent(event)
  158 +
  159 + def __setitem__(self, event, value):
  160 + if value is None:
  161 + if event in self.events:
  162 + del self.events[event]
  163 + else:
  164 + self.events[event] = value
  165 +
  166 +
  167 +class Call(Object):
  168 + """A call between functions.
  169 +
  170 + There should be at most one call object for every pair of functions.
  171 + """
  172 +
  173 + def __init__(self, callee_id):
  174 + Object.__init__(self)
  175 + self.callee_id = callee_id
  176 +
  177 +
  178 +class Function(Object):
  179 + """A function."""
  180 +
  181 + def __init__(self, id, name):
  182 + Object.__init__(self)
  183 + self.id = id
  184 + self.name = name
  185 + self.calls = {}
  186 + self.cycle = None
  187 +
  188 + def add_call(self, call):
  189 + if call.callee_id in self.calls:
  190 + sys.stderr.write('warning: overwriting call from function %s to %s\n' % (str(self.id), str(call.callee_id)))
  191 + self.calls[call.callee_id] = call
  192 +
  193 + # TODO: write utility functions
  194 +
  195 + def __repr__(self):
  196 + return self.name
  197 +
  198 +
  199 +class Cycle(Object):
  200 + """A cycle made from recursive function calls."""
  201 +
  202 + def __init__(self):
  203 + Object.__init__(self)
  204 + # XXX: Do cycles need an id?
  205 + self.functions = set()
  206 +
  207 + def add_function(self, function):
  208 + assert function not in self.functions
  209 + self.functions.add(function)
  210 + # XXX: Aggregate events?
  211 + if function.cycle is not None:
  212 + for other in function.cycle.functions:
  213 + if function not in self.functions:
  214 + self.add_function(other)
  215 + function.cycle = self
  216 +
  217 +
  218 +class Profile(Object):
  219 + """The whole profile."""
  220 +
  221 + def __init__(self):
  222 + Object.__init__(self)
  223 + self.functions = {}
  224 + self.cycles = []
  225 +
  226 + def add_function(self, function):
  227 + if function.id in self.functions:
  228 + sys.stderr.write('warning: overwriting function %s (id %s)\n' % (function.name, str(function.id)))
  229 + self.functions[function.id] = function
  230 +
  231 + def add_cycle(self, cycle):
  232 + self.cycles.append(cycle)
  233 +
  234 + def validate(self):
  235 + """Validate the edges."""
  236 +
  237 + for function in self.functions.itervalues():
  238 + for callee_id in function.calls.keys():
  239 + assert function.calls[callee_id].callee_id == callee_id
  240 + if callee_id not in self.functions:
  241 + sys.stderr.write('warning: call to undefined function %s from function %s\n' % (str(callee_id), function.name))
  242 + del function.calls[callee_id]
  243 +
  244 + def find_cycles(self):
  245 + """Find cycles using Tarjan's strongly connected components algorithm."""
  246 +
  247 + # Apply the Tarjan's algorithm successively until all functions are visited
  248 + visited = set()
  249 + for function in self.functions.itervalues():
  250 + if function not in visited:
  251 + self._tarjan(function, 0, [], {}, {}, visited)
  252 + cycles = []
  253 + for function in self.functions.itervalues():
  254 + if function.cycle is not None and function.cycle not in cycles:
  255 + cycles.append(function.cycle)
  256 + self.cycles = cycles
  257 + if 0:
  258 + for cycle in cycles:
  259 + sys.stderr.write("Cycle:\n")
  260 + for member in cycle.functions:
  261 + sys.stderr.write("\tFunction %s\n" % member.name)
  262 +
  263 + def _tarjan(self, function, order, stack, orders, lowlinks, visited):
  264 + """Tarjan's strongly connected components algorithm.
  265 +
  266 + See also:
  267 + - http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
  268 + """
  269 +
  270 + visited.add(function)
  271 + orders[function] = order
  272 + lowlinks[function] = order
  273 + order += 1
  274 + pos = len(stack)
  275 + stack.append(function)
  276 + for call in function.calls.itervalues():
  277 + callee = self.functions[call.callee_id]
  278 + # TODO: use a set to optimize lookup
  279 + if callee not in orders:
  280 + order = self._tarjan(callee, order, stack, orders, lowlinks, visited)
  281 + lowlinks[function] = min(lowlinks[function], lowlinks[callee])
  282 + elif callee in stack:
  283 + lowlinks[function] = min(lowlinks[function], orders[callee])
  284 + if lowlinks[function] == orders[function]:
  285 + # Strongly connected component found
  286 + members = stack[pos:]
  287 + del stack[pos:]
  288 + if len(members) > 1:
  289 + cycle = Cycle()
  290 + for member in members:
  291 + cycle.add_function(member)
  292 + return order
  293 +
  294 + def call_ratios(self, event):
  295 + # Aggregate for incoming calls
  296 + cycle_totals = {}
  297 + for cycle in self.cycles:
  298 + cycle_totals[cycle] = 0.0
  299 + function_totals = {}
  300 + for function in self.functions.itervalues():
  301 + function_totals[function] = 0.0
  302 + for function in self.functions.itervalues():
  303 + for call in function.calls.itervalues():
  304 + if call.callee_id != function.id:
  305 + callee = self.functions[call.callee_id]
  306 + function_totals[callee] += call[event]
  307 + if callee.cycle is not None and callee.cycle is not function.cycle:
  308 + cycle_totals[callee.cycle] += call[event]
  309 +
  310 + # Compute the ratios
  311 + for function in self.functions.itervalues():
  312 + for call in function.calls.itervalues():
  313 + assert CALL_RATIO not in call
  314 + if call.callee_id != function.id:
  315 + callee = self.functions[call.callee_id]
  316 + if callee.cycle is not None and callee.cycle is not function.cycle:
  317 + total = cycle_totals[callee.cycle]
  318 + else:
  319 + total = function_totals[callee]
  320 + call[CALL_RATIO] = ratio(call[event], total)
  321 +
  322 + def integrate(self, outevent, inevent):
  323 + """Propagate function time ratio allong the function calls.
  324 +
  325 + Must be called after finding the cycles.
  326 +
  327 + See also:
  328 + - http://citeseer.ist.psu.edu/graham82gprof.html
  329 + """
  330 +
  331 + # Sanity checking
  332 + assert outevent not in self
  333 + for function in self.functions.itervalues():
  334 + assert outevent not in function
  335 + assert inevent in function
  336 + for call in function.calls.itervalues():
  337 + assert outevent not in call
  338 + if call.callee_id != function.id:
  339 + assert CALL_RATIO in call
  340 +
  341 + # Aggregate the input for each cycle
  342 + for cycle in self.cycles:
  343 + total = inevent.null()
  344 + for function in self.functions.itervalues():
  345 + total = inevent.aggregate(total, function[inevent])
  346 + self[inevent] = total
  347 +
  348 + # Integrate along the edges
  349 + total = inevent.null()
  350 + for function in self.functions.itervalues():
  351 + total = inevent.aggregate(total, function[inevent])
  352 + self._integrate_function(function, outevent, inevent)
  353 + self[outevent] = total
  354 +
  355 + def _integrate_function(self, function, outevent, inevent):
  356 + if function.cycle is not None:
  357 + return self._integrate_cycle(function.cycle, outevent, inevent)
  358 + else:
  359 + if outevent not in function:
  360 + total = function[inevent]
  361 + for call in function.calls.itervalues():
  362 + if call.callee_id != function.id:
  363 + total += self._integrate_call(call, outevent, inevent)
  364 + function[outevent] = total
  365 + return function[outevent]
  366 +
  367 + def _integrate_call(self, call, outevent, inevent):
  368 + assert outevent not in call
  369 + assert CALL_RATIO in call
  370 + callee = self.functions[call.callee_id]
  371 + subtotal = call[CALL_RATIO]*self._integrate_function(callee, outevent, inevent)
  372 + call[outevent] = subtotal
  373 + return subtotal
  374 +
  375 + def _integrate_cycle(self, cycle, outevent, inevent):
  376 + if outevent not in cycle:
  377 +
  378 + # Compute the outevent for the whole cycle
  379 + total = inevent.null()
  380 + for member in cycle.functions:
  381 + subtotal = member[inevent]
  382 + for call in member.calls.itervalues():
  383 + callee = self.functions[call.callee_id]
  384 + if callee.cycle is not cycle:
  385 + subtotal += self._integrate_call(call, outevent, inevent)
  386 + total += subtotal
  387 + cycle[outevent] = total
  388 +
  389 + # Compute the time propagated to callers of this cycle
  390 + callees = {}
  391 + for function in self.functions.itervalues():
  392 + if function.cycle is not cycle:
  393 + for call in function.calls.itervalues():
  394 + callee = self.functions[call.callee_id]
  395 + if callee.cycle is cycle:
  396 + try:
  397 + callees[callee] += call[CALL_RATIO]
  398 + except KeyError:
  399 + callees[callee] = call[CALL_RATIO]
  400 +
  401 + for member in cycle.functions:
  402 + member[outevent] = outevent.null()
  403 +
  404 + for callee, call_ratio in callees.iteritems():
  405 + ranks = {}
  406 + call_ratios = {}
  407 + partials = {}
  408 + self._rank_cycle_function(cycle, callee, 0, ranks)
  409 + self._call_ratios_cycle(cycle, callee, ranks, call_ratios, set())
  410 + partial = self._integrate_cycle_function(cycle, callee, call_ratio, partials, ranks, call_ratios, outevent, inevent)
  411 + assert partial == max(partials.values())
  412 + assert not total or abs(1.0 - partial/(call_ratio*total)) <= 0.001
  413 +
  414 + return cycle[outevent]
  415 +
  416 + def _rank_cycle_function(self, cycle, function, rank, ranks):
  417 + if function not in ranks or ranks[function] > rank:
  418 + ranks[function] = rank
  419 + for call in function.calls.itervalues():
  420 + if call.callee_id != function.id:
  421 + callee = self.functions[call.callee_id]
  422 + if callee.cycle is cycle:
  423 + self._rank_cycle_function(cycle, callee, rank + 1, ranks)
  424 +
  425 + def _call_ratios_cycle(self, cycle, function, ranks, call_ratios, visited):
  426 + if function not in visited:
  427 + visited.add(function)
  428 + for call in function.calls.itervalues():
  429 + if call.callee_id != function.id:
  430 + callee = self.functions[call.callee_id]
  431 + if callee.cycle is cycle:
  432 + if ranks[callee] > ranks[function]:
  433 + call_ratios[callee] = call_ratios.get(callee, 0.0) + call[CALL_RATIO]
  434 + self._call_ratios_cycle(cycle, callee, ranks, call_ratios, visited)
  435 +
  436 + def _integrate_cycle_function(self, cycle, function, partial_ratio, partials, ranks, call_ratios, outevent, inevent):
  437 + if function not in partials:
  438 + partial = partial_ratio*function[inevent]
  439 + for call in function.calls.itervalues():
  440 + if call.callee_id != function.id:
  441 + callee = self.functions[call.callee_id]
  442 + if callee.cycle is not cycle:
  443 + assert outevent in call
  444 + partial += partial_ratio*call[outevent]
  445 + else:
  446 + if ranks[callee] > ranks[function]:
  447 + callee_partial = self._integrate_cycle_function(cycle, callee, partial_ratio, partials, ranks, call_ratios, outevent, inevent)
  448 + call_ratio = ratio(call[CALL_RATIO], call_ratios[callee])
  449 + call_partial = call_ratio*callee_partial
  450 + try:
  451 + call[outevent] += call_partial
  452 + except UndefinedEvent:
  453 + call[outevent] = call_partial
  454 + partial += call_partial
  455 + partials[function] = partial
  456 + try:
  457 + function[outevent] += partial
  458 + except UndefinedEvent:
  459 + function[outevent] = partial
  460 + return partials[function]
  461 +
  462 + def aggregate(self, event):
  463 + """Aggregate an event for the whole profile."""
  464 +
  465 + total = event.null()
  466 + for function in self.functions.itervalues():
  467 + try:
  468 + total = event.aggregate(total, function[event])
  469 + except UndefinedEvent:
  470 + return
  471 + self[event] = total
  472 +
  473 + def ratio(self, outevent, inevent):
  474 + assert outevent not in self
  475 + assert inevent in self
  476 + for function in self.functions.itervalues():
  477 + assert outevent not in function
  478 + assert inevent in function
  479 + function[outevent] = ratio(function[inevent], self[inevent])
  480 + for call in function.calls.itervalues():
  481 + assert outevent not in call
  482 + if inevent in call:
  483 + call[outevent] = ratio(call[inevent], self[inevent])
  484 + self[outevent] = 1.0
  485 +
  486 + def prune(self, node_thres, edge_thres):
  487 + """Prune the profile"""
  488 +
  489 + # compute the prune ratios
  490 + for function in self.functions.itervalues():
  491 + try:
  492 + function[PRUNE_RATIO] = function[TOTAL_TIME_RATIO]
  493 + except UndefinedEvent:
  494 + pass
  495 +
  496 + for call in function.calls.itervalues():
  497 + callee = self.functions[call.callee_id]
  498 +
  499 + if TOTAL_TIME_RATIO in call:
  500 + # handle exact cases first
  501 + call[PRUNE_RATIO] = call[TOTAL_TIME_RATIO]
  502 + else:
  503 + try:
  504 + # make a safe estimate
  505 + call[PRUNE_RATIO] = min(function[TOTAL_TIME_RATIO], callee[TOTAL_TIME_RATIO])
  506 + except UndefinedEvent:
  507 + pass
  508 +
  509 + # prune the nodes
  510 + for function_id in self.functions.keys():
  511 + function = self.functions[function_id]
  512 + try:
  513 + if function[PRUNE_RATIO] < node_thres:
  514 + del self.functions[function_id]
  515 + except UndefinedEvent:
  516 + pass
  517 +
  518 + # prune the egdes
  519 + for function in self.functions.itervalues():
  520 + for callee_id in function.calls.keys():
  521 + call = function.calls[callee_id]
  522 + try:
  523 + if callee_id not in self.functions or call[PRUNE_RATIO] < edge_thres:
  524 + del function.calls[callee_id]
  525 + except UndefinedEvent:
  526 + pass
  527 +
  528 + def dump(self):
  529 + for function in self.functions.itervalues():
  530 + sys.stderr.write('Function %s:\n' % (function.name,))
  531 + self._dump_events(function.events)
  532 + for call in function.calls.itervalues():
  533 + callee = self.functions[call.callee_id]
  534 + sys.stderr.write(' Call %s:\n' % (callee.name,))
  535 + self._dump_events(call.events)
  536 + for cycle in self.cycles:
  537 + sys.stderr.write('Cycle:\n')
  538 + self._dump_events(cycle.events)
  539 + for function in cycle.functions:
  540 + sys.stderr.write(' Function %s\n' % (function.name,))
  541 +
  542 + def _dump_events(self, events):
  543 + for event, value in events.iteritems():
  544 + sys.stderr.write(' %s: %s\n' % (event.name, event.format(value)))
  545 +
  546 +
  547 +class Struct:
  548 + """Masquerade a dictionary with a structure-like behavior."""
  549 +
  550 + def __init__(self, attrs = None):
  551 + if attrs is None:
  552 + attrs = {}
  553 + self.__dict__['_attrs'] = attrs
  554 +
  555 + def __getattr__(self, name):
  556 + try:
  557 + return self._attrs[name]
  558 + except KeyError:
  559 + raise AttributeError(name)
  560 +
  561 + def __setattr__(self, name, value):
  562 + self._attrs[name] = value
  563 +
  564 + def __str__(self):
  565 + return str(self._attrs)
  566 +
  567 + def __repr__(self):
  568 + return repr(self._attrs)
  569 +
  570 +
  571 +class ParseError(Exception):
  572 + """Raised when parsing to signal mismatches."""
  573 +
  574 + def __init__(self, msg, line):
  575 + self.msg = msg
  576 + # TODO: store more source line information
  577 + self.line = line
  578 +
  579 + def __str__(self):
  580 + return '%s: %r' % (self.msg, self.line)
  581 +
  582 +
  583 +class Parser:
  584 + """Parser interface."""
  585 +
  586 + def __init__(self):
  587 + pass
  588 +
  589 + def parse(self):
  590 + raise NotImplementedError
  591 +
  592 +
  593 +class LineParser(Parser):
  594 + """Base class for parsers that read line-based formats."""
  595 +
  596 + def __init__(self, file):
  597 + Parser.__init__(self)
  598 + self._file = file
  599 + self.__line = None
  600 + self.__eof = False
  601 +
  602 + def readline(self):
  603 + line = self._file.readline()
  604 + if not line:
  605 + self.__line = ''
  606 + self.__eof = True
  607 + self.__line = line.rstrip('\r\n')
  608 +
  609 + def lookahead(self):
  610 + assert self.__line is not None
  611 + return self.__line
  612 +
  613 + def consume(self):
  614 + assert self.__line is not None
  615 + line = self.__line
  616 + self.readline()
  617 + return line
  618 +
  619 + def eof(self):
  620 + assert self.__line is not None
  621 + return self.__eof
  622 +
  623 +
  624 +XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF = range(4)
  625 +
  626 +
  627 +class XmlToken:
  628 +
  629 + def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
  630 + assert type in (XML_ELEMENT_START, XML_ELEMENT_END, XML_CHARACTER_DATA, XML_EOF)
  631 + self.type = type
  632 + self.name_or_data = name_or_data
  633 + self.attrs = attrs
  634 + self.line = line
  635 + self.column = column
  636 +
  637 + def __str__(self):
  638 + if self.type == XML_ELEMENT_START:
  639 + return '<' + self.name_or_data + ' ...>'
  640 + if self.type == XML_ELEMENT_END:
  641 + return '</' + self.name_or_data + '>'
  642 + if self.type == XML_CHARACTER_DATA:
  643 + return self.name_or_data
  644 + if self.type == XML_EOF:
  645 + return 'end of file'
  646 + assert 0
  647 +
  648 +
  649 +class XmlTokenizer:
  650 + """Expat based XML tokenizer."""
  651 +
  652 + def __init__(self, fp, skip_ws = True):
  653 + self.fp = fp
  654 + self.tokens = []
  655 + self.index = 0
  656 + self.final = False
  657 + self.skip_ws = skip_ws
  658 +
  659 + self.character_pos = 0, 0
  660 + self.character_data = ''
  661 +
  662 + self.parser = xml.parsers.expat.ParserCreate()
  663 + self.parser.StartElementHandler = self.handle_element_start
  664 + self.parser.EndElementHandler = self.handle_element_end
  665 + self.parser.CharacterDataHandler = self.handle_character_data
  666 +
  667 + def handle_element_start(self, name, attributes):
  668 + self.finish_character_data()
  669 + line, column = self.pos()
  670 + token = XmlToken(XML_ELEMENT_START, name, attributes, line, column)
  671 + self.tokens.append(token)
  672 +
  673 + def handle_element_end(self, name):
  674 + self.finish_character_data()
  675 + line, column = self.pos()
  676 + token = XmlToken(XML_ELEMENT_END, name, None, line, column)
  677 + self.tokens.append(token)
  678 +
  679 + def handle_character_data(self, data):
  680 + if not self.character_data:
  681 + self.character_pos = self.pos()
  682 + self.character_data += data
  683 +
  684 + def finish_character_data(self):
  685 + if self.character_data:
  686 + if not self.skip_ws or not self.character_data.isspace():
  687 + line, column = self.character_pos
  688 + token = XmlToken(XML_CHARACTER_DATA, self.character_data, None, line, column)
  689 + self.tokens.append(token)
  690 + self.character_data = ''
  691 +
  692 + def next(self):
  693 + size = 16*1024
  694 + while self.index >= len(self.tokens) and not self.final:
  695 + self.tokens = []
  696 + self.index = 0
  697 + data = self.fp.read(size)
  698 + self.final = len(data) < size
  699 + try:
  700 + self.parser.Parse(data, self.final)
  701 + except xml.parsers.expat.ExpatError, e:
  702 + #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
  703 + if e.code == 3:
  704 + pass
  705 + else:
  706 + raise e
  707 + if self.index >= len(self.tokens):
  708 + line, column = self.pos()
  709 + token = XmlToken(XML_EOF, None, None, line, column)
  710 + else:
  711 + token = self.tokens[self.index]
  712 + self.index += 1
  713 + return token
  714 +
  715 + def pos(self):
  716 + return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
  717 +
  718 +
  719 +class XmlTokenMismatch(Exception):
  720 +
  721 + def __init__(self, expected, found):
  722 + self.expected = expected
  723 + self.found = found
  724 +
  725 + def __str__(self):
  726 + return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
  727 +
  728 +
  729 +class XmlParser(Parser):
  730 + """Base XML document parser."""
  731 +
  732 + def __init__(self, fp):
  733 + Parser.__init__(self)
  734 + self.tokenizer = XmlTokenizer(fp)
  735 + self.consume()
  736 +
  737 + def consume(self):
  738 + self.token = self.tokenizer.next()
  739 +
  740 + def match_element_start(self, name):
  741 + return self.token.type == XML_ELEMENT_START and self.token.name_or_data == name
  742 +
  743 + def match_element_end(self, name):
  744 + return self.token.type == XML_ELEMENT_END and self.token.name_or_data == name
  745 +
  746 + def element_start(self, name):
  747 + while self.token.type == XML_CHARACTER_DATA:
  748 + self.consume()
  749 + if self.token.type != XML_ELEMENT_START:
  750 + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token)
  751 + if self.token.name_or_data != name:
  752 + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_START, name), self.token)
  753 + attrs = self.token.attrs
  754 + self.consume()
  755 + return attrs
  756 +
  757 + def element_end(self, name):
  758 + while self.token.type == XML_CHARACTER_DATA:
  759 + self.consume()
  760 + if self.token.type != XML_ELEMENT_END:
  761 + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token)
  762 + if self.token.name_or_data != name:
  763 + raise XmlTokenMismatch(XmlToken(XML_ELEMENT_END, name), self.token)
  764 + self.consume()
  765 +
  766 + def character_data(self, strip = True):
  767 + data = ''
  768 + while self.token.type == XML_CHARACTER_DATA:
  769 + data += self.token.name_or_data
  770 + self.consume()
  771 + if strip:
  772 + data = data.strip()
  773 + return data
  774 +
  775 +
  776 +class GprofParser(Parser):
  777 + """Parser for GNU gprof output.
  778 +
  779 + See also:
  780 + - Chapter "Interpreting gprof's Output" from the GNU gprof manual
  781 + http://sourceware.org/binutils/docs-2.18/gprof/Call-Graph.html#Call-Graph
  782 + - File "cg_print.c" from the GNU gprof source code
  783 + http://sourceware.org/cgi-bin/cvsweb.cgi/~checkout~/src/gprof/cg_print.c?rev=1.12&cvsroot=src
  784 + """
  785 +
  786 + def __init__(self, fp):
  787 + Parser.__init__(self)
  788 + self.fp = fp
  789 + self.functions = {}
  790 + self.cycles = {}
  791 +
  792 + def readline(self):
  793 + line = self.fp.readline()
  794 + if not line:
  795 + sys.stderr.write('error: unexpected end of file\n')
  796 + sys.exit(1)
  797 + line = line.rstrip('\r\n')
  798 + return line
  799 +
  800 + _int_re = re.compile(r'^\d+$')
  801 + _float_re = re.compile(r'^\d+\.\d+$')
  802 +
  803 + def translate(self, mo):
  804 + """Extract a structure from a match object, while translating the types in the process."""
  805 + attrs = {}
  806 + groupdict = mo.groupdict()
  807 + for name, value in groupdict.iteritems():
  808 + if value is None:
  809 + value = None
  810 + elif self._int_re.match(value):
  811 + value = int(value)
  812 + elif self._float_re.match(value):
  813 + value = float(value)
  814 + attrs[name] = (value)
  815 + return Struct(attrs)
  816 +
  817 + _cg_header_re = re.compile(
  818 + # original gprof header
  819 + r'^\s+called/total\s+parents\s*$|' +
  820 + r'^index\s+%time\s+self\s+descendents\s+called\+self\s+name\s+index\s*$|' +
  821 + r'^\s+called/total\s+children\s*$|' +
  822 + # GNU gprof header
  823 + r'^index\s+%\s+time\s+self\s+children\s+called\s+name\s*$'
  824 + )
  825 +
  826 + _cg_ignore_re = re.compile(
  827 + # spontaneous
  828 + r'^\s+<spontaneous>\s*$|'
  829 + # internal calls (such as "mcount")
  830 + r'^.*\((\d+)\)$'
  831 + )
  832 +
  833 + _cg_primary_re = re.compile(
  834 + r'^\[(?P<index>\d+)\]?' +
  835 + r'\s+(?P<percentage_time>\d+\.\d+)' +
  836 + r'\s+(?P<self>\d+\.\d+)' +
  837 + r'\s+(?P<descendants>\d+\.\d+)' +
  838 + r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' +
  839 + r'\s+(?P<name>\S.*?)' +
  840 + r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
  841 + r'\s\[(\d+)\]$'
  842 + )
  843 +
  844 + _cg_parent_re = re.compile(
  845 + r'^\s+(?P<self>\d+\.\d+)?' +
  846 + r'\s+(?P<descendants>\d+\.\d+)?' +
  847 + r'\s+(?P<called>\d+)(?:/(?P<called_total>\d+))?' +
  848 + r'\s+(?P<name>\S.*?)' +
  849 + r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
  850 + r'\s\[(?P<index>\d+)\]$'
  851 + )
  852 +
  853 + _cg_child_re = _cg_parent_re
  854 +
  855 + _cg_cycle_header_re = re.compile(
  856 + r'^\[(?P<index>\d+)\]?' +
  857 + r'\s+(?P<percentage_time>\d+\.\d+)' +
  858 + r'\s+(?P<self>\d+\.\d+)' +
  859 + r'\s+(?P<descendants>\d+\.\d+)' +
  860 + r'\s+(?:(?P<called>\d+)(?:\+(?P<called_self>\d+))?)?' +
  861 + r'\s+<cycle\s(?P<cycle>\d+)\sas\sa\swhole>' +
  862 + r'\s\[(\d+)\]$'
  863 + )
  864 +
  865 + _cg_cycle_member_re = re.compile(
  866 + r'^\s+(?P<self>\d+\.\d+)?' +
  867 + r'\s+(?P<descendants>\d+\.\d+)?' +
  868 + r'\s+(?P<called>\d+)(?:\+(?P<called_self>\d+))?' +
  869 + r'\s+(?P<name>\S.*?)' +
  870 + r'(?:\s+<cycle\s(?P<cycle>\d+)>)?' +
  871 + r'\s\[(?P<index>\d+)\]$'
  872 + )
  873 +
  874 + _cg_sep_re = re.compile(r'^--+$')
  875 +
  876 + def parse_function_entry(self, lines):
  877 + parents = []
  878 + children = []
  879 +
  880 + while True:
  881 + if not lines:
  882 + sys.stderr.write('warning: unexpected end of entry\n')
  883 + line = lines.pop(0)
  884 + if line.startswith('['):
  885 + break
  886 +
  887 + # read function parent line
  888 + mo = self._cg_parent_re.match(line)
  889 + if not mo:
  890 + if self._cg_ignore_re.match(line):
  891 + continue
  892 + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
  893 + else:
  894 + parent = self.translate(mo)
  895 + parents.append(parent)
  896 +
  897 + # read primary line
  898 + mo = self._cg_primary_re.match(line)
  899 + if not mo:
  900 + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
  901 + return
  902 + else:
  903 + function = self.translate(mo)
  904 +
  905 + while lines:
  906 + line = lines.pop(0)
  907 +
  908 + # read function subroutine line
  909 + mo = self._cg_child_re.match(line)
  910 + if not mo:
  911 + if self._cg_ignore_re.match(line):
  912 + continue
  913 + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
  914 + else:
  915 + child = self.translate(mo)
  916 + children.append(child)
  917 +
  918 + function.parents = parents
  919 + function.children = children
  920 +
  921 + self.functions[function.index] = function
  922 +
  923 + def parse_cycle_entry(self, lines):
  924 +
  925 + # read cycle header line
  926 + line = lines[0]
  927 + mo = self._cg_cycle_header_re.match(line)
  928 + if not mo:
  929 + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
  930 + return
  931 + cycle = self.translate(mo)
  932 +
  933 + # read cycle member lines
  934 + cycle.functions = []
  935 + for line in lines[1:]:
  936 + mo = self._cg_cycle_member_re.match(line)
  937 + if not mo:
  938 + sys.stderr.write('warning: unrecognized call graph entry: %r\n' % line)
  939 + continue
  940 + call = self.translate(mo)
  941 + cycle.functions.append(call)
  942 +
  943 + self.cycles[cycle.cycle] = cycle
  944 +
  945 + def parse_cg_entry(self, lines):
  946 + if lines[0].startswith("["):
  947 + self.parse_cycle_entry(lines)
  948 + else:
  949 + self.parse_function_entry(lines)
  950 +
  951 + def parse_cg(self):
  952 + """Parse the call graph."""
  953 +
  954 + # skip call graph header
  955 + while not self._cg_header_re.match(self.readline()):
  956 + pass
  957 + line = self.readline()
  958 + while self._cg_header_re.match(line):
  959 + line = self.readline()
  960 +
  961 + # process call graph entries
  962 + entry_lines = []
  963 + while line != '\014': # form feed
  964 + if line and not line.isspace():
  965 + if self._cg_sep_re.match(line):
  966 + self.parse_cg_entry(entry_lines)
  967 + entry_lines = []
  968 + else:
  969 + entry_lines.append(line)
  970 + line = self.readline()
  971 +
  972 + def parse(self):
  973 + self.parse_cg()
  974 + self.fp.close()
  975 +
  976 + profile = Profile()
  977 + profile[TIME] = 0.0
  978 +
  979 + cycles = {}
  980 + for index in self.cycles.iterkeys():
  981 + cycles[index] = Cycle()
  982 +
  983 + for entry in self.functions.itervalues():
  984 + # populate the function
  985 + function = Function(entry.index, entry.name)
  986 + function[TIME] = entry.self
  987 + if entry.called is not None:
  988 + function[CALLS] = entry.called
  989 + if entry.called_self is not None:
  990 + call = Call(entry.index)
  991 + call[CALLS] = entry.called_self
  992 + function[CALLS] += entry.called_self
  993 +
  994 + # populate the function calls
  995 + for child in entry.children:
  996 + call = Call(child.index)
  997 +
  998 + assert child.called is not None
  999 + call[CALLS] = child.called
  1000 +
  1001 + if child.index not in self.functions:
  1002 + # NOTE: functions that were never called but were discovered by gprof's
  1003 + # static call graph analysis dont have a call graph entry so we need
  1004 + # to add them here
  1005 + missing = Function(child.index, child.name)
  1006 + function[TIME] = 0.0
  1007 + function[CALLS] = 0
  1008 + profile.add_function(missing)
  1009 +
  1010 + function.add_call(call)
  1011 +
  1012 + profile.add_function(function)
  1013 +
  1014 + if entry.cycle is not None:
  1015 + try:
  1016 + cycle = cycles[entry.cycle]
  1017 + except KeyError:
  1018 + sys.stderr.write('warning: <cycle %u as a whole> entry missing\n' % entry.cycle)
  1019 + cycle = Cycle()
  1020 + cycles[entry.cycle] = cycle
  1021 + cycle.add_function(function)
  1022 +
  1023 + profile[TIME] = profile[TIME] + function[TIME]
  1024 +
  1025 + for cycle in cycles.itervalues():
  1026 + profile.add_cycle(cycle)
  1027 +
  1028 + # Compute derived events
  1029 + profile.validate()
  1030 + profile.ratio(TIME_RATIO, TIME)
  1031 + profile.call_ratios(CALLS)
  1032 + profile.integrate(TOTAL_TIME, TIME)
  1033 + profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
  1034 +
  1035 + return profile
  1036 +
  1037 +
  1038 +class OprofileParser(LineParser):
  1039 + """Parser for oprofile callgraph output.
  1040 +
  1041 + See also:
  1042 + - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph
  1043 + """
  1044 +
  1045 + _fields_re = {
  1046 + 'samples': r'(?P<samples>\d+)',
  1047 + '%': r'(?P<percentage>\S+)',
  1048 + 'linenr info': r'(?P<source>\(no location information\)|\S+:\d+)',
  1049 + 'image name': r'(?P<image>\S+(?:\s\(tgid:[^)]*\))?)',
  1050 + 'app name': r'(?P<application>\S+)',
  1051 + 'symbol name': r'(?P<symbol>\(no symbols\)|.+?)',
  1052 + }
  1053 +
  1054 + def __init__(self, infile):
  1055 + LineParser.__init__(self, infile)
  1056 + self.entries = {}
  1057 + self.entry_re = None
  1058 +
  1059 + def add_entry(self, callers, function, callees):
  1060 + try:
  1061 + entry = self.entries[function.id]
  1062 + except KeyError:
  1063 + self.entries[function.id] = (callers, function, callees)
  1064 + else:
  1065 + callers_total, function_total, callees_total = entry
  1066 + self.update_subentries_dict(callers_total, callers)
  1067 + function_total.samples += function.samples
  1068 + self.update_subentries_dict(callees_total, callees)
  1069 +
  1070 + def update_subentries_dict(self, totals, partials):
  1071 + for partial in partials.itervalues():
  1072 + try:
  1073 + total = totals[partial.id]
  1074 + except KeyError:
  1075 + totals[partial.id] = partial
  1076 + else:
  1077 + total.samples += partial.samples
  1078 +
  1079 + def parse(self):
  1080 + # read lookahead
  1081 + self.readline()
  1082 +
  1083 + self.parse_header()
  1084 + while self.lookahead():
  1085 + self.parse_entry()
  1086 +
  1087 + profile = Profile()
  1088 +
  1089 + reverse_call_samples = {}
  1090 +
  1091 + # populate the profile
  1092 + profile[SAMPLES] = 0
  1093 + for _callers, _function, _callees in self.entries.itervalues():
  1094 + function = Function(_function.id, _function.name)
  1095 + function[SAMPLES] = _function.samples
  1096 + profile.add_function(function)
  1097 + profile[SAMPLES] += _function.samples
  1098 +
  1099 + if _function.application:
  1100 + function[PROCESS] = os.path.basename(_function.application)
  1101 + if _function.image:
  1102 + function[MODULE] = os.path.basename(_function.image)
  1103 +
  1104 + total_callee_samples = 0
  1105 + for _callee in _callees.itervalues():
  1106 + total_callee_samples += _callee.samples
  1107 +
  1108 + for _callee in _callees.itervalues():
  1109 + if not _callee.self:
  1110 + call = Call(_callee.id)
  1111 + call[SAMPLES2] = _callee.samples
  1112 + function.add_call(call)
  1113 +
  1114 + # compute derived data
  1115 + profile.validate()
  1116 + profile.find_cycles()
  1117 + profile.ratio(TIME_RATIO, SAMPLES)
  1118 + profile.call_ratios(SAMPLES2)
  1119 + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
  1120 +
  1121 + return profile
  1122 +
  1123 + def parse_header(self):
  1124 + while not self.match_header():
  1125 + self.consume()
  1126 + line = self.lookahead()
  1127 + fields = re.split(r'\s\s+', line)
  1128 + entry_re = r'^\s*' + r'\s+'.join([self._fields_re[field] for field in fields]) + r'(?P<self>\s+\[self\])?$'
  1129 + self.entry_re = re.compile(entry_re)
  1130 + self.skip_separator()
  1131 +
  1132 + def parse_entry(self):
  1133 + callers = self.parse_subentries()
  1134 + if self.match_primary():
  1135 + function = self.parse_subentry()
  1136 + if function is not None:
  1137 + callees = self.parse_subentries()
  1138 + self.add_entry(callers, function, callees)
  1139 + self.skip_separator()
  1140 +
  1141 + def parse_subentries(self):
  1142 + subentries = {}
  1143 + while self.match_secondary():
  1144 + subentry = self.parse_subentry()
  1145 + subentries[subentry.id] = subentry
  1146 + return subentries
  1147 +
  1148 + def parse_subentry(self):
  1149 + entry = Struct()
  1150 + line = self.consume()
  1151 + mo = self.entry_re.match(line)
  1152 + if not mo:
  1153 + raise ParseError('failed to parse', line)
  1154 + fields = mo.groupdict()
  1155 + entry.samples = int(fields.get('samples', 0))
  1156 + entry.percentage = float(fields.get('percentage', 0.0))
  1157 + if 'source' in fields and fields['source'] != '(no location information)':
  1158 + source = fields['source']
  1159 + filename, lineno = source.split(':')
  1160 + entry.filename = filename
  1161 + entry.lineno = int(lineno)
  1162 + else:
  1163 + source = ''
  1164 + entry.filename = None
  1165 + entry.lineno = None
  1166 + entry.image = fields.get('image', '')
  1167 + entry.application = fields.get('application', '')
  1168 + if 'symbol' in fields and fields['symbol'] != '(no symbols)':
  1169 + entry.symbol = fields['symbol']
  1170 + else:
  1171 + entry.symbol = ''
  1172 + if entry.symbol.startswith('"') and entry.symbol.endswith('"'):
  1173 + entry.symbol = entry.symbol[1:-1]
  1174 + entry.id = ':'.join((entry.application, entry.image, source, entry.symbol))
  1175 + entry.self = fields.get('self', None) != None
  1176 + if entry.self:
  1177 + entry.id += ':self'
  1178 + if entry.symbol:
  1179 + entry.name = entry.symbol
  1180 + else:
  1181 + entry.name = entry.image
  1182 + return entry
  1183 +
  1184 + def skip_separator(self):
  1185 + while not self.match_separator():
  1186 + self.consume()
  1187 + self.consume()
  1188 +
  1189 + def match_header(self):
  1190 + line = self.lookahead()
  1191 + return line.startswith('samples')
  1192 +
  1193 + def match_separator(self):
  1194 + line = self.lookahead()
  1195 + return line == '-'*len(line)
  1196 +
  1197 + def match_primary(self):
  1198 + line = self.lookahead()
  1199 + return not line[:1].isspace()
  1200 +
  1201 + def match_secondary(self):
  1202 + line = self.lookahead()
  1203 + return line[:1].isspace()
  1204 +
  1205 +
  1206 +class SysprofParser(XmlParser):
  1207 +
  1208 + def __init__(self, stream):
  1209 + XmlParser.__init__(self, stream)
  1210 +
  1211 + def parse(self):
  1212 + objects = {}
  1213 + nodes = {}
  1214 +
  1215 + self.element_start('profile')
  1216 + while self.token.type == XML_ELEMENT_START:
  1217 + if self.token.name_or_data == 'objects':
  1218 + assert not objects
  1219 + objects = self.parse_items('objects')
  1220 + elif self.token.name_or_data == 'nodes':
  1221 + assert not nodes
  1222 + nodes = self.parse_items('nodes')
  1223 + else:
  1224 + self.parse_value(self.token.name_or_data)
  1225 + self.element_end('profile')
  1226 +
  1227 + return self.build_profile(objects, nodes)
  1228 +
  1229 + def parse_items(self, name):
  1230 + assert name[-1] == 's'
  1231 + items = {}
  1232 + self.element_start(name)
  1233 + while self.token.type == XML_ELEMENT_START:
  1234 + id, values = self.parse_item(name[:-1])
  1235 + assert id not in items
  1236 + items[id] = values
  1237 + self.element_end(name)
  1238 + return items
  1239 +
  1240 + def parse_item(self, name):
  1241 + attrs = self.element_start(name)
  1242 + id = int(attrs['id'])
  1243 + values = self.parse_values()
  1244 + self.element_end(name)
  1245 + return id, values
  1246 +
  1247 + def parse_values(self):
  1248 + values = {}
  1249 + while self.token.type == XML_ELEMENT_START:
  1250 + name = self.token.name_or_data
  1251 + value = self.parse_value(name)
  1252 + assert name not in values
  1253 + values[name] = value
  1254 + return values
  1255 +
  1256 + def parse_value(self, tag):
  1257 + self.element_start(tag)
  1258 + value = self.character_data()
  1259 + self.element_end(tag)
  1260 + if value.isdigit():
  1261 + return int(value)
  1262 + if value.startswith('"') and value.endswith('"'):
  1263 + return value[1:-1]
  1264 + return value
  1265 +
  1266 + def build_profile(self, objects, nodes):
  1267 + profile = Profile()
  1268 +
  1269 + profile[SAMPLES] = 0
  1270 + for id, object in objects.iteritems():
  1271 + # Ignore fake objects (process names, modules, "Everything", "kernel", etc.)
  1272 + if object['self'] == 0:
  1273 + continue
  1274 +
  1275 + function = Function(id, object['name'])
  1276 + function[SAMPLES] = object['self']
  1277 + profile.add_function(function)
  1278 + profile[SAMPLES] += function[SAMPLES]
  1279 +
  1280 + for id, node in nodes.iteritems():
  1281 + # Ignore fake calls
  1282 + if node['self'] == 0:
  1283 + continue
  1284 +
  1285 + # Find a non-ignored parent
  1286 + parent_id = node['parent']
  1287 + while parent_id != 0:
  1288 + parent = nodes[parent_id]
  1289 + caller_id = parent['object']
  1290 + if objects[caller_id]['self'] != 0:
  1291 + break
  1292 + parent_id = parent['parent']
  1293 + if parent_id == 0:
  1294 + continue
  1295 +
  1296 + callee_id = node['object']
  1297 +
  1298 + assert objects[caller_id]['self']
  1299 + assert objects[callee_id]['self']
  1300 +
  1301 + function = profile.functions[caller_id]
  1302 +
  1303 + samples = node['self']
  1304 + try:
  1305 + call = function.calls[callee_id]
  1306 + except KeyError:
  1307 + call = Call(callee_id)
  1308 + call[SAMPLES2] = samples
  1309 + function.add_call(call)
  1310 + else:
  1311 + call[SAMPLES2] += samples
  1312 +
  1313 + # Compute derived events
  1314 + profile.validate()
  1315 + profile.find_cycles()
  1316 + profile.ratio(TIME_RATIO, SAMPLES)
  1317 + profile.call_ratios(SAMPLES2)
  1318 + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
  1319 +
  1320 + return profile
  1321 +
  1322 +
  1323 +class SharkParser(LineParser):
  1324 + """Parser for MacOSX Shark output.
  1325 +
  1326 + Author: tom@dbservice.com
  1327 + """
  1328 +
  1329 + def __init__(self, infile):
  1330 + LineParser.__init__(self, infile)
  1331 + self.stack = []
  1332 + self.entries = {}
  1333 +
  1334 + def add_entry(self, function):
  1335 + try:
  1336 + entry = self.entries[function.id]
  1337 + except KeyError:
  1338 + self.entries[function.id] = (function, { })
  1339 + else:
  1340 + function_total, callees_total = entry
  1341 + function_total.samples += function.samples
  1342 +
  1343 + def add_callee(self, function, callee):
  1344 + func, callees = self.entries[function.id]
  1345 + try:
  1346 + entry = callees[callee.id]
  1347 + except KeyError:
  1348 + callees[callee.id] = callee
  1349 + else:
  1350 + entry.samples += callee.samples
  1351 +
  1352 + def parse(self):
  1353 + self.readline()
  1354 + self.readline()
  1355 + self.readline()
  1356 + self.readline()
  1357 +
  1358 + match = re.compile(r'(?P<prefix>[|+ ]*)(?P<samples>\d+), (?P<symbol>[^,]+), (?P<image>.*)')
  1359 +
  1360 + while self.lookahead():
  1361 + line = self.consume()
  1362 + mo = match.match(line)
  1363 + if not mo:
  1364 + raise ParseError('failed to parse', line)
  1365 +
  1366 + fields = mo.groupdict()
  1367 + prefix = len(fields.get('prefix', 0)) / 2 - 1
  1368 +
  1369 + symbol = str(fields.get('symbol', 0))
  1370 + image = str(fields.get('image', 0))
  1371 +
  1372 + entry = Struct()
  1373 + entry.id = ':'.join([symbol, image])
  1374 + entry.samples = int(fields.get('samples', 0))
  1375 +
  1376 + entry.name = symbol
  1377 + entry.image = image
  1378 +
  1379 + # adjust the callstack
  1380 + if prefix < len(self.stack):
  1381 + del self.stack[prefix:]
  1382 +
  1383 + if prefix == len(self.stack):
  1384 + self.stack.append(entry)
  1385 +
  1386 + # if the callstack has had an entry, it's this functions caller
  1387 + if prefix > 0:
  1388 + self.add_callee(self.stack[prefix - 1], entry)
  1389 +
  1390 + self.add_entry(entry)
  1391 +
  1392 + profile = Profile()
  1393 + profile[SAMPLES] = 0
  1394 + for _function, _callees in self.entries.itervalues():
  1395 + function = Function(_function.id, _function.name)
  1396 + function[SAMPLES] = _function.samples
  1397 + profile.add_function(function)
  1398 + profile[SAMPLES] += _function.samples
  1399 +
  1400 + if _function.image:
  1401 + function[MODULE] = os.path.basename(_function.image)
  1402 +
  1403 + for _callee in _callees.itervalues():
  1404 + call = Call(_callee.id)
  1405 + call[SAMPLES] = _callee.samples
  1406 + function.add_call(call)
  1407 +
  1408 + # compute derived data
  1409 + profile.validate()
  1410 + profile.find_cycles()
  1411 + profile.ratio(TIME_RATIO, SAMPLES)
  1412 + profile.call_ratios(SAMPLES)
  1413 + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
  1414 +
  1415 + return profile
  1416 +
  1417 +
  1418 +class SleepyParser(Parser):
  1419 + """Parser for GNU gprof output.
  1420 +
  1421 + See also:
  1422 + - http://www.codersnotes.com/sleepy/
  1423 + - http://sleepygraph.sourceforge.net/
  1424 + """
  1425 +
  1426 + def __init__(self, filename):
  1427 + Parser.__init__(self)
  1428 +
  1429 + from zipfile import ZipFile
  1430 +
  1431 + self.database = ZipFile(filename)
  1432 +
  1433 + self.symbols = {}
  1434 + self.calls = {}
  1435 +
  1436 + self.profile = Profile()
  1437 +
  1438 + _symbol_re = re.compile(
  1439 + r'^(?P<id>\w+)' +
  1440 + r'\s+"(?P<module>[^"]*)"' +
  1441 + r'\s+"(?P<procname>[^"]*)"' +
  1442 + r'\s+"(?P<sourcefile>[^"]*)"' +
  1443 + r'\s+(?P<sourceline>\d+)$'
  1444 + )
  1445 +
  1446 + def parse_symbols(self):
  1447 + lines = self.database.read('symbols.txt').splitlines()
  1448 + for line in lines:
  1449 + mo = self._symbol_re.match(line)
  1450 + if mo:
  1451 + symbol_id, module, procname, sourcefile, sourceline = mo.groups()
  1452 +
  1453 + function_id = ':'.join([module, procname])
  1454 +
  1455 + try:
  1456 + function = self.profile.functions[function_id]
  1457 + except KeyError:
  1458 + function = Function(function_id, procname)
  1459 + function[SAMPLES] = 0
  1460 + self.profile.add_function(function)
  1461 +
  1462 + self.symbols[symbol_id] = function
  1463 +
  1464 + def parse_callstacks(self):
  1465 + lines = self.database.read("callstacks.txt").splitlines()
  1466 + for line in lines:
  1467 + fields = line.split()
  1468 + samples = int(fields[0])
  1469 + callstack = fields[1:]
  1470 +
  1471 + callstack = [self.symbols[symbol_id] for symbol_id in callstack]
  1472 +
  1473 + callee = callstack[0]
  1474 +
  1475 + callee[SAMPLES] += samples
  1476 + self.profile[SAMPLES] += samples
  1477 +
  1478 + for caller in callstack[1:]:
  1479 + try:
  1480 + call = caller.calls[callee.id]
  1481 + except KeyError:
  1482 + call = Call(callee.id)
  1483 + call[SAMPLES2] = samples
  1484 + caller.add_call(call)
  1485 + else:
  1486 + call[SAMPLES2] += samples
  1487 +
  1488 + callee = caller
  1489 +
  1490 + def parse(self):
  1491 + profile = self.profile
  1492 + profile[SAMPLES] = 0
  1493 +
  1494 + self.parse_symbols()
  1495 + self.parse_callstacks()
  1496 +
  1497 + # Compute derived events
  1498 + profile.validate()
  1499 + profile.find_cycles()
  1500 + profile.ratio(TIME_RATIO, SAMPLES)
  1501 + profile.call_ratios(SAMPLES2)
  1502 + profile.integrate(TOTAL_TIME_RATIO, TIME_RATIO)
  1503 +
  1504 + return profile
  1505 +
  1506 +
  1507 +class AQtimeTable:
  1508 +
  1509 + def __init__(self, name, fields):
  1510 + self.name = name
  1511 +
  1512 + self.fields = fields
  1513 + self.field_column = {}
  1514 + for column in range(len(fields)):
  1515 + self.field_column[fields[column]] = column
  1516 + self.rows = []
  1517 +
  1518 + def __len__(self):
  1519 + return len(self.rows)
  1520 +
  1521 + def __iter__(self):
  1522 + for values, children in self.rows:
  1523 + fields = {}
  1524 + for name, value in zip(self.fields, values):
  1525 + fields[name] = value
  1526 + children = dict([(child.name, child) for child in children])
  1527 + yield fields, children
  1528 + raise StopIteration
  1529 +
  1530 + def add_row(self, values, children=()):
  1531 + self.rows.append((values, children))
  1532 +
  1533 +
  1534 +class AQtimeParser(XmlParser):
  1535 +
  1536 + def __init__(self, stream):
  1537 + XmlParser.__init__(self, stream)
  1538 + self.tables = {}
  1539 +
  1540 + def parse(self):
  1541 + self.element_start('AQtime_Results')
  1542 + self.parse_headers()
  1543 + results = self.parse_results()
  1544 + self.element_end('AQtime_Results')
  1545 + return self.build_profile(results)
  1546 +
  1547 + def parse_headers(self):
  1548 + self.element_start('HEADERS')
  1549 + while self.token.type == XML_ELEMENT_START:
  1550 + self.parse_table_header()
  1551 + self.element_end('HEADERS')
  1552 +
  1553 + def parse_table_header(self):
  1554 + attrs = self.element_start('TABLE_HEADER')
  1555 + name = attrs['NAME']
  1556 + id = int(attrs['ID'])
  1557 + field_types = []
  1558 + field_names = []
  1559 + while self.token.type == XML_ELEMENT_START:
  1560 + field_type, field_name = self.parse_table_field()
  1561 + field_types.append(field_type)
  1562 + field_names.append(field_name)
  1563 + self.element_end('TABLE_HEADER')
  1564 + self.tables[id] = name, field_types, field_names
  1565 +
  1566 + def parse_table_field(self):
  1567 + attrs = self.element_start('TABLE_FIELD')
  1568 + type = attrs['TYPE']
  1569 + name = self.character_data()
  1570 + self.element_end('TABLE_FIELD')
  1571 + return type, name
  1572 +
  1573 + def parse_results(self):
  1574 + self.element_start('RESULTS')
  1575 + table = self.parse_data()
  1576 + self.element_end('RESULTS')
  1577 + return table
  1578 +
  1579 + def parse_data(self):
  1580 + rows = []
  1581 + attrs = self.element_start('DATA')
  1582 + table_id = int(attrs['TABLE_ID'])
  1583 + table_name, field_types, field_names = self.tables[table_id]
  1584 + table = AQtimeTable(table_name, field_names)
  1585 + while self.token.type == XML_ELEMENT_START:
  1586 + row, children = self.parse_row(field_types)
  1587 + table.add_row(row, children)
  1588 + self.element_end('DATA')
  1589 + return table
  1590 +
  1591 + def parse_row(self, field_types):
  1592 + row = [None]*len(field_types)
  1593 + children = []
  1594 + self.element_start('ROW')
  1595 + while self.token.type == XML_ELEMENT_START:
  1596 + if self.token.name_or_data == 'FIELD':
  1597 + field_id, field_value = self.parse_field(field_types)
  1598 + row[field_id] = field_value
  1599 + elif self.token.name_or_data == 'CHILDREN':
  1600 + children = self.parse_children()
  1601 + else:
  1602 + raise XmlTokenMismatch("<FIELD ...> or <CHILDREN ...>", self.token)
  1603 + self.element_end('ROW')
  1604 + return row, children
  1605 +
  1606 + def parse_field(self, field_types):
  1607 + attrs = self.element_start('FIELD')
  1608 + id = int(attrs['ID'])
  1609 + type = field_types[id]
  1610 + value = self.character_data()
  1611 + if type == 'Integer':
  1612 + value = int(value)
  1613 + elif type == 'Float':
  1614 + value = float(value)
  1615 + elif type == 'Address':
  1616 + value = int(value)
  1617 + elif type == 'String':
  1618 + pass
  1619 + else:
  1620 + assert False
  1621 + self.element_end('FIELD')
  1622 + return id, value
  1623 +
  1624 + def parse_children(self):
  1625 + children = []
  1626 + self.element_start('CHILDREN')
  1627 + while self.token.type == XML_ELEMENT_START:
  1628 + table = self.parse_data()
  1629 + assert table.name not in children
  1630 + children.append(table)
  1631 + self.element_end('CHILDREN')
  1632 + return children
  1633 +
  1634 + def build_profile(self, results):
  1635 + assert results.name == 'Routines'
  1636 + profile = Profile()
  1637 + profile[TIME] = 0.0
  1638 + for fields, tables in results:
  1639 + function = self.build_function(fields)
  1640 + children = tables['Children']
  1641 + for fields, _ in children:
  1642 + call = self.build_call(fields)
  1643 + function.add_call(call)
  1644 + profile.add_function(function)
  1645 + profile[TIME] = profile[TIME] + function[TIME]
  1646 + profile[TOTAL_TIME] = profile[TIME]
  1647 + profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
  1648 + return profile
  1649 +
  1650 + def build_function(self, fields):
  1651 + function = Function(self.build_id(fields), self.build_name(fields))
  1652 + function[TIME] = fields['Time']
  1653 + function[TOTAL_TIME] = fields['Time with Children']
  1654 + #function[TIME_RATIO] = fields['% Time']/100.0
  1655 + #function[TOTAL_TIME_RATIO] = fields['% with Children']/100.0
  1656 + return function
  1657 +
  1658 + def build_call(self, fields):
  1659 + call = Call(self.build_id(fields))
  1660 + call[TIME] = fields['Time']
  1661 + call[TOTAL_TIME] = fields['Time with Children']
  1662 + #call[TIME_RATIO] = fields['% Time']/100.0
  1663 + #call[TOTAL_TIME_RATIO] = fields['% with Children']/100.0
  1664 + return call
  1665 +
  1666 + def build_id(self, fields):
  1667 + return ':'.join([fields['Module Name'], fields['Unit Name'], fields['Routine Name']])
  1668 +
  1669 + def build_name(self, fields):
  1670 + # TODO: use more fields
  1671 + return fields['Routine Name']
  1672 +
  1673 +
  1674 +class PstatsParser:
  1675 + """Parser python profiling statistics saved with te pstats module."""
  1676 +
  1677 + def __init__(self, *filename):
  1678 + import pstats
  1679 + try:
  1680 + self.stats = pstats.Stats(*filename)
  1681 + except ValueError:
  1682 + import hotshot.stats
  1683 + self.stats = hotshot.stats.load(filename[0])
  1684 + self.profile = Profile()
  1685 + self.function_ids = {}
  1686 +
  1687 + def get_function_name(self, (filename, line, name)):
  1688 + module = os.path.splitext(filename)[0]
  1689 + module = os.path.basename(module)
  1690 + return "%s:%d:%s" % (module, line, name)
  1691 +
  1692 + def get_function(self, key):
  1693 + try:
  1694 + id = self.function_ids[key]
  1695 + except KeyError:
  1696 + id = len(self.function_ids)
  1697 + name = self.get_function_name(key)
  1698 + function = Function(id, name)
  1699 + self.profile.functions[id] = function
  1700 + self.function_ids[key] = id
  1701 + else:
  1702 + function = self.profile.functions[id]
  1703 + return function
  1704 +
  1705 + def parse(self):
  1706 + self.profile[TIME] = 0.0
  1707 + self.profile[TOTAL_TIME] = self.stats.total_tt
  1708 + for fn, (cc, nc, tt, ct, callers) in self.stats.stats.iteritems():
  1709 + callee = self.get_function(fn)
  1710 + callee[CALLS] = nc
  1711 + callee[TOTAL_TIME] = ct
  1712 + callee[TIME] = tt
  1713 + self.profile[TIME] += tt
  1714 + self.profile[TOTAL_TIME] = max(self.profile[TOTAL_TIME], ct)
  1715 + for fn, value in callers.iteritems():
  1716 + caller = self.get_function(fn)
  1717 + call = Call(callee.id)
  1718 + if isinstance(value, tuple):
  1719 + for i in xrange(0, len(value), 4):
  1720 + nc, cc, tt, ct = value[i:i+4]
  1721 + if CALLS in call:
  1722 + call[CALLS] += cc
  1723 + else:
  1724 + call[CALLS] = cc
  1725 +
  1726 + if TOTAL_TIME in call:
  1727 + call[TOTAL_TIME] += ct
  1728 + else:
  1729 + call[TOTAL_TIME] = ct
  1730 +
  1731 + else:
  1732 + call[CALLS] = value
  1733 + call[TOTAL_TIME] = ratio(value, nc)*ct
  1734 +
  1735 + caller.add_call(call)
  1736 + #self.stats.print_stats()
  1737 + #self.stats.print_callees()
  1738 +
  1739 + # Compute derived events
  1740 + self.profile.validate()
  1741 + self.profile.ratio(TIME_RATIO, TIME)
  1742 + self.profile.ratio(TOTAL_TIME_RATIO, TOTAL_TIME)
  1743 +
  1744 + return self.profile
  1745 +
  1746 +
  1747 +class Theme:
  1748 +
  1749 + def __init__(self,
  1750 + bgcolor = (0.0, 0.0, 1.0),
  1751 + mincolor = (0.0, 0.0, 0.0),
  1752 + maxcolor = (0.0, 0.0, 1.0),
  1753 + fontname = "Arial",
  1754 + minfontsize = 10.0,
  1755 + maxfontsize = 10.0,
  1756 + minpenwidth = 0.5,
  1757 + maxpenwidth = 4.0,
  1758 + gamma = 2.2,
  1759 + skew = 1.0):
  1760 + self.bgcolor = bgcolor
  1761 + self.mincolor = mincolor
  1762 + self.maxcolor = maxcolor
  1763 + self.fontname = fontname
  1764 + self.minfontsize = minfontsize
  1765 + self.maxfontsize = maxfontsize
  1766 + self.minpenwidth = minpenwidth
  1767 + self.maxpenwidth = maxpenwidth
  1768 + self.gamma = gamma
  1769 + self.skew = skew
  1770 +
  1771 + def graph_bgcolor(self):
  1772 + return self.hsl_to_rgb(*self.bgcolor)
  1773 +
  1774 + def graph_fontname(self):
  1775 + return self.fontname
  1776 +
  1777 + def graph_fontsize(self):
  1778 + return self.minfontsize
  1779 +
  1780 + def node_bgcolor(self, weight):
  1781 + return self.color(weight)
  1782 +
  1783 + def node_fgcolor(self, weight):
  1784 + return self.graph_bgcolor()
  1785 +
  1786 + def node_fontsize(self, weight):
  1787 + return self.fontsize(weight)
  1788 +
  1789 + def edge_color(self, weight):
  1790 + return self.color(weight)
  1791 +
  1792 + def edge_fontsize(self, weight):
  1793 + return self.fontsize(weight)
  1794 +
  1795 + def edge_penwidth(self, weight):
  1796 + return max(weight*self.maxpenwidth, self.minpenwidth)
  1797 +
  1798 + def edge_arrowsize(self, weight):
  1799 + return 0.5 * math.sqrt(self.edge_penwidth(weight))
  1800 +
  1801 + def fontsize(self, weight):
  1802 + return max(weight**2 * self.maxfontsize, self.minfontsize)
  1803 +
  1804 + def color(self, weight):
  1805 + weight = min(max(weight, 0.0), 1.0)
  1806 +
  1807 + hmin, smin, lmin = self.mincolor
  1808 + hmax, smax, lmax = self.maxcolor
  1809 +
  1810 + if self.skew < 0:
  1811 + raise ValueError("Skew must be greater than 0")
  1812 + elif self.skew == 1.0:
  1813 + h = hmin + weight*(hmax - hmin)
  1814 + s = smin + weight*(smax - smin)
  1815 + l = lmin + weight*(lmax - lmin)
  1816 + else:
  1817 + base = self.skew
  1818 + h = hmin + ((hmax-hmin)*(-1.0 + (base ** weight)) / (base - 1.0))
  1819 + s = smin + ((smax-smin)*(-1.0 + (base ** weight)) / (base - 1.0))
  1820 + l = lmin + ((lmax-lmin)*(-1.0 + (base ** weight)) / (base - 1.0))
  1821 +
  1822 + return self.hsl_to_rgb(h, s, l)
  1823 +
  1824 + def hsl_to_rgb(self, h, s, l):
  1825 + """Convert a color from HSL color-model to RGB.
  1826 +
  1827 + See also:
  1828 + - http://www.w3.org/TR/css3-color/#hsl-color
  1829 + """
  1830 +
  1831 + h = h % 1.0
  1832 + s = min(max(s, 0.0), 1.0)
  1833 + l = min(max(l, 0.0), 1.0)
  1834 +
  1835 + if l <= 0.5:
  1836 + m2 = l*(s + 1.0)
  1837 + else:
  1838 + m2 = l + s - l*s
  1839 + m1 = l*2.0 - m2
  1840 + r = self._hue_to_rgb(m1, m2, h + 1.0/3.0)
  1841 + g = self._hue_to_rgb(m1, m2, h)
  1842 + b = self._hue_to_rgb(m1, m2, h - 1.0/3.0)
  1843 +
  1844 + # Apply gamma correction
  1845 + r **= self.gamma
  1846 + g **= self.gamma
  1847 + b **= self.gamma
  1848 +
  1849 + return (r, g, b)
  1850 +
  1851 + def _hue_to_rgb(self, m1, m2, h):
  1852 + if h < 0.0:
  1853 + h += 1.0
  1854 + elif h > 1.0:
  1855 + h -= 1.0
  1856 + if h*6 < 1.0:
  1857 + return m1 + (m2 - m1)*h*6.0
  1858 + elif h*2 < 1.0:
  1859 + return m2
  1860 + elif h*3 < 2.0:
  1861 + return m1 + (m2 - m1)*(2.0/3.0 - h)*6.0
  1862 + else:
  1863 + return m1
  1864 +
  1865 +
  1866 +TEMPERATURE_COLORMAP = Theme(
  1867 + mincolor = (2.0/3.0, 0.80, 0.25), # dark blue
  1868 + maxcolor = (0.0, 1.0, 0.5), # satured red
  1869 + gamma = 1.0
  1870 +)
  1871 +
  1872 +PINK_COLORMAP = Theme(
  1873 + mincolor = (0.0, 1.0, 0.90), # pink
  1874 + maxcolor = (0.0, 1.0, 0.5), # satured red
  1875 +)
  1876 +
  1877 +GRAY_COLORMAP = Theme(
  1878 + mincolor = (0.0, 0.0, 0.85), # light gray
  1879 + maxcolor = (0.0, 0.0, 0.0), # black
  1880 +)
  1881 +
  1882 +BW_COLORMAP = Theme(
  1883 + minfontsize = 8.0,
  1884 + maxfontsize = 24.0,
  1885 + mincolor = (0.0, 0.0, 0.0), # black
  1886 + maxcolor = (0.0, 0.0, 0.0), # black
  1887 + minpenwidth = 0.1,
  1888 + maxpenwidth = 8.0,
  1889 +)
  1890 +
  1891 +
  1892 +class DotWriter:
  1893 + """Writer for the DOT language.
  1894 +
  1895 + See also:
  1896 + - "The DOT Language" specification
  1897 + http://www.graphviz.org/doc/info/lang.html
  1898 + """
  1899 +
  1900 + def __init__(self, fp):
  1901 + self.fp = fp
  1902 +
  1903 + def graph(self, profile, theme):
  1904 + self.begin_graph()
  1905 +
  1906 + fontname = theme.graph_fontname()
  1907 +
  1908 + self.attr('graph', fontname=fontname, ranksep=0.25, nodesep=0.125)
  1909 + self.attr('node', fontname=fontname, shape="box", style="filled", fontcolor="white", width=0, height=0)
  1910 + self.attr('edge', fontname=fontname)
  1911 +
  1912 + for function in profile.functions.itervalues():
  1913 + labels = []
  1914 + for event in PROCESS, MODULE:
  1915 + if event in function.events:
  1916 + label = event.format(function[event])
  1917 + labels.append(label)
  1918 + labels.append(function.name)
  1919 + for event in TOTAL_TIME_RATIO, TIME_RATIO, CALLS:
  1920 + if event in function.events:
  1921 + label = event.format(function[event])
  1922 + labels.append(label)
  1923 +
  1924 + try:
  1925 + weight = function[PRUNE_RATIO]
  1926 + except UndefinedEvent:
  1927 + weight = 0.0
  1928 +
  1929 + label = '\n'.join(labels)
  1930 + self.node(function.id,
  1931 + label = label,
  1932 + color = self.color(theme.node_bgcolor(weight)),
  1933 + fontcolor = self.color(theme.node_fgcolor(weight)),
  1934 + fontsize = "%.2f" % theme.node_fontsize(weight),
  1935 + )
  1936 +
  1937 + for call in function.calls.itervalues():
  1938 + callee = profile.functions[call.callee_id]
  1939 +
  1940 + labels = []
  1941 + for event in TOTAL_TIME_RATIO, CALLS:
  1942 + if event in call.events:
  1943 + label = event.format(call[event])
  1944 + labels.append(label)
  1945 +
  1946 + try:
  1947 + weight = call[PRUNE_RATIO]
  1948 + except UndefinedEvent:
  1949 + try:
  1950 + weight = callee[PRUNE_RATIO]
  1951 + except UndefinedEvent:
  1952 + weight = 0.0
  1953 +
  1954 + label = '\n'.join(labels)
  1955 +
  1956 + self.edge(function.id, call.callee_id,
  1957 + label = label,
  1958 + color = self.color(theme.edge_color(weight)),
  1959 + fontcolor = self.color(theme.edge_color(weight)),
  1960 + fontsize = "%.2f" % theme.edge_fontsize(weight),
  1961 + penwidth = "%.2f" % theme.edge_penwidth(weight),
  1962 + labeldistance = "%.2f" % theme.edge_penwidth(weight),
  1963 + arrowsize = "%.2f" % theme.edge_arrowsize(weight),
  1964 + )
  1965 +
  1966 + self.end_graph()
  1967 +
  1968 + def begin_graph(self):
  1969 + self.write('digraph {\n')
  1970 +
  1971 + def end_graph(self):
  1972 + self.write('}\n')
  1973 +
  1974 + def attr(self, what, **attrs):
  1975 + self.write("\t")
  1976 + self.write(what)
  1977 + self.attr_list(attrs)
  1978 + self.write(";\n")
  1979 +
  1980 + def node(self, node, **attrs):
  1981 + self.write("\t")
  1982 + self.id(node)
  1983 + self.attr_list(attrs)
  1984 + self.write(";\n")
  1985 +
  1986 + def edge(self, src, dst, **attrs):
  1987 + self.write("\t")
  1988 + self.id(src)
  1989 + self.write(" -> ")
  1990 + self.id(dst)
  1991 + self.attr_list(attrs)
  1992 + self.write(";\n")
  1993 +
  1994 + def attr_list(self, attrs):
  1995 + if not attrs:
  1996 + return
  1997 + self.write(' [')
  1998 + first = True
  1999 + for name, value in attrs.iteritems():
  2000 + if first:
  2001 + first = False
  2002 + else:
  2003 + self.write(", ")
  2004 + self.id(name)
  2005 + self.write('=')
  2006 + self.id(value)
  2007 + self.write(']')
  2008 +
  2009 + def id(self, id):
  2010 + if isinstance(id, (int, float)):
  2011 + s = str(id)
  2012 + elif isinstance(id, basestring):
  2013 + if id.isalnum():
  2014 + s = id
  2015 + else:
  2016 + s = self.escape(id)
  2017 + else:
  2018 + raise TypeError
  2019 + self.write(s)
  2020 +
  2021 + def color(self, (r, g, b)):
  2022 +
  2023 + def float2int(f):
  2024 + if f <= 0.0:
  2025 + return 0
  2026 + if f >= 1.0:
  2027 + return 255
  2028 + return int(255.0*f + 0.5)
  2029 +
  2030 + return "#" + "".join(["%02x" % float2int(c) for c in (r, g, b)])
  2031 +
  2032 + def escape(self, s):
  2033 + s = s.encode('utf-8')
  2034 + s = s.replace('\\', r'\\')
  2035 + s = s.replace('\n', r'\n')
  2036 + s = s.replace('\t', r'\t')
  2037 + s = s.replace('"', r'\"')
  2038 + return '"' + s + '"'
  2039 +
  2040 + def write(self, s):
  2041 + self.fp.write(s)
  2042 +
  2043 +
  2044 +class Main:
  2045 + """Main program."""
  2046 +
  2047 + themes = {
  2048 + "color": TEMPERATURE_COLORMAP,
  2049 + "pink": PINK_COLORMAP,
  2050 + "gray": GRAY_COLORMAP,
  2051 + "bw": BW_COLORMAP,
  2052 + }
  2053 +
  2054 + def main(self):
  2055 + """Main program."""
  2056 +
  2057 + parser = optparse.OptionParser(
  2058 + usage="\n\t%prog [options] [file] ...",
  2059 + version="%%prog %s" % __version__)
  2060 + parser.add_option(
  2061 + '-o', '--output', metavar='FILE',
  2062 + type="string", dest="output",
  2063 + help="output filename [stdout]")
  2064 + parser.add_option(
  2065 + '-n', '--node-thres', metavar='PERCENTAGE',
  2066 + type="float", dest="node_thres", default=0.5,
  2067 + help="eliminate nodes below this threshold [default: %default]")
  2068 + parser.add_option(
  2069 + '-e', '--edge-thres', metavar='PERCENTAGE',
  2070 + type="float", dest="edge_thres", default=0.1,
  2071 + help="eliminate edges below this threshold [default: %default]")
  2072 + parser.add_option(
  2073 + '-f', '--format',
  2074 + type="choice", choices=('prof', 'oprofile', 'sysprof', 'pstats', 'shark', 'sleepy', 'aqtime'),
  2075 + dest="format", default="prof",
  2076 + help="profile format: prof, oprofile, sysprof, shark, sleepy, aqtime, or pstats [default: %default]")
  2077 + parser.add_option(
  2078 + '-c', '--colormap',
  2079 + type="choice", choices=('color', 'pink', 'gray', 'bw'),
  2080 + dest="theme", default="color",
  2081 + help="color map: color, pink, gray, or bw [default: %default]")
  2082 + parser.add_option(
  2083 + '-s', '--strip',
  2084 + action="store_true",
  2085 + dest="strip", default=False,
  2086 + help="strip function parameters, template parameters, and const modifiers from demangled C++ function names")
  2087 + parser.add_option(
  2088 + '-w', '--wrap',
  2089 + action="store_true",
  2090 + dest="wrap", default=False,
  2091 + help="wrap function names")
  2092 + # add a new option to control skew of the colorization curve
  2093 + parser.add_option(
  2094 + '--skew',
  2095 + type="float", dest="theme_skew", default=1.0,
  2096 + help="skew the colorization curve. Values < 1.0 give more variety to lower percentages. Value > 1.0 give less variety to lower percentages")
  2097 + (self.options, self.args) = parser.parse_args(sys.argv[1:])
  2098 +
  2099 + if len(self.args) > 1 and self.options.format != 'pstats':
  2100 + parser.error('incorrect number of arguments')
  2101 +
  2102 + try:
  2103 + self.theme = self.themes[self.options.theme]
  2104 + except KeyError:
  2105 + parser.error('invalid colormap \'%s\'' % self.options.theme)
  2106 +
  2107 + # set skew on the theme now that it has been picked.
  2108 + if self.options.theme_skew:
  2109 + self.theme.skew = self.options.theme_skew
  2110 +
  2111 + if self.options.format == 'prof':
  2112 + if not self.args:
  2113 + fp = sys.stdin
  2114 + else:
  2115 + fp = open(self.args[0], 'rt')
  2116 + parser = GprofParser(fp)
  2117 + elif self.options.format == 'oprofile':
  2118 + if not self.args:
  2119 + fp = sys.stdin
  2120 + else:
  2121 + fp = open(self.args[0], 'rt')
  2122 + parser = OprofileParser(fp)
  2123 + elif self.options.format == 'sysprof':
  2124 + if not self.args:
  2125 + fp = sys.stdin
  2126 + else:
  2127 + fp = open(self.args[0], 'rt')
  2128 + parser = SysprofParser(fp)
  2129 + elif self.options.format == 'pstats':
  2130 + if not self.args:
  2131 + parser.error('at least a file must be specified for pstats input')
  2132 + parser = PstatsParser(*self.args)
  2133 + elif self.options.format == 'shark':
  2134 + if not self.args:
  2135 + fp = sys.stdin
  2136 + else:
  2137 + fp = open(self.args[0], 'rt')
  2138 + parser = SharkParser(fp)
  2139 + elif self.options.format == 'sleepy':
  2140 + if len(self.args) != 1:
  2141 + parser.error('exactly one file must be specified for sleepy input')
  2142 + parser = SleepyParser(self.args[0])
  2143 + elif self.options.format == 'aqtime':
  2144 + if not self.args:
  2145 + fp = sys.stdin
  2146 + else:
  2147 + fp = open(self.args[0], 'rt')
  2148 + parser = AQtimeParser(fp)
  2149 + else:
  2150 + parser.error('invalid format \'%s\'' % self.options.format)
  2151 +
  2152 + self.profile = parser.parse()
  2153 +
  2154 + if self.options.output is None:
  2155 + self.output = sys.stdout
  2156 + else:
  2157 + self.output = open(self.options.output, 'wt')
  2158 +
  2159 + self.write_graph()
  2160 +
  2161 + _parenthesis_re = re.compile(r'\([^()]*\)')
  2162 + _angles_re = re.compile(r'<[^<>]*>')
  2163 + _const_re = re.compile(r'\s+const$')
  2164 +
  2165 + def strip_function_name(self, name):
  2166 + """Remove extraneous information from C++ demangled function names."""
  2167 +
  2168 + # Strip function parameters from name by recursively removing paired parenthesis
  2169 + while True:
  2170 + name, n = self._parenthesis_re.subn('', name)
  2171 + if not n:
  2172 + break
  2173 +
  2174 + # Strip const qualifier
  2175 + name = self._const_re.sub('', name)
  2176 +
  2177 + # Strip template parameters from name by recursively removing paired angles
  2178 + while True:
  2179 + name, n = self._angles_re.subn('', name)
  2180 + if not n:
  2181 + break
  2182 +
  2183 + return name
  2184 +
  2185 + def wrap_function_name(self, name):
  2186 + """Split the function name on multiple lines."""
  2187 +
  2188 + if len(name) > 32:
  2189 + ratio = 2.0/3.0
  2190 + height = max(int(len(name)/(1.0 - ratio) + 0.5), 1)
  2191 + width = max(len(name)/height, 32)
  2192 + # TODO: break lines in symbols
  2193 + name = textwrap.fill(name, width, break_long_words=False)
  2194 +
  2195 + # Take away spaces
  2196 + name = name.replace(", ", ",")
  2197 + name = name.replace("> >", ">>")
  2198 + name = name.replace("> >", ">>") # catch consecutive
  2199 +
  2200 + return name
  2201 +
  2202 + def compress_function_name(self, name):
  2203 + """Compress function name according to the user preferences."""
  2204 +
  2205 + if self.options.strip:
  2206 + name = self.strip_function_name(name)
  2207 +
  2208 + if self.options.wrap:
  2209 + name = self.wrap_function_name(name)
  2210 +
  2211 + # TODO: merge functions with same resulting name
  2212 +
  2213 + return name
  2214 +
  2215 + def write_graph(self):
  2216 + dot = DotWriter(self.output)
  2217 + profile = self.profile
  2218 + profile.prune(self.options.node_thres/100.0, self.options.edge_thres/100.0)
  2219 +
  2220 + for function in profile.functions.itervalues():
  2221 + function.name = self.compress_function_name(function.name)
  2222 +
  2223 + dot.graph(profile, self.theme)
  2224 +
  2225 +
  2226 +if __name__ == '__main__':
  2227 + Main().main()
This file is too large to display.
  1 +gprof图形化输出工具: gprof2dot.py graphviz-2.18.tar.gz build_gprof2dot.sh
  2 +
  3 +dot:
  4 + graphviz-2.18.tar.gz 绘图工具
  5 + build_gprof2dot.sh 编译graphviz,命令为dot。
  6 + 要求是sudoer,需要sudo make install。
  7 +
  8 +gprof2dot.py:
  9 + 将gprof的日志绘图。
  10 +
  11 +使用方法:
  12 +1. srs配置时,指定 --with-pg
  13 + 脚本会加入编译参数"-pg -lc_p",gcc -g -pg -lc_p -c xxx -o xxx.o,即在configure中打开 Performance="-pg -lc_p"
  14 + 链接时,加入链接选项"-pg",否则无法工作:gcc -pg -o srs xxxx.o,即在configure中打开 PerformanceLink="-pg"
  15 +2. 编译和启动程序:make && ./objs/srs -c conf/srs.conf
  16 + 退出程序,按CTRL+C,可以看到生成了gmon.out,这个就是性能的统计数据。
  17 +3. gprof生成报表:
  18 + 用gprof生成报表:gprof -b ./objs/srs gmon.out > ~/t.log
  19 +4. 将报表生成图片:
  20 + ../3rdparty/gprof/gprof2dot.py ~/t.log | dot -Tpng -o ~/out.png
  21 +
  22 +缩写语句:
  23 + # 生成 ~/winlin.log ~/winlin.png
  24 + rm -f gmon.out; ./objs/srs -c conf/srs.conf
  25 + # 用户按CTRL+C
  26 + file="winlin";gprof -b ./objs/srs gmon.out > ~/${file}.log; ../thirdparty/gprof2dot.py ~/${file}.log| dot -Tpng -o ~/${file}.png
  27 +
  28 +备注:
  29 + 其实gprof生成的日志就可以看,不一定要图形化。
  30 + 也就是dot和gprof2dot都不用执行。