| #include <CoreFoundation/CoreFoundation.h> |
| |
| #include "lldb-perf/lib/Measurement.h" |
| #include "lldb-perf/lib/Metric.h" |
| #include "lldb-perf/lib/TestCase.h" |
| #include "lldb-perf/lib/Timer.h" |
| #include "lldb-perf/lib/Xcode.h" |
| |
| #include <getopt.h> |
| #include <string> |
| #include <unistd.h> |
| |
| using namespace lldb_perf; |
| |
| class StepTest : public TestCase { |
| typedef void (*no_function)(void); |
| |
| public: |
| StepTest(bool use_single_stepping = false) |
| : m_main_source("stepping-testcase.cpp"), |
| m_use_single_stepping(use_single_stepping), |
| m_time_measurements(nullptr) {} |
| |
| virtual ~StepTest() {} |
| |
| virtual bool Setup(int &argc, const char **&argv) { |
| TestCase::Setup(argc, argv); |
| |
| // Toggle the fast stepping command on or off as required. |
| const char *single_step_cmd = "settings set target.use-fast-stepping false"; |
| const char *fast_step_cmd = "settings set target.use-fast-stepping true"; |
| const char *cmd_to_use; |
| |
| if (m_use_single_stepping) |
| cmd_to_use = single_step_cmd; |
| else |
| cmd_to_use = fast_step_cmd; |
| |
| SBCommandReturnObject return_object; |
| m_debugger.GetCommandInterpreter().HandleCommand(cmd_to_use, return_object); |
| if (!return_object.Succeeded()) { |
| if (return_object.GetError() != NULL) |
| printf("Got an error running settings set: %s.\n", |
| return_object.GetError()); |
| else |
| printf("Failed running settings set, no error.\n"); |
| } |
| |
| m_target = m_debugger.CreateTarget(m_app_path.c_str()); |
| m_first_bp = m_target.BreakpointCreateBySourceRegex( |
| "Here is some code to stop at originally.", m_main_source); |
| |
| const char *file_arg = m_app_path.c_str(); |
| const char *empty = nullptr; |
| const char *args[] = {file_arg, empty}; |
| SBLaunchInfo launch_info(args); |
| |
| return Launch(launch_info); |
| } |
| |
| void WriteResults(Results &results) { |
| // Gotta turn off the last timer now. |
| m_individual_step_times.push_back(m_time_measurements.Stop()); |
| |
| size_t num_time_measurements = m_individual_step_times.size(); |
| |
| Results::Dictionary &results_dict = results.GetDictionary(); |
| const char *short_format_string = "step-time-%0.2d"; |
| const size_t short_size = strlen(short_format_string) + 5; |
| char short_buffer[short_size]; |
| const char *long_format_string = |
| "The time it takes for step %d in the step sequence."; |
| const size_t long_size = strlen(long_format_string) + 5; |
| char long_buffer[long_size]; |
| |
| for (size_t i = 0; i < num_time_measurements; i++) { |
| snprintf(short_buffer, short_size, short_format_string, i); |
| snprintf(long_buffer, long_size, long_format_string, i); |
| |
| results_dict.AddDouble(short_buffer, long_buffer, |
| m_individual_step_times[i]); |
| } |
| results_dict.AddDouble("total-time", "Total time spent stepping.", |
| m_time_measurements.GetMetric().GetSum()); |
| results_dict.AddDouble( |
| "stddev-time", "StdDev of time spent stepping.", |
| m_time_measurements.GetMetric().GetStandardDeviation()); |
| |
| results.Write(m_out_path.c_str()); |
| } |
| |
| const char *GetExecutablePath() const { |
| if (m_app_path.empty()) |
| return NULL; |
| return m_app_path.c_str(); |
| } |
| |
| const char *GetResultFilePath() const { |
| if (m_out_path.empty()) |
| return NULL; |
| return m_out_path.c_str(); |
| } |
| |
| void SetExecutablePath(const char *path) { |
| if (path && path[0]) |
| m_app_path = path; |
| else |
| m_app_path.clear(); |
| } |
| |
| void SetResultFilePath(const char *path) { |
| if (path && path[0]) |
| m_out_path = path; |
| else |
| m_out_path.clear(); |
| } |
| |
| void SetUseSingleStep(bool use_it) { m_use_single_stepping = use_it; } |
| |
| private: |
| virtual void TestStep(int counter, ActionWanted &next_action) { |
| if (counter > 0) { |
| m_individual_step_times.push_back(m_time_measurements.Stop()); |
| } |
| |
| // Disable the breakpoint, just in case it gets multiple locations we don't |
| // want that confusing the stepping. |
| if (counter == 0) |
| m_first_bp.SetEnabled(false); |
| |
| next_action.StepOver(m_process.GetThreadAtIndex(0)); |
| m_time_measurements.Start(); |
| } |
| |
| SBBreakpoint m_first_bp; |
| SBFileSpec m_main_source; |
| TimeMeasurement<no_function> m_time_measurements; |
| std::vector<double> m_individual_step_times; |
| bool m_use_single_stepping; |
| std::string m_app_path; |
| std::string m_out_path; |
| }; |
| |
| struct Options { |
| std::string test_file_path; |
| std::string out_file; |
| bool verbose; |
| bool fast_step; |
| bool error; |
| bool print_help; |
| |
| Options() |
| : verbose(false), fast_step(true), error(false), print_help(false) {} |
| }; |
| |
| static struct option g_long_options[] = { |
| {"verbose", no_argument, NULL, 'v'}, |
| {"single-step", no_argument, NULL, 's'}, |
| {"test-file", required_argument, NULL, 't'}, |
| {"out-file", required_argument, NULL, 'o'}, |
| {NULL, 0, NULL, 0}}; |
| |
| std::string GetShortOptionString(struct option *long_options) { |
| std::string option_string; |
| for (int i = 0; long_options[i].name != NULL; ++i) { |
| if (long_options[i].flag == NULL) { |
| option_string.push_back((char)long_options[i].val); |
| switch (long_options[i].has_arg) { |
| default: |
| case no_argument: |
| break; |
| case required_argument: |
| option_string.push_back(':'); |
| break; |
| case optional_argument: |
| option_string.append(2, ':'); |
| break; |
| } |
| } |
| } |
| return option_string; |
| } |
| |
| int main(int argc, const char *argv[]) { |
| |
| // Prepare for & make calls to getopt_long_only. |
| |
| std::string short_option_string(GetShortOptionString(g_long_options)); |
| |
| StepTest test; |
| |
| Options option_data; |
| bool done = false; |
| |
| #if __GLIBC__ |
| optind = 0; |
| #else |
| optreset = 1; |
| optind = 1; |
| #endif |
| while (!done) { |
| int long_options_index = -1; |
| const int short_option = ::getopt_long_only( |
| argc, const_cast<char **>(argv), short_option_string.c_str(), |
| g_long_options, &long_options_index); |
| |
| switch (short_option) { |
| case 0: |
| // Already handled |
| break; |
| |
| case -1: |
| done = true; |
| break; |
| |
| case '?': |
| option_data.print_help = true; |
| break; |
| |
| case 'h': |
| option_data.print_help = true; |
| break; |
| |
| case 'v': |
| option_data.verbose = true; |
| break; |
| |
| case 's': |
| option_data.fast_step = false; |
| test.SetUseSingleStep(true); |
| break; |
| |
| case 't': { |
| SBFileSpec file(optarg); |
| if (file.Exists()) |
| test.SetExecutablePath(optarg); |
| else |
| fprintf(stderr, "error: file specified in --test-file (-t) option " |
| "doesn't exist: '%s'\n", |
| optarg); |
| } break; |
| |
| case 'o': |
| test.SetResultFilePath(optarg); |
| break; |
| |
| default: |
| option_data.error = true; |
| option_data.print_help = true; |
| fprintf(stderr, "error: unrecognized option %c\n", short_option); |
| break; |
| } |
| } |
| |
| if (option_data.print_help) { |
| puts(R"( |
| NAME |
| lldb-perf-stepping -- a tool that measures LLDB peformance of simple stepping operations. |
| |
| SYNOPSIS |
| lldb-perf-stepping --test-file=FILE [--out-file=PATH --verbose --fast-step] |
| |
| DESCRIPTION |
| Runs a set of stepping operations, timing each step and outputs results |
| to a plist file. |
| )"); |
| exit(0); |
| } |
| if (option_data.error) { |
| exit(1); |
| } |
| |
| if (test.GetExecutablePath() == NULL) { |
| // --clang is mandatory |
| option_data.print_help = true; |
| option_data.error = true; |
| fprintf(stderr, "error: the '--test-file=PATH' option is mandatory\n"); |
| } |
| |
| // Update argc and argv after parsing options |
| argc -= optind; |
| argv += optind; |
| |
| test.SetVerbose(true); |
| TestCase::Run(test, argc, argv); |
| return 0; |
| } |