diff --git a/wperf-lib/wperf-lib.vcxproj b/wperf-lib/wperf-lib.vcxproj index dff9fdc..f682ac2 100644 --- a/wperf-lib/wperf-lib.vcxproj +++ b/wperf-lib/wperf-lib.vcxproj @@ -161,7 +161,7 @@ true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -181,7 +181,7 @@ true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -202,7 +202,7 @@ true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -223,7 +223,7 @@ true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -247,7 +247,7 @@ true true true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -272,7 +272,7 @@ true true true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -293,7 +293,7 @@ true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -314,7 +314,7 @@ true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) @@ -339,7 +339,7 @@ true true true - $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj + $(CoreLibraryDependencies);%(AdditionalDependencies);events.obj;output.obj;padding.obj;parsers.obj;pe_file.obj;pmu_device.obj;spe_device.obj;process_api.obj;user_request.obj;utils.obj;wperf.obj;metric.obj;config.obj;timeline.obj;perfdata.obj;arg_parser.obj;arg_parser_arg.obj $(SolutionDir)wperf\$(IntDir) diff --git a/wperf-test/wperf-test-arg_parser.cpp b/wperf-test/wperf-test-arg_parser.cpp index d4cebad..1dfcfcc 100644 --- a/wperf-test/wperf-test-arg_parser.cpp +++ b/wperf-test/wperf-test-arg_parser.cpp @@ -32,6 +32,7 @@ #include "CppUnitTest.h" #include #include "wperf/arg_parser.h" +#include "wperf/exception.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace ArgParser; @@ -61,7 +62,7 @@ namespace wperftest const wchar_t* argv[] = { L"wperf", L"test", L"-v", L"--json", L"random" }; const int argc = _countof(argv); arg_parser parser; - Assert::ExpectException([&parser, argc, &argv]() { + Assert::ExpectException([&parser, argc, &argv]() { parser.parse(argc, argv); } ); @@ -117,7 +118,7 @@ namespace wperftest const wchar_t* argv[] = { L"wperf", L"sample", L"--timeout" }; const int argc = _countof(argv); arg_parser parser; - Assert::ExpectException([&parser, argc, &argv]() { + Assert::ExpectException([&parser, argc, &argv]() { parser.parse(argc, argv); } ); @@ -129,7 +130,7 @@ namespace wperftest const wchar_t* argv[] = { L"wperf", L"invalid_command" }; const int argc = _countof(argv); arg_parser parser; - Assert::ExpectException([&parser, argc, &argv]() { + Assert::ExpectException([&parser, argc, &argv]() { parser.parse(argc, argv); } ); @@ -150,7 +151,7 @@ namespace wperftest const wchar_t* argv[] = { L"wperf", L"sample", L"--timeout", L"5.4", L"ms" }; const int argc = _countof(argv); arg_parser parser; - Assert::ExpectException([&parser, argc, &argv]() { + Assert::ExpectException([&parser, argc, &argv]() { parser.parse(argc, argv); } ); @@ -266,7 +267,7 @@ namespace wperftest const wchar_t* argv[] = { L"wperf", L"sample", L"--unknown" }; const int argc = _countof(argv); arg_parser parser; - Assert::ExpectException([&parser, argc, &argv]() { + Assert::ExpectException([&parser, argc, &argv]() { parser.parse(argc, argv); } ); @@ -278,7 +279,7 @@ namespace wperftest const wchar_t* argv[] = { L"wperf", L"--annotate", L"--json" }; const int argc = _countof(argv); arg_parser parser; - Assert::ExpectException([&parser, argc, &argv]() { + Assert::ExpectException([&parser, argc, &argv]() { parser.parse(argc, argv); } ); @@ -309,7 +310,7 @@ namespace wperftest Assert::IsFalse(parser.version_command.is_set()); Assert::IsFalse(parser.detect_command.is_set()); Assert::IsFalse(parser.sample_command.is_set()); - Assert::IsTrue(parser.count_command.is_set()); + Assert::IsTrue(parser.stat_command.is_set()); Assert::IsFalse(parser.man_command.is_set()); // Check all _arg flags setup @@ -358,7 +359,7 @@ namespace wperftest Assert::IsFalse(parser.version_command.is_set()); Assert::IsFalse(parser.detect_command.is_set()); Assert::IsFalse(parser.sample_command.is_set()); - Assert::IsFalse(parser.count_command.is_set()); + Assert::IsFalse(parser.stat_command.is_set()); Assert::IsFalse(parser.man_command.is_set()); // Check all _arg flags setup @@ -416,7 +417,7 @@ namespace wperftest Assert::IsFalse(parser.version_command.is_set()); Assert::IsFalse(parser.detect_command.is_set()); Assert::IsFalse(parser.sample_command.is_set()); - Assert::IsFalse(parser.count_command.is_set()); + Assert::IsFalse(parser.stat_command.is_set()); Assert::IsFalse(parser.man_command.is_set()); // Check all _opt flags setup diff --git a/wperf-test/wperf-test-arg_parser_arg-utils.cpp b/wperf-test/wperf-test-arg_parser_arg-utils.cpp index 26790c0..54bcf9d 100644 --- a/wperf-test/wperf-test-arg_parser_arg-utils.cpp +++ b/wperf-test/wperf-test-arg_parser_arg-utils.cpp @@ -69,21 +69,21 @@ namespace wperftest { std::wstring input = L"Line one.\nLine two is a bit longer.\nShort."; size_t max_width = 15; - std::wstring expected = L"Line one.\n\nLine two is a\nbit longer.\n\nShort."; + std::wstring expected = L"Line one.\nLine two is a\nbit longer.\nShort."; Assert::AreEqual(expected, arg_parser_format_string_to_length(input, max_width)); } TEST_METHOD(TestTrailingNewlineRemoval) { std::wstring input = L"Line one.\nLine two is a bit longer.\nShort.\n"; size_t max_width = 15; - std::wstring expected = L"Line one.\n\nLine two is a\nbit longer.\n\nShort."; + std::wstring expected = L"Line one.\nLine two is a\nbit longer.\nShort."; Assert::AreEqual(expected, arg_parser_format_string_to_length(input, max_width)); } TEST_METHOD(TestMultipleLinesAndMultipleTrailingReturnToLines) { std::wstring input = L"Line one.\nLine two is a bit longer.\nShort.\n\n\n\n\n\n\n"; size_t max_width = 15; - std::wstring expected = L"Line one.\n\nLine two is a\nbit longer.\n\nShort.\n\n\n\n\n\n"; + std::wstring expected = L"Line one.\nLine two is a\nbit longer.\nShort.\n\n\n\n\n\n"; Assert::AreEqual(expected, arg_parser_format_string_to_length(input, max_width)); } TEST_METHOD(MultipleRetrunToLines) diff --git a/wperf/arg_parser.cpp b/wperf/arg_parser.cpp index f512c59..dd7eb3f 100644 --- a/wperf/arg_parser.cpp +++ b/wperf/arg_parser.cpp @@ -28,13 +28,15 @@ // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "arg_parser.h" #include #include #include #include #include #include +#include "arg_parser.h" +#include "output.h" +#include "exception.h" namespace ArgParser { arg_parser::arg_parser() {} @@ -82,6 +84,7 @@ namespace ArgParser { { if (current_flag->parse(raw_args)) { raw_args.erase(raw_args.begin(), raw_args.begin() + current_flag->get_arg_count() + 1); + parsed_args.push_back(current_flag); } } catch (const std::exception& err) @@ -101,26 +104,25 @@ namespace ArgParser { void arg_parser::print_help() const { - std::wcout << L"NAME:\n" - + m_out.GetOutputStream() << L"NAME:\n" << L"\twperf - Performance analysis tools for Windows on Arm\n\n" << L"\tUsage: wperf [options]\n\n" << L"SYNOPSIS:\n\n"; for (auto& command : m_commands_list) { - std::wcout << L"\t" << command->get_all_flags_string() << L"\n" << command->get_usage_text() << L"\n"; + m_out.GetOutputStream() << L"\t" << command->get_all_flags_string() << L"\n" << command->get_usage_text() << L"\n"; } - std::wcout << L"OPTIONS:\n\n"; + m_out.GetOutputStream() << L"OPTIONS:\n\n"; for (auto& flag : m_flags_list) { - std::wcout << L" " << flag->get_help() << L"\n"; + m_out.GetOutputStream() << flag->get_help() << L"\n\n"; } - std::wcout << L"EXAMPLES:\n\n"; + m_out.GetOutputStream() << L"EXAMPLES:\n\n"; for (auto& command : m_commands_list) { if (command->get_examples().empty()) continue; - std::wcout << L" " << command->get_examples() << L"\n"; + m_out.GetOutputStream() << command->get_examples() << L"\n\n"; } } @@ -143,11 +145,6 @@ namespace ArgParser { std::wstring indicator(pos, L'~'); indicator += L'^'; - /* - TODO: THIS function should change to use GetErrorOutputStream before migrating to wperf - - */ - std::wostringstream error_message; error_message << L"Invalid argument detected:\n" << command << L"\n" @@ -155,8 +152,8 @@ namespace ArgParser { if (!additional_message.empty()) { error_message << additional_message << L"\n"; } - std::wcerr << error_message.str(); - throw std::invalid_argument("INVALID_ARGUMENT"); + m_out.GetErrorOutputStream() << error_message.str(); + throw fatal_exception("INVALID_ARGUMENT"); } #pragma endregion @@ -171,7 +168,7 @@ namespace ArgParser { { std::wstring example_output; for (auto& example : m_examples) { - example_output += example + L"\n"; + example_output += example + L"\n\n"; } return arg_parser_add_wstring_behind_multiline_text(arg_parser_format_string_to_length(example_output), L"\t"); } diff --git a/wperf/arg_parser.h b/wperf/arg_parser.h index 1e8d4e5..407aa9e 100644 --- a/wperf/arg_parser.h +++ b/wperf/arg_parser.h @@ -58,7 +58,7 @@ namespace ArgParser { MAN, NO_COMMAND }; - class arg_parser_arg_command : public arg_parser_arg_opt { + class arg_parser_arg_command : public arg_parser_arg_pos { public: arg_parser_arg_command( @@ -67,8 +67,9 @@ namespace ArgParser { const std::wstring description, const std::wstring useage_text, const COMMAND_CLASS command, - const wstr_vec examples - ) : arg_parser_arg_opt(name, alias, description), m_examples(examples), m_command(command), m_useage_text(useage_text) {}; + const wstr_vec examples, + const int arg_count = 0 + ) : arg_parser_arg_pos(name, alias, description, {}, arg_count), m_examples(examples), m_command(command), m_useage_text(useage_text) {}; const COMMAND_CLASS m_command = COMMAND_CLASS::NO_COMMAND; const wstr_vec m_examples; const std::wstring m_useage_text; @@ -103,7 +104,7 @@ namespace ArgParser { L"wperf list [-v] [--json] [--force-lock]", COMMAND_CLASS::LIST, { - L"> wperf list -v List all events and metrics available on your host with extended information." + L"> wperf list -v \nList all events and metrics available on your host with extended information." } ); arg_parser_arg_command test_command = arg_parser_arg_command::arg_parser_arg_command( @@ -141,36 +142,36 @@ namespace ArgParser { arg_parser_arg_command sample_command = arg_parser_arg_command::arg_parser_arg_command( L"sample", { L"" }, - L"Sampling mode, for determining the frequencies of event occurrences produced by program locations at the function, basic block, and /or instruction levels.", + L"Sampling mode, for determining the frequencies of event occurrences produced by program locations at the function, basic block, and/or instruction levels.", L"wperf sample [-e] [--timeout] [-c] [-C] [-E] [-q] [--json] [--output] [--config] [--image_name] [--pe_file] [--pdb_file] [--sample-display-long] [--force-lock] [--sample-display-row] [--symbol] [--record_spawn_delay] [--annotate] [--disassemble]", COMMAND_CLASS::SAMPLE, { - L"> wperf sample -e ld_spec:100000 --pe_file python_d.exe -c 1 Sample event `ld_spec` with frequency `100000` already running process `python_d.exe` on core #1. Press Ctrl + C to stop sampling and see the results.", + L"> wperf sample -e ld_spec:100000 --pe_file python_d.exe -c 1 \nSample event `ld_spec` with frequency `100000` already running process `python_d.exe` on core #1. Press Ctrl + C to stop sampling and see the results.", } ); arg_parser_arg_command record_command = arg_parser_arg_command::arg_parser_arg_command( L"record", { L"" }, - L"Same as sample but also automatically spawns the process and pins it to the core specified by `-c`. Process name is defined by COMMAND.User can pass verbatim arguments to the process with[ARGS].", + L"Same as sample but also automatically spawns the process and pins it to the core specified by `-c`. Process name is defined by COMMAND. User can pass verbatim arguments to the process with[ARGS].", L"wperf record [-e] [--timeout] [-c] [-C] [-E] [-q] [--json] [--output] [--config] [--image_name] [--pe_file] [--pdb_file] [--sample-display-long] [--force-lock] [--sample-display-row] [--symbol] [--record_spawn_delay] [--annotate] [--disassemble] --COMMAND[ARGS]", COMMAND_CLASS::RECORD, { - L"> wperf record -e ld_spec:100000 -c 1 --timeout 30 -- python_d.exe -c 10**10**100 Launch `python_d.exe - c 10 * *10 * *100` process and start sampling event `ld_spec` with frequency `100000` on core #1 for 30 seconds. Hint: add `--annotate` or `--disassemble` to `wperf record` command line parameters to increase sampling \"resolution\"." + L"> wperf record -e ld_spec:100000 -c 1 --timeout 30 -- python_d.exe -c 10**10**100 \nLaunch `python_d.exe - c 10 * *10 * *100` process and start sampling event `ld_spec` with frequency `100000` on core #1 for 30 seconds. \nHint: add `--annotate` or `--disassemble` to `wperf record` command line parameters to increase sampling \"resolution\"." #ifdef ENABLE_SPE - ,L"> wperf record -e arm_spe_0/ld=1/ -c 8 --cpython\\PCbuild\\arm64\\python_d.exe -c 10**10**100 Launch `python_d.exe -c 10**10**100` process on core no. 8 and start SPE sampling, enable collection of load sampled operations, including atomic operations that return a value to a register. Hint: add `--annotate` or `--disassemble` to `wperf record` command." + ,L"> wperf record -e arm_spe_0/ld=1/ -c 8 -- python_d.exe -c 10**10**100 \nLaunch `python_d.exe -c 10**10**100` process on core no. 8 and start SPE sampling, enable collection of load sampled operations, including atomic operations that return a value to a register. Hint: add `--annotate` or `--disassemble` to `wperf record` command." #endif } ); - arg_parser_arg_command count_command = arg_parser_arg_command::arg_parser_arg_command( + arg_parser_arg_command stat_command = arg_parser_arg_command::arg_parser_arg_command( L"stat", { L"" }, L"Counting mode, for obtaining aggregate counts of occurrences of special events.", - L"wperf stat [-e] [-m] [-t] [-i] [-n] [-c] [-C] [-E] [-k] [--dmc] [-q] [--json] [--output][--config] [--force-lock] --COMMAND[ARGS]", + L"wperf stat [-e] [-m] [-t] [-i] [-n] [-c] [-C] [-E] [-k] [--dmc] [-q] [--json] [--output][--config] [--force-lock] --COMMAND [ARGS]", COMMAND_CLASS::STAT, { - L"> wperf stat -e inst_spec,vfp_spec,ase_spec,ld_spec -c 0 --timeout 3 Count events `inst_spec`, `vfp_spec`, `ase_spec` and `ld_spec` on core #0 for 3 seconds.", - L"> wperf stat -m imix -e l1i_cache -c 7 --timeout 10.5 Count metric `imix` (metric events will be grouped) and additional event `l1i_cache` on core #7 for 10.5 seconds.", - L"> wperf stat -m imix -c 1 -t -i 2 -n 3 --timeout 5 Count in timeline mode(output counting to CSV file) metric `imix` 3 times on core #1 with 2 second intervals(delays between counts).Each count will last 5 seconds." + L"> wperf stat -e inst_spec,vfp_spec,ase_spec,ld_spec -c 0 --timeout 3 \nCount events `inst_spec`, `vfp_spec`, `ase_spec` and `ld_spec` on core #0 for 3 seconds.", + L"> wperf stat -m imix -e l1i_cache -c 7 --timeout 10.5 \nCount metric `imix` (metric events will be grouped) and additional event `l1i_cache` on core #7 for 10.5 seconds.", + L"> wperf stat -m imix -c 1 -t -i 2 -n 3 --timeout 5 \nCount in timeline mode(output counting to CSV file) metric `imix` 3 times on core #1 with 2 second intervals(delays between counts). Each count will last 5 seconds." } ); arg_parser_arg_command man_command = arg_parser_arg_command::arg_parser_arg_command( @@ -180,7 +181,8 @@ namespace ArgParser { L"wperf man [--json]", COMMAND_CLASS::MAN, { - } + }, + 1 ); #pragma endregion @@ -241,7 +243,12 @@ namespace ArgParser { L"Enable timeline mode (count multiple times with specified interval). Use `-i` to specify timeline interval, and `-n` to specify number of counts.", {} ); - + arg_parser_arg_opt export_perf_data_opt = arg_parser_arg_opt::arg_parser_arg_opt( + L"--export_perf_data", + {}, + L"Generate local `perf.data` file with partial profiling information (experimental only).", + {} + ); #pragma endregion @@ -249,7 +256,7 @@ namespace ArgParser { arg_parser_arg_pos extra_args_arg = arg_parser_arg_pos::arg_parser_arg_pos( L"--", {}, - L"-- Process name is defined by COMMAND. User can pass verbatim arguments to the process with[ARGS].", + L"Double-dash is a syntax used signify end of command options. It separates `wperf` command line options from arguments that the process spawn command operates on. Use `--` to separate `wperf.exe` command line options from the process you want to spawn followed by its verbatim arguments.", {}, -1 ); @@ -346,7 +353,7 @@ namespace ArgParser { {} ); arg_parser_arg_pos iteration_arg = arg_parser_arg_pos::arg_parser_arg_pos( - L"-i", + L"-n", {}, L"Number of consecutive counts in timeline mode (disabled by default).", {} @@ -360,13 +367,13 @@ namespace ArgParser { arg_parser_arg_pos metrics_arg = arg_parser_arg_pos::arg_parser_arg_pos( L"-m", {}, - L"Specify comma separated list of metrics to count.\n\nNote: see list of available metric names using `list` command.", + L"Specify comma separated list of metrics to count.\nNote: see list of available metric names using `list` command.", {} ); arg_parser_arg_pos events_arg = arg_parser_arg_pos::arg_parser_arg_pos( L"-e", {}, - L"Specify comma separated list of event names (or raw events) to count, for example `ld_spec,vfp_spec,r10`. Use curly braces to group events. Specify comma separated list of event names with sampling frequency to sample, for example `ld_spec:100000`. Raw events: specify raw evens with `r` where `` is a 16-bit hexadecimal event index value without leading `0x`. For example `r10` is event with index `0x10`. Note: see list of available event names using `list` command.", + L"Specify comma separated list of event names (or raw events) to count, for example `ld_spec,vfp_spec,r10`. Use curly braces to group events. \nSpecify comma separated list of event names with sampling frequency to sample, for example `ld_spec:100000`. \nRaw events: specify raw evens with `r` where `` is a 16-bit hexadecimal event index value without leading `0x`. For example `r10` is event with index `0x10`. \nNote: see list of available event names using `list` command.", {} ); #pragma endregion @@ -378,7 +385,7 @@ namespace ArgParser { &help_command, &version_command, &sample_command, - &count_command, + &stat_command, &record_command, &list_command, &test_command, @@ -415,9 +422,10 @@ namespace ArgParser { &interval_arg, &iteration_arg, &dmc_arg, + &export_perf_data_opt, &extra_args_arg }; - + std::vector parsed_args = {}; wstr_vec m_arg_array; #pragma endregion diff --git a/wperf/arg_parser_arg.cpp b/wperf/arg_parser_arg.cpp index 8754e51..929cbd0 100644 --- a/wperf/arg_parser_arg.cpp +++ b/wperf/arg_parser_arg.cpp @@ -182,9 +182,6 @@ namespace ArgParserArg { formatted_str += L"\n"; continue; } - if (!formatted_str.empty()) { - formatted_str += L"\n"; - } std::wstring current_line; std::wistringstream word_stream(line); std::wstring word; diff --git a/wperf/main.cpp b/wperf/main.cpp index f4fe611..8bd1b81 100644 --- a/wperf/main.cpp +++ b/wperf/main.cpp @@ -50,6 +50,7 @@ #include "config.h" #include "perfdata.h" #include "disassembler.h" +#include "arg_parser.h" static bool no_ctrl_c = true; @@ -81,10 +82,12 @@ wmain( ) { auto exit_code = EXIT_SUCCESS; + ArgParser::arg_parser arg_parser; + arg_parser.parse(argc, argv); + user_request request; pmu_device pmu_device; - wstr_vec raw_args; LLVMDisassembler disassembler; bool spawned_process = false; @@ -92,24 +95,14 @@ wmain( PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); - //* Handle CLI options before we initialize PMU device(s) - for (int i = 1; i < argc; i++) - raw_args.push_back(argv[i]); - - pmu_device.do_force_lock = user_request::is_force_lock(raw_args); - - if (raw_args.size() == 1 && user_request::is_help(raw_args)) + if (arg_parser.m_command == ArgParser::COMMAND_CLASS::HELP) { - user_request::print_help(); + arg_parser.print_help(); goto clean_exit; } - if (raw_args.empty()) - { - user_request::print_help_header(); - user_request::print_help_prompt(); - goto clean_exit; - } + pmu_device.do_force_lock = arg_parser.force_lock_opt.is_set(); + //* Handle CLI options before we initialize PMU device(s) try { @@ -162,7 +155,7 @@ wmain( { struct pmu_device_cfg pmu_cfg; pmu_device.get_pmu_device_cfg(pmu_cfg); - request.init(raw_args, pmu_cfg, + request.init(arg_parser, pmu_cfg, pmu_device.builtin_metrics, pmu_device.get_product_groups_metrics_names(), pmu_events::extra_events); @@ -183,11 +176,6 @@ wmain( goto clean_exit; } - if (request.do_help) - { - user_request::print_help(); - goto clean_exit; - } if (request.do_man) { std::vector col1, col2; @@ -1340,7 +1328,6 @@ wmain( #if defined(ENABLE_ETW_TRACING_APP) EventUnregisterWindowsPerf_App(); #endif - if(spawned_process) { TerminateProcess(pi.hProcess, 0); diff --git a/wperf/user_request.cpp b/wperf/user_request.cpp index 1667935..ab8c5b3 100644 --- a/wperf/user_request.cpp +++ b/wperf/user_request.cpp @@ -42,255 +42,11 @@ #include "wperf-common/public.h" #include "wperf/config.h" -void user_request::print_help_usage() -{ - std::wstring wsHelp = LR"( -NAME: - wperf - Performance analysis tools for Windows on Arm - -SYNOPSIS: - wperf [--version] [--help] [OPTIONS] - - wperf stat [-e] [-m] [-t] [-i] [-n] [-c] [-C] [-E] [-k] [--dmc] [-q] [--json] - [--output] [--config] [--force-lock] - wperf stat [-e] [-m] [-t] [-i] [-n] [-c] [-C] [-E] [-k] [--dmc] [-q] [--json] - [--output] [--config] -- COMMAND [ARGS] - Counting mode, for obtaining aggregate counts of occurrences of special - events. - - wperf sample [-e] [--timeout] [-c] [-C] [-E] [-q] [--json] [--output] [--config] - [--image_name] [--pe_file] [--pdb_file] [--sample-display-long] [--force-lock] - [--sample-display-row] [--symbol] [--record_spawn_delay] [--annotate] [--disassemble] - Sampling mode, for determining the frequencies of event occurrences - produced by program locations at the function, basic block, and/or - instruction levels. - - wperf record [-e] [--timeout] [-c] [-C] [-E] [-q] [--json] [--output] [--config] - [--image_name] [--pe_file] [--pdb_file] [--sample-display-long] [--force-lock] - [--sample-display-row] [--symbol] [--record_spawn_delay] [--annotate] [--disassemble] -- COMMAND [ARGS] - Same as sample but also automatically spawns the process and pins it to - the core specified by `-c`. Process name is defined by COMMAND. User can - pass verbatim arguments to the process with [ARGS]. - - wperf list [-v] [--json] [--force-lock] - List supported events and metrics. Enable verbose mode for more details. - - wperf test [--json] [OPTIONS] - Configuration information about driver and application. - - wperf detect [--json] [OPTIONS] - List installed WindowsPerf-like Kernel Drivers (match GUID). - - wperf man [--json] - Plain text information about one or more specified event(s), metric(s), and or group metric(s). - -OPTIONS: - -h, --help - Run wperf help command. - - --version - Display version. - - -v, --verbose - Enable verbose output also in JSON output. - - -q - Quiet mode, no output is produced. - - -e - Specify comma separated list of event names (or raw events) to count, for - example `ld_spec,vfp_spec,r10`. Use curly braces to group events. - Specify comma separated list of event names with sampling frequency to - sample, for example `ld_spec:100000`. - - Raw events: specify raw evens with `r` where `` is a 16-bit - hexadecimal event index value without leading `0x`. For example `r10` is - event with index `0x10`. - - Note: see list of available event names using `list` command. - - -m - Specify comma separated list of metrics to count. - - Note: see list of available metric names using `list` command. - - --timeout - Specify counting or sampling duration. If not specified, press - Ctrl+C to interrupt counting or sampling. Input may be suffixed by - one (or none) of the following units, with up to 2 decimal - points: "ms", "s", "m", "h", "d" (i.e. milliseconds, seconds, - minutes, hours, days). If no unit is provided, the default unit - is seconds. Accuracy is 0.1 sec. - - -t - Enable timeline mode (count multiple times with specified interval). - Use `-i` to specify timeline interval, and `-n` to specify number of - counts. - - -i - Specify counting interval. `0` seconds is allowed. Input may be - suffixed with one (or none) of the following units, with up to - 2 decimal points: "ms", "s", "m", "h", "d" (i.e. milliseconds, - seconds, minutes, hours, days). If no unit is provided, the default - unit is seconds (60s by default). - - -n - Number of consecutive counts in timeline mode (disabled by default). - - --annotate - Enable translating addresses taken from samples in sample/record mode - into source code line numbers. - - --disassemble - Enable disassemble output on sampling mode. Implies 'annotate'. - - --image_name - Specify the image (base) name of a module to sample. - - --pe_file - Specify the PE filename (and path) to sample. - - --pdb_file - Specify the PDB filename (and path), PDB file should directly - corresponds to a PE file set with `--pe_file`. - - --sample-display-long - Display decorated symbol names. - - --sample-display-row - Set how many samples you want to see in the summary (50 by default). - - --symbol - Filter results for specific symbols (for use with 'record' and 'sample' commands). - - --record_spawn_delay - Set the waiting time, in milliseconds, before reading process data after - spawning it with `record`. - - --force-lock - Force driver to give lock to current `wperf` process, use when you want - to interrupt currently executing `wperf` session or to recover from the lock. - - -c, --cpu - Specify comma separated list of CPU cores, and or ranges of CPU cores, to count - on, or one CPU to sample on. - - -k - Count kernel mode as well (disabled by default). - - --dmc - Profile on the specified DDR controller. Skip `--dmc` to count on all - DMCs. - - -C - Provide customized config file which describes metrics. - - -E - Provide customized config file which describes custom events or - provide custom events from the command line. - - --json - Define output type as JSON. - - --output, -o - Specify JSON output filename. - - --output-csv - Specify CSV output filename. Only with timeline `-t`. - - --output-prefix, --cwd - Set current working dir for storing output JSON and CSV file. - - --config - Specify configuration parameters. - -OPTIONS aliases: - -l - Alias of 'list'. - - sleep - Alias of `--timeout`. - -s - Alias of `--symbol`. - -EXAMPLES: - - > wperf list -v - List all events and metrics available on your host with extended - information. - - > wperf stat -e inst_spec,vfp_spec,ase_spec,ld_spec -c 0 --timeout 3 - Count events `inst_spec`, `vfp_spec`, `ase_spec` and `ld_spec` on core #0 - for 3 seconds. - - > wperf stat -m imix -e l1i_cache -c 7 --timeout 10.5 - Count metric `imix` (metric events will be grouped) and additional event - `l1i_cache` on core #7 for 10.5 seconds. - - > wperf stat -m imix -c 1 -t -i 2 -n 3 --timeout 5 - Count in timeline mode (output counting to CSV file) metric `imix` 3 times - on core #1 with 2 second intervals (delays between counts). Each count - will last 5 seconds. - - > wperf sample -e ld_spec:100000 --pe_file python_d.exe -c 1 - Sample event `ld_spec` with frequency `100000` already running process - `python_d.exe` on core #1. Press Ctrl+C to stop sampling and see the results. - - > wperf record -e ld_spec:100000 -c 1 --timeout 30 -- python_d.exe -c 10**10**100 - Launch `python_d.exe -c 10**10**100` process and start sampling event `ld_spec` - with frequency `100000` on core #1 for 30 seconds. - Hint: add `--annotate` or `--disassemble` to `wperf record` command line - parameters to increase sampling "resolution". -)"; - -#ifdef ENABLE_SPE - wsHelp += LR"( - > wperf record -e arm_spe_0/ld=1/ -c 8 --cpython\PCbuild\arm64\python_d.exe -c 10**10**100 - Launch `python_d.exe -c 10**10**100` process on core no. 8 and start SPE sampling, enable - collection of load sampled operations, including atomic operations that return a value to a register. - Hint: add `--annotate` or `--disassemble` to `wperf record` command. -)"; -#endif - - m_out.GetOutputStream() << wsHelp << std::endl; -} - // // This file will be modified with wperf's pre-build step // #include "wperf-common\gitver.h" -void user_request::print_help_header() -{ - m_out.GetOutputStream() << L"WindowsPerf" - << L" ver. " << MAJOR << "." << MINOR << "." << PATCH - << L" (" - << WPERF_GIT_VER_STR - << L"/" -#ifdef _DEBUG - << L"Debug" -#else - << L"Release" -#endif - << ENABLE_FEAT_STR - << L") WOA profiling with performance counters." - << std::endl; - - m_out.GetOutputStream() << L"Report bugs to: https://github.com/arm-developer-tools/windowsperf/issues" - << std::endl; -} - -void user_request::print_help_prompt() -{ - m_out.GetOutputStream() << L"Use --help for help." << std::endl; -} - -void user_request::print_help() -{ - print_help_header(); - print_help_usage(); -} - user_request::user_request() : do_list{ false }, do_disassembly(false), do_count(false), do_kernel(false), do_timeline(false), do_sample(false), do_record(false), do_annotate(false), do_version(false), do_verbose(false), do_test(false), @@ -319,7 +75,7 @@ bool user_request::is_help(const wstr_vec& raw_args) || is_cli_option_in_args(raw_args, std::wstring(L"-h")); } -void user_request::init(wstr_vec& raw_args, const struct pmu_device_cfg& pmu_cfg, +void user_request::init(ArgParser::arg_parser& parsed_args, const struct pmu_device_cfg& pmu_cfg, std::map& builtin_metrics, const std::map >& groups_of_metrics, std::map>& extra_events) @@ -332,7 +88,7 @@ void user_request::init(wstr_vec& raw_args, const struct pmu_device_cfg& pmu_cfg cores_idx.resize(pmu_cfg.core_num); std::iota(cores_idx.begin(), cores_idx.end(), (UINT8)0); - parse_raw_args(raw_args, pmu_cfg, events, groups, builtin_metrics, groups_of_metrics, extra_events); + parse_raw_args(parsed_args, pmu_cfg, events, groups, builtin_metrics, groups_of_metrics, extra_events); // Deduce image name and PDB file name from PE file name if (sample_pe_file.size()) @@ -392,120 +148,232 @@ void user_request::init(wstr_vec& raw_args, const struct pmu_device_cfg& pmu_cfg check_events(EVT_DMC_CLKDIV2, MAX_MANAGED_DMC_CLKDIV2_EVENTS); } -void user_request::parse_raw_args(wstr_vec& raw_args, const struct pmu_device_cfg& pmu_cfg, +void user_request::parse_raw_args(ArgParser::arg_parser& parsed_args, const struct pmu_device_cfg& pmu_cfg, std::map>& events, std::map>& groups, std::map& builtin_metrics, const std::map >& groups_of_metrics, std::map>& extra_events) { - bool waiting_events = false; - bool waiting_metrics = false; - bool waiting_core_idx = false; - bool waiting_dmc_idx = false; - bool waiting_duration = false; - bool waiting_interval = false; - bool waiting_metric_config = false; - bool waiting_events_config = false; - bool waiting_output_filename = false; - bool waiting_output_csv_filename = false; - bool waiting_image_name = false; - bool waiting_pe_file = false; - bool waiting_pdb_file = false; - bool waiting_sample_display_row = false; - bool waiting_timeline_count = false; - bool waiting_config = false; - bool waiting_commandline = false; - bool waiting_record_spawn_delay = false; - bool waiting_man_query = false; - bool waiting_cwd = false; - bool waiting_symbol = false; + bool sample_pe_file_given = false; std::wstring waiting_duration_arg; std::wstring output_filename, output_csv_filename; - if (raw_args.empty()) + switch (parsed_args.m_command) { - print_help_header(); - print_help_prompt(); + case ArgParser::COMMAND_CLASS::STAT: + do_count = true; + break; + case ArgParser::COMMAND_CLASS::SAMPLE: + do_sample = true; + break; + case ArgParser::COMMAND_CLASS::RECORD: + do_record = true; + break; + case ArgParser::COMMAND_CLASS::TEST: + do_test = true; + break; + case ArgParser::COMMAND_CLASS::DETECT: + do_detect = true; + break; + case ArgParser::COMMAND_CLASS::HELP: + do_help = true; + break; + case ArgParser::COMMAND_CLASS::VERSION: + do_version = true; + break; + case ArgParser::COMMAND_CLASS::LIST: + do_list = true; + break; + case ArgParser::COMMAND_CLASS::MAN: + do_man = true; + break; + default: + break; } - for (const auto& a : raw_args) + if (parsed_args.metric_config_arg.is_set()) + { + load_config_metrics(parsed_args.metric_config_arg.get_values().front(), pmu_cfg); + } + if (parsed_args.event_config_arg.is_set()) + { + load_config_events(parsed_args.event_config_arg.get_values().front(), extra_events); + } + + if (builtin_metrics.size()) { - if (waiting_metric_config) + for (const auto& [key, value] : builtin_metrics) { - waiting_metric_config = false; - load_config_metrics(a, pmu_cfg); - continue; + if (metrics.find(key) == metrics.end()) + metrics[key] = value; } + } + if (parsed_args.extra_args_arg.is_set()) + { + if (sample_pe_file.empty()) + sample_pe_file = parsed_args.extra_args_arg.get_values().front(); + + record_commandline += WStringJoin(parsed_args.extra_args_arg.get_values(), L" "); + if (!(do_record || do_count)) + m_out.GetErrorOutputStream() << L"warning: only `stat` and `record` support process spawn!" << std::endl; + + sample_pe_file.clear(); + record_commandline.clear(); + } + if (parsed_args.output_prefix_arg.is_set()) + { + m_cwd = parsed_args.output_prefix_arg.get_values().front(); + } + if (parsed_args.output_filename_arg.is_set()) + { + output_filename = parsed_args.output_filename_arg.get_values().front(); + } - if (a == L"-C") - { - waiting_metric_config = true; - continue; - } + if (parsed_args.output_csv_filename_arg.is_set()) + { + output_csv_filename = parsed_args.output_csv_filename_arg.get_values().front(); + } - if (waiting_events_config) - { - waiting_events_config = false; - load_config_events(a, extra_events); - continue; - } + if (parsed_args.config_arg.is_set()) + { + wstring config_values = parsed_args.config_arg.get_values().front(); + if (drvconfig::set(config_values) == false) + m_out.GetErrorOutputStream() << L"error: can't set '" << config_values << "' config" << std::endl; + } + if (parsed_args.image_name_arg.is_set()) + { + sample_image_name = parsed_args.image_name_arg.get_values().front(); + } - if (a == L"-E") + if (parsed_args.pe_file_arg.is_set()) + { + wstring parsed_pe_file = parsed_args.pe_file_arg.get_values().front(); + if (std::filesystem::exists(parsed_pe_file) == false) { - waiting_events_config = true; - continue; + m_out.GetErrorOutputStream() << L"PE file '" << parsed_pe_file << L"' doesn't exist" + << std::endl; + throw fatal_exception("ERROR_PE_FILE_PATH"); } + sample_pe_file = parsed_pe_file; + sample_pe_file_given = true; } - - if (builtin_metrics.size()) + + if (parsed_args.pdb_file_arg.is_set()) { - for (const auto& [key, value] : builtin_metrics) + wstring parsed_pdb_file = parsed_args.pdb_file_arg.get_values().front(); + if (std::filesystem::exists(parsed_pdb_file) == false) { - if (metrics.find(key) == metrics.end()) - metrics[key] = value; + m_out.GetErrorOutputStream() << L"PDB file '" << parsed_pdb_file << L"' doesn't exist" + << std::endl; + throw fatal_exception("ERROR_PDB_FILE_PATH"); } + sample_pdb_file = parsed_pdb_file; } - for (const auto& a : raw_args) + + if (parsed_args.cores_arg.is_set()) { - if (waiting_commandline) - { - if (sample_pe_file.empty()) - { - sample_pe_file = a; - record_commandline = a; - } - else - record_commandline += L" " + a; - continue; - } + wstring core_idx_str = WStringJoin(parsed_args.cores_arg.get_values(), L","); - if (waiting_metric_config) + if (TokenizeWideStringOfInts(core_idx_str.c_str(), L',', cores_idx) == false) { - waiting_metric_config = false; - continue; + m_out.GetErrorOutputStream() << L"option -c format not supported, use comma separated list of integers, or range of integers" + << std::endl; + throw fatal_exception("ERROR_CORES"); } - if (waiting_events_config) + if (cores_idx.size() > MAX_PMU_CTL_CORES_COUNT) { - waiting_events_config = false; - continue; + m_out.GetErrorOutputStream() << L"you can specify up to " << int(MAX_PMU_CTL_CORES_COUNT) + << L"cores with -c option" + << std::endl; + throw fatal_exception("ERROR_CORES"); } + } - if (waiting_events) + if (parsed_args.iteration_arg.is_set()) + { + count_timeline = _wtoi(parsed_args.iteration_arg.get_values().front().c_str()); + } + + if (parsed_args.record_spawn_delay_arg.is_set()) + { + uint32_t val = _wtoi(parsed_args.record_spawn_delay_arg.get_values().front().c_str()); + assert(val <= UINT32_MAX); + record_spawn_delay = val; + } + + if (parsed_args.dmc_arg.is_set()) + { + + int val = _wtoi(parsed_args.output_filename_arg.get_values().front().c_str()); + assert(val <= UCHAR_MAX); + dmc_idx = (uint8_t)val; + } + + if (parsed_args.timeout_arg.is_set()) + { + count_duration = convert_timeout_arg_to_seconds(parsed_args.output_filename_arg.get_values().front(), parsed_args.timeout_arg.get_name()); + } + + if (parsed_args.interval_arg.is_set()) + { + count_interval = convert_timeout_arg_to_seconds(parsed_args.interval_arg.get_values().front(), parsed_args.interval_arg.get_name()); + } + + if (parsed_args.sample_display_row_arg.is_set()) + { + sample_display_row = _wtoi(parsed_args.sample_display_row_arg.get_values().front().c_str()); + } + + if (parsed_args.man_command.is_set()) + { + man_query_args = parsed_args.man_command.get_values().front(); + } + if (parsed_args.symbol_arg.is_set()) + { + symbol_arg = parsed_args.symbol_arg.get_values().front(); + do_symbol = true; + + } + do_disassembly = parsed_args.disassembly_opt.is_set(); + do_annotate = parsed_args.annotate_opt.is_set() || parsed_args.disassembly_opt.is_set(); + do_force_lock = parsed_args.force_lock_opt.is_set(); + do_kernel = parsed_args.kernel_opt.is_set(); + if (parsed_args.timeline_opt.is_set()) + { + do_timeline = true; + if (count_interval == -1.0) + count_interval = 60; + if (count_duration == -1.0) + count_duration = 1; + } + sample_display_short = !parsed_args.sample_display_long_opt.is_set(); + do_verbose = parsed_args.verbose_opt.is_set(); + m_out.m_isQuiet = parsed_args.quite_opt.is_set(); + if (parsed_args.json_opt.is_set() && m_outputType != TableType::ALL) + { + m_outputType = TableType::JSON; + m_out.m_isQuiet = true; + } + do_export_perf_data = parsed_args.export_perf_data_opt.is_set(); + + + if (parsed_args.events_arg.is_set()) { + wstring raw_events_string = WStringJoin(parsed_args.events_arg.get_values(), L","); if (do_sample || do_record) { m_sampling_flags.clear(); // Prepare to collect SPE filters - if (parse_events_str_for_feat_spe(a, m_sampling_flags)) // Check if we are sampling with SPE + if (parse_events_str_for_feat_spe(raw_events_string, m_sampling_flags)) // Check if we are sampling with SPE { if (pmu_cfg.has_spe == false) { - m_out.GetErrorOutputStream() << L"SPE is not supported by your hardware: " << a << std::endl; + m_out.GetErrorOutputStream() << L"SPE is not supported by your hardware: " << raw_events_string << std::endl; throw fatal_exception("ERROR_SPE_NOT_SUPP"); } @@ -538,7 +406,7 @@ void user_request::parse_raw_args(wstr_vec& raw_args, const struct pmu_device_cf } else // Software sampling (no SPE) { - parse_events_str_for_sample(a, ioctl_events_sample, sampling_inverval); + parse_events_str_for_sample(raw_events_string, ioctl_events_sample, sampling_inverval); // After events are parsed check if we can fulfil this request as sampling does not have multiplexing if (ioctl_events_sample.size() > pmu_cfg.total_gpc_num) { @@ -556,44 +424,14 @@ void user_request::parse_raw_args(wstr_vec& raw_args, const struct pmu_device_cf } } else - parse_events_str(a, events, groups, L"", pmu_cfg); - waiting_events = false; - continue; - } - - if (waiting_cwd) - { - waiting_cwd = false; - m_cwd = a; - continue; - } - - if (waiting_output_filename) - { - waiting_output_filename = false; - output_filename = a; - continue; - } - - if (waiting_output_csv_filename) - { - waiting_output_csv_filename = false; - output_csv_filename = a; - continue; - } - - if (waiting_config) - { - waiting_config = false; - if (drvconfig::set(a) == false) - m_out.GetErrorOutputStream() << L"error: can't set '" << a << "' config" << std::endl; - continue; + parse_events_str(raw_events_string, events, groups, L"", pmu_cfg); } - if (waiting_metrics) + if (parsed_args.metrics_arg.is_set()) { + wstring raw_metrics_string = WStringJoin(parsed_args.metrics_arg.get_values(), L","); std::vector list_of_defined_metrics; - std::wistringstream metric_stream(a); + std::wistringstream metric_stream(raw_metrics_string); std::wstring metric_token; while (std::getline(metric_stream, metric_token, L',')) @@ -642,392 +480,8 @@ void user_request::parse_raw_args(wstr_vec& raw_args, const struct pmu_device_cf else if (metric == L"ddr_bw") report_ddr_bw_metric = true; } - - waiting_metrics = false; - continue; - } - - if (waiting_image_name) - { - sample_image_name = a; - waiting_image_name = false; - continue; - } - - if (waiting_pe_file) - { - if (std::filesystem::exists(a) == false) - { - m_out.GetErrorOutputStream() << L"PE file '" << a << L"' doesn't exist" - << std::endl; - throw fatal_exception("ERROR_PE_FILE_PATH"); - } - - sample_pe_file = a; - sample_pe_file_given = true; - waiting_pe_file = false; - continue; - } - - if (waiting_pdb_file) - { - if (std::filesystem::exists(a) == false) - { - m_out.GetErrorOutputStream() << L"PDB file '" << a << L"' doesn't exist" - << std::endl; - throw fatal_exception("ERROR_PDB_FILE_PATH"); - } - - sample_pdb_file = a; - waiting_pdb_file = false; - continue; } - if (waiting_core_idx) - { - if (TokenizeWideStringOfInts(a.c_str(), L',', cores_idx) == false) - { - m_out.GetErrorOutputStream() << L"option -c format not supported, use comma separated list of integers, or range of integers" - << std::endl; - throw fatal_exception("ERROR_CORES"); - } - - if (cores_idx.size() > MAX_PMU_CTL_CORES_COUNT) - { - m_out.GetErrorOutputStream() << L"you can specify up to " << int(MAX_PMU_CTL_CORES_COUNT) - << L"cores with -c option" - << std::endl; - throw fatal_exception("ERROR_CORES"); - } - - waiting_core_idx = false; - continue; - } - - if (waiting_timeline_count) - { - count_timeline = _wtoi(a.c_str()); - waiting_timeline_count = false; - continue; - } - - if (waiting_record_spawn_delay) - { - uint32_t val = _wtoi(a.c_str()); - assert(val <= UINT32_MAX); - record_spawn_delay = val; - waiting_record_spawn_delay = false; - continue; - } - - if (waiting_dmc_idx) - { - int val = _wtoi(a.c_str()); - assert(val <= UCHAR_MAX); - dmc_idx = (uint8_t)val; - waiting_dmc_idx = false; - continue; - } - - if (waiting_duration) - { - count_duration = convert_timeout_arg_to_seconds(a, waiting_duration_arg); - waiting_duration = false; - continue; - } - - if (waiting_interval) - { - count_interval = convert_timeout_arg_to_seconds(a, waiting_duration_arg); - waiting_interval = false; - continue; - } - - if (waiting_sample_display_row) - { - sample_display_row = _wtoi(a.c_str()); - waiting_sample_display_row = false; - continue; - } - - if (waiting_man_query) - { - man_query_args = a; - waiting_man_query = false; - continue; - } - - if (waiting_symbol) - { - symbol_arg = a; - waiting_symbol = false; - continue; - } - - // For compatibility with Linux perf - if (a == L"list" || a == L"-l") - { - do_list = true; - continue; - } - - if (a == L"-e") - { - waiting_events = true; - continue; - } - - if (a == L"-C") // Skip this one as it was handled before any other args - { - waiting_metric_config = true; - continue; - } - - if (a == L"-E") // Skip this one as it was handled before any other args - { - waiting_events_config = true; - continue; - } - - if (a == L"-m") - { - waiting_metrics = true; - continue; - } - - if (a == L"stat") - { - do_count = true; - continue; - } - - if (a == L"record") - { - do_record = true; - continue; - } - - if (a == L"sample") - { - do_sample = true; - continue; - } - - if (a == L"detect") - { - do_detect = true; - continue; - } - if (a == L"man") - { - do_man = true; - waiting_man_query = true; - continue; - } - - if (a == L"--image_name") - { - waiting_image_name = true; - continue; - } - - if (a == L"--disassemble") - { - do_disassembly = true; - do_annotate = true; - continue; - } - - if (a == L"--force-lock") - { - do_force_lock = true; - continue; - } - - if (a == L"--export_perf_data") - { - do_export_perf_data = true; - continue; - } - - if (a == L"--record_spawn_delay") - { - waiting_record_spawn_delay = true; - continue; - } - - if (a == L"--pe_file") - { - waiting_pe_file = true; - continue; - } - - if (a == L"--pdb_file") - { - waiting_pdb_file = true; - continue; - } - - if (a == L"--timeout" || a == L"sleep") - { - waiting_duration = true; - waiting_duration_arg = a; - continue; - } - - if (a == L"--cwd" || a == L"--output-prefix") - { - waiting_cwd = true; - continue; - } - - if (a == L"--output" || a == L"-o") - { - waiting_output_filename = true; - continue; - } - - if (a == L"--output-csv") - { - waiting_output_csv_filename = true; - continue; - } - - if (a == L"--config") - { - waiting_config = true; - continue; - } - - if (a == L"-k") - { - do_kernel = true; - continue; - } - - if (a == L"-t") - { - do_timeline = true; - if (count_interval == -1.0) - count_interval = 60; - if (count_duration == -1.0) - count_duration = 1; - continue; - } - - if (a == L"-n") - { - waiting_timeline_count = true; - continue; - } - - if (a == L"--sample-display-row") - { - waiting_sample_display_row = true; - continue; - } - - if (a == L"--sample-display-long") - { - sample_display_short = false; - continue; - } - - if (a == L"-i") - { - waiting_duration_arg = a; - waiting_interval = true; - continue; - } - - if (a == L"-c" || a == L"--cpu") - { - waiting_core_idx = true; - continue; - } - - if (a == L"--dmc") - { - waiting_dmc_idx = true; - continue; - } - - if (a == L"-v" || a == L"--verbose") - { - do_verbose = true; - continue; - } - - if (a == L"--annotate") - { - do_annotate = true; - continue; - } - - if (a == L"--version") - { - do_version = true; - continue; - } - - if (a == L"-h" || a == L"--help") - { - do_help = true; - continue; - } - - if (a == L"-q") - { - m_out.m_isQuiet = true; - continue; - } - - if (a == L"--json") - { - if (m_outputType != TableType::ALL) - { - m_outputType = TableType::JSON; - m_out.m_isQuiet = true; - } - continue; - } - - if (a == L"test") - { - do_test = true; - continue; - } - - if (a == L"-s" || a == L"--symbol") - { - do_symbol = true; - waiting_symbol = true; - continue; - } - - /* This will finish command line argument parsing. After "--" we will see process to spawn in form of: - * - * wperf ... -- PROCESS_NAME ARG ARG ARG ARG ... - * - * From now on we will parse process spawn name and verbatim arguments - */ - if (a == L"--") - { - if (!(do_record || do_count)) - m_out.GetErrorOutputStream() << L"warning: only `stat` and `record` support process spawn!" << std::endl; - - waiting_commandline = true; - /* We will reload here PE file name to spawn. `waiting_commandline` will expect this - * to be empty to detect beggining of the scan. - */ - sample_pe_file.clear(); - record_commandline.clear(); - continue; - } - - m_out.GetErrorOutputStream() << L"warning: unexpected arg '" << a << L"' ignored" << std::endl; - } - std::wstring output_filename_full_path = output_filename; std::wstring output_filename_csv_full_path = output_csv_filename; diff --git a/wperf/user_request.h b/wperf/user_request.h index 25dad10..faa6888 100644 --- a/wperf/user_request.h +++ b/wperf/user_request.h @@ -35,7 +35,7 @@ #include "utils.h" #include "events.h" #include "output.h" - +#include "arg_parser.h" typedef std::vector wstr_vec; class user_request @@ -43,12 +43,12 @@ class user_request public: user_request(); - void init(wstr_vec& raw_args, const struct pmu_device_cfg& pmu_cfg, + void init(ArgParser::arg_parser& parsed_args, const struct pmu_device_cfg& pmu_cfg, std::map& builtin_metrics, const std::map >& groups_of_metrics, std::map>& extra_events); - void parse_raw_args(wstr_vec& raw_args, const struct pmu_device_cfg& pmu_cfg, + void parse_raw_args(ArgParser::arg_parser& parsed_args, const struct pmu_device_cfg& pmu_cfg, std::map>& events, std::map>& groups, std::map& builtin_metrics, @@ -62,10 +62,6 @@ class user_request std::map>& extra_events); void load_config_metrics(std::wstring config_name, const struct pmu_device_cfg& pmu_cfg); - static void print_help(); - static void print_help_header(); - static void print_help_prompt(); - static void print_help_usage(); static bool is_cli_option_in_args(const wstr_vec& raw_args, std::wstring opt); // Return true if `opt` is in CLI options static bool is_force_lock(const wstr_vec& raw_args); // Return true if `--force-lock` is in CLI options static bool is_help(const wstr_vec& raw_args); // Return true if `--help` is in CLI options