1"""
2Some helper functions to analyze the output of sys.getdxp() (which is
3only available if Python was built with -DDYNAMIC_EXECUTION_PROFILE).
4These will tell you which opcodes have been executed most frequently
5in the current process, and, if Python was also built with -DDXPAIRS,
6will tell you which instruction _pairs_ were executed most frequently,
7which may help in choosing new instructions.
8
9If Python was built without -DDYNAMIC_EXECUTION_PROFILE, importing
10this module will raise a RuntimeError.
11
12If you're running a script you want to profile, a simple way to get
13the 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
23import copy
24import opcode
25import operator
26import sys
27import threading
28
29if 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.
39def 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
46def 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
54def 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
71def 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
78def 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
96def 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
116def 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