]> git.cworth.org Git - apitrace/blobdiff - retrace/retrace_main.cpp
replay: Add a --loop option to repeatedly loop over the final frame
[apitrace] / retrace / retrace_main.cpp
index f9eabcb3b5cfe26729f17e28ab3fbb39bad02ab7..0c358e185517a80e08973a23666a3ad89c62379c 100644 (file)
@@ -1,6 +1,8 @@
 /**************************************************************************
  *
  * Copyright 2011 Jose Fonseca
+ * Copyright (C) 2013 Intel Corporation. All rights reversed.
+ * Author: Shuang He <shuang.he@intel.com>
  * All Rights Reserved.
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -25,7 +27,9 @@
 
 
 #include <string.h>
+#include <limits.h> // for CHAR_MAX
 #include <iostream>
+#include <getopt.h>
 
 #include "os_binary.hpp"
 #include "os_time.hpp"
 #include "image.hpp"
 #include "trace_callset.hpp"
 #include "trace_dump.hpp"
+#include "trace_option.hpp"
 #include "retrace.hpp"
 
 
 static bool waitOnFinish = false;
+static bool loopOnFinish = false;
 
 static const char *comparePrefix = NULL;
 static const char *snapshotPrefix = NULL;
 static trace::CallSet snapshotFrequency;
 static trace::CallSet compareFrequency;
+static trace::ParseBookmark lastFrameStart;
 
 static unsigned dumpStateCallNo = ~0;
 
@@ -59,6 +66,9 @@ int verbosity = 0;
 bool debug = true;
 bool dumpingState = false;
 
+Driver driver = DRIVER_DEFAULT;
+const char *driverModule = NULL;
+
 bool doubleBuffer = true;
 bool coreProfile = false;
 
@@ -66,6 +76,9 @@ bool profiling = false;
 bool profilingGpuTimes = false;
 bool profilingCpuTimes = false;
 bool profilingPixelsDrawn = false;
+bool profilingMemoryUsage = false;
+bool useCallNos = true;
+bool singleThread = false;
 
 unsigned frameNo = 0;
 unsigned callNo = 0;
@@ -77,8 +90,18 @@ frameComplete(trace::Call &call) {
 }
 
 
+static Dumper defaultDumper;
+
+Dumper *dumper = &defaultDumper;
+
+
+/**
+ * Take/compare snapshots.
+ */
 static void
 takeSnapshot(unsigned call_no) {
+    static unsigned snapshot_no = 0;
+
     assert(snapshotPrefix || comparePrefix);
 
     image::Image *ref = NULL;
@@ -94,18 +117,22 @@ takeSnapshot(unsigned call_no) {
         }
     }
 
