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