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 "os_binary.hpp"
35 #include "os_time.hpp"
36 #include "os_thread.hpp"
38 #include "trace_callset.hpp"
39 #include "trace_dump.hpp"
40 #include "trace_option.hpp"
41 #include "retrace.hpp"
44 static bool waitOnFinish = false;
45 static bool loopOnFinish = false;
47 static const char *comparePrefix = NULL;
48 static const char *snapshotPrefix = NULL;
49 static trace::CallSet snapshotFrequency;
50 static trace::CallSet compareFrequency;
51 static trace::ParseBookmark lastFrameStart;
53 static unsigned dumpStateCallNo = ~0;
55 retrace::Retracer retracer;
62 trace::Profiler profiler;
67 bool dumpingState = false;
69 Driver driver = DRIVER_DEFAULT;
70 const char *driverModule = NULL;
72 bool doubleBuffer = true;
73 bool coreProfile = false;
75 bool profiling = false;
76 bool profilingGpuTimes = false;
77 bool profilingCpuTimes = false;
78 bool profilingPixelsDrawn = false;
79 bool profilingMemoryUsage = false;
80 bool useCallNos = true;
81 bool singleThread = false;
88 frameComplete(trace::Call &call) {
93 static Dumper defaultDumper;
95 Dumper *dumper = &defaultDumper;
99 * Take/compare snapshots.
102 takeSnapshot(unsigned call_no) {
103 static unsigned snapshot_no = 0;
105 assert(snapshotPrefix || comparePrefix);
107 image::Image *ref = NULL;
110 os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
111 ref = image::readPNG(filename);
115 if (retrace::verbosity >= 0) {
116 std::cout << "Read " << filename << "\n";
120 image::Image *src = dumper->getSnapshot();
122 std::cout << "Failed to get snapshot\n";
126 if (snapshotPrefix) {
127 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
129 snprintf(comment, sizeof comment, "%u",
130 useCallNos ? call_no : snapshot_no);
131 src->writePNM(std::cout, comment);
133 os::String filename = os::String::format("%s%010u.png",
135 useCallNos ? call_no : snapshot_no);
136 if (src->writePNG(filename) && retrace::verbosity >= 0) {
137 std::cout << "Wrote " << filename << "\n";
143 std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
158 * Take snapshots before/after retracing (as appropriate) and dispatch it to
159 * the respective handler.
162 retraceCall(trace::Call *call) {
163 bool swapRenderTarget = call->flags &
164 trace::CALL_FLAG_SWAP_RENDERTARGET;
165 bool doSnapshot = snapshotFrequency.contains(*call) ||
166 compareFrequency.contains(*call);
168 // For calls which cause rendertargets to be swaped, we take the
169 // snapshot _before_ swapping the rendertargets.
170 if (doSnapshot && swapRenderTarget) {
171 if (call->flags & trace::CALL_FLAG_END_FRAME) {
172 // For swapbuffers/presents we still use this
173 // call number, spite not have been executed yet.
174 takeSnapshot(call->no);
176 // Whereas for ordinate fbo/rendertarget changes we
177 // use the previous call's number.
178 takeSnapshot(call->no - 1);
183 retracer.retrace(*call);
185 if (doSnapshot && !swapRenderTarget)
186 takeSnapshot(call->no);
188 if (call->no >= dumpStateCallNo &&
189 dumper->dumpState(std::cout)) {
199 * Implement multi-threading by mimicking a relay race.
205 * Runners indexed by the leg they run (i.e, the thread_ids from the
208 std::vector<RelayRunner*> runners;
216 getRunner(unsigned leg);
227 passBaton(trace::Call *call);
238 * Each runner is a thread.
240 * The fore runner doesn't have its own thread, but instead uses the thread
241 * where the race started.
246 friend class RelayRace;
253 os::condition_variable wake_cond;
256 * There are protected by the mutex.
264 runnerThread(RelayRunner *_this);
267 RelayRunner(RelayRace *race, unsigned _leg) :
273 /* The fore runner does not need a new thread */
275 thread = os::thread(runnerThread, this);
284 os::unique_lock<os::mutex> lock(mutex);
287 while (!finished && !baton) {
288 wake_cond.wait(lock);
296 trace::Call *call = baton;
302 if (0) std::cerr << "leg " << leg << " actually finishing\n";
310 * Interpret successive calls.
313 runLeg(trace::Call *call) {
315 /* Consume successive calls for this thread. */
317 bool callEndsFrame = false;
318 static trace::ParseBookmark frameStart;
321 assert(call->thread_id == leg);
323 if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
324 callEndsFrame = true;
325 parser.getBookmark(frameStart);
330 call = parser.parse_call();
332 /* Restart last frame if looping is requested. */
335 parser.setBookmark(lastFrameStart);
336 call = parser.parse_call();
337 } else if (callEndsFrame) {
338 lastFrameStart = frameStart;
342 } while (call && call->thread_id == leg);
346 assert(call->thread_id != leg);
348 race->passBaton(call);
350 /* Reached the finish line */
351 if (0) std::cerr << "finished on leg " << leg << "\n";
353 /* Notify the fore runner */
356 /* We are the fore runner */
363 * Called by other threads when relinquishing the baton.
366 receiveBaton(trace::Call *call) {
367 assert (call->thread_id == leg);
377 * Called by the fore runner when the race is over.
381 if (0) std::cerr << "notify finish to leg " << leg << "\n";
393 RelayRunner::runnerThread(RelayRunner *_this) {
399 RelayRace::RelayRace() {
400 runners.push_back(new RelayRunner(this, 0));
404 RelayRace::~RelayRace() {
405 assert(runners.size() >= 1);
406 std::vector<RelayRunner*>::const_iterator it;
407 for (it = runners.begin(); it != runners.end(); ++it) {
408 RelayRunner* runner = *it;
417 * Get (or instantiate) a runner for the specified leg.
420 RelayRace::getRunner(unsigned leg) {
423 if (leg >= runners.size()) {
424 runners.resize(leg + 1);
427 runner = runners[leg];
430 runner = new RelayRunner(this, leg);
431 runners[leg] = runner;
441 RelayRace::run(void) {
443 call = parser.parse_call();
449 /* If the user wants to loop we need to get a bookmark target. We
450 * usually get this after replaying a call that ends a frame, but
451 * for a trace that has only one frame we need to get it at the
454 parser.getBookmark(lastFrameStart);
457 RelayRunner *foreRunner = getForeRunner();
458 if (call->thread_id == 0) {
459 /* We are the forerunner thread, so no need to pass baton */
460 foreRunner->baton = call;
465 /* Start the forerunner thread */
466 foreRunner->runRace();
471 * Pass the baton (i.e., the call) to the appropriate thread.
474 RelayRace::passBaton(trace::Call *call) {
475 if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
476 RelayRunner *runner = getRunner(call->thread_id);
477 runner->receiveBaton(call);
482 * Called when a runner other than the forerunner reaches the finish line.
484 * Only the fore runner can finish the race, so inform him that the race is
488 RelayRace::finishLine(void) {
489 RelayRunner *foreRunner = getForeRunner();
490 foreRunner->finishRace();
495 * Called by the fore runner after finish line to stop all other runners.
498 RelayRace::stopRunners(void) {
499 std::vector<RelayRunner*>::const_iterator it;
500 for (it = runners.begin() + 1; it != runners.end(); ++it) {
501 RelayRunner* runner = *it;
503 runner->finishRace();
511 addCallbacks(retracer);
513 long long startTime = 0;
516 startTime = os::getTime();
521 call = parser.parse_call();
523 /* If the user wants to loop we need to get a bookmark target. We
524 * usually get this after replaying a call that ends a frame, but
525 * for a trace that has only one frame we need to get it at the
528 parser.getBookmark(lastFrameStart);
532 bool callEndsFrame = false;
533 static trace::ParseBookmark frameStart;
535 if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
536 callEndsFrame = true;
537 parser.getBookmark(frameStart);
542 call = parser.parse_call();
544 /* Restart last frame if looping is requested. */
547 parser.setBookmark(lastFrameStart);
548 call = parser.parse_call();
549 } else if (callEndsFrame) {
550 lastFrameStart = frameStart;
562 long long endTime = os::getTime();
563 float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
565 if ((retrace::verbosity >= -1) || (retrace::profiling)) {
567 "Rendered " << frameNo << " frames"
568 " in " << timeInterval << " secs,"
569 " average of " << (frameNo/timeInterval) << " fps\n";
580 } /* namespace retrace */
584 usage(const char *argv0) {
586 "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
589 " -b, --benchmark benchmark mode (no error checking or warning messages)\n"
590 " --pcpu cpu profiling (cpu times per call)\n"
591 " --pgpu gpu profiling (gpu times per draw call)\n"
592 " --ppd pixels drawn profiling (pixels drawn per draw call)\n"
593 " --pmem memory usage profiling (vsize rss per call)\n"
594 " -c, --compare=PREFIX compare against snapshots with given PREFIX\n"
595 " -C, --calls=CALLSET calls to compare (default is every frame)\n"
596 " --call-nos[=BOOL] use call numbers in snapshot filenames\n"
597 " --core use core profile\n"
598 " --db use a double buffer visual (default)\n"
599 " --driver=DRIVER force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n"
600 " --sb use a single buffer visual\n"
601 " -s, --snapshot-prefix=PREFIX take snapshots; `-` for PNM stdout output\n"
602 " -S, --snapshot=CALLSET calls to snapshot (default is every frame)\n"
603 " -v, --verbose increase output verbosity\n"
604 " -D, --dump-state=CALL dump state at specific call no\n"
605 " -w, --wait waitOnFinish on final frame\n"
606 " --loop continuously loop, replaying final frame.\n"
607 " --singlethread use a single thread to replay command stream\n";
611 CALL_NOS_OPT = CHAR_MAX + 1,
625 shortOptions = "bc:C:D:hs:S:vw";
627 const static struct option
629 {"benchmark", no_argument, 0, 'b'},
630 {"call-nos", optional_argument, 0, CALL_NOS_OPT },
631 {"calls", required_argument, 0, 'C'},
632 {"compare", required_argument, 0, 'c'},
633 {"core", no_argument, 0, CORE_OPT},
634 {"db", no_argument, 0, DB_OPT},
635 {"driver", required_argument, 0, DRIVER_OPT},
636 {"dump-state", required_argument, 0, 'D'},
637 {"help", no_argument, 0, 'h'},
638 {"pcpu", no_argument, 0, PCPU_OPT},
639 {"pgpu", no_argument, 0, PGPU_OPT},
640 {"ppd", no_argument, 0, PPD_OPT},
641 {"pmem", no_argument, 0, PMEM_OPT},
642 {"sb", no_argument, 0, SB_OPT},
643 {"snapshot-prefix", required_argument, 0, 's'},
644 {"snapshot", required_argument, 0, 'S'},
645 {"verbose", no_argument, 0, 'v'},
646 {"wait", no_argument, 0, 'w'},
647 {"loop", no_argument, 0, LOOP_OPT},
648 {"singlethread", no_argument, 0, SINGLETHREAD_OPT},
653 static void exceptionCallback(void)
655 std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
660 int main(int argc, char **argv)
662 using namespace retrace;
665 assert(compareFrequency.empty());
666 assert(snapshotFrequency.empty());
669 while ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
675 retrace::debug = false;
676 retrace::verbosity = -1;
679 useCallNos = trace::boolOption(optarg);
682 comparePrefix = optarg;
683 if (compareFrequency.empty()) {
684 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
688 compareFrequency = trace::CallSet(optarg);
689 if (comparePrefix == NULL) {
694 dumpStateCallNo = atoi(optarg);
696 retrace::verbosity = -2;
699 retrace::coreProfile = true;
702 retrace::doubleBuffer = true;
705 if (strcasecmp(optarg, "hw") == 0) {
706 driver = DRIVER_HARDWARE;
707 } else if (strcasecmp(optarg, "sw") == 0) {
708 driver = DRIVER_SOFTWARE;
709 } else if (strcasecmp(optarg, "ref") == 0) {
710 driver = DRIVER_REFERENCE;
711 } else if (strcasecmp(optarg, "null") == 0) {
712 driver = DRIVER_NULL;
714 driver = DRIVER_MODULE;
715 driverModule = optarg;
719 retrace::doubleBuffer = false;
721 case SINGLETHREAD_OPT:
722 retrace::singleThread = true;
725 snapshotPrefix = optarg;
726 if (snapshotFrequency.empty()) {
727 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
729 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
730 os::setBinaryMode(stdout);
731 retrace::verbosity = -2;
735 snapshotFrequency = trace::CallSet(optarg);
736 if (snapshotPrefix == NULL) {
741 ++retrace::verbosity;
750 retrace::debug = false;
751 retrace::profiling = true;
752 retrace::verbosity = -1;
754 retrace::profilingGpuTimes = true;
757 retrace::debug = false;
758 retrace::profiling = true;
759 retrace::verbosity = -1;
761 retrace::profilingCpuTimes = true;
764 retrace::debug = false;
765 retrace::profiling = true;
766 retrace::verbosity = -1;
768 retrace::profilingPixelsDrawn = true;
771 retrace::debug = false;
772 retrace::profiling = true;
773 retrace::verbosity = -1;
775 retrace::profilingMemoryUsage = true;
778 std::cerr << "error: unknown option " << opt << "\n";
785 if (retrace::profiling) {
786 retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage);
789 os::setExceptionCallback(exceptionCallback);
791 for (i = optind; i < argc; ++i) {
792 if (!retrace::parser.open(argv[i])) {
798 retrace::parser.close();
801 os::resetExceptionCallback();
803 // XXX: X often hangs on XCloseDisplay
804 //retrace::cleanUp();