]> git.cworth.org Git - apitrace/blob - cli/cli_trim.cpp
trim: Avoid doing any analysis past the end 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 <set>
32
33 #include "cli.hpp"
34
35 #include "os_string.hpp"
36
37 #include "trace_callset.hpp"
38 #include "trace_parser.hpp"
39 #include "trace_writer.hpp"
40
41 static const char *synopsis = "Create a new trace by trimming an existing trace.";
42
43 static void
44 usage(void)
45 {
46     std::cout
47         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
48         << synopsis << "\n"
49         "\n"
50         "    -h, --help               Show detailed help for trim options and exit\n"
51         "        --calls=CALLSET      Include specified calls in the trimmed output.\n"
52         "        --deps               Include additional calls to satisfy dependencies\n"
53         "        --no-deps            Do not include calls from dependency analysis\n"
54         "        --prune              Omit uninteresting calls from the trace output\n"
55         "        --no-prune           Do not prune uninteresting calls from the trace.\n"
56         "    -x, --exact              Include exactly the calls specified in --calls\n"
57         "                             Equivalent to both --no-deps and --no-prune\n"
58         "        --thread=THREAD_ID   Only retain calls from specified thread\n"
59         "    -o, --output=TRACE_FILE  Output trace file\n"
60     ;
61 }
62
63 static void
64 help()
65 {
66     std::cout
67         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
68         << synopsis << "\n"
69         "\n"
70         "    -h, --help               Show this help message and exit\n"
71         "\n"
72         "        --calls=CALLSET      Include specified calls in the trimmed output.\n"
73         "                             Note that due to dependency analysis and pruning\n"
74         "                             of uninteresting calls the resulting trace may\n"
75         "                             include more and less calls than specified.\n"
76         "                             See --no-deps, --no-prune, and --exact to change\n"
77         "                             this behavior.\n"
78         "\n"
79         "        --deps               Perform dependency analysis and include dependent\n"
80         "                             calls as needed, (even if those calls were not\n"
81         "                             explicitly requested with --calls). This is the\n"
82         "                             default behavior. See --no-deps and --exact.\n"
83         "\n"
84         "        --no-deps            Do not perform dependency analysis. In this mode\n"
85         "                             the trimmed trace will never include calls from\n"
86         "                             outside the range specified in --calls.\n"
87         "\n"
88         "        --prune              Omit calls that have no side effects, even if the\n"
89         "                             call is within the range specified by --calls.\n"
90         "                             This is the default behavior. See --no-prune\n"
91         "\n"
92         "        --no-prune           Do not prune uninteresting calls from the trace.\n"
93         "                             In this mode the trimmed trace will never omit\n"
94         "                             any calls within the range specified in --calls.\n"
95         "\n"
96         "    -x, --exact              Trim the trace to exactly the calls specified in\n"
97         "                             --calls. This option is equivalent to passing\n"
98         "                             both --no-deps and --no-prune.\n"
99         "\n"
100         "        --thread=THREAD_ID   Only retain calls from specified thread\n"
101         "\n"
102         "    -o, --output=TRACE_FILE  Output trace file\n"
103         "\n"
104     ;
105 }
106
107 enum {
108     CALLS_OPT = CHAR_MAX + 1,
109     DEPS_OPT,
110     NO_DEPS_OPT,
111     PRUNE_OPT,
112     NO_PRUNE_OPT,
113     THREAD_OPT,
114 };
115
116 const static char *
117 shortOptions = "ho:x";
118
119 const static struct option
120 longOptions[] = {
121     {"help", no_argument, 0, 'h'},
122     {"calls", required_argument, 0, CALLS_OPT},
123     {"deps", no_argument, 0, DEPS_OPT},
124     {"no-deps", no_argument, 0, NO_DEPS_OPT},
125     {"prune", no_argument, 0, PRUNE_OPT},
126     {"no-prune", no_argument, 0, NO_PRUNE_OPT},
127     {"exact", no_argument, 0, 'x'},
128     {"thread", required_argument, 0, THREAD_OPT},
129     {"output", required_argument, 0, 'o'},
130     {0, 0, 0, 0}
131 };
132
133 struct stringCompare {
134     bool operator() (const char *a, const char *b) const {
135         return strcmp(a, b) < 0;
136     }
137 };
138
139 class TraceAnalyzer {
140     /* Map for tracking resource dependencies between calls. */
141     std::map<const char *, std::set<unsigned>, stringCompare > resources;
142
143     /* The final set of calls required. This consists of calls added
144      * explicitly with the require() method as well as all calls
145      * implicitly required by those through resource dependencies. */
146     std::set<unsigned> required;
147
148 public:
149     TraceAnalyzer() {}
150     ~TraceAnalyzer() {}
151
152     /* Compute and record all the resources provided by this call. */
153     void analyze(trace::Call *call) {
154         /* If there are no side effects, this call provides nothing. */
155         if (call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) {
156             return;
157         }
158
159         /* Similarly, calls that swap buffers don't have other side effects. */
160         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
161             call->flags & trace::CALL_FLAG_END_FRAME) {
162             return;
163         }
164
165         /* By default, assume this call affects the state somehow. */
166         resources["state"].insert(call->no);
167     }
168
169     /* Require this call and all of its dependencies to be included in
170      * the final trace. */
171     void require(trace::Call *call) {
172         std::set<unsigned> *dependencies;
173         std::set<unsigned>::iterator i;
174
175         /* First, find and insert all calls that this call depends on. */
176         dependencies = &resources["state"];
177         for (i = dependencies->begin(); i != dependencies->end(); i++) {
178             required.insert(*i);
179         }
180         resources["state"].clear();
181
182         /* Then insert this call itself. */
183         required.insert(call->no);
184     }
185
186     /* Return a set of all the required calls, (both those calls added
187      * explicitly with require() and those implicitly depended
188      * upon. */
189     std::set<unsigned> *get_required(void) {
190         return &required;
191     }
192 };
193
194 struct trim_options {
195     /* Calls to be included in trace. */
196     trace::CallSet calls;
197
198     /* Whether dependency analysis should be performed. */
199     bool dependency_analysis;
200
201     /* Whether uninteresting calls should be pruned.. */
202     bool prune_uninteresting;
203
204     /* Output filename */
205     std::string output;
206
207     /* Emit only calls from this thread (-1 == all threads) */
208     int thread;
209 };
210
211 static int
212 trim_trace(const char *filename, struct trim_options *options)
213 {
214     trace::ParseBookmark beginning;
215     trace::Parser p;
216     TraceAnalyzer analyzer;
217     std::set<unsigned> *required;
218
219     if (!p.open(filename)) {
220         std::cerr << "error: failed to open " << filename << "\n";
221         return 1;
222     }
223
224     /* Mark the beginning so we can return here for pass 2. */
225     p.getBookmark(beginning);
226
227     /* In pass 1, analyze which calls are needed. */
228     trace::Call *call;
229     while ((call = p.parse_call())) {
230
231         /* There's no use doing any work past the last call requested
232          * by the user. */
233         if (call->no > options->calls.getLast())
234             break;
235
236         /* If requested, ignore all calls not belonging to the specified thread. */
237         if (options->thread != -1 && call->thread_id != options->thread)
238             continue;
239
240         /* Also, prune if uninteresting (unless the user asked for no pruning. */
241         if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_UNINTERESTING) {
242             continue;
243         }
244
245         /* If this call is included in the user-specified call
246          * set, then we don't need to perform any analysis on
247          * it. We know it must be included. */
248         if (options->calls.contains(*call)) {
249             analyzer.require(call);
250         } else {
251             if (options->dependency_analysis)
252                 analyzer.analyze(call);
253         }
254     }
255
256     /* Prepare output file and writer for output. */
257     if (options->output.empty()) {
258         os::String base(filename);
259         base.trimExtension();
260
261         options->output = std::string(base.str()) + std::string("-trim.trace");
262     }
263
264     trace::Writer writer;
265     if (!writer.open(options->output.c_str())) {
266         std::cerr << "error: failed to create " << filename << "\n";
267         return 1;
268     }
269
270     /* Reset bookmark for pass 2. */
271     p.setBookmark(beginning);
272
273     /* In pass 2, emit the calls that are required. */
274     required = analyzer.get_required();
275
276     while ((call = p.parse_call())) {
277
278         /* There's no use doing any work past the last call requested
279          * by the user. */
280         if (call->no > options->calls.getLast())
281             break;
282
283         if (required->find(call->no) != required->end()) {
284             writer.writeCall(call);
285         }
286         delete call;
287     }
288
289     std::cout << "Trimmed trace is available as " << options->output << "\n";
290
291     return 0;
292 }
293
294 static int
295 command(int argc, char *argv[])
296 {
297     struct trim_options options;
298
299     options.calls = trace::CallSet(trace::FREQUENCY_ALL);
300     options.dependency_analysis = true;
301     options.prune_uninteresting = true;
302     options.output = "";
303     options.thread = -1;
304
305     int opt;
306     while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
307         switch (opt) {
308         case 'h':
309             help();
310             return 0;
311         case CALLS_OPT:
312             options.calls = trace::CallSet(optarg);
313             break;
314         case DEPS_OPT:
315             options.dependency_analysis = true;
316             break;
317         case NO_DEPS_OPT:
318             options.dependency_analysis = false;
319             break;
320         case PRUNE_OPT:
321             options.prune_uninteresting = true;
322             break;
323         case NO_PRUNE_OPT:
324             options.prune_uninteresting = false;
325             break;
326         case 'x':
327             options.dependency_analysis = false;
328             options.prune_uninteresting = false;
329             break;
330         case THREAD_OPT:
331             options.thread = atoi(optarg);
332             break;
333         case 'o':
334             options.output = optarg;
335             break;
336         default:
337             std::cerr << "error: unexpected option `" << opt << "`\n";
338             usage();
339             return 1;
340         }
341     }
342
343     if (optind >= argc) {
344         std::cerr << "error: apitrace trim requires a trace file as an argument.\n";
345         usage();
346         return 1;
347     }
348
349     if (argc > optind + 1) {
350         std::cerr << "error: extraneous arguments:";
351         for (int i = optind + 1; i < argc; i++) {
352             std::cerr << " " << argv[i];
353         }
354         std::cerr << "\n";
355         usage();
356         return 1;
357     }
358
359     return trim_trace(argv[optind], &options);
360 }
361
362 const Command trim_command = {
363     "trim",
364     synopsis,
365     help,
366     command
367 };