-    image::Image *src = getSnapshot();
+    image::Image *src = dumper->getSnapshot();
     if (!src) {
+        std::cout << "Failed to get snapshot\n";
         return;
     }
 
     if (snapshotPrefix) {
         if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
             char comment[21];
-            snprintf(comment, sizeof comment, "%u", call_no);
+            snprintf(comment, sizeof comment, "%u",
+                     useCallNos ? call_no : snapshot_no);
             src->writePNM(std::cout, comment);
         } else {
-            os::String filename = os::String::format("%s%010u.png", snapshotPrefix, call_no);
+            os::String filename = os::String::format("%s%010u.png",
+                                                     snapshotPrefix,
+                                                     useCallNos ? call_no : snapshot_no);
             if (src->writePNG(filename) && retrace::verbosity >= 0) {
                 std::cout << "Wrote " << filename << "\n";
             }
@@ -119,13 +146,18 @@ takeSnapshot(unsigned call_no) {
 
     delete src;
 
+    snapshot_no++;
+
     return;
 }
 
 
-class RelayRunner;
-
-
+/**
+ * Retrace one call.
+ *
+ * Take snapshots before/after retracing (as appropriate) and dispatch it to
+ * the respective handler.
+ */
 static void
 retraceCall(trace::Call *call) {
     bool swapRenderTarget = call->flags &
@@ -154,60 +186,99 @@ retraceCall(trace::Call *call) {
         takeSnapshot(call->no);
 
     if (call->no >= dumpStateCallNo &&
-        dumpState(std::cout)) {
+        dumper->dumpState(std::cout)) {
         exit(0);
     }
 }
 
 
+class RelayRunner;
+
+
+/**
+ * Implement multi-threading by mimicking a relay race.
+ */
 class RelayRace
 {
-public:
+private:
+    /**
+     * Runners indexed by the leg they run (i.e, the thread_ids from the
+     * trace).
+     */
     std::vector<RelayRunner*> runners;
 
+public:
     RelayRace();
 
+    ~RelayRace();
+
     RelayRunner *
     getRunner(unsigned leg);
 
+    inline RelayRunner *
+    getForeRunner() {
+        return getRunner(0);
+    }
+
     void
-    startRace(void);
+    run(void);
 
     void
     passBaton(trace::Call *call);
 
     void
-    finishRace();
+    finishLine();
+
+    void
+    stopRunners();
 };
 
 
+/**
+ * Each runner is a thread.
+ *
+ * The fore runner doesn't have its own thread, but instead uses the thread
+ * where the race started.
+ */
 class RelayRunner
 {
-public:
+private:
+    friend class RelayRace;
+
     RelayRace *race;
+
     unsigned leg;
+    
     os::mutex mutex;
     os::condition_variable wake_cond;
 
+    /**
+     * There are protected by the mutex.
+     */
     bool finished;
     trace::Call *baton;
-    os::thread *thread;
+
+    os::thread thread;
 
     static void *
     runnerThread(RelayRunner *_this);
 
+public:
     RelayRunner(RelayRace *race, unsigned _leg) :
         race(race),
         leg(_leg),
         finished(false),
-        baton(0),
-        thread(0)
+        baton(0)
     {
+        /* The fore runner does not need a new thread */
         if (leg) {
-            thread = new os::thread(runnerThread, this);
+            thread = os::thread(runnerThread, this);
         }
     }
 
+    /**
+     * Thread main loop.
+     */
     void
     runRace(void) {
         os::unique_lock<os::mutex> lock(mutex);
@@ -231,38 +302,68 @@ public:
         if (0) std::cerr << "leg " << leg << " actually finishing\n";
 
         if (leg == 0) {
-            std::vector<RelayRunner*>::iterator it;
-            for (it = race->runners.begin() + 1; it != race->runners.end(); ++it) {
-                RelayRunner* runner = *it;
-                runner->finishRace();
-            }
+            race->stopRunners();
         }
     }
 
-    void runLeg(trace::Call *call) {
+    /**
+     * Interpret successive calls.
+     */
+    void
+    runLeg(trace::Call *call) {
+
+        /* Consume successive calls for this thread. */
         do {
+            bool callEndsFrame = false;
+            static trace::ParseBookmark frameStart;
+
             assert(call);
             assert(call->thread_id == leg);
+
+            if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
+                callEndsFrame = true;
+                parser.getBookmark(frameStart);
+            }
+
             retraceCall(call);
             delete call;
             call = parser.parse_call();
+
+            /* Restart last frame if looping is requested. */
+            if (loopOnFinish) {
+                if (!call) {
+                    parser.setBookmark(lastFrameStart);
+                    call = parser.parse_call();
+                } else if (callEndsFrame) {
+                    lastFrameStart = frameStart;
+                }
+            }
+
         } while (call && call->thread_id == leg);
 
         if (call) {
+            /* Pass the baton */
             assert(call->thread_id != leg);
             flushRendering();
             race->passBaton(call);
         } else {
+            /* Reached the finish line */
             if (0) std::cerr << "finished on leg " << leg << "\n";
             if (leg) {
-                race->finishRace();
+                /* Notify the fore runner */
+                race->finishLine();
             } else {
+                /* We are the fore runner */
                 finished = true;
             }
         }
     }
 
-    void receiveBaton(trace::Call *call) {
+    /**
+     * Called by other threads when relinquishing the baton.
+     */
+    void
+    receiveBaton(trace::Call *call) {
         assert (call->thread_id == leg);
 
         mutex.lock();
@@ -272,7 +373,11 @@ public:
         wake_cond.signal();
     }
 
-    void finishRace() {
+    /**
+     * Called by the fore runner when the race is over.
+     */
+    void
+    finishRace() {
         if (0) std::cerr << "notify finish to leg " << leg << "\n";
 
         mutex.lock();
@@ -283,6 +388,7 @@ public:
     }
 };
 
+
 void *
 RelayRunner::runnerThread(RelayRunner *_this) {
     _this->runRace();
@@ -294,6 +400,22 @@ RelayRace::RelayRace() {
     runners.push_back(new RelayRunner(this, 0));
 }
 
+
+RelayRace::~RelayRace() {
+    assert(runners.size() >= 1);
+    std::vector<RelayRunner*>::const_iterator it;
+    for (it = runners.begin(); it != runners.end(); ++it) {
+        RelayRunner* runner = *it;
+        if (runner) {
+            delete runner;
+        }
+    }
+}
+
+
+/**
+ * Get (or instantiate) a runner for the specified leg.
+ */
 RelayRunner *
 RelayRace::getRunner(unsigned leg) {
     RelayRunner *runner;
@@ -311,27 +433,43 @@ RelayRace::getRunner(unsigned leg) {
     return runner;
 }
 
+
+/**
+ * Start the race.
+ */
 void
-RelayRace::startRace(void) {
+RelayRace::run(void) {
     trace::Call *call;
     call = parser.parse_call();
-
     if (!call) {
+        /* Nothing to do */
         return;
     }
 
-    assert(call->thread_id == 0);
+    /* If the user wants to loop we need to get a bookmark target. We
+     * usually get this after replaying a call that ends a frame, but
+     * for a trace that has only one frame we need to get it at the
+     * beginning. */
+    if (loopOnFinish) {
+        parser.getBookmark(lastFrameStart);
+    }
 
-    RelayRunner *foreRunner = getRunner(0);
+    RelayRunner *foreRunner = getForeRunner();
     if (call->thread_id == 0) {
+        /* We are the forerunner thread, so no need to pass baton */
         foreRunner->baton = call;
     } else {
         passBaton(call);
     }
 
+    /* Start the forerunner thread */
     foreRunner->runRace();
 }
 
+
+/**
+ * Pass the baton (i.e., the call) to the appropriate thread.
+ */
 void
 RelayRace::passBaton(trace::Call *call) {
     if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
@@ -339,10 +477,32 @@ RelayRace::passBaton(trace::Call *call) {
     runner->receiveBaton(call);
 }
 
+
+/**
+ * Called when a runner other than the forerunner reaches the finish line.
+ *
+ * Only the fore runner can finish the race, so inform him that the race is
+ * finished.
+ */
+void
+RelayRace::finishLine(void) {
+    RelayRunner *foreRunner = getForeRunner();
+    foreRunner->finishRace();
+}
+
+
+/**
+ * Called by the fore runner after finish line to stop all other runners.
+ */
 void
-RelayRace::finishRace(void) {
-    RelayRunner *runner = getRunner(0);
-    runner->finishRace();
+RelayRace::stopRunners(void) {
+    std::vector<RelayRunner*>::const_iterator it;
+    for (it = runners.begin() + 1; it != runners.end(); ++it) {
+        RelayRunner* runner = *it;
+        if (runner) {
+            runner->finishRace();
+        }
+    }
 }
 
 
@@ -355,8 +515,17 @@ mainLoop() {
 
     startTime = os::getTime();
 
-    RelayRace race;
-    race.startRace();
+    if (singleThread) {
+        trace::Call *call;
+        while ((call = parser.parse_call())) {
+            retraceCall(call);
+            delete call;
+        };
+        flushRendering();
+    } else {
+        RelayRace race;
+        race.run();
+    }
 
     long long endTime = os::getTime();
     float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
@@ -385,20 +554,73 @@ usage(const char *argv0) {
         "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
         "Replay TRACE.\n"
         "\n"
-        "  -b           benchmark mode (no error checking or warning messages)\n"
-        "  -pcpu        cpu profiling (cpu times per call)\n"
-        "  -pgpu        gpu profiling (gpu times per draw call)\n"
-        "  -ppd         pixels drawn profiling (pixels drawn per draw call)\n"
-        "  -c PREFIX    compare against snapshots\n"
-        "  -C CALLSET   calls to compare (default is every frame)\n"
-        "  -core        use core profile\n"
-        "  -db          use a double buffer visual (default)\n"
-        "  -sb          use a single buffer visual\n"
-        "  -s PREFIX    take snapshots; `-` for PNM stdout output\n"
-        "  -S CALLSET   calls to snapshot (default is every frame)\n"
-        "  -v           increase output verbosity\n"
-        "  -D CALLNO    dump state at specific call no\n"
-        "  -w           waitOnFinish on final frame\n";
+        "  -b, --benchmark         benchmark mode (no error checking or warning messages)\n"
+        "      --pcpu              cpu profiling (cpu times per call)\n"
+        "      --pgpu              gpu profiling (gpu times per draw call)\n"
+        "      --ppd               pixels drawn profiling (pixels drawn per draw call)\n"
+        "      --pmem              memory usage profiling (vsize rss per call)\n"
+        "  -c, --compare=PREFIX    compare against snapshots with given PREFIX\n"
+        "  -C, --calls=CALLSET     calls to compare (default is every frame)\n"
+        "      --call-nos[=BOOL]   use call numbers in snapshot filenames\n"
+        "      --core              use core profile\n"
+        "      --db                use a double buffer visual (default)\n"
+        "      --driver=DRIVER     force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n"
+        "      --sb                use a single buffer visual\n"
+        "  -s, --snapshot-prefix=PREFIX    take snapshots; `-` for PNM stdout output\n"
+        "  -S, --snapshot=CALLSET  calls to snapshot (default is every frame)\n"
+        "  -v, --verbose           increase output verbosity\n"
+        "  -D, --dump-state=CALL   dump state at specific call no\n"
+        "  -w, --wait              waitOnFinish on final frame\n"
+        "      --loop              continuously loop, replaying final frame.\n"
+        "      --singlethread      use a single thread to replay command stream\n";
+}
+
+enum {
+    CALL_NOS_OPT = CHAR_MAX + 1,
+    CORE_OPT,
+    DB_OPT,
+    DRIVER_OPT,
+    PCPU_OPT,
+    PGPU_OPT,
+    PPD_OPT,
+    PMEM_OPT,
+    SB_OPT,
+    LOOP_OPT,
+    SINGLETHREAD_OPT
+};
+
+const static char *
+shortOptions = "bc:C:D:hs:S:vw";
+
+const static struct option
+longOptions[] = {
+    {"benchmark", no_argument, 0, 'b'},
+    {"call-nos", optional_argument, 0, CALL_NOS_OPT },
+    {"calls", required_argument, 0, 'C'},
+    {"compare", required_argument, 0, 'c'},
+    {"core", no_argument, 0, CORE_OPT},
+    {"db", no_argument, 0, DB_OPT},
+    {"driver", required_argument, 0, DRIVER_OPT},
+    {"dump-state", required_argument, 0, 'D'},
+    {"help", no_argument, 0, 'h'},
+    {"pcpu", no_argument, 0, PCPU_OPT},
+    {"pgpu", no_argument, 0, PGPU_OPT},
+    {"ppd", no_argument, 0, PPD_OPT},
+    {"pmem", no_argument, 0, PMEM_OPT},
+    {"sb", no_argument, 0, SB_OPT},
+    {"snapshot-prefix", required_argument, 0, 's'},
+    {"snapshot", required_argument, 0, 'S'},
+    {"verbose", no_argument, 0, 'v'},
+    {"wait", no_argument, 0, 'w'},
+    {"loop", no_argument, 0, LOOP_OPT},
+    {"singlethread", no_argument, 0, SINGLETHREAD_OPT},
+    {0, 0, 0, 0}
+};
+
+
+static void exceptionCallback(void)
+{
+    std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
 }
 
 
@@ -406,48 +628,69 @@ extern "C"
 int main(int argc, char **argv)
 {
     using namespace retrace;
+    int i;
 
     assert(compareFrequency.empty());
     assert(snapshotFrequency.empty());
 
-    int i;
-    for (i = 1; i < argc; ++i) {
-        const char *arg = argv[i];
-
-        if (arg[0] != '-') {
-            break;
-        }
-
-        if (!strcmp(arg, "--")) {
-            break;
-        } else if (!strcmp(arg, "-b")) {
+    int opt;
+    while  ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
+        switch (opt) {
+        case 'h':
+            usage(argv[0]);
+            return 0;
+        case 'b':
             retrace::debug = false;
             retrace::verbosity = -1;
-        } else if (!strcmp(arg, "-c")) {
-            comparePrefix = argv[++i];
+            break;
+        case CALL_NOS_OPT:
+            useCallNos = trace::boolOption(optarg);
+            break;
+        case 'c':
+            comparePrefix = optarg;
             if (compareFrequency.empty()) {
                 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
             }
-        } else if (!strcmp(arg, "-C")) {
-            compareFrequency = trace::CallSet(argv[++i]);
+            break;
+        case 'C':
+            compareFrequency = trace::CallSet(optarg);
             if (comparePrefix == NULL) {
                 comparePrefix = "";
             }
-        } else if (!strcmp(arg, "-D")) {
-            dumpStateCallNo = atoi(argv[++i]);
+            break;
+        case 'D':
+            dumpStateCallNo = atoi(optarg);
             dumpingState = true;
             retrace::verbosity = -2;
-        } else if (!strcmp(arg, "-core")) {
+            break;
+        case CORE_OPT:
             retrace::coreProfile = true;
-        } else if (!strcmp(arg, "-db")) {
+            break;
+        case DB_OPT:
             retrace::doubleBuffer = true;
-        } else if (!strcmp(arg, "-sb")) {
+            break;
+        case DRIVER_OPT:
+            if (strcasecmp(optarg, "hw") == 0) {
+                driver = DRIVER_HARDWARE;
+            } else if (strcasecmp(optarg, "sw") == 0) {
+                driver = DRIVER_SOFTWARE;
+            } else if (strcasecmp(optarg, "ref") == 0) {
+                driver = DRIVER_REFERENCE;
+            } else if (strcasecmp(optarg, "null") == 0) {
+                driver = DRIVER_NULL;
+            } else {
+                driver = DRIVER_MODULE;
+                driverModule = optarg;
+            }
+            break;
+        case SB_OPT:
             retrace::doubleBuffer = false;
-        } else if (!strcmp(arg, "--help")) {
-            usage(argv[0]);
-            return 0;
-        } else if (!strcmp(arg, "-s")) {
-            snapshotPrefix = argv[++i];
+            break;
+        case SINGLETHREAD_OPT:
+            retrace::singleThread = true;
+            break;
+        case 's':
+            snapshotPrefix = optarg;
             if (snapshotFrequency.empty()) {
                 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
             }
@@ -455,29 +698,52 @@ int main(int argc, char **argv)
                 os::setBinaryMode(stdout);
                 retrace::verbosity = -2;
             }
-        } else if (!strcmp(arg, "-S")) {
-            snapshotFrequency = trace::CallSet(argv[++i]);
+            break;
+        case 'S':
+            snapshotFrequency = trace::CallSet(optarg);
             if (snapshotPrefix == NULL) {
                 snapshotPrefix = "";
             }
-        } else if (!strcmp(arg, "-v")) {
+            break;
+        case 'v':
             ++retrace::verbosity;
-        } else if (!strcmp(arg, "-w")) {
+            break;
+        case 'w':
             waitOnFinish = true;
-        } else if (arg[1] == 'p') {
+            break;
+        case LOOP_OPT:
+            loopOnFinish = true;
+            break;
+        case PGPU_OPT:
             retrace::debug = false;
             retrace::profiling = true;
             retrace::verbosity = -1;
 
-            if (!strcmp(arg, "-pcpu")) {
-                retrace::profilingCpuTimes = true;
-            } else if (!strcmp(arg, "-pgpu")) {
-                retrace::profilingGpuTimes = true;
-            } else if (!strcmp(arg, "-ppd")) {
-                retrace::profilingPixelsDrawn = true;
-            }
-        } else {
-            std::cerr << "error: unknown option " << arg << "\n";
+            retrace::profilingGpuTimes = true;
+            break;
+        case PCPU_OPT:
+            retrace::debug = false;
+            retrace::profiling = true;
+            retrace::verbosity = -1;
+
+            retrace::profilingCpuTimes = true;
+            break;
+        case PPD_OPT:
+            retrace::debug = false;
+            retrace::profiling = true;
+            retrace::verbosity = -1;
+
+            retrace::profilingPixelsDrawn = true;
+            break;
+        case PMEM_OPT:
+            retrace::debug = false;
+            retrace::profiling = true;
+            retrace::verbosity = -1;
+
+            retrace::profilingMemoryUsage = true;
+            break;
+        default:
+            std::cerr << "error: unknown option " << opt << "\n";
             usage(argv[0]);
             return 1;
         }
@@ -485,12 +751,13 @@ int main(int argc, char **argv)
 
     retrace::setUp();
     if (retrace::profiling) {
-        retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
+        retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage);
     }
 
-    for ( ; i < argc; ++i) {
+    os::setExceptionCallback(exceptionCallback);
+
+    for (i = optind; i < argc; ++i) {
         if (!retrace::parser.open(argv[i])) {
-            std::cerr << "error: failed to open " << argv[i] << "\n";
             return 1;
         }
 
@@ -498,6 +765,8 @@ int main(int argc, char **argv)
 
         retrace::parser.close();
     }
+    
+    os::resetExceptionCallback();
 
     // XXX: X often hangs on XCloseDisplay
     //retrace::cleanUp();