1 The lldb-perf infrastructure for LLDB performance testing 2 =========================================================== 3 4 lldb-perf is an infrastructure meant to simplify the creation of performance 5 tests for the LLDB debugger. It is contained in liblldbperf.a which is part of 6 the standard opensource checkout of LLDB 7 8 Its main concepts are: 9 - Gauges: a gauge is a thing that takes a sample. Samples include elapsed time, 10 memory used, and energy consumed. 11 - Metrics: a metric is a collection of samples that knows how to do statistics 12 like sum() and average(). Metrics can be extended as needed. 13 - Measurements: a measurement is the thing that stores an action, a gauge and 14 a metric. You define measurements as in take the time to run this function, 15 take the memory to run this block of code, and then after you invoke it, 16 your stats will automagically be there. 17 - Tests: a test is a sequence of steps and measurements. 18 19 Tests cases should be added as targets to the lldbperf.xcodeproj project. It 20 is probably easiest to duplicate one of the existing targets. In order to 21 write a test based on lldb-perf, you need to subclass lldb_perf::TestCase: 22 23 using namespace lldb_perf; 24 25 class FormattersTest : public TestCase 26 { 27 28 Usually, you will define measurements as variables of your test case class: 29 30 private: 31 // C++ formatters 32 TimeMeasurement<std::function<void(SBValue)>> m_dump_std_vector_measurement; 33 TimeMeasurement<std::function<void(SBValue)>> m_dump_std_list_measurement; 34 TimeMeasurement<std::function<void(SBValue)>> m_dump_std_map_measurement; 35 TimeMeasurement<std::function<void(SBValue)>> m_dump_std_string_measurement; 36 37 // Cocoa formatters 38 TimeMeasurement<std::function<void(SBValue)>> m_dump_nsstring_measurement; 39 TimeMeasurement<std::function<void(SBValue)>> m_dump_nsarray_measurement; 40 TimeMeasurement<std::function<void(SBValue)>> m_dump_nsdictionary_measurement; 41 TimeMeasurement<std::function<void(SBValue)>> m_dump_nsset_measurement; 42 TimeMeasurement<std::function<void(SBValue)>> m_dump_nsbundle_measurement; 43 TimeMeasurement<std::function<void(SBValue)>> m_dump_nsdate_measurement; 44 45 A TimeMeasurement is, obviously, a class that measures how much time to run 46 this block of code. The block of code is passed as an std::function which you 47 can construct with a lambda! You need to give the prototype of your block of 48 code. In this example, we run blocks of code that take an SBValue and return 49 nothing. 50 51 These blocks look like: 52 53 m_dump_std_vector_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 54 lldb_perf::Xcode::FetchVariable (value,1,false); 55 }, "std-vector", "time to dump an std::vector"); 56 57 Here we are saying: make me a measurement named std-vector, whose 58 description is time to dump an std::vector and that takes the time required 59 to call lldb_perf::Xcode::FetchVariable(value,1,false). 60 61 The Xcode class is a collection of utility functions that replicate common 62 Xcode patterns (FetchVariable unsurprisingly calls API functions that Xcode 63 could use when populating a variables view entry - the 1 means expand 1 level 64 of depth and the false means do not dump the data to stdout) 65 66 A full constructor for a TestCase looks like: 67 68 FormattersTest () : TestCase() 69 { 70 m_dump_std_vector_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 71 lldb_perf::Xcode::FetchVariable (value,1,false); 72 }, "std-vector", "time to dump an std::vector"); 73 m_dump_std_list_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 74 lldb_perf::Xcode::FetchVariable (value,1,false); 75 }, "std-list", "time to dump an std::list"); 76 m_dump_std_map_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 77 lldb_perf::Xcode::FetchVariable (value,1,false); 78 }, "std-map", "time to dump an std::map"); 79 m_dump_std_string_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 80 lldb_perf::Xcode::FetchVariable (value,1,false); 81 }, "std-string", "time to dump an std::string"); 82 83 m_dump_nsstring_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 84 lldb_perf::Xcode::FetchVariable (value,0,false); 85 }, "ns-string", "time to dump an NSString"); 86 87 m_dump_nsarray_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 88 lldb_perf::Xcode::FetchVariable (value,1,false); 89 }, "ns-array", "time to dump an NSArray"); 90 91 m_dump_nsdictionary_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 92 lldb_perf::Xcode::FetchVariable (value,1,false); 93 }, "ns-dictionary", "time to dump an NSDictionary"); 94 95 m_dump_nsset_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 96 lldb_perf::Xcode::FetchVariable (value,1,false); 97 }, "ns-set", "time to dump an NSSet"); 98 99 m_dump_nsbundle_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 100 lldb_perf::Xcode::FetchVariable (value,1,false); 101 }, "ns-bundle", "time to dump an NSBundle"); 102 103 m_dump_nsdate_measurement = CreateTimeMeasurement([] (SBValue value) -> void { 104 lldb_perf::Xcode::FetchVariable (value,0,false); 105 }, "ns-date", "time to dump an NSDate"); 106 } 107 108 Once your test case is constructed, Setup() is called on it: 109 110 virtual bool 111 Setup (int argc, const char** argv) 112 { 113 m_app_path.assign(argv[1]); 114 m_out_path.assign(argv[2]); 115 m_target = m_debugger.CreateTarget(m_app_path.c_str()); 116 m_target.BreakpointCreateByName("main"); 117 SBLaunchInfo launch_info (argv); 118 return Launch (launch_info); 119 } 120 121 Setup() returns a boolean value that indicates if setup was successful. 122 In Setup() you fill out a SBLaunchInfo with any needed settings for launching 123 your process like arguments, environment variables, working directory, and 124 much more. 125 126 The last thing you want to do in setup is call Launch(): 127 128 bool 129 Launch (coSBLaunchInfo &launch_info); 130 131 This ensures your target is now alive. Make sure to have a breakpoint created. 132 133 Once you launched, the event loop is entered. The event loop waits for stops, 134 and when it gets one, it calls your test cases TestStep() function: 135 136 virtual void 137 TestStep (int counter, ActionWanted &next_action) 138 139 the counter is the step id (a monotonically increasing counter). In TestStep() 140 you will essentially run your measurements and then return what you want the 141 driver to do by filling in the ActionWanted object named "next_action". 142 143 Possible options are: 144 - continue process next_action.Continue(); 145 - kill process next_action.Kill(); 146 - Step-out on a thread next_action.StepOut(SBThread) 147 - step-over on a thread. next_action.StepOver(SBThread) 148 149 If you use ActionWanted::Next() or ActionWanted::Finish() you need to specify 150 a thread to use. By default the TestCase class will select the first thread 151 that had a stop reason other than eStopReasonNone and place it into the 152 m_thread member variable of TestCase. This means if your test case hits a 153 breakpoint or steps, the thread that hit the breakpoint or finished the step 154 will automatically be selected in the process (m_process) and m_thread will 155 be set to this thread. If you have one or more threads that will stop with a 156 reason simultaneously, you will need to find those threads manually by 157 iterating through the process list and determine what to do next. 158 159 For your convenience TestCase has m_debugger, m_target and m_process as member 160 variables. As state above m_thread will be filled in with the first thread 161 that has a stop reason. 162 163 An example: 164 165 virtual void 166 TestStep (int counter, ActionWanted &next_action) 167 { 168 case 0: 169 m_target.BreakpointCreateByLocation("fmts_tester.mm", 68); 170 next_action.Continue(); 171 break; 172 case 1: 173 DoTest (); 174 next_action.Continue(); 175 break; 176 case 2: 177 DoTest (); 178 next_action.StepOver(m_thread); 179 break; 180 181 DoTest() is a function I define in my own class that calls the measurements: 182 void 183 DoTest () 184 { 185 SBThread thread_main(m_thread); 186 SBFrame frame_zero(thread_main.GetFrameAtIndex(0)); 187 188 m_dump_nsarray_measurement(frame_zero.FindVariable("nsarray", lldb::eDynamicCanRunTarget)); 189 m_dump_nsarray_measurement(frame_zero.FindVariable("nsmutablearray", lldb::eDynamicCanRunTarget)); 190 191 m_dump_nsdictionary_measurement(frame_zero.FindVariable("nsdictionary", lldb::eDynamicCanRunTarget)); 192 m_dump_nsdictionary_measurement(frame_zero.FindVariable("nsmutabledictionary", lldb::eDynamicCanRunTarget)); 193 194 m_dump_nsstring_measurement(frame_zero.FindVariable("str0", lldb::eDynamicCanRunTarget)); 195 m_dump_nsstring_measurement(frame_zero.FindVariable("str1", lldb::eDynamicCanRunTarget)); 196 m_dump_nsstring_measurement(frame_zero.FindVariable("str2", lldb::eDynamicCanRunTarget)); 197 m_dump_nsstring_measurement(frame_zero.FindVariable("str3", lldb::eDynamicCanRunTarget)); 198 m_dump_nsstring_measurement(frame_zero.FindVariable("str4", lldb::eDynamicCanRunTarget)); 199 200 m_dump_nsdate_measurement(frame_zero.FindVariable("me", lldb::eDynamicCanRunTarget)); 201 m_dump_nsdate_measurement(frame_zero.FindVariable("cutie", lldb::eDynamicCanRunTarget)); 202 m_dump_nsdate_measurement(frame_zero.FindVariable("mom", lldb::eDynamicCanRunTarget)); 203 m_dump_nsdate_measurement(frame_zero.FindVariable("dad", lldb::eDynamicCanRunTarget)); 204 m_dump_nsdate_measurement(frame_zero.FindVariable("today", lldb::eDynamicCanRunTarget)); 205 206 m_dump_nsbundle_measurement(frame_zero.FindVariable("bundles", lldb::eDynamicCanRunTarget)); 207 m_dump_nsbundle_measurement(frame_zero.FindVariable("frameworks", lldb::eDynamicCanRunTarget)); 208 209 m_dump_nsset_measurement(frame_zero.FindVariable("nsset", lldb::eDynamicCanRunTarget)); 210 m_dump_nsset_measurement(frame_zero.FindVariable("nsmutableset", lldb::eDynamicCanRunTarget)); 211 212 m_dump_std_vector_measurement(frame_zero.FindVariable("vector", lldb::eDynamicCanRunTarget)); 213 m_dump_std_list_measurement(frame_zero.FindVariable("list", lldb::eDynamicCanRunTarget)); 214 m_dump_std_map_measurement(frame_zero.FindVariable("map", lldb::eDynamicCanRunTarget)); 215 216 m_dump_std_string_measurement(frame_zero.FindVariable("sstr0", lldb::eDynamicCanRunTarget)); 217 m_dump_std_string_measurement(frame_zero.FindVariable("sstr1", lldb::eDynamicCanRunTarget)); 218 m_dump_std_string_measurement(frame_zero.FindVariable("sstr2", lldb::eDynamicCanRunTarget)); 219 m_dump_std_string_measurement(frame_zero.FindVariable("sstr3", lldb::eDynamicCanRunTarget)); 220 m_dump_std_string_measurement(frame_zero.FindVariable("sstr4", lldb::eDynamicCanRunTarget)); 221 } 222 223 Essentially, you call your measurements as if they were functions, passing 224 them arguments and all, and they will do the right thing with gathering stats. 225 226 The last step is usually to KILL the inferior and bail out: 227 228 virtual ActionWanted 229 TestStep (int counter) 230 { 231 ... 232 case 9: 233 DoTest (); 234 next_action.Continue(); 235 break; 236 case 10: 237 DoTest (); 238 next_action.Continue(); 239 break; 240 default: 241 next_action.Kill(); 242 break; 243 } 244 245 246 At the end, you define a Results() function: 247 248 void 249 Results () 250 { 251 CFCMutableArray array; 252 m_dump_std_vector_measurement.Write(array); 253 m_dump_std_list_measurement.Write(array); 254 m_dump_std_map_measurement.Write(array); 255 m_dump_std_string_measurement.Write(array); 256 257 m_dump_nsstring_measurement.Write(array); 258 m_dump_nsarray_measurement.Write(array); 259 m_dump_nsdictionary_measurement.Write(array); 260 m_dump_nsset_measurement.Write(array); 261 m_dump_nsbundle_measurement.Write(array); 262 m_dump_nsdate_measurement.Write(array); 263 264 CFDataRef xmlData = CFPropertyListCreateData (kCFAllocatorDefault, 265 array.get(), 266 kCFPropertyListXMLFormat_v1_0, 267 0, 268 NULL); 269 270 CFURLRef file = CFURLCreateFromFileSystemRepresentation (NULL, 271 (const UInt8*)m_out_path.c_str(), 272 m_out_path.size(), 273 FALSE); 274 275 CFURLWriteDataAndPropertiesToResource(file,xmlData,NULL,NULL); 276 } 277 278 For now, pretty much copy this and just call Write() on all your measurements. 279 I plan to move this higher in the hierarchy (e.g. make a 280 TestCase::Write(filename) fairly soon). 281 282 Your main() will look like: 283 284 int main(int argc, const char * argv[]) 285 { 286 MyTest test; 287 TestCase::Run (test, argc, argv); 288 return 0; 289 } 290 291 If you are debugging your test, before Run() call 292 293 test.SetVerbose(true); 294 295 Feel free to send any questions and ideas for improvements. 296