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 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