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