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