X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=retrace%2Fretrace_main.cpp;h=0c358e185517a80e08973a23666a3ad89c62379c;hb=448a82a8d4070b8911f6db667d55693d1dc8f99f;hp=c15af29fe3daf2edf9696957aa5b8b321d670fd3;hpb=463d59fe6d053d3164d2922f59b271dec1c25031;p=apitrace diff --git a/retrace/retrace_main.cpp b/retrace/retrace_main.cpp index c15af29..0c358e1 100644 --- a/retrace/retrace_main.cpp +++ b/retrace/retrace_main.cpp @@ -1,6 +1,8 @@ /************************************************************************** * * Copyright 2011 Jose Fonseca + * Copyright (C) 2013 Intel Corporation. All rights reversed. + * Author: Shuang He * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,42 +27,61 @@ #include +#include // for CHAR_MAX #include +#include #include "os_binary.hpp" #include "os_time.hpp" +#include "os_thread.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; +retrace::Retracer retracer; + namespace retrace { trace::Parser parser; +trace::Profiler profiler; int verbosity = 0; bool debug = true; -bool profiling = false; +bool dumpingState = false; +Driver driver = DRIVER_DEFAULT; +const char *driverModule = NULL; bool doubleBuffer = true; bool coreProfile = false; +bool profiling = false; +bool profilingGpuTimes = false; +bool profilingCpuTimes = false; +bool profilingPixelsDrawn = false; +bool profilingMemoryUsage = false; +bool useCallNos = true; +bool singleThread = false; -static unsigned frameNo = 0; +unsigned frameNo = 0; +unsigned callNo = 0; void @@ -69,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; @@ -86,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"; } @@ -111,58 +146,386 @@ takeSnapshot(unsigned call_no) { delete src; + snapshot_no++; + return; } +/** + * Retrace one call. + * + * Take snapshots before/after retracing (as appropriate) and dispatch it to + * the respective handler. + */ static void -mainLoop() { - retrace::Retracer retracer; +retraceCall(trace::Call *call) { + bool swapRenderTarget = call->flags & + trace::CALL_FLAG_SWAP_RENDERTARGET; + bool doSnapshot = snapshotFrequency.contains(*call) || + compareFrequency.contains(*call); + + // For calls which cause rendertargets to be swaped, we take the + // snapshot _before_ swapping the rendertargets. + if (doSnapshot && swapRenderTarget) { + if (call->flags & trace::CALL_FLAG_END_FRAME) { + // For swapbuffers/presents we still use this + // call number, spite not have been executed yet. + takeSnapshot(call->no); + } else { + // Whereas for ordinate fbo/rendertarget changes we + // use the previous call's number. + takeSnapshot(call->no - 1); + } + } - addCallbacks(retracer); + callNo = call->no; + retracer.retrace(*call); - long long startTime = 0; + if (doSnapshot && !swapRenderTarget) + takeSnapshot(call->no); - startTime = os::getTime(); - trace::Call *call; + if (call->no >= dumpStateCallNo && + dumper->dumpState(std::cout)) { + exit(0); + } +} - while ((call = retrace::parser.parse_call())) { - bool swapRenderTarget = call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET; - bool doSnapshot = - snapshotFrequency.contains(*call) || - compareFrequency.contains(*call) - ; - - // For calls which cause rendertargets to be swaped, we take the - // snapshot _before_ swapping the rendertargets. - if (doSnapshot && swapRenderTarget) { - if (call->flags & trace::CALL_FLAG_END_FRAME) { - // For swapbuffers/presents we still use this call number, - // spite not have been executed yet. - takeSnapshot(call->no); - } else { - // Whereas for ordinate fbo/rendertarget changes we use the - // previous call's number. - takeSnapshot(call->no - 1); + +class RelayRunner; + + +/** + * Implement multi-threading by mimicking a relay race. + */ +class RelayRace +{ +private: + /** + * Runners indexed by the leg they run (i.e, the thread_ids from the + * trace). + */ + std::vector runners; + +public: + RelayRace(); + + ~RelayRace(); + + RelayRunner * + getRunner(unsigned leg); + + inline RelayRunner * + getForeRunner() { + return getRunner(0); + } + + void + run(void); + + void + passBaton(trace::Call *call); + + void + 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 +{ +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; + + static void * + runnerThread(RelayRunner *_this); + +public: + RelayRunner(RelayRace *race, unsigned _leg) : + race(race), + leg(_leg), + finished(false), + baton(0) + { + /* The fore runner does not need a new thread */ + if (leg) { + thread = os::thread(runnerThread, this); + } + } + + /** + * Thread main loop. + */ + void + runRace(void) { + os::unique_lock lock(mutex); + + while (1) { + while (!finished && !baton) { + wake_cond.wait(lock); + } + + if (finished) { + break; } + + assert(baton); + trace::Call *call = baton; + baton = 0; + + runLeg(call); } - retracer.retrace(*call); + if (0) std::cerr << "leg " << leg << " actually finishing\n"; - if (doSnapshot && !swapRenderTarget) { - takeSnapshot(call->no); + if (leg == 0) { + race->stopRunners(); } + } + + /** + * 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) { + /* Notify the fore runner */ + race->finishLine(); + } else { + /* We are the fore runner */ + finished = true; + } + } + } + + /** + * Called by other threads when relinquishing the baton. + */ + void + receiveBaton(trace::Call *call) { + assert (call->thread_id == leg); + + mutex.lock(); + baton = call; + mutex.unlock(); + + wake_cond.signal(); + } + + /** + * Called by the fore runner when the race is over. + */ + void + finishRace() { + if (0) std::cerr << "notify finish to leg " << leg << "\n"; + + mutex.lock(); + finished = true; + mutex.unlock(); + + wake_cond.signal(); + } +}; + + +void * +RelayRunner::runnerThread(RelayRunner *_this) { + _this->runRace(); + return 0; +} + - if (call->no >= dumpStateCallNo && - dumpState(std::cout)) { - exit(0); +RelayRace::RelayRace() { + runners.push_back(new RelayRunner(this, 0)); +} + + +RelayRace::~RelayRace() { + assert(runners.size() >= 1); + std::vector::const_iterator it; + for (it = runners.begin(); it != runners.end(); ++it) { + RelayRunner* runner = *it; + if (runner) { + delete runner; } + } +} + - delete call; +/** + * Get (or instantiate) a runner for the specified leg. + */ +RelayRunner * +RelayRace::getRunner(unsigned leg) { + RelayRunner *runner; + + if (leg >= runners.size()) { + runners.resize(leg + 1); + runner = 0; + } else { + runner = runners[leg]; + } + if (!runner) { + runner = new RelayRunner(this, leg); + runners[leg] = runner; } + return runner; +} + - // Reached the end of trace - flushRendering(); +/** + * Start the race. + */ +void +RelayRace::run(void) { + trace::Call *call; + call = parser.parse_call(); + if (!call) { + /* Nothing to do */ + return; + } + + /* 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 = 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"; + RelayRunner *runner = getRunner(call->thread_id); + 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::stopRunners(void) { + std::vector::const_iterator it; + for (it = runners.begin() + 1; it != runners.end(); ++it) { + RelayRunner* runner = *it; + if (runner) { + runner->finishRace(); + } + } +} + + +static void +mainLoop() { + addCallbacks(retracer); + + long long startTime = 0; + frameNo = 0; + + startTime = os::getTime(); + + 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); @@ -177,7 +540,7 @@ mainLoop() { if (waitOnFinish) { waitForInput(); } else { - exit(0); + return; } } @@ -188,71 +551,146 @@ mainLoop() { static void usage(const char *argv0) { std::cout << - "Usage: " << argv0 << " [OPTION] TRACE\n" + "Usage: " << argv0 << " [OPTION] TRACE [...]\n" "Replay TRACE.\n" "\n" - " -b benchmark mode (no error checking or warning messages)\n" - " -p profiling mode (run whole trace, dump profiling info)\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"; } extern "C" int main(int argc, char **argv) { - assert(compareFrequency.empty()); - assert(snapshotFrequency.empty()); - + using namespace retrace; int i; - for (i = 1; i < argc; ++i) { - const char *arg = argv[i]; - if (arg[0] != '-') { - break; - } + assert(compareFrequency.empty()); + assert(snapshotFrequency.empty()); - if (!strcmp(arg, "--")) { - break; - } else if (!strcmp(arg, "-b")) { - retrace::debug = false; - retrace::verbosity = -1; - } else if (!strcmp(arg, "-p")) { + 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::profiling = true; 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); } @@ -260,27 +698,66 @@ 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 { - std::cerr << "error: unknown option " << arg << "\n"; + break; + case LOOP_OPT: + loopOnFinish = true; + break; + case PGPU_OPT: + retrace::debug = false; + retrace::profiling = true; + retrace::verbosity = -1; + + 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; } } retrace::setUp(); + if (retrace::profiling) { + retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage); + } + + os::setExceptionCallback(exceptionCallback); - for ( ; i < argc; ++i) { + for (i = optind; i < argc; ++i) { if (!retrace::parser.open(argv[i])) { - std::cerr << "error: failed to open " << argv[i] << "\n"; return 1; } @@ -288,8 +765,11 @@ int main(int argc, char **argv) retrace::parser.close(); } + + os::resetExceptionCallback(); - retrace::cleanUp(); + // XXX: X often hangs on XCloseDisplay + //retrace::cleanUp(); return 0; }