]> git.cworth.org Git - apitrace/commitdiff
trim: Add framework for performing dependency analysis while trimming
authorCarl Worth <cworth@cworth.org>
Fri, 10 Aug 2012 17:15:30 +0000 (10:15 -0700)
committerJosé Fonseca <jose.r.fonseca@gmail.com>
Thu, 22 Nov 2012 08:02:47 +0000 (08:02 +0000)
The current analysis is as brain-dead as possible. All calls are
interpreted as affecting the current "state" resource, and all calls
are also interpreted as depending on the current "state". In other
words, no calls at all will be trimmed. Obviously, this is not useful
analysis, but all the framewok is now present for easily doing more
interesting things.

A new --no-deps option is now made available to "apitrace trim" so
that users can avoid all dependency analysis and still get the prior
behavior where only calls explicitly included in the --calls option
are included in the trim output.

cli/cli_trim.cpp

index eea55535e95395f8707afd7f6d1090e6edc873ec..c54565f8e983ec1d5173aa6c221506cd4b6432fb 100644 (file)
  *
  **************************************************************************/
 
-
 #include <string.h>
 #include <limits.h> // for CHAR_MAX
 #include <getopt.h>
 
+#include <set>
+
 #include "cli.hpp"
 
 #include "os_string.hpp"
@@ -46,16 +47,22 @@ usage(void)
         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
         << synopsis << "\n"
         "\n"
-        "    -h, --help               show this help message and exit\n"
-        "        --calls=CALLSET      only retain specified calls\n"
-        "        --thread=THREAD_ID   only retain calls from specified thread\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,
+    DEPS_OPT,
+    NO_DEPS_OPT,
     THREAD_OPT,
 };
 
@@ -66,18 +73,151 @@ const static struct option
 longOptions[] = {
     {"help", no_argument, 0, 'h'},
     {"calls", required_argument, 0, CALLS_OPT},
+    {"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<const char *, std::set<unsigned>, 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<unsigned> 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<unsigned> *dependencies;
+        std::set<unsigned>::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<unsigned> *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<unsigned> *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 thread = -1;
-    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) {
@@ -86,13 +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:
-            thread = atoi(optarg);
+            options.thread = atoi(optarg);
             break;
         case 'o':
-            output = optarg;
+            options.output = optarg;
             break;
         default:
             std::cerr << "error: unexpected option `" << opt << "`\n";
@@ -107,38 +253,7 @@ command(int argc, char *argv[])
         return 1;
     }
 
-    for (i = optind; i < argc; ++i) {
-        trace::Parser p;
-        if (!p.open(argv[i])) {
-            return 1;
-        }
-
-        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) &&
-                (thread == -1 || call->thread_id == thread)) {
-                writer.writeCall(call);
-            }
-            delete call;
-        }
-
-        std::cout << "Trimmed trace is available as " << output << "\n";
-    }
-
-    return 0;
+    return trim_trace(argv[optind], &options);
 }
 
 const Command trim_command = {