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