1 /**************************************************************************
3 * Copyright 2011 Jose Fonseca
4 * Copyright (C) 2013 Intel Corporation. All rights reversed.
5 * Author: Shuang He <shuang.he@intel.com>
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 **************************************************************************/
30 #include <limits.h> // for CHAR_MAX
34 #include <unistd.h> // for isatty()
37 #include "os_binary.hpp"
38 #include "os_time.hpp"
39 #include "os_thread.hpp"
41 #include "trace_callset.hpp"
42 #include "trace_dump.hpp"
43 #include "trace_option.hpp"
44 #include "retrace.hpp"
47 static bool waitOnFinish = false;
48 static bool loopOnFinish = false;
50 static const char *snapshotPrefix = NULL;
54 } snapshotFormat = PNM_FMT;
56 static trace::CallSet snapshotFrequency;
57 static trace::ParseBookmark lastFrameStart;
59 static unsigned dumpStateCallNo = ~0;
61 retrace::Retracer retracer;
68 trace::Profiler profiler;
73 bool dumpingState = false;
75 Driver driver = DRIVER_DEFAULT;
76 const char *driverModule = NULL;
78 bool doubleBuffer = true;
79 bool coreProfile = false;
81 bool profiling = false;
82 bool profilingGpuTimes = false;
83 bool profilingCpuTimes = false;
84 bool profilingPixelsDrawn = false;
85 bool profilingMemoryUsage = false;
86 bool useCallNos = true;
87 bool singleThread = false;
94 frameComplete(trace::Call &call) {
99 static Dumper defaultDumper;
101 Dumper *dumper = &defaultDumper;
108 takeSnapshot(unsigned call_no) {
109 static unsigned snapshot_no = 0;
111 assert(snapshotPrefix);
113 image::Image *src = dumper->getSnapshot();
115 std::cerr << call_no << ": warning: failed to get snapshot\n";
119 if (snapshotPrefix) {
120 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
122 snprintf(comment, sizeof comment, "%u",
123 useCallNos ? call_no : snapshot_no);
124 if (snapshotFormat == RAW_RGB)
125 src->writeRAW(std::cout);
127 src->writePNM(std::cout, comment);
129 os::String filename = os::String::format("%s%010u.png",
131 useCallNos ? call_no : snapshot_no);
132 if (src->writePNG(filename) && retrace::verbosity >= 0) {
133 std::cout << "Wrote " << filename << "\n";
149 * Take snapshots before/after retracing (as appropriate) and dispatch it to
150 * the respective handler.
153 retraceCall(trace::Call *call) {
154 bool swapRenderTarget = call->flags &
155 trace::CALL_FLAG_SWAP_RENDERTARGET;
156 bool doSnapshot = snapshotFrequency.contains(*call);
158 // For calls which cause rendertargets to be swaped, we take the
159 // snapshot _before_ swapping the rendertargets.
160 if (doSnapshot && swapRenderTarget) {
161 if (call->flags & trace::CALL_FLAG_END_FRAME) {
162 // For swapbuffers/presents we still use this
163 // call number, spite not have been executed yet.
164 takeSnapshot(call->no);
166 // Whereas for ordinate fbo/rendertarget changes we
167 // use the previous call's number.
168 takeSnapshot(call->no - 1);
173 retracer.retrace(*call);
175 if (doSnapshot && !swapRenderTarget)
176 takeSnapshot(call->no);
178 if (call->no >= dumpStateCallNo &&
179 dumper->dumpState(std::cout)) {
189 * Implement multi-threading by mimicking a relay race.
195 * Runners indexed by the leg they run (i.e, the thread_ids from the
198 std::vector<RelayRunner*> runners;
206 getRunner(unsigned leg);
217 passBaton(trace::Call *call);
228 * Each runner is a thread.
230 * The fore runner doesn't have its own thread, but instead uses the thread
231 * where the race started.
236 friend class RelayRace;
243 os::condition_variable wake_cond;
246 * There are protected by the mutex.
254 runnerThread(RelayRunner *_this);
257 RelayRunner(RelayRace *race, unsigned _leg) :
263 /* The fore runner does not need a new thread */
265 thread = os::thread(runnerThread, this);
270 if (thread.joinable()) {
280 os::unique_lock<os::mutex> lock(mutex);
283 while (!finished && !baton) {
284 wake_cond.wait(lock);
292 trace::Call *call = baton;
298 if (0) std::cerr << "leg " << leg << " actually finishing\n";
306 * Interpret successive calls.
309 runLeg(trace::Call *call) {
311 /* Consume successive calls for this thread. */
313 bool callEndsFrame = false;
314 static trace::ParseBookmark frameStart;
317 assert(call->thread_id == leg);
319 if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
320 callEndsFrame = true;
321 parser.getBookmark(frameStart);
326 call = parser.parse_call();
328 /* Restart last frame if looping is requested. */
331 parser.setBookmark(lastFrameStart);
332 call = parser.parse_call();
333 } else if (callEndsFrame) {
334 lastFrameStart = frameStart;
338 } while (call && call->thread_id == leg);
342 assert(call->thread_id != leg);
344 race->passBaton(call);
346 /* Reached the finish line */
347 if (0) std::cerr << "finished on leg " << leg << "\n";
349 /* Notify the fore runner */
352 /* We are the fore runner */
359 * Called by other threads when relinquishing the baton.
362 receiveBaton(trace::Call *call) {
363 assert (call->thread_id == leg);
373 * Called by the fore runner when the race is over.
377 if (0) std::cerr << "notify finish to leg " << leg << "\n";
389 RelayRunner::runnerThread(RelayRunner *_this) {
395 RelayRace::RelayRace() {
396 runners.push_back(new RelayRunner(this, 0));
400 RelayRace::~RelayRace() {
401 assert(runners.size() >= 1);
402 std::vector<RelayRunner*>::const_iterator it;
403 for (it = runners.begin(); it != runners.end(); ++it) {
404 RelayRunner* runner = *it;
413 * Get (or instantiate) a runner for the specified leg.
416 RelayRace::getRunner(unsigned leg) {
419 if (leg >= runners.size()) {
420 runners.resize(leg + 1);
423 runner = runners[leg];
426 runner = new RelayRunner(this, leg);
427 runners[leg] = runner;
437 RelayRace::run(void) {
439 call = parser.parse_call();
445 /* If the user wants to loop we need to get a bookmark target. We
446 * usually get this after replaying a call that ends a frame, but
447 * for a trace that has only one frame we need to get it at the
450 parser.getBookmark(lastFrameStart);
453 RelayRunner *foreRunner = getForeRunner();
454 if (call->thread_id == 0) {
455 /* We are the forerunner thread, so no need to pass baton */
456 foreRunner->baton = call;
461 /* Start the forerunner thread */
462 foreRunner->runRace();
467 * Pass the baton (i.e., the call) to the appropriate thread.
470 RelayRace::passBaton(trace::Call *call) {
471 if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
472 RelayRunner *runner = getRunner(call->thread_id);
473 runner->receiveBaton(call);
478 * Called when a runner other than the forerunner reaches the finish line.
480 * Only the fore runner can finish the race, so inform him that the race is
484 RelayRace::finishLine(void) {
485 RelayRunner *foreRunner = getForeRunner();
486 foreRunner->finishRace();
491 * Called by the fore runner after finish line to stop all other runners.
494 RelayRace::stopRunners(void) {
495 std::vector<RelayRunner*>::const_iterator it;
496 for (it = runners.begin() + 1; it != runners.end(); ++it) {
497 RelayRunner* runner = *it;
499 runner->finishRace();
507 addCallbacks(retracer);
509 long long startTime = 0;
512 startTime = os::getTime();
516 while ((call = parser.parse_call())) {
526 long long endTime = os::getTime();
527 float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
529 if ((retrace::verbosity >= -1) || (retrace::profiling)) {
531 "Rendered " << frameNo << " frames"
532 " in " << timeInterval << " secs,"
533 " average of " << (frameNo/timeInterval) << " fps\n";
544 } /* namespace retrace */
548 usage(const char *argv0) {
550 "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
553 " -b, --benchmark benchmark mode (no error checking or warning messages)\n"
554 " --pcpu cpu profiling (cpu times per call)\n"
555 " --pgpu gpu profiling (gpu times per draw call)\n"
556 " --ppd pixels drawn profiling (pixels drawn per draw call)\n"
557 " --pmem memory usage profiling (vsize rss per call)\n"
558 " --call-nos[=BOOL] use call numbers in snapshot filenames\n"
559 " --core use core profile\n"
560 " --db use a double buffer visual (default)\n"
561 " --driver=DRIVER force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n"
562 " --sb use a single buffer visual\n"
563 " -s, --snapshot-prefix=PREFIX take snapshots; `-` for PNM stdout output\n"
564 " --snapshot-format=FMT use (PNM or RGB; default is PNM) when writing to stdout output\n"
565 " -S, --snapshot=CALLSET calls to snapshot (default is every frame)\n"
566 " -v, --verbose increase output verbosity\n"
567 " -D, --dump-state=CALL dump state at specific call no\n"
568 " -w, --wait waitOnFinish on final frame\n"
569 " --loop continuously loop, replaying final frame.\n"
570 " --singlethread use a single thread to replay command stream\n";
574 CALL_NOS_OPT = CHAR_MAX + 1,
589 shortOptions = "bD:hs:S:vw";
591 const static struct option
593 {"benchmark", no_argument, 0, 'b'},
594 {"call-nos", optional_argument, 0, CALL_NOS_OPT },
595 {"core", no_argument, 0, CORE_OPT},
596 {"db", no_argument, 0, DB_OPT},
597 {"driver", required_argument, 0, DRIVER_OPT},
598 {"dump-state", required_argument, 0, 'D'},
599 {"help", no_argument, 0, 'h'},
600 {"pcpu", no_argument, 0, PCPU_OPT},
601 {"pgpu", no_argument, 0, PGPU_OPT},
602 {"ppd", no_argument, 0, PPD_OPT},
603 {"pmem", no_argument, 0, PMEM_OPT},
604 {"sb", no_argument, 0, SB_OPT},
605 {"snapshot-prefix", required_argument, 0, 's'},
606 {"snapshot-format", required_argument, 0, SNAPSHOT_FORMAT_OPT},
607 {"snapshot", required_argument, 0, 'S'},
608 {"verbose", no_argument, 0, 'v'},
609 {"wait", no_argument, 0, 'w'},
610 {"loop", no_argument, 0, LOOP_OPT},
611 {"singlethread", no_argument, 0, SINGLETHREAD_OPT},
616 static void exceptionCallback(void)
618 std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
623 int main(int argc, char **argv)
625 using namespace retrace;
628 assert(snapshotFrequency.empty());
631 while ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
637 retrace::debug = false;
638 retrace::verbosity = -1;
641 useCallNos = trace::boolOption(optarg);
644 dumpStateCallNo = atoi(optarg);
646 retrace::verbosity = -2;
649 retrace::coreProfile = true;
652 retrace::doubleBuffer = true;
655 if (strcasecmp(optarg, "hw") == 0) {
656 driver = DRIVER_HARDWARE;
657 } else if (strcasecmp(optarg, "sw") == 0) {
658 driver = DRIVER_SOFTWARE;
659 } else if (strcasecmp(optarg, "ref") == 0) {
660 driver = DRIVER_REFERENCE;
661 } else if (strcasecmp(optarg, "null") == 0) {
662 driver = DRIVER_NULL;
664 driver = DRIVER_MODULE;
665 driverModule = optarg;
669 retrace::doubleBuffer = false;
671 case SINGLETHREAD_OPT:
672 retrace::singleThread = true;
675 snapshotPrefix = optarg;
676 if (snapshotFrequency.empty()) {
677 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
679 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
680 os::setBinaryMode(stdout);
681 retrace::verbosity = -2;
684 * Create the snapshot directory if it does not exist.
686 * We can't just use trimFilename() because when applied to
687 * "/foo/boo/" it would merely return "/foo".
689 * XXX: create nested directories.
691 os::String prefix(snapshotPrefix);
692 os::String::iterator sep = prefix.rfindSep(false);
693 if (sep != prefix.end()) {
694 prefix.erase(sep, prefix.end());
695 if (!prefix.exists() && !os::createDirectory(prefix)) {
696 std::cerr << "error: failed to create `" << prefix.str() << "` directory\n";
701 case SNAPSHOT_FORMAT_OPT:
702 if (strcmp(optarg, "RGB") == 0)
703 snapshotFormat = RAW_RGB;
705 snapshotFormat = PNM_FMT;
708 snapshotFrequency = trace::CallSet(optarg);
709 if (snapshotPrefix == NULL) {
714 ++retrace::verbosity;
723 retrace::debug = false;
724 retrace::profiling = true;
725 retrace::verbosity = -1;
727 retrace::profilingGpuTimes = true;
730 retrace::debug = false;
731 retrace::profiling = true;
732 retrace::verbosity = -1;
734 retrace::profilingCpuTimes = true;
737 retrace::debug = false;
738 retrace::profiling = true;
739 retrace::verbosity = -1;
741 retrace::profilingPixelsDrawn = true;
744 retrace::debug = false;
745 retrace::profiling = true;
746 retrace::verbosity = -1;
748 retrace::profilingMemoryUsage = true;
751 std::cerr << "error: unknown option " << opt << "\n";
758 if (!isatty(STDOUT_FILENO)) {
759 dumpFlags |= trace::DUMP_FLAG_NO_COLOR;
764 if (retrace::profiling) {
765 retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage);
768 os::setExceptionCallback(exceptionCallback);
770 for (i = optind; i < argc; ++i) {
771 if (!retrace::parser.open(argv[i])) {
777 retrace::parser.close();
780 os::resetExceptionCallback();
782 // XXX: X often hangs on XCloseDisplay
783 //retrace::cleanUp();