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