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