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