]> git.cworth.org Git - apitrace/blob - retrace/retrace_main.cpp
ddcc27fb82e1133bd2febf717b4ebb0b7b99d8c9
[apitrace] / retrace / retrace_main.cpp
1 /**************************************************************************
2  *
3  * Copyright 2011 Jose Fonseca
4  * Copyright (C) 2013 Intel Corporation. All rights reversed.
5  * Author: Shuang He <shuang.he@intel.com>
6  * All Rights Reserved.
7  *
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:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
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
24  * THE SOFTWARE.
25  *
26  **************************************************************************/
27
28
29 #include <string.h>
30 #include <limits.h> // for CHAR_MAX
31 #include <iostream>
32 #include <getopt.h>
33
34 #include "os_binary.hpp"
35 #include "os_time.hpp"
36 #include "os_thread.hpp"
37 #include "image.hpp"
38 #include "trace_callset.hpp"
39 #include "trace_dump.hpp"
40 #include "trace_option.hpp"
41 #include "retrace.hpp"
42
43
44 static bool waitOnFinish = false;
45 static bool loopOnFinish = false;
46
47 static const char *comparePrefix = NULL;
48 static const char *snapshotPrefix = NULL;
49 static enum {
50     PNM_FMT,
51     RAW_RGB
52 } snapshotFormat = PNM_FMT;
53
54 static trace::CallSet snapshotFrequency;
55 static trace::CallSet compareFrequency;
56 static trace::ParseBookmark lastFrameStart;
57
58 static unsigned dumpStateCallNo = ~0;
59
60 retrace::Retracer retracer;
61
62
63 namespace retrace {
64
65
66 trace::Parser parser;
67 trace::Profiler profiler;
68
69
70 int verbosity = 0;
71 bool debug = true;
72 bool dumpingState = false;
73
74 Driver driver = DRIVER_DEFAULT;
75 const char *driverModule = NULL;
76
77 bool doubleBuffer = true;
78 bool coreProfile = false;
79
80 bool profiling = false;
81 bool profilingGpuTimes = false;
82 bool profilingCpuTimes = false;
83 bool profilingPixelsDrawn = false;
84 bool profilingMemoryUsage = false;
85 bool useCallNos = true;
86 bool singleThread = false;
87
88 unsigned frameNo = 0;
89 unsigned callNo = 0;
90
91
92 void
93 frameComplete(trace::Call &call) {
94     ++frameNo;
95 }
96
97
98 static Dumper defaultDumper;
99
100 Dumper *dumper = &defaultDumper;
101
102
103 /**
104  * Take/compare snapshots.
105  */
106 static void
107 takeSnapshot(unsigned call_no) {
108     static unsigned snapshot_no = 0;
109
110     assert(snapshotPrefix || comparePrefix);
111
112     image::Image *ref = NULL;
113
114     if (comparePrefix) {
115         os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
116         ref = image::readPNG(filename);
117         if (!ref) {
118             return;
119         }
120         if (retrace::verbosity >= 0) {
121             std::cout << "Read " << filename << "\n";
122         }
123     }
124
125     image::Image *src = dumper->getSnapshot();
126     if (!src) {
127         std::cerr << call_no << ": warning: failed to get snapshot\n";
128         return;
129     }
130
131     if (snapshotPrefix) {
132         if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
133             char comment[21];
134             snprintf(comment, sizeof comment, "%u",
135                      useCallNos ? call_no : snapshot_no);
136             if (snapshotFormat == RAW_RGB)
137                 src->writeRAW(std::cout);
138             else
139                 src->writePNM(std::cout, comment);
140         } else {
141             os::String filename = os::String::format("%s%010u.png",
142                                                      snapshotPrefix,
143                                                      useCallNos ? call_no : snapshot_no);
144             if (src->writePNG(filename) && retrace::verbosity >= 0) {
145                 std::cout << "Wrote " << filename << "\n";
146             }
147         }
148     }
149
150     if (ref) {
151         std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
152         delete ref;
153     }
154
155     delete src;
156
157     snapshot_no++;
158
159     return;
160 }
161
162
163 /**
164  * Retrace one call.
165  *
166  * Take snapshots before/after retracing (as appropriate) and dispatch it to
167  * the respective handler.
168  */
169 static void
170 retraceCall(trace::Call *call) {
171     bool swapRenderTarget = call->flags &
172         trace::CALL_FLAG_SWAP_RENDERTARGET;
173     bool doSnapshot = snapshotFrequency.contains(*call) ||
174         compareFrequency.contains(*call);
175
176     // For calls which cause rendertargets to be swaped, we take the
177     // snapshot _before_ swapping the rendertargets.
178     if (doSnapshot && swapRenderTarget) {
179         if (call->flags & trace::CALL_FLAG_END_FRAME) {
180             // For swapbuffers/presents we still use this
181             // call number, spite not have been executed yet.
182             takeSnapshot(call->no);
183         } else {
184             // Whereas for ordinate fbo/rendertarget changes we
185             // use the previous call's number.
186             takeSnapshot(call->no - 1);
187         }
188     }
189
190     callNo = call->no;
191     retracer.retrace(*call);
192
193     if (doSnapshot && !swapRenderTarget)
194         takeSnapshot(call->no);
195
196     if (call->no >= dumpStateCallNo &&
197         dumper->dumpState(std::cout)) {
198         exit(0);
199     }
200 }
201
202
203 class RelayRunner;
204
205
206 /**
207  * Implement multi-threading by mimicking a relay race.
208  */
209 class RelayRace
210 {
211 private:
212     /**
213      * Runners indexed by the leg they run (i.e, the thread_ids from the
214      * trace).
215      */
216     std::vector<RelayRunner*> runners;
217
218 public:
219     RelayRace();
220
221     ~RelayRace();
222
223     RelayRunner *
224     getRunner(unsigned leg);
225
226     inline RelayRunner *
227     getForeRunner() {
228         return getRunner(0);
229     }
230
231     void
232     run(void);
233
234     void
235     passBaton(trace::Call *call);
236
237     void
238     finishLine();
239
240     void
241     stopRunners();
242 };
243
244
245 /**
246  * Each runner is a thread.
247  *
248  * The fore runner doesn't have its own thread, but instead uses the thread
249  * where the race started.
250  */
251 class RelayRunner
252 {
253 private:
254     friend class RelayRace;
255
256     RelayRace *race;
257
258     unsigned leg;
259     
260     os::mutex mutex;
261     os::condition_variable wake_cond;
262
263     /**
264      * There are protected by the mutex.
265      */
266     bool finished;
267     trace::Call *baton;
268
269     os::thread thread;
270
271     static void *
272     runnerThread(RelayRunner *_this);
273
274 public:
275     RelayRunner(RelayRace *race, unsigned _leg) :
276         race(race),
277         leg(_leg),
278         finished(false),
279         baton(0)
280     {
281         /* The fore runner does not need a new thread */
282         if (leg) {
283             thread = os::thread(runnerThread, this);
284         }
285     }
286
287     ~RelayRunner() {
288         if (thread.joinable()) {
289             thread.join();
290         }
291     }
292
293     /**
294      * Thread main loop.
295      */
296     void
297     runRace(void) {
298         os::unique_lock<os::mutex> lock(mutex);
299
300         while (1) {
301             while (!finished && !baton) {
302                 wake_cond.wait(lock);
303             }
304
305             if (finished) {
306                 break;
307             }
308
309             assert(baton);
310             trace::Call *call = baton;
311             baton = 0;
312
313             runLeg(call);
314         }
315
316         if (0) std::cerr << "leg " << leg << " actually finishing\n";
317
318         if (leg == 0) {
319             race->stopRunners();
320         }
321     }
322
323     /**
324      * Interpret successive calls.
325      */
326     void
327     runLeg(trace::Call *call) {
328
329         /* Consume successive calls for this thread. */
330         do {
331             bool callEndsFrame = false;
332             static trace::ParseBookmark frameStart;
333
334             assert(call);
335             assert(call->thread_id == leg);
336
337             if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
338                 callEndsFrame = true;
339                 parser.getBookmark(frameStart);
340             }
341
342             retraceCall(call);
343             delete call;
344             call = parser.parse_call();
345
346             /* Restart last frame if looping is requested. */
347             if (loopOnFinish) {
348                 if (!call) {
349                     parser.setBookmark(lastFrameStart);
350                     call = parser.parse_call();
351                 } else if (callEndsFrame) {
352                     lastFrameStart = frameStart;
353                 }
354             }
355
356         } while (call && call->thread_id == leg);
357
358         if (call) {
359             /* Pass the baton */
360             assert(call->thread_id != leg);
361             flushRendering();
362             race->passBaton(call);
363         } else {
364             /* Reached the finish line */
365             if (0) std::cerr << "finished on leg " << leg << "\n";
366             if (leg) {
367                 /* Notify the fore runner */
368                 race->finishLine();
369             } else {
370                 /* We are the fore runner */
371                 finished = true;
372             }
373         }
374     }
375
376     /**
377      * Called by other threads when relinquishing the baton.
378      */
379     void
380     receiveBaton(trace::Call *call) {
381         assert (call->thread_id == leg);
382
383         mutex.lock();
384         baton = call;
385         mutex.unlock();
386
387         wake_cond.signal();
388     }
389
390     /**
391      * Called by the fore runner when the race is over.
392      */
393     void
394     finishRace() {
395         if (0) std::cerr << "notify finish to leg " << leg << "\n";
396
397         mutex.lock();
398         finished = true;
399         mutex.unlock();
400
401         wake_cond.signal();
402     }
403 };
404
405
406 void *
407 RelayRunner::runnerThread(RelayRunner *_this) {
408     _this->runRace();
409     return 0;
410 }
411
412
413 RelayRace::RelayRace() {
414     runners.push_back(new RelayRunner(this, 0));
415 }
416
417
418 RelayRace::~RelayRace() {
419     assert(runners.size() >= 1);
420     std::vector<RelayRunner*>::const_iterator it;
421     for (it = runners.begin(); it != runners.end(); ++it) {
422         RelayRunner* runner = *it;
423         if (runner) {
424             delete runner;
425         }
426     }
427 }
428
429
430 /**
431  * Get (or instantiate) a runner for the specified leg.
432  */
433 RelayRunner *
434 RelayRace::getRunner(unsigned leg) {
435     RelayRunner *runner;
436
437     if (leg >= runners.size()) {
438         runners.resize(leg + 1);
439         runner = 0;
440     } else {
441         runner = runners[leg];
442     }
443     if (!runner) {
444         runner = new RelayRunner(this, leg);
445         runners[leg] = runner;
446     }
447     return runner;
448 }
449
450
451 /**
452  * Start the race.
453  */
454 void
455 RelayRace::run(void) {
456     trace::Call *call;
457     call = parser.parse_call();
458     if (!call) {
459         /* Nothing to do */
460         return;
461     }
462
463     /* If the user wants to loop we need to get a bookmark target. We
464      * usually get this after replaying a call that ends a frame, but
465      * for a trace that has only one frame we need to get it at the
466      * beginning. */
467     if (loopOnFinish) {
468         parser.getBookmark(lastFrameStart);
469     }
470
471     RelayRunner *foreRunner = getForeRunner();
472     if (call->thread_id == 0) {
473         /* We are the forerunner thread, so no need to pass baton */
474         foreRunner->baton = call;
475     } else {
476         passBaton(call);
477     }
478
479     /* Start the forerunner thread */
480     foreRunner->runRace();
481 }
482
483
484 /**
485  * Pass the baton (i.e., the call) to the appropriate thread.
486  */
487 void
488 RelayRace::passBaton(trace::Call *call) {
489     if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
490     RelayRunner *runner = getRunner(call->thread_id);
491     runner->receiveBaton(call);
492 }
493
494
495 /**
496  * Called when a runner other than the forerunner reaches the finish line.
497  *
498  * Only the fore runner can finish the race, so inform him that the race is
499  * finished.
500  */
501 void
502 RelayRace::finishLine(void) {
503     RelayRunner *foreRunner = getForeRunner();
504     foreRunner->finishRace();
505 }
506
507
508 /**
509  * Called by the fore runner after finish line to stop all other runners.
510  */
511 void
512 RelayRace::stopRunners(void) {
513     std::vector<RelayRunner*>::const_iterator it;
514     for (it = runners.begin() + 1; it != runners.end(); ++it) {
515         RelayRunner* runner = *it;
516         if (runner) {
517             runner->finishRace();
518         }
519     }
520 }
521
522
523 static void
524 mainLoop() {
525     addCallbacks(retracer);
526
527     long long startTime = 0; 
528     frameNo = 0;
529
530     startTime = os::getTime();
531
532     if (singleThread) {
533         trace::Call *call;
534         while ((call = parser.parse_call())) {
535             retraceCall(call);
536             delete call;
537         };
538         flushRendering();
539     } else {
540         RelayRace race;
541         race.run();
542     }
543
544     long long endTime = os::getTime();
545     float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
546
547     if ((retrace::verbosity >= -1) || (retrace::profiling)) {
548         std::cout << 
549             "Rendered " << frameNo << " frames"
550             " in " <<  timeInterval << " secs,"
551             " average of " << (frameNo/timeInterval) << " fps\n";
552     }
553
554     if (waitOnFinish) {
555         waitForInput();
556     } else {
557         return;
558     }
559 }
560
561
562 } /* namespace retrace */
563
564
565 static void
566 usage(const char *argv0) {
567     std::cout << 
568         "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
569         "Replay TRACE.\n"
570         "\n"
571         "  -b, --benchmark         benchmark mode (no error checking or warning messages)\n"
572         "      --pcpu              cpu profiling (cpu times per call)\n"
573         "      --pgpu              gpu profiling (gpu times per draw call)\n"
574         "      --ppd               pixels drawn profiling (pixels drawn per draw call)\n"
575         "      --pmem              memory usage profiling (vsize rss per call)\n"
576         "  -c, --compare=PREFIX    compare against snapshots with given PREFIX\n"
577         "  -C, --calls=CALLSET     calls to compare (default is every frame)\n"
578         "      --call-nos[=BOOL]   use call numbers in snapshot filenames\n"
579         "      --core              use core profile\n"
580         "      --db                use a double buffer visual (default)\n"
581         "      --driver=DRIVER     force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n"
582         "      --sb                use a single buffer visual\n"
583         "  -s, --snapshot-prefix=PREFIX    take snapshots; `-` for PNM stdout output\n"
584         "      --snapshot-format=FMT       use (PNM or RGB; default is PNM) when writing to stdout output\n"
585         "  -S, --snapshot=CALLSET  calls to snapshot (default is every frame)\n"
586         "  -v, --verbose           increase output verbosity\n"
587         "  -D, --dump-state=CALL   dump state at specific call no\n"
588         "  -w, --wait              waitOnFinish on final frame\n"
589         "      --loop              continuously loop, replaying final frame.\n"
590         "      --singlethread      use a single thread to replay command stream\n";
591 }
592
593 enum {
594     CALL_NOS_OPT = CHAR_MAX + 1,
595     CORE_OPT,
596     DB_OPT,
597     DRIVER_OPT,
598     PCPU_OPT,
599     PGPU_OPT,
600     PPD_OPT,
601     PMEM_OPT,
602     SB_OPT,
603     SNAPSHOT_FORMAT_OPT,
604     LOOP_OPT,
605     SINGLETHREAD_OPT
606 };
607
608 const static char *
609 shortOptions = "bc:C:D:hs:S:vw";
610
611 const static struct option
612 longOptions[] = {
613     {"benchmark", no_argument, 0, 'b'},
614     {"call-nos", optional_argument, 0, CALL_NOS_OPT },
615     {"calls", required_argument, 0, 'C'},
616     {"compare", required_argument, 0, 'c'},
617     {"core", no_argument, 0, CORE_OPT},
618     {"db", no_argument, 0, DB_OPT},
619     {"driver", required_argument, 0, DRIVER_OPT},
620     {"dump-state", required_argument, 0, 'D'},
621     {"help", no_argument, 0, 'h'},
622     {"pcpu", no_argument, 0, PCPU_OPT},
623     {"pgpu", no_argument, 0, PGPU_OPT},
624     {"ppd", no_argument, 0, PPD_OPT},
625     {"pmem", no_argument, 0, PMEM_OPT},
626     {"sb", no_argument, 0, SB_OPT},
627     {"snapshot-prefix", required_argument, 0, 's'},
628     {"snapshot-format", required_argument, 0, SNAPSHOT_FORMAT_OPT},
629     {"snapshot", required_argument, 0, 'S'},
630     {"verbose", no_argument, 0, 'v'},
631     {"wait", no_argument, 0, 'w'},
632     {"loop", no_argument, 0, LOOP_OPT},
633     {"singlethread", no_argument, 0, SINGLETHREAD_OPT},
634     {0, 0, 0, 0}
635 };
636
637
638 static void exceptionCallback(void)
639 {
640     std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
641 }
642
643
644 extern "C"
645 int main(int argc, char **argv)
646 {
647     using namespace retrace;
648     int i;
649
650     assert(compareFrequency.empty());
651     assert(snapshotFrequency.empty());
652
653     int opt;
654     while  ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
655         switch (opt) {
656         case 'h':
657             usage(argv[0]);
658             return 0;
659         case 'b':
660             retrace::debug = false;
661             retrace::verbosity = -1;
662             break;
663         case CALL_NOS_OPT:
664             useCallNos = trace::boolOption(optarg);
665             break;
666         case 'c':
667             comparePrefix = optarg;
668             if (compareFrequency.empty()) {
669                 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
670             }
671             break;
672         case 'C':
673             compareFrequency = trace::CallSet(optarg);
674             if (comparePrefix == NULL) {
675                 comparePrefix = "";
676             }
677             break;
678         case 'D':
679             dumpStateCallNo = atoi(optarg);
680             dumpingState = true;
681             retrace::verbosity = -2;
682             break;
683         case CORE_OPT:
684             retrace::coreProfile = true;
685             break;
686         case DB_OPT:
687             retrace::doubleBuffer = true;
688             break;
689         case DRIVER_OPT:
690             if (strcasecmp(optarg, "hw") == 0) {
691                 driver = DRIVER_HARDWARE;
692             } else if (strcasecmp(optarg, "sw") == 0) {
693                 driver = DRIVER_SOFTWARE;
694             } else if (strcasecmp(optarg, "ref") == 0) {
695                 driver = DRIVER_REFERENCE;
696             } else if (strcasecmp(optarg, "null") == 0) {
697                 driver = DRIVER_NULL;
698             } else {
699                 driver = DRIVER_MODULE;
700                 driverModule = optarg;
701             }
702             break;
703         case SB_OPT:
704             retrace::doubleBuffer = false;
705             break;
706         case SINGLETHREAD_OPT:
707             retrace::singleThread = true;
708             break;
709         case 's':
710             snapshotPrefix = optarg;
711             if (snapshotFrequency.empty()) {
712                 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
713             }
714             if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
715                 os::setBinaryMode(stdout);
716                 retrace::verbosity = -2;
717             } else {
718                 /*
719                  * Create the snapshot directory if it does not exist.
720                  *
721                  * We can't just use trimFilename() because when applied to
722                  * "/foo/boo/" it would merely return "/foo".
723                  *
724                  * XXX: create nested directories.
725                  */
726                 os::String prefix(snapshotPrefix);
727                 os::String::iterator sep = prefix.rfindSep(false);
728                 if (sep != prefix.end()) {
729                     prefix.erase(sep, prefix.end());
730                     if (!os::createDirectory(prefix)) {
731                         std::cerr << "error: failed to create " << prefix.str() << "\n";
732                     }
733                 }
734             }
735             break;
736         case SNAPSHOT_FORMAT_OPT:
737             if (strcmp(optarg, "RGB") == 0)
738                 snapshotFormat = RAW_RGB;
739             else
740                 snapshotFormat = PNM_FMT;
741             break;
742         case 'S':
743             snapshotFrequency = trace::CallSet(optarg);
744             if (snapshotPrefix == NULL) {
745                 snapshotPrefix = "";
746             }
747             break;
748         case 'v':
749             ++retrace::verbosity;
750             break;
751         case 'w':
752             waitOnFinish = true;
753             break;
754         case LOOP_OPT:
755             loopOnFinish = true;
756             break;
757         case PGPU_OPT:
758             retrace::debug = false;
759             retrace::profiling = true;
760             retrace::verbosity = -1;
761
762             retrace::profilingGpuTimes = true;
763             break;
764         case PCPU_OPT:
765             retrace::debug = false;
766             retrace::profiling = true;
767             retrace::verbosity = -1;
768
769             retrace::profilingCpuTimes = true;
770             break;
771         case PPD_OPT:
772             retrace::debug = false;
773             retrace::profiling = true;
774             retrace::verbosity = -1;
775
776             retrace::profilingPixelsDrawn = true;
777             break;
778         case PMEM_OPT:
779             retrace::debug = false;
780             retrace::profiling = true;
781             retrace::verbosity = -1;
782
783             retrace::profilingMemoryUsage = true;
784             break;
785         default:
786             std::cerr << "error: unknown option " << opt << "\n";
787             usage(argv[0]);
788             return 1;
789         }
790     }
791
792     retrace::setUp();
793     if (retrace::profiling) {
794         retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage);
795     }
796
797     os::setExceptionCallback(exceptionCallback);
798
799     for (i = optind; i < argc; ++i) {
800         if (!retrace::parser.open(argv[i])) {
801             return 1;
802         }
803
804         retrace::mainLoop();
805
806         retrace::parser.close();
807     }
808     
809     os::resetExceptionCallback();
810
811     // XXX: X often hangs on XCloseDisplay
812     //retrace::cleanUp();
813
814     return 0;
815 }
816