Home | History | Annotate | Download | only in scripts
      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())
    130