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