xref: /llvm-project/lldb/examples/python/performance.py (revision b9c1b51e45b845debb76d8658edabca70ca56079)
1#!/usr/bin/python
2
3#----------------------------------------------------------------------
4# Be sure to add the python path that points to the LLDB shared library.
5# On MacOSX csh, tcsh:
6#   setenv PYTHONPATH /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python
7# On MacOSX sh, bash:
8#   export PYTHONPATH=/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python
9#----------------------------------------------------------------------
10
11import commands
12import optparse
13import os
14import platform
15import re
16import resource
17import sys
18import time
19import types
20
21#----------------------------------------------------------------------
22# Code that auto imports LLDB
23#----------------------------------------------------------------------
24try:
25    # Just try for LLDB in case PYTHONPATH is already correctly setup
26    import lldb
27except ImportError:
28    lldb_python_dirs = list()
29    # lldb is not in the PYTHONPATH, try some defaults for the current platform
30    platform_system = platform.system()
31    if platform_system == 'Darwin':
32        # On Darwin, try the currently selected Xcode directory
33        xcode_dir = commands.getoutput("xcode-select --print-path")
34        if xcode_dir:
35            lldb_python_dirs.append(
36                os.path.realpath(
37                    xcode_dir +
38                    '/../SharedFrameworks/LLDB.framework/Resources/Python'))
39            lldb_python_dirs.append(
40                xcode_dir + '/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
41        lldb_python_dirs.append(
42            '/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
43    success = False
44    for lldb_python_dir in lldb_python_dirs:
45        if os.path.exists(lldb_python_dir):
46            if not (sys.path.__contains__(lldb_python_dir)):
47                sys.path.append(lldb_python_dir)
48                try:
49                    import lldb
50                except ImportError:
51                    pass
52                else:
53                    print 'imported lldb from: "%s"' % (lldb_python_dir)
54                    success = True
55                    break
56    if not success:
57        print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
58        sys.exit(1)
59
60
61class Timer:
62
63    def __enter__(self):
64        self.start = time.clock()
65        return self
66
67    def __exit__(self, *args):
68        self.end = time.clock()
69        self.interval = self.end - self.start
70
71
72class Action(object):
73    """Class that encapsulates actions to take when a thread stops for a reason."""
74
75    def __init__(self, callback=None, callback_owner=None):
76        self.callback = callback
77        self.callback_owner = callback_owner
78
79    def ThreadStopped(self, thread):
80        assert False, "performance.Action.ThreadStopped(self, thread) must be overridden in a subclass"
81
82
83class PlanCompleteAction (Action):
84
85    def __init__(self, callback=None, callback_owner=None):
86        Action.__init__(self, callback, callback_owner)
87
88    def ThreadStopped(self, thread):
89        if thread.GetStopReason() == lldb.eStopReasonPlanComplete:
90            if self.callback:
91                if self.callback_owner:
92                    self.callback(self.callback_owner, thread)
93                else:
94                    self.callback(thread)
95            return True
96        return False
97
98
99class BreakpointAction (Action):
100
101    def __init__(
102            self,
103            callback=None,
104            callback_owner=None,
105            name=None,
106            module=None,
107            file=None,
108            line=None,
109            breakpoint=None):
110        Action.__init__(self, callback, callback_owner)
111        self.modules = lldb.SBFileSpecList()
112        self.files = lldb.SBFileSpecList()
113        self.breakpoints = list()
114        # "module" can be a list or a string
115        if breakpoint:
116            self.breakpoints.append(breakpoint)
117        else:
118            if module:
119                if isinstance(module, types.ListType):
120                    for module_path in module:
121                        self.modules.Append(
122                            lldb.SBFileSpec(module_path, False))
123                elif isinstance(module, types.StringTypes):
124                    self.modules.Append(lldb.SBFileSpec(module, False))
125            if name:
126                # "file" can be a list or a string
127                if file:
128                    if isinstance(file, types.ListType):
129                        self.files = lldb.SBFileSpecList()
130                        for f in file:
131                            self.files.Append(lldb.SBFileSpec(f, False))
132                    elif isinstance(file, types.StringTypes):
133                        self.files.Append(lldb.SBFileSpec(file, False))
134                self.breakpoints.append(
135                    self.target.BreakpointCreateByName(
136                        name, self.modules, self.files))
137            elif file and line:
138                self.breakpoints.append(
139                    self.target.BreakpointCreateByLocation(
140                        file, line))
141
142    def ThreadStopped(self, thread):
143        if thread.GetStopReason() == lldb.eStopReasonBreakpoint:
144            for bp in self.breakpoints:
145                if bp.GetID() == thread.GetStopReasonDataAtIndex(0):
146                    if self.callback:
147                        if self.callback_owner:
148                            self.callback(self.callback_owner, thread)
149                        else:
150                            self.callback(thread)
151                    return True
152        return False
153
154
155class TestCase:
156    """Class that aids in running performance tests."""
157
158    def __init__(self):
159        self.verbose = False
160        self.debugger = lldb.SBDebugger.Create()
161        self.target = None
162        self.process = None
163        self.thread = None
164        self.launch_info = None
165        self.done = False
166        self.listener = self.debugger.GetListener()
167        self.user_actions = list()
168        self.builtin_actions = list()
169        self.bp_id_to_dict = dict()
170
171    def Setup(self, args):
172        self.launch_info = lldb.SBLaunchInfo(args)
173
174    def Run(self, args):
175        assert False, "performance.TestCase.Run(self, args) must be subclassed"
176
177    def Launch(self):
178        if self.target:
179            error = lldb.SBError()
180            self.process = self.target.Launch(self.launch_info, error)
181            if not error.Success():
182                print "error: %s" % error.GetCString()
183            if self.process:
184                self.process.GetBroadcaster().AddListener(self.listener,
185                                                          lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitInterrupt)
186                return True
187        return False
188
189    def WaitForNextProcessEvent(self):
190        event = None
191        if self.process:
192            while event is None:
193                process_event = lldb.SBEvent()
194                if self.listener.WaitForEvent(lldb.UINT32_MAX, process_event):
195                    state = lldb.SBProcess.GetStateFromEvent(process_event)
196                    if self.verbose:
197                        print "event = %s" % (lldb.SBDebugger.StateAsCString(state))
198                    if lldb.SBProcess.GetRestartedFromEvent(process_event):
199                        continue
200                    if state == lldb.eStateInvalid or state == lldb.eStateDetached or state == lldb.eStateCrashed or state == lldb.eStateUnloaded or state == lldb.eStateExited:
201                        event = process_event
202                        self.done = True
203                    elif state == lldb.eStateConnected or state == lldb.eStateAttaching or state == lldb.eStateLaunching or state == lldb.eStateRunning or state == lldb.eStateStepping or state == lldb.eStateSuspended:
204                        continue
205                    elif state == lldb.eStateStopped:
206                        event = process_event
207                        call_test_step = True
208                        fatal = False
209                        selected_thread = False
210                        for thread in self.process:
211                            frame = thread.GetFrameAtIndex(0)
212                            select_thread = False
213
214                            stop_reason = thread.GetStopReason()
215                            if self.verbose:
216                                print "tid = %#x pc = %#x " % (thread.GetThreadID(), frame.GetPC()),
217                            if stop_reason == lldb.eStopReasonNone:
218                                if self.verbose:
219                                    print "none"
220                            elif stop_reason == lldb.eStopReasonTrace:
221                                select_thread = True
222                                if self.verbose:
223                                    print "trace"
224                            elif stop_reason == lldb.eStopReasonPlanComplete:
225                                select_thread = True
226                                if self.verbose:
227                                    print "plan complete"
228                            elif stop_reason == lldb.eStopReasonThreadExiting:
229                                if self.verbose:
230                                    print "thread exiting"
231                            elif stop_reason == lldb.eStopReasonExec:
232                                if self.verbose:
233                                    print "exec"
234                            elif stop_reason == lldb.eStopReasonInvalid:
235                                if self.verbose:
236                                    print "invalid"
237                            elif stop_reason == lldb.eStopReasonException:
238                                select_thread = True
239                                if self.verbose:
240                                    print "exception"
241                                fatal = True
242                            elif stop_reason == lldb.eStopReasonBreakpoint:
243                                select_thread = True
244                                bp_id = thread.GetStopReasonDataAtIndex(0)
245                                bp_loc_id = thread.GetStopReasonDataAtIndex(1)
246                                if self.verbose:
247                                    print "breakpoint id = %d.%d" % (bp_id, bp_loc_id)
248                            elif stop_reason == lldb.eStopReasonWatchpoint:
249                                select_thread = True
250                                if self.verbose:
251                                    print "watchpoint id = %d" % (thread.GetStopReasonDataAtIndex(0))
252                            elif stop_reason == lldb.eStopReasonSignal:
253                                select_thread = True
254                                if self.verbose:
255                                    print "signal %d" % (thread.GetStopReasonDataAtIndex(0))
256
257                            if select_thread and not selected_thread:
258                                self.thread = thread
259                                selected_thread = self.process.SetSelectedThread(
260                                    thread)
261
262                            for action in self.user_actions:
263                                action.ThreadStopped(thread)
264
265                        if fatal:
266                            # if self.verbose:
267                            #     Xcode.RunCommand(self.debugger,"bt all",true)
268                            sys.exit(1)
269        return event
270
271
272class Measurement:
273    '''A class that encapsulates a measurement'''
274
275    def __init__(self):
276        object.__init__(self)
277
278    def Measure(self):
279        assert False, "performance.Measurement.Measure() must be subclassed"
280
281
282class MemoryMeasurement(Measurement):
283    '''A class that can measure memory statistics for a process.'''
284
285    def __init__(self, pid):
286        Measurement.__init__(self)
287        self.pid = pid
288        self.stats = [
289            "rprvt",
290            "rshrd",
291            "rsize",
292            "vsize",
293            "vprvt",
294            "kprvt",
295            "kshrd",
296            "faults",
297            "cow",
298            "pageins"]
299        self.command = "top -l 1 -pid %u -stats %s" % (
300            self.pid, ",".join(self.stats))
301        self.value = dict()
302
303    def Measure(self):
304        output = commands.getoutput(self.command).split("\n")[-1]
305        values = re.split('[-+\s]+', output)
306        for (idx, stat) in enumerate(values):
307            multiplier = 1
308            if stat:
309                if stat[-1] == 'K':
310                    multiplier = 1024
311                    stat = stat[:-1]
312                elif stat[-1] == 'M':
313                    multiplier = 1024 * 1024
314                    stat = stat[:-1]
315                elif stat[-1] == 'G':
316                    multiplier = 1024 * 1024 * 1024
317                elif stat[-1] == 'T':
318                    multiplier = 1024 * 1024 * 1024 * 1024
319                    stat = stat[:-1]
320                self.value[self.stats[idx]] = int(stat) * multiplier
321
322    def __str__(self):
323        '''Dump the MemoryMeasurement current value'''
324        s = ''
325        for key in self.value.keys():
326            if s:
327                s += "\n"
328            s += "%8s = %s" % (key, self.value[key])
329        return s
330
331
332class TesterTestCase(TestCase):
333
334    def __init__(self):
335        TestCase.__init__(self)
336        self.verbose = True
337        self.num_steps = 5
338
339    def BreakpointHit(self, thread):
340        bp_id = thread.GetStopReasonDataAtIndex(0)
341        loc_id = thread.GetStopReasonDataAtIndex(1)
342        print "Breakpoint %i.%i hit: %s" % (bp_id, loc_id, thread.process.target.FindBreakpointByID(bp_id))
343        thread.StepOver()
344
345    def PlanComplete(self, thread):
346        if self.num_steps > 0:
347            thread.StepOver()
348            self.num_steps = self.num_steps - 1
349        else:
350            thread.process.Kill()
351
352    def Run(self, args):
353        self.Setup(args)
354        with Timer() as total_time:
355            self.target = self.debugger.CreateTarget(args[0])
356            if self.target:
357                with Timer() as breakpoint_timer:
358                    bp = self.target.BreakpointCreateByName("main")
359                print(
360                    'Breakpoint time = %.03f sec.' %
361                    breakpoint_timer.interval)
362
363                self.user_actions.append(
364                    BreakpointAction(
365                        breakpoint=bp,
366                        callback=TesterTestCase.BreakpointHit,
367                        callback_owner=self))
368                self.user_actions.append(
369                    PlanCompleteAction(
370                        callback=TesterTestCase.PlanComplete,
371                        callback_owner=self))
372
373                if self.Launch():
374                    while not self.done:
375                        self.WaitForNextProcessEvent()
376                else:
377                    print "error: failed to launch process"
378            else:
379                print "error: failed to create target with '%s'" % (args[0])
380        print('Total time = %.03f sec.' % total_time.interval)
381
382
383if __name__ == '__main__':
384    lldb.SBDebugger.Initialize()
385    test = TesterTestCase()
386    test.Run(sys.argv[1:])
387    mem = MemoryMeasurement(os.getpid())
388    mem.Measure()
389    print str(mem)
390    lldb.SBDebugger.Terminate()
391    # print "sleeeping for 100 seconds"
392    # time.sleep(100)
393