Download analze_dxp.py

1 """
    2 Some helper functions to analyze the output of sys.getdxp() (which is
    3 only available if Python was built with -DDYNAMIC_EXECUTION_PROFILE).
    4 These will tell you which opcodes have been executed most frequently
    5 in the current process, and, if Python was also built with -DDXPAIRS,
    6 will tell you which instruction _pairs_ were executed most frequently,
    7 which may help in choosing new instructions.
    8 
    9 If Python was built without -DDYNAMIC_EXECUTION_PROFILE, importing
   10 this module will raise a RuntimeError.
   11 
   12 If you're running a script you want to profile, a simple way to get
   13 the common pairs is:
   14 
   15 $ PYTHONPATH=$PYTHONPATH:<python_srcdir>/Tools/scripts \
   16 ./python -i -O the_script.py --args
   17 ...
   18 > from analyze_dxp import *
   19 > s = render_common_pairs()
   20 > open('/tmp/some_file', 'w').write(s)
   21 """
   22 
   23 import copy
   24 import opcode
   25 import operator
   26 import sys
   27 import threading
   28 
   29 if not hasattr(sys, "getdxp"):
   30     raise RuntimeError("Can't import analyze_dxp: Python built without"
   31                        " -DDYNAMIC_EXECUTION_PROFILE.")
   32 
   33 
   34 _profile_lock = threading.RLock()
   35 _cumulative_profile = sys.getdxp()
   36 
   37 # If Python was built with -DDXPAIRS, sys.getdxp() returns a list of
   38 # lists of ints.  Otherwise it returns just a list of ints.
   39 def has_pairs(profile):
   40     """Returns True if the Python that produced the argument profile
   41     was built with -DDXPAIRS."""
   42 
   43     return len(profile) > 0 and isinstance(profile[0], list)
   44 
   45 
   46 def reset_profile():
   47     """Forgets any execution profile that has been gathered so far."""
   48     with _profile_lock:
   49         sys.getdxp()  # Resets the internal profile
   50         global _cumulative_profile
   51         _cumulative_profile = sys.getdxp()  # 0s out our copy.
   52 
   53 
   54 def merge_profile():
   55     """Reads sys.getdxp() and merges it into this module's cached copy.
   56 
   57     We need this because sys.getdxp() 0s itself every time it's called."""
   58 
   59     with _profile_lock:
   60         new_profile = sys.getdxp()
   61         if has_pairs(new_profile):
   62             for first_inst in range(len(_cumulative_profile)):
   63                 for second_inst in range(len(_cumulative_profile[first_inst])):
   64                     _cumulative_profile[first_inst][second_inst] += (
   65                         new_profile[first_inst][second_inst])
   66         else:
   67             for inst in range(len(_cumulative_profile)):
   68                 _cumulative_profile[inst] += new_profile[inst]
   69 
   70 
   71 def snapshot_profile():
   72     """Returns the cumulative execution profile until this call."""
   73     with _profile_lock:
   74         merge_profile()
   75         return copy.deepcopy(_cumulative_profile)
   76 
   77 
   78 def common_instructions(profile):
   79     """Returns the most common opcodes in order of descending frequency.
   80 
   81     The result is a list of tuples of the form
   82       (opcode, opname, # of occurrences)
   83 
   84     """
   85     if has_pairs(profile) and profile:
   86         inst_list = profile[-1]
   87     else:
   88         inst_list = profile
   89     result = [(op, opcode.opname[op], count)
   90               for op, count in enumerate(inst_list)
   91               if count > 0]
   92     result.sort(key=operator.itemgetter(2), reverse=True)
   93     return result
   94 
   95 
   96 def common_pairs(profile):
   97     """Returns the most common opcode pairs in order of descending frequency.
   98 
   99     The result is a list of tuples of the form
  100       ((1st opcode, 2nd opcode),
  101        (1st opname, 2nd opname),
  102        # of occurrences of the pair)
  103 
  104     """
  105     if not has_pairs(profile):
  106         return []
  107     result = [((op1, op2), (opcode.opname[op1], opcode.opname[op2]), count)
  108               # Drop the row of single-op profiles with [:-1]
  109               for op1, op1profile in enumerate(profile[:-1])
  110               for op2, count in enumerate(op1profile)
  111               if count > 0]
  112     result.sort(key=operator.itemgetter(2), reverse=True)
  113     return result
  114 
  115 
  116 def render_common_pairs(profile=None):
  117     """Renders the most common opcode pairs to a string in order of
  118     descending frequency.
  119 
  120     The result is a series of lines of the form:
  121       # of occurrences: ('1st opname', '2nd opname')
  122 
  123     """
  124     if profile is None:
  125         profile = snapshot_profile()
  126     def seq():
  127         for _, ops, count in common_pairs(profile):
  128             yield "%s: %s\n" % (count, ops)
  129     return ''.join(seq())
Disturbed Dotterel