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