X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=cli%2Fcli_trim.cpp;h=75670af93f7f7ce9b281a40f6edd3e115569732a;hb=f630d9d87c8758269a9b1aeec2db008cbc105b60;hp=206e24e687cc1b8cd60cae8e02f23079badfeb49;hpb=5245ded8cba4462a4a7552cb10e1722e80e49d42;p=apitrace diff --git a/cli/cli_trim.cpp b/cli/cli_trim.cpp index 206e24e..75670af 100644 --- a/cli/cli_trim.cpp +++ b/cli/cli_trim.cpp @@ -24,11 +24,12 @@ * **************************************************************************/ - #include #include // for CHAR_MAX #include +#include + #include "cli.hpp" #include "os_string.hpp" @@ -46,15 +47,23 @@ usage(void) << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n" << synopsis << "\n" "\n" - " -h, --help show this help message and exit\n" - " --calls=CALLSET only trim specified calls\n" - " -o, --output=TRACE_FILE output trace file\n" + " -h, --help Show this help message and exit\n" + " --calls=CALLSET Include specified calls in the trimmed output\n" + " --deps Perform dependency analysis and include dependent\n" + " calls as needed. This is the default behavior.\n" + " --no-deps Do not perform dependency analysis. Include only\n" + " those calls explicitly listed in --calls\n" + " --thread=THREAD_ID Only retain calls from specified thread\n" + " -o, --output=TRACE_FILE Output trace file\n" "\n" ; } enum { - CALLS_OPT = CHAR_MAX + 1, + CALLS_OPT = CHAR_MAX + 1, + DEPS_OPT, + NO_DEPS_OPT, + THREAD_OPT, }; const static char * @@ -64,16 +73,151 @@ const static struct option longOptions[] = { {"help", no_argument, 0, 'h'}, {"calls", required_argument, 0, CALLS_OPT}, - {"output", optional_argument, 0, 'o'}, + {"deps", no_argument, 0, DEPS_OPT}, + {"no-deps", no_argument, 0, NO_DEPS_OPT}, + {"thread", required_argument, 0, THREAD_OPT}, + {"output", required_argument, 0, 'o'}, {0, 0, 0, 0} }; +struct stringCompare { + bool operator() (const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +class TraceAnalyzer { + /* Map for tracking resource dependencies between calls. */ + std::map, stringCompare > resources; + + /* The final set of calls required. This consists of calls added + * explicitly with the require() method as well as all calls + * implicitly required by those through resource dependencies. */ + std::set required; + +public: + TraceAnalyzer() {} + ~TraceAnalyzer() {} + + /* Compute and record all the resources provided by this call. */ + void analyze(trace::Call *call) { + resources["state"].insert(call->no); + } + + /* Require this call and all of its dependencies to be included in + * the final trace. */ + void require(trace::Call *call) { + std::set *dependencies; + std::set::iterator i; + + /* First, find and insert all calls that this call depends on. */ + dependencies = &resources["state"]; + for (i = dependencies->begin(); i != dependencies->end(); i++) { + required.insert(*i); + } + resources["state"].clear(); + + /* Then insert this call itself. */ + required.insert(call->no); + } + + /* Return a set of all the required calls, (both those calls added + * explicitly with require() and those implicitly depended + * upon. */ + std::set *get_required(void) { + return &required; + } +}; + +struct trim_options { + /* Calls to be included in trace. */ + trace::CallSet calls; + + /* Whether dependency analysis should be performed. */ + bool dependency_analysis; + + /* Output filename */ + std::string output; + + /* Emit only calls from this thread (-1 == all threads) */ + int thread; +}; + +static int +trim_trace(const char *filename, struct trim_options *options) +{ + trace::ParseBookmark beginning; + trace::Parser p; + TraceAnalyzer analyzer; + std::set *required; + + if (!p.open(filename)) { + std::cerr << "error: failed to open " << filename << "\n"; + return 1; + } + + /* Mark the beginning so we can return here for pass 2. */ + p.getBookmark(beginning); + + /* In pass 1, analyze which calls are needed. */ + trace::Call *call; + while ((call = p.parse_call())) { + /* If requested, ignore all calls not belonging to the specified thread. */ + if (options->thread != -1 && call->thread_id != options->thread) + continue; + + /* If this call is included in the user-specified call + * set, then we don't need to perform any analysis on + * it. We know it must be included. */ + if (options->calls.contains(*call)) { + analyzer.require(call); + } else { + if (options->dependency_analysis) + analyzer.analyze(call); + } + } + + /* Prepare output file and writer for output. */ + if (options->output.empty()) { + os::String base(filename); + base.trimExtension(); + + options->output = std::string(base.str()) + std::string("-trim.trace"); + } + + trace::Writer writer; + if (!writer.open(options->output.c_str())) { + std::cerr << "error: failed to create " << filename << "\n"; + return 1; + } + + /* Reset bookmark for pass 2. */ + p.setBookmark(beginning); + + /* In pass 2, emit the calls that are required. */ + required = analyzer.get_required(); + + while ((call = p.parse_call())) { + if (required->find(call->no) != required->end()) { + writer.writeCall(call); + } + delete call; + } + + std::cout << "Trimmed trace is available as " << options->output << "\n"; + + return 0; +} + static int command(int argc, char *argv[]) { - std::string output; - trace::CallSet calls(trace::FREQUENCY_ALL); - int i; + struct trim_options options; + + options.calls = trace::CallSet(trace::FREQUENCY_ALL); + options.dependency_analysis = true; + options.output = ""; + options.thread = -1; int opt; while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { @@ -82,10 +226,19 @@ command(int argc, char *argv[]) usage(); return 0; case CALLS_OPT: - calls = trace::CallSet(optarg); + options.calls = trace::CallSet(optarg); + break; + case DEPS_OPT: + options.dependency_analysis = true; + break; + case NO_DEPS_OPT: + options.dependency_analysis = false; + break; + case THREAD_OPT: + options.thread = atoi(optarg); break; case 'o': - output = optarg; + options.output = optarg; break; default: std::cerr << "error: unexpected option `" << opt << "`\n"; @@ -100,38 +253,17 @@ command(int argc, char *argv[]) return 1; } - for (i = optind; i < argc; ++i) { - trace::Parser p; - if (!p.open(argv[i])) { - std::cerr << "error: failed to open " << argv[i] << "\n"; - return 1; + if (argc > optind + 1) { + std::cerr << "error: extraneous arguments:"; + for (int i = optind + 1; i < argc; i++) { + std::cerr << " " << argv[i]; } - - if (output.empty()) { - os::String base(argv[i]); - base.trimExtension(); - - output = std::string(base.str()) + std::string("-trim.trace"); - } - - trace::Writer writer; - if (!writer.open(output.c_str())) { - std::cerr << "error: failed to create " << argv[i] << "\n"; - return 1; - } - - trace::Call *call; - while ((call = p.parse_call())) { - if (calls.contains(*call)) { - writer.writeCall(call); - } - delete call; - } - - std::cout << "Trimmed trace is available as " << output << "\n"; + std::cerr << "\n"; + usage(); + return 1; } - return 0; + return trim_trace(argv[optind], &options); } const Command trim_command = {