diff options
Diffstat (limited to 'tools/lldb-perf/README')
-rw-r--r-- | tools/lldb-perf/README | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/tools/lldb-perf/README b/tools/lldb-perf/README new file mode 100644 index 0000000000000..7cec4faac2c8e --- /dev/null +++ b/tools/lldb-perf/README @@ -0,0 +1,295 @@ + The lldb-perf infrastructure for LLDB performance testing +=========================================================== + +lldb-perf is an infrastructure meant to simplify the creation of performance +tests for the LLDB debugger. It is contained in liblldbperf.a which is part of +the standard opensource checkout of LLDB + +Its main concepts are: +- Gauges: a gauge is a thing that takes a sample. Samples include elapsed time, + memory used, and energy consumed. +- Metrics: a metric is a collection of samples that knows how to do statistics + like sum() and average(). Metrics can be extended as needed. +- Measurements: a measurement is the thing that stores an action, a gauge and + a metric. You define measurements as in “take the time to run this function”, + “take the memory to run this block of code”, and then after you invoke it, + your stats will automagically be there. +- Tests: a test is a sequence of steps and measurements. + +Tests cases should be added as targets to the lldbperf.xcodeproj project. It +is probably easiest to duplicate one of the existing targets. In order to +write a test based on lldb-perf, you need to subclass lldb_perf::TestCase: + +using namespace lldb_perf; + +class FormattersTest : public TestCase +{ + +Usually, you will define measurements as variables of your test case class: + +private: + // C++ formatters + TimeMeasurement<std::function<void(SBValue)>> m_dump_std_vector_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_std_list_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_std_map_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_std_string_measurement; + + // Cocoa formatters + TimeMeasurement<std::function<void(SBValue)>> m_dump_nsstring_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_nsarray_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_nsdictionary_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_nsset_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_nsbundle_measurement; + TimeMeasurement<std::function<void(SBValue)>> m_dump_nsdate_measurement; + +A TimeMeasurement is, obviously, a class that measures “how much time to run +this block of code”. The block of code is passed as an std::function which you +can construct with a lambda! You need to give the prototype of your block of +code. In this example, we run blocks of code that take an SBValue and return +nothing. + +These blocks look like: + + m_dump_std_vector_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "std-vector", "time to dump an std::vector"); + +Here we are saying: make me a measurement named “std-vector”, whose +description is “time to dump an std::vector” and that takes the time required +to call lldb_perf::Xcode::FetchVariable(value,1,false). + +The Xcode class is a collection of utility functions that replicate common +Xcode patterns (FetchVariable unsurprisingly calls API functions that Xcode +could use when populating a variables view entry - the 1 means “expand 1 level +of depth” and the false means “do not dump the data to stdout”) + +A full constructor for a TestCase looks like: + +FormattersTest () : TestCase() +{ + m_dump_std_vector_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "std-vector", "time to dump an std::vector"); + m_dump_std_list_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "std-list", "time to dump an std::list"); + m_dump_std_map_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "std-map", "time to dump an std::map"); + m_dump_std_string_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "std-string", "time to dump an std::string"); + + m_dump_nsstring_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,0,false); + }, "ns-string", "time to dump an NSString"); + + m_dump_nsarray_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "ns-array", "time to dump an NSArray"); + + m_dump_nsdictionary_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "ns-dictionary", "time to dump an NSDictionary"); + + m_dump_nsset_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "ns-set", "time to dump an NSSet"); + + m_dump_nsbundle_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,1,false); + }, "ns-bundle", "time to dump an NSBundle"); + + m_dump_nsdate_measurement = CreateTimeMeasurement([] (SBValue value) -> void { + lldb_perf::Xcode::FetchVariable (value,0,false); + }, "ns-date", "time to dump an NSDate"); +} + +Once your test case is constructed, Setup() is called on it: + + virtual bool + Setup (int argc, const char** argv) + { + m_app_path.assign(argv[1]); + m_out_path.assign(argv[2]); + m_target = m_debugger.CreateTarget(m_app_path.c_str()); + m_target.BreakpointCreateByName("main"); + SBLaunchInfo launch_info (argv); + return Launch (launch_info); + } + +Setup() returns a boolean value that indicates if setup was successful. +In Setup() you fill out a SBLaunchInfo with any needed settings for launching +your process like arguments, environment variables, working directory, and +much more. + +The last thing you want to do in setup is call Launch(): + + bool + Launch (coSBLaunchInfo &launch_info); + +This ensures your target is now alive. Make sure to have a breakpoint created. + +Once you launched, the event loop is entered. The event loop waits for stops, +and when it gets one, it calls your test case’s TestStep() function: + + virtual void + TestStep (int counter, ActionWanted &next_action) + +the counter is the step id (a monotonically increasing counter). In TestStep() +you will essentially run your measurements and then return what you want the +driver to do by filling in the ActionWanted object named "next_action". + +Possible options are: +- continue process next_action.Continue(); +- kill process next_action.Kill(); +- Step-out on a thread next_action.StepOut(SBThread) +- step-over on a thread. next_action.StepOver(SBThread) + +If you use ActionWanted::Next() or ActionWanted::Finish() you need to specify +a thread to use. By default the TestCase class will select the first thread +that had a stop reason other than eStopReasonNone and place it into the +m_thread member variable of TestCase. This means if your test case hits a +breakpoint or steps, the thread that hit the breakpoint or finished the step +will automatically be selected in the process (m_process) and m_thread will +be set to this thread. If you have one or more threads that will stop with a +reason simultaneously, you will need to find those threads manually by +iterating through the process list and determine what to do next. + +For your convenience TestCase has m_debugger, m_target and m_process as member +variables. As state above m_thread will be filled in with the first thread +that has a stop reason. + +An example: + + virtual void + TestStep (int counter, ActionWanted &next_action) + { + case 0: + m_target.BreakpointCreateByLocation("fmts_tester.mm", 68); + next_action.Continue(); + break; + case 1: + DoTest (); + next_action.Continue(); + break; + case 2: + DoTest (); + next_action.StepOver(m_thread); + break; + +DoTest() is a function I define in my own class that calls the measurements: + void + DoTest () + { + SBThread thread_main(m_thread); + SBFrame frame_zero(thread_main.GetFrameAtIndex(0)); + + m_dump_nsarray_measurement(frame_zero.FindVariable("nsarray", lldb::eDynamicCanRunTarget)); + m_dump_nsarray_measurement(frame_zero.FindVariable("nsmutablearray", lldb::eDynamicCanRunTarget)); + + m_dump_nsdictionary_measurement(frame_zero.FindVariable("nsdictionary", lldb::eDynamicCanRunTarget)); + m_dump_nsdictionary_measurement(frame_zero.FindVariable("nsmutabledictionary", lldb::eDynamicCanRunTarget)); + + m_dump_nsstring_measurement(frame_zero.FindVariable("str0", lldb::eDynamicCanRunTarget)); + m_dump_nsstring_measurement(frame_zero.FindVariable("str1", lldb::eDynamicCanRunTarget)); + m_dump_nsstring_measurement(frame_zero.FindVariable("str2", lldb::eDynamicCanRunTarget)); + m_dump_nsstring_measurement(frame_zero.FindVariable("str3", lldb::eDynamicCanRunTarget)); + m_dump_nsstring_measurement(frame_zero.FindVariable("str4", lldb::eDynamicCanRunTarget)); + + m_dump_nsdate_measurement(frame_zero.FindVariable("me", lldb::eDynamicCanRunTarget)); + m_dump_nsdate_measurement(frame_zero.FindVariable("cutie", lldb::eDynamicCanRunTarget)); + m_dump_nsdate_measurement(frame_zero.FindVariable("mom", lldb::eDynamicCanRunTarget)); + m_dump_nsdate_measurement(frame_zero.FindVariable("dad", lldb::eDynamicCanRunTarget)); + m_dump_nsdate_measurement(frame_zero.FindVariable("today", lldb::eDynamicCanRunTarget)); + + m_dump_nsbundle_measurement(frame_zero.FindVariable("bundles", lldb::eDynamicCanRunTarget)); + m_dump_nsbundle_measurement(frame_zero.FindVariable("frameworks", lldb::eDynamicCanRunTarget)); + + m_dump_nsset_measurement(frame_zero.FindVariable("nsset", lldb::eDynamicCanRunTarget)); + m_dump_nsset_measurement(frame_zero.FindVariable("nsmutableset", lldb::eDynamicCanRunTarget)); + + m_dump_std_vector_measurement(frame_zero.FindVariable("vector", lldb::eDynamicCanRunTarget)); + m_dump_std_list_measurement(frame_zero.FindVariable("list", lldb::eDynamicCanRunTarget)); + m_dump_std_map_measurement(frame_zero.FindVariable("map", lldb::eDynamicCanRunTarget)); + + m_dump_std_string_measurement(frame_zero.FindVariable("sstr0", lldb::eDynamicCanRunTarget)); + m_dump_std_string_measurement(frame_zero.FindVariable("sstr1", lldb::eDynamicCanRunTarget)); + m_dump_std_string_measurement(frame_zero.FindVariable("sstr2", lldb::eDynamicCanRunTarget)); + m_dump_std_string_measurement(frame_zero.FindVariable("sstr3", lldb::eDynamicCanRunTarget)); + m_dump_std_string_measurement(frame_zero.FindVariable("sstr4", lldb::eDynamicCanRunTarget)); + } + +Essentially, you call your measurements as if they were functions, passing +them arguments and all, and they will do the right thing with gathering stats. + +The last step is usually to KILL the inferior and bail out: + + virtual ActionWanted + TestStep (int counter) + { +... + case 9: + DoTest (); + next_action.Continue(); + break; + case 10: + DoTest (); + next_action.Continue(); + break; + default: + next_action.Kill(); + break; + } + + +At the end, you define a Results() function: + + void + Results () + { + CFCMutableArray array; + m_dump_std_vector_measurement.Write(array); + m_dump_std_list_measurement.Write(array); + m_dump_std_map_measurement.Write(array); + m_dump_std_string_measurement.Write(array); + + m_dump_nsstring_measurement.Write(array); + m_dump_nsarray_measurement.Write(array); + m_dump_nsdictionary_measurement.Write(array); + m_dump_nsset_measurement.Write(array); + m_dump_nsbundle_measurement.Write(array); + m_dump_nsdate_measurement.Write(array); + + CFDataRef xmlData = CFPropertyListCreateData (kCFAllocatorDefault, + array.get(), + kCFPropertyListXMLFormat_v1_0, + 0, + NULL); + + CFURLRef file = CFURLCreateFromFileSystemRepresentation (NULL, + (const UInt8*)m_out_path.c_str(), + m_out_path.size(), + FALSE); + + CFURLWriteDataAndPropertiesToResource(file,xmlData,NULL,NULL); + } + +For now, pretty much copy this and just call Write() on all your measurements. +I plan to move this higher in the hierarchy (e.g. make a +TestCase::Write(filename) fairly soon). + +Your main() will look like: + +int main(int argc, const char * argv[]) +{ + MyTest test; + TestCase::Run (test, argc, argv); + return 0; +} + +If you are debugging your test, before Run() call + + test.SetVerbose(true); + +Feel free to send any questions and ideas for improvements. |