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