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