]> git.cworth.org Git - apitrace/blob - cli/cli_trim.cpp
1d2abffa1cee24e0eda199e8a6cef66e9cfad896
[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         resources["state"].insert(call->no);
155     }
156
157     /* Require this call and all of its dependencies to be included in
158      * the final trace. */
159     void require(trace::Call *call) {
160         std::set<unsigned> *dependencies;
161         std::set<unsigned>::iterator i;
162
163         /* First, find and insert all calls that this call depends on. */
164         dependencies = &resources["state"];
165         for (i = dependencies->begin(); i != dependencies->end(); i++) {
166             required.insert(*i);
167         }
168         resources["state"].clear();
169
170         /* Then insert this call itself. */
171         required.insert(call->no);
172     }
173
174     /* Return a set of all the required calls, (both those calls added
175      * explicitly with require() and those implicitly depended
176      * upon. */
177     std::set<unsigned> *get_required(void) {
178         return &required;
179     }
180 };
181
182 struct trim_options {
183     /* Calls to be included in trace. */
184     trace::CallSet calls;
185
186     /* Whether dependency analysis should be performed. */
187     bool dependency_analysis;
188
189     /* Whether uninteresting calls should be pruned.. */
190     bool prune_uninteresting;
191
192     /* Output filename */
193     std::string output;
194
195     /* Emit only calls from this thread (-1 == all threads) */
196     int thread;
197 };
198
199 static int
200 trim_trace(const char *filename, struct trim_options *options)
201 {
202     trace::ParseBookmark beginning;
203     trace::Parser p;
204     TraceAnalyzer analyzer;
205     std::set<unsigned> *required;
206
207     if (!p.open(filename)) {
208         std::cerr << "error: failed to open " << filename << "\n";
209         return 1;
210     }
211
212     /* Mark the beginning so we can return here for pass 2. */
213     p.getBookmark(beginning);
214
215     /* In pass 1, analyze which calls are needed. */
216     trace::Call *call;
217     while ((call = p.parse_call())) {
218         /* If requested, ignore all calls not belonging to the specified thread. */
219         if (options->thread != -1 && call->thread_id != options->thread)
220             continue;
221
222         /* Also, prune if uninteresting (unless the user asked for no pruning. */
223         if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_UNINTERESTING) {
224             continue;
225         }
226
227         /* If this call is included in the user-specified call
228          * set, then we don't need to perform any analysis on
229          * it. We know it must be included. */
230         if (options->calls.contains(*call)) {
231             analyzer.require(call);
232         } else {
233             if (options->dependency_analysis)
234                 analyzer.analyze(call);
235         }
236     }
237
238     /* Prepare output file and writer for output. */
239     if (options->output.empty()) {
240         os::String base(filename);
241         base.trimExtension();
242
243         options->output = std::string(base.str()) + std::string("-trim.trace");
244     }
245
246     trace::Writer writer;
247     if (!writer.open(options->output.c_str())) {
248         std::cerr << "error: failed to create " << filename << "\n";
249         return 1;
250     }
251
252     /* Reset bookmark for pass 2. */
253     p.setBookmark(beginning);
254
255     /* In pass 2, emit the calls that are required. */
256     required = analyzer.get_required();
257
258     while ((call = p.parse_call())) {
259         if (required->find(call->no) != required->end()) {
260             writer.writeCall(call);
261         }
262         delete call;
263     }
264
265     std::cout << "Trimmed trace is available as " << options->output << "\n";
266
267     return 0;
268 }
269
270 static int
271 command(int argc, char *argv[])
272 {
273     struct trim_options options;
274
275     options.calls = trace::CallSet(trace::FREQUENCY_ALL);
276     options.dependency_analysis = true;
277     options.prune_uninteresting = true;
278     options.output = "";
279     options.thread = -1;
280
281     int opt;
282     while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
283         switch (opt) {
284         case 'h':
285             help();
286             return 0;
287         case CALLS_OPT:
288             options.calls = trace::CallSet(optarg);
289             break;
290         case DEPS_OPT:
291             options.dependency_analysis = true;
292             break;
293         case NO_DEPS_OPT:
294             options.dependency_analysis = false;
295             break;
296         case PRUNE_OPT:
297             options.prune_uninteresting = true;
298             break;
299         case NO_PRUNE_OPT:
300             options.prune_uninteresting = false;
301             break;
302         case 'x':
303             options.dependency_analysis = false;
304             options.prune_uninteresting = false;
305             break;
306         case THREAD_OPT:
307             options.thread = atoi(optarg);
308             break;
309         case 'o':
310             options.output = optarg;
311             break;
312         default:
313             std::cerr << "error: unexpected option `" << opt << "`\n";
314             usage();
315             return 1;
316         }
317     }
318
319     if (optind >= argc) {
320         std::cerr << "error: apitrace trim requires a trace file as an argument.\n";
321         usage();
322         return 1;
323     }
324
325     if (argc > optind + 1) {
326         std::cerr << "error: extraneous arguments:";
327         for (int i = optind + 1; i < argc; i++) {
328             std::cerr << " " << argv[i];
329         }
330         std::cerr << "\n";
331         usage();
332         return 1;
333     }
334
335     return trim_trace(argv[optind], &options);
336 }
337
338 const Command trim_command = {
339     "trim",
340     synopsis,
341     help,
342     command
343 };