xref: /openbsd-src/gnu/llvm/lldb/examples/python/performance.py (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1#!/usr/bin/env 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 optparse
12import os
13import platform
14import re
15import resource
16import sys
17import subprocess
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 = subprocess.check_output("xcode-select --print-path", shell=True)
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()), end=' ')
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                            elif stop_reason == lldb.eStopReasonFork:
257                                if self.verbose:
258                                    print("fork pid = %d" % (thread.GetStopReasonDataAtIndex(0)))
259                            elif stop_reason == lldb.eStopReasonVFork:
260                                if self.verbose:
261                                    print("vfork pid = %d" % (thread.GetStopReasonDataAtIndex(0)))
262                            elif stop_reason == lldb.eStopReasonVForkDone:
263                                if self.verbose:
264                                    print("vfork done")
265
266                            if select_thread and not selected_thread:
267                                self.thread = thread
268                                selected_thread = self.process.SetSelectedThread(
269                                    thread)
270
271                            for action in self.user_actions:
272                                action.ThreadStopped(thread)
273
274                        if fatal:
275                            # if self.verbose:
276                            #     Xcode.RunCommand(self.debugger,"bt all",true)
277                            sys.exit(1)
278        return event
279
280
281class Measurement:
282    '''A class that encapsulates a measurement'''
283
284    def __init__(self):
285        object.__init__(self)
286
287    def Measure(self):
288        assert False, "performance.Measurement.Measure() must be subclassed"
289
290
291class MemoryMeasurement(Measurement):
292    '''A class that can measure memory statistics for a process.'''
293
294    def __init__(self, pid):
295        Measurement.__init__(self)
296        self.pid = pid
297        self.stats = [
298            "rprvt",
299            "rshrd",
300            "rsize",
301            "vsize",
302            "vprvt",
303            "kprvt",
304            "kshrd",
305            "faults",
306            "cow",
307            "pageins"]
308        self.command = "top -l 1 -pid %u -stats %s" % (
309            self.pid, ",".join(self.stats))
310        self.value = dict()
311
312    def Measure(self):
313        output = subprocess.getoutput(self.command).split("\n")[-1]
314        values = re.split('[-+\s]+', output)
315        for (idx, stat) in enumerate(values):
316            multiplier = 1
317            if stat:
318                if stat[-1] == 'K':
319                    multiplier = 1024
320                    stat = stat[:-1]
321                elif stat[-1] == 'M':
322                    multiplier = 1024 * 1024
323                    stat = stat[:-1]
324                elif stat[-1] == 'G':
325                    multiplier = 1024 * 1024 * 1024
326                elif stat[-1] == 'T':
327                    multiplier = 1024 * 1024 * 1024 * 1024
328                    stat = stat[:-1]
329                self.value[self.stats[idx]] = int(stat) * multiplier
330
331    def __str__(self):
332        '''Dump the MemoryMeasurement current value'''
333        s = ''
334        for key in self.value.keys():
335            if s:
336                s += "\n"
337            s += "%8s = %s" % (key, self.value[key])
338        return s
339
340
341class TesterTestCase(TestCase):
342
343    def __init__(self):
344        TestCase.__init__(self)
345        self.verbose = True
346        self.num_steps = 5
347
348    def BreakpointHit(self, thread):
349        bp_id = thread.GetStopReasonDataAtIndex(0)
350        loc_id = thread.GetStopReasonDataAtIndex(1)
351        print("Breakpoint %i.%i hit: %s" % (bp_id, loc_id, thread.process.target.FindBreakpointByID(bp_id)))
352        thread.StepOver()
353
354    def PlanComplete(self, thread):
355        if self.num_steps > 0:
356            thread.StepOver()
357            self.num_steps = self.num_steps - 1
358        else:
359            thread.process.Kill()
360
361    def Run(self, args):
362        self.Setup(args)
363        with Timer() as total_time:
364            self.target = self.debugger.CreateTarget(args[0])
365            if self.target:
366                with Timer() as breakpoint_timer:
367                    bp = self.target.BreakpointCreateByName("main")
368                print(
369                    'Breakpoint time = %.03f sec.' %
370                    breakpoint_timer.interval)
371
372                self.user_actions.append(
373                    BreakpointAction(
374                        breakpoint=bp,
375                        callback=TesterTestCase.BreakpointHit,
376                        callback_owner=self))
377                self.user_actions.append(
378                    PlanCompleteAction(
379                        callback=TesterTestCase.PlanComplete,
380                        callback_owner=self))
381
382                if self.Launch():
383                    while not self.done:
384                        self.WaitForNextProcessEvent()
385                else:
386                    print("error: failed to launch process")
387            else:
388                print("error: failed to create target with '%s'" % (args[0]))
389        print('Total time = %.03f sec.' % total_time.interval)
390
391
392if __name__ == '__main__':
393    lldb.SBDebugger.Initialize()
394    test = TesterTestCase()
395    test.Run(sys.argv[1:])
396    mem = MemoryMeasurement(os.getpid())
397    mem.Measure()
398    print(str(mem))
399    lldb.SBDebugger.Terminate()
400    # print "sleeeping for 100 seconds"
401    # time.sleep(100)
402