]> git.cworth.org Git - apitrace/blob - cli/cli_trim.cpp
trim: Trim most drawing operations outside of the user-specified range
[apitrace] / cli / cli_trim.cpp
1 /**************************************************************************
2  *
3  * Copyright 2010 VMware, Inc.
4  * Copyright 2011 Intel corporation
5  * All Rights Reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  *
25  **************************************************************************/
26
27 #include <string.h>
28 #include <limits.h> // for CHAR_MAX
29 #include <getopt.h>
30
31 #include <GL/gl.h>
32 #include <GL/glext.h>
33
34 #include <set>
35
36 #include "cli.hpp"
37
38 #include "os_string.hpp"
39
40 #include "trace_callset.hpp"
41 #include "trace_parser.hpp"
42 #include "trace_writer.hpp"
43
44 static const char *synopsis = "Create a new trace by trimming an existing trace.";
45
46 static void
47 usage(void)
48 {
49     std::cout
50         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
51         << synopsis << "\n"
52         "\n"
53         "    -h, --help               Show detailed help for trim options and exit\n"
54         "        --calls=CALLSET      Include specified calls in the trimmed output.\n"
55         "        --deps               Include additional calls to satisfy dependencies\n"
56         "        --no-deps            Do not include calls from dependency analysis\n"
57         "        --prune              Omit uninteresting calls from the trace output\n"
58         "        --no-prune           Do not prune uninteresting calls from the trace.\n"
59         "    -x, --exact              Include exactly the calls specified in --calls\n"
60         "                             Equivalent to both --no-deps and --no-prune\n"
61         "        --thread=THREAD_ID   Only retain calls from specified thread\n"
62         "    -o, --output=TRACE_FILE  Output trace file\n"
63     ;
64 }
65
66 static void
67 help()
68 {
69     std::cout
70         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
71         << synopsis << "\n"
72         "\n"
73         "    -h, --help               Show this help message and exit\n"
74         "\n"
75         "        --calls=CALLSET      Include specified calls in the trimmed output.\n"
76         "                             Note that due to dependency analysis and pruning\n"
77         "                             of uninteresting calls the resulting trace may\n"
78         "                             include more and less calls than specified.\n"
79         "                             See --no-deps, --no-prune, and --exact to change\n"
80         "                             this behavior.\n"
81         "\n"
82         "        --deps               Perform dependency analysis and include dependent\n"
83         "                             calls as needed, (even if those calls were not\n"
84         "                             explicitly requested with --calls). This is the\n"
85         "                             default behavior. See --no-deps and --exact.\n"
86         "\n"
87         "        --no-deps            Do not perform dependency analysis. In this mode\n"
88         "                             the trimmed trace will never include calls from\n"
89         "                             outside the range specified in --calls.\n"
90         "\n"
91         "        --prune              Omit calls that have no side effects, even if the\n"
92         "                             call is within the range specified by --calls.\n"
93         "                             This is the default behavior. See --no-prune\n"
94         "\n"
95         "        --no-prune           Do not prune uninteresting calls from the trace.\n"
96         "                             In this mode the trimmed trace will never omit\n"
97         "                             any calls within the range specified in --calls.\n"
98         "\n"
99         "    -x, --exact              Trim the trace to exactly the calls specified in\n"
100         "                             --calls. This option is equivalent to passing\n"
101         "                             both --no-deps and --no-prune.\n"
102         "\n"
103         "        --thread=THREAD_ID   Only retain calls from specified thread\n"
104         "\n"
105         "    -o, --output=TRACE_FILE  Output trace file\n"
106         "\n"
107     ;
108 }
109
110 enum {
111     CALLS_OPT = CHAR_MAX + 1,
112     DEPS_OPT,
113     NO_DEPS_OPT,
114     PRUNE_OPT,
115     NO_PRUNE_OPT,
116     THREAD_OPT,
117 };
118
119 const static char *
120 shortOptions = "ho:x";
121
122 const static struct option
123 longOptions[] = {
124     {"help", no_argument, 0, 'h'},
125     {"calls", required_argument, 0, CALLS_OPT},
126     {"deps", no_argument, 0, DEPS_OPT},
127     {"no-deps", no_argument, 0, NO_DEPS_OPT},
128     {"prune", no_argument, 0, PRUNE_OPT},
129     {"no-prune", no_argument, 0, NO_PRUNE_OPT},
130     {"exact", no_argument, 0, 'x'},
131     {"thread", required_argument, 0, THREAD_OPT},
132     {"output", required_argument, 0, 'o'},
133     {0, 0, 0, 0}
134 };
135
136 struct stringCompare {
137     bool operator() (const char *a, const char *b) const {
138         return strcmp(a, b) < 0;
139     }
140 };
141
142 class TraceAnalyzer {
143     /* Map for tracking resource dependencies between calls. */
144     std::map<const char *, std::set<unsigned>, stringCompare > resources;
145
146     /* The final set of calls required. This consists of calls added
147      * explicitly with the require() method as well as all calls
148      * implicitly required by those through resource dependencies. */
149     std::set<unsigned> required;
150
151     bool transformFeedbackActive;
152     bool framebufferObjectActive;
153     bool insideBeginEnd;
154
155     /* Rendering often has no side effects, but it can in some cases,
156      * (such as when transform feedback is active, or when rendering
157      * targets a framebuffer object). */
158     bool renderingHasSideEffect() {
159         return transformFeedbackActive || framebufferObjectActive;
160     }
161
162     /* Provide: Record that the given call affects the given resource
163      * as a side effect. */
164     void provide(const char *resource, trace::CallNo call_no) {
165         resources[resource].insert(call_no);
166     }
167
168     /* Consume: Add all calls that provide the given resource to the
169      * required list, then clear the list for this resource. */
170     void consume(const char *resource) {
171
172         std::set<unsigned> *calls;
173         std::set<unsigned>::iterator call;
174
175         /* Insert as required all calls that provide 'resource',
176          * then clear these calls. */
177         if (resources.count(resource)) {
178             calls = &resources[resource];
179             for (call = calls->begin(); call != calls->end(); call++) {
180                 required.insert(*call);
181             }
182             resources.erase(resource);
183         }
184     }
185
186     void stateTrackPreCall(trace::Call *call) {
187
188         if (strcmp(call->name(), "glBegin") == 0) {
189             insideBeginEnd = true;
190             return;
191         }
192
193         if (strcmp(call->name(), "glBeginTransformFeedback") == 0) {
194             transformFeedbackActive = true;
195             return;
196         }
197
198         if (strcmp(call->name(), "glBindFramebuffer") == 0) {
199             GLenum target;
200             GLuint framebuffer;
201
202             target = static_cast<GLenum>(call->arg(0).toSInt());
203             framebuffer = call->arg(1).toUInt();
204
205             if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) {
206                 if (framebuffer == 0) {
207                     framebufferObjectActive = false;
208                 } else {
209                     framebufferObjectActive = true;
210                 }
211             }
212             return;
213         }
214     }
215
216     void stateTrackPostCall(trace::Call *call) {
217
218         if (strcmp(call->name(), "glEnd") == 0) {
219             insideBeginEnd = false;
220             return;
221         }
222
223         if (strcmp(call->name(), "glEndTransformFeedback") == 0) {
224             transformFeedbackActive = false;
225             return;
226         }
227
228         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
229             call->flags & trace::CALL_FLAG_END_FRAME) {
230             resources.erase("framebuffer");
231             return;
232         }
233     }
234
235     void recordSideEffects(trace::Call *call) {
236         /* If call is flagged as no side effects, then we are done here. */
237         if (call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) {
238             return;
239         }
240
241         /* Similarly, swap-buffers calls don't have interesting side effects. */
242         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
243             call->flags & trace::CALL_FLAG_END_FRAME) {
244             return;
245         }
246
247         /* Handle all rendering operations, (even though only glEnd is
248          * flagged as a rendering operation we treat everything from
249          * glBegin through glEnd as a rendering operation). */
250         if (call->flags & trace::CALL_FLAG_RENDER ||
251             insideBeginEnd) {
252
253             provide("framebuffer", call->no);
254
255             /* In some cases, rendering has side effects beyond the
256              * framebuffer update. */
257             if (renderingHasSideEffect()) {
258                 provide("state", call->no);
259             }
260
261             return;
262         }
263
264         /* By default, assume this call affects the state somehow. */
265         resources["state"].insert(call->no);
266     }
267
268     void requireDependencies(trace::Call *call) {
269
270         /* Swap-buffers calls depend on framebuffer state. */
271         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
272             call->flags & trace::CALL_FLAG_END_FRAME) {
273             consume("framebuffer");
274         }
275
276         /* By default, just assume this call depends on generic state. */
277         consume("state");
278     }
279
280
281 public:
282     TraceAnalyzer(): transformFeedbackActive(false),
283                      framebufferObjectActive(false),
284                      insideBeginEnd(false)
285     {}
286
287     ~TraceAnalyzer() {}
288
289     /* Analyze this call by tracking state and recording all the
290      * resources provided by this call as side effects.. */
291     void analyze(trace::Call *call) {
292
293         stateTrackPreCall(call);
294
295         recordSideEffects(call);
296
297         stateTrackPostCall(call);
298     }
299
300     /* Require this call and all of its dependencies to be included in
301      * the final trace. */
302     void require(trace::Call *call) {
303
304         /* First, find and insert all calls that this call depends on. */
305         requireDependencies(call);
306
307         /* Then insert this call itself. */
308         required.insert(call->no);
309     }
310
311     /* Return a set of all the required calls, (both those calls added
312      * explicitly with require() and those implicitly depended
313      * upon. */
314     std::set<unsigned> *get_required(void) {
315         return &required;
316     }
317 };
318
319 struct trim_options {
320     /* Calls to be included in trace. */
321     trace::CallSet calls;
322
323     /* Whether dependency analysis should be performed. */
324     bool dependency_analysis;
325
326     /* Whether uninteresting calls should be pruned.. */
327     bool prune_uninteresting;
328
329     /* Output filename */
330     std::string output;
331
332     /* Emit only calls from this thread (-1 == all threads) */
333     int thread;
334 };
335
336 static int
337 trim_trace(const char *filename, struct trim_options *options)
338 {
339     trace::ParseBookmark beginning;
340     trace::Parser p;
341     TraceAnalyzer analyzer;
342     std::set<unsigned> *required;
343
344     if (!p.open(filename)) {
345         std::cerr << "error: failed to open " << filename << "\n";
346         return 1;
347     }
348
349     /* Mark the beginning so we can return here for pass 2. */
350     p.getBookmark(beginning);
351
352     /* In pass 1, analyze which calls are needed. */
353     trace::Call *call;
354     while ((call = p.parse_call())) {
355
356         /* There's no use doing any work past the last call requested
357          * by the user. */
358         if (call->no > options->calls.getLast())
359             break;
360
361         /* If requested, ignore all calls not belonging to the specified thread. */
362         if (options->thread != -1 && call->thread_id != options->thread)
363             continue;
364
365         /* Also, prune if uninteresting (unless the user asked for no pruning. */
366         if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_UNINTERESTING) {
367             continue;
368         }
369
370         /* If this call is included in the user-specified call set,
371          * then require it (and all dependencies) in the trimmed
372          * output. */
373         if (options->calls.contains(*call)) {
374             analyzer.require(call);
375         }
376
377         /* Regardless of whether we include this call or not, we do
378          * some dependency tracking (unless disabled by the user). We
379          * do this even for calls we have included in the output so
380          * that any state updates get performed. */
381         if (options->dependency_analysis) {
382             analyzer.analyze(call);
383         }
384     }
385
386     /* Prepare output file and writer for output. */
387     if (options->output.empty()) {
388         os::String base(filename);
389         base.trimExtension();
390
391         options->output = std::string(base.str()) + std::string("-trim.trace");
392     }
393
394     trace::Writer writer;
395     if (!writer.open(options->output.c_str())) {
396         std::cerr << "error: failed to create " << filename << "\n";
397         return 1;
398     }
399
400     /* Reset bookmark for pass 2. */
401     p.setBookmark(beginning);
402
403     /* In pass 2, emit the calls that are required. */
404     required = analyzer.get_required();
405
406     while ((call = p.parse_call())) {
407
408         /* There's no use doing any work past the last call requested
409          * by the user. */
410         if (call->no > options->calls.getLast())
411             break;
412
413         if (required->find(call->no) != required->end()) {
414             writer.writeCall(call);
415         }
416         delete call;
417     }
418
419     std::cout << "Trimmed trace is available as " << options->output << "\n";
420
421     return 0;
422 }
423
424 static int
425 command(int argc, char *argv[])
426 {
427     struct trim_options options;
428
429     options.calls = trace::CallSet(trace::FREQUENCY_ALL);
430     options.dependency_analysis = true;
431     options.prune_uninteresting = true;
432     options.output = "";
433     options.thread = -1;
434
435     int opt;
436     while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
437         switch (opt) {
438         case 'h':
439             help();
440             return 0;
441         case CALLS_OPT:
442             options.calls = trace::CallSet(optarg);
443             break;
444         case DEPS_OPT:
445             options.dependency_analysis = true;
446             break;
447         case NO_DEPS_OPT:
448             options.dependency_analysis = false;
449             break;
450         case PRUNE_OPT:
451             options.prune_uninteresting = true;
452             break;
453         case NO_PRUNE_OPT:
454             options.prune_uninteresting = false;
455             break;
456         case 'x':
457             options.dependency_analysis = false;
458             options.prune_uninteresting = false;
459             break;
460         case THREAD_OPT:
461             options.thread = atoi(optarg);
462             break;
463         case 'o':
464             options.output = optarg;
465             break;
466         default:
467             std::cerr << "error: unexpected option `" << opt << "`\n";
468             usage();
469             return 1;
470         }
471     }
472
473     if (optind >= argc) {
474         std::cerr << "error: apitrace trim requires a trace file as an argument.\n";
475         usage();
476         return 1;
477     }
478
479     if (argc > optind + 1) {
480         std::cerr << "error: extraneous arguments:";
481         for (int i = optind + 1; i < argc; i++) {
482             std::cerr << " " << argv[i];
483         }
484         std::cerr << "\n";
485         usage();
486         return 1;
487     }
488
489     return trim_trace(argv[optind], &options);
490 }
491
492 const Command trim_command = {
493     "trim",
494     synopsis,
495     help,
496     command
497 };