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