From: José Fonseca Date: Thu, 26 Jan 2012 19:08:32 +0000 (+0000) Subject: Allow to use call sets instead of call numbers / frequencies. X-Git-Url: https://git.cworth.org/git?p=apitrace;a=commitdiff_plain;h=225193db0f50a8f3a1d59d7cff351c9be31e8044 Allow to use call sets instead of call numbers / frequencies. Inspired on Carl Worth's --call=Range option, but: - with the extra machinery to allow semantic divisors, in addition to numeric ones. - allows reading the call numbers of a text file. --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 24e2afc..22f7677 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,7 @@ else () endif () add_library (common STATIC + common/trace_callset.cpp common/trace_dump.cpp common/trace_file.cpp common/trace_file_read.cpp diff --git a/README.markdown b/README.markdown index 0a1bceb..38dfa5e 100644 --- a/README.markdown +++ b/README.markdown @@ -64,6 +64,34 @@ Advanced command line usage =========================== +Call sets +--------- + +Several tools take `CALLSET` arguments, e.g: + + apitrace dump --calls CALLSET foo.trace + glretrace -S CALLSET foo.trace + +The call syntax is very flexible. Here are a few examples: + + * `4` one call + + * `1,2,4,5` set of calls + + * `"1 2 4 5"` set of calls (commas are optional and can be replaced with whitespace) + + * `1-100/2` calls 1, 3, 5, ..., 99 + + * `1-1000/draw` all draw calls between 1 and 1000 + + * `1-1000/fbo` all fbo changes between calls 1 and 1000 + + * `frame` all calls at end of frames + + * `@foo.txt` read call numbers from `foo.txt`, using the same syntax as above + + + Tracing manually ---------------- diff --git a/cli/cli_dump.cpp b/cli/cli_dump.cpp index adecb22..3f6bea2 100644 --- a/cli/cli_dump.cpp +++ b/cli/cli_dump.cpp @@ -32,6 +32,7 @@ #include "trace_parser.hpp" #include "trace_dump.hpp" +#include "trace_callset.hpp" enum ColorOption { @@ -44,6 +45,8 @@ static ColorOption color = COLOR_OPTION_AUTO; static bool verbose = false; +static trace::CallSet calls(trace::FREQUENCY_ALL); + static const char *synopsis = "Dump given trace(s) to standard output."; static void @@ -54,6 +57,7 @@ usage(void) << synopsis << "\n" "\n" " -v, --verbose verbose output\n" + " --calls Only dump specified calls\n" " --color=\n" " --colour= Colored syntax highlighting\n" " WHEN is 'auto', 'always', or 'never'\n" @@ -70,13 +74,15 @@ command(int argc, char *argv[]) int i; - for (i = 0; i < argc; ++i) { + for (i = 0; i < argc;) { const char *arg = argv[i]; if (arg[0] != '-') { break; } + ++i; + if (!strcmp(arg, "--")) { break; } else if (!strcmp(arg, "--help")) { @@ -85,6 +91,8 @@ command(int argc, char *argv[]) } else if (strcmp(arg, "-v") == 0 || strcmp(arg, "--verbose") == 0) { verbose = true; + } else if (!strcmp(arg, "--calls")) { + calls = trace::CallSet(argv[i++]); } else if (!strcmp(arg, "--color=auto") || !strcmp(arg, "--colour=auto")) { color = COLOR_OPTION_AUTO; @@ -132,12 +140,14 @@ command(int argc, char *argv[]) trace::Call *call; while ((call = p.parse_call())) { - if (verbose || - !(call->flags & trace::CALL_FLAG_VERBOSE)) { - if (dumpThreadIds) { - std::cout << std::hex << call->thread_id << std::dec << " "; + if (calls.contains(*call)) { + if (verbose || + !(call->flags & trace::CALL_FLAG_VERBOSE)) { + if (dumpThreadIds) { + std::cout << std::hex << call->thread_id << std::dec << " "; + } + trace::dump(*call, std::cout, dumpFlags); } - trace::dump(*call, std::cout, dumpFlags); } delete call; } diff --git a/common/trace_callset.cpp b/common/trace_callset.cpp new file mode 100644 index 0000000..3c33087 --- /dev/null +++ b/common/trace_callset.cpp @@ -0,0 +1,247 @@ +/************************************************************************** + * + * Copyright 2012 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#include +#include + +#include +#include +#include +#include + +#include + + +using namespace trace; + + +// Parser class for call sets +class CallSetParser +{ + CallSet &set; + +protected: + char lookahead; + + CallSetParser(CallSet &_set) : + set(_set), + lookahead(0) + {} + +public: + void parse() { + skipWhiteSpace(); + while (lookahead) { + assert(!isSpace()); + parseRange(); + // skip any comma + isOperator(','); + } + } + +private: + void parseRange() { + CallNo start = std::numeric_limits::min(); + CallNo stop = std::numeric_limits::max(); + CallNo step = 1; + CallFlags freq = FREQUENCY_ALL; + if (isAlpha()) { + freq = parseFrequency(); + } else { + if (isOperator('*')) { + // no-change + } else { + start = parseCallNo(); + if (isOperator('-')) { + if (isDigit()) { + stop = parseCallNo(); + } else { + // no-change + } + } else { + stop = start; + } + } + if (isOperator('/')) { + if (isDigit()) { + step = parseCallNo(); + } else { + freq = parseFrequency(); + } + } + } + set.addRange(CallRange(start, stop, step, freq)); + } + + // match and consume an operator + bool isOperator(char c) { + if (lookahead == c) { + consume(); + skipWhiteSpace(); + return true; + } else { + return false; + } + } + + CallNo parseCallNo() { + CallNo number = 0; + if (isDigit()) { + do { + CallNo digit = consume() - '0'; + number = number * 10 + digit; + } while (isDigit()); + } else { + std::cerr << "error: expected digit, found '" << lookahead << "'\n"; + exit(0); + } + skipWhiteSpace(); + return number; + } + + CallFlags parseFrequency() { + std::string freq; + if (isAlpha()) { + do { + freq.push_back(consume()); + } while (isAlpha()); + } else { + std::cerr << "error: expected frequency, found '" << lookahead << "'\n"; + exit(0); + } + skipWhiteSpace(); + if (freq == "frame") { + return FREQUENCY_FRAME; + } else if (freq == "rendertarget" || freq == "fbo") { + return FREQUENCY_RENDERTARGET; + } else if (freq == "render" || freq == "draw") { + return FREQUENCY_RENDER; + } else { + std::cerr << "error: expected frequency, found '" << freq << "'\n"; + exit(0); + return FREQUENCY_NONE; + } + } + + // match lookahead with a digit (does not consume) + bool isDigit() const { + return lookahead >= '0' && lookahead <= '9'; + } + + bool isAlpha() const { + return lookahead >= 'a' && lookahead <= 'z'; + } + + void skipWhiteSpace() { + while (isSpace()) { + consume(); + } + } + + bool isSpace() const { + return lookahead == ' ' || + lookahead == '\t' || + lookahead == '\r' || + lookahead == '\n'; + } + + virtual char consume() = 0; +}; + + +class StringCallSetParser : public CallSetParser +{ + const char *buf; + +public: + StringCallSetParser(CallSet &_set, const char *_buf) : + CallSetParser(_set), + buf(_buf) + { + lookahead = *buf; + } + + char consume() { + char c = lookahead; + if (lookahead) { + ++buf; + lookahead = *buf; + } + return c; + } +}; + + +class FileCallSetParser : public CallSetParser +{ + std::ifstream stream; + +public: + FileCallSetParser(CallSet &_set, const char *filename) : + CallSetParser(_set) + { + stream.open(filename); + if (!stream.is_open()) { + std::cerr << "error: failed to open \"" << filename << "\"\n"; + exit(1); + } + + stream.get(lookahead); + } + + char consume() { + char c = lookahead; + if (stream.eof()) { + lookahead = 0; + } else { + stream.get(lookahead); + } + return c; + } +}; + + +CallSet::CallSet(const char *string) +{ + if (*string == '@') { + FileCallSetParser parser(*this, &string[1]); + parser.parse(); + } else { + StringCallSetParser parser(*this, string); + parser.parse(); + } +} + + +CallSet::CallSet(CallFlags freq) { + if (freq != FREQUENCY_NONE) { + CallNo start = std::numeric_limits::min(); + CallNo stop = std::numeric_limits::max(); + CallNo step = 1; + addRange(CallRange(start, stop, step, freq)); + assert(!empty()); + } +} diff --git a/common/trace_callset.hpp b/common/trace_callset.hpp new file mode 100644 index 0000000..b679d94 --- /dev/null +++ b/common/trace_callset.hpp @@ -0,0 +1,167 @@ +/************************************************************************** + * + * Copyright 2012 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Representation of call sets. + * + * Grammar: + * + * set = '@' filename + * | range ( ',' ? range ) * + * + * range = interval ( '/' frequency ) + * + * interval = '*' + * | number + * | start_number '-' end_number + * + * frequency = divisor + * | "frame" + * | "rendertarget" | "fbo" + * | "render | "draw" + * + */ + +#ifndef _TRACE_CALLSET_HPP_ +#define _TRACE_CALLSET_HPP_ + + +#include + +#include "trace_model.hpp" + + +namespace trace { + + + // Should match Call::no + typedef unsigned CallNo; + + + // Aliases for call flags + enum { + FREQUENCY_NONE = 0, + FREQUENCY_FRAME = CALL_FLAG_END_FRAME, + FREQUENCY_RENDERTARGET = CALL_FLAG_END_FRAME | CALL_FLAG_SWAP_RENDERTARGET, + FREQUENCY_RENDER = CALL_FLAG_RENDER, + FREQUENCY_ALL = 0xffffffff + }; + + // A linear range of calls + class CallRange + { + public: + CallNo start; + CallNo stop; + CallNo step; + CallFlags freq; + + CallRange(CallNo callNo) : + start(callNo), + stop(callNo), + step(1), + freq(FREQUENCY_ALL) + {} + + CallRange(CallNo _start, CallNo _stop, CallNo _step = 1, CallFlags _freq = FREQUENCY_ALL) : + start(_start), + stop(_stop), + step(_step), + freq(_freq) + {} + + bool + contains(CallNo callNo, CallFlags callFlags) const { + return callNo >= start && + callNo <= stop && + ((callNo - start) % step) == 0 && + ((callFlags & freq) || + freq == FREQUENCY_ALL); + } + }; + + + // A collection of call ranges + class CallSet + { + public: + // TODO: use binary tree to speed up lookups + typedef std::list< CallRange > RangeList; + RangeList ranges; + + CallSet() {} + + CallSet(CallFlags freq); + + CallSet(const char *str); + + // Not empty set + inline bool + empty() const { + return ranges.empty(); + } + + void + addRange(const CallRange & range) { + if (range.start <= range.stop && + range.freq != FREQUENCY_NONE) { + + RangeList::iterator it = ranges.begin(); + while (it != ranges.end() && it->start < range.start) { + ++it; + } + + ranges.insert(it, range); + } + } + + inline bool + contains(CallNo callNo, CallFlags callFlags = FREQUENCY_ALL) const { + if (empty()) { + return false; + } + RangeList::const_iterator it; + for (it = ranges.begin(); it != ranges.end() && it->start <= callNo; ++it) { + if (it->contains(callNo, callFlags)) { + return true; + } + } + return false; + } + + inline bool + contains(const trace::Call &call) { + return contains(call.no, call.flags); + } + }; + + + CallSet parse(const char *string); + + +} /* namespace trace */ + + +#endif /* _TRACE_CALLSET_HPP_ */ diff --git a/glretrace.hpp b/glretrace.hpp index 41e2997..8353522 100644 --- a/glretrace.hpp +++ b/glretrace.hpp @@ -46,17 +46,7 @@ extern unsigned frame; extern long long startTime; extern bool wait; -enum frequency { - FREQUENCY_NEVER = 0, - FREQUENCY_FRAME, - FREQUENCY_FRAMEBUFFER, - FREQUENCY_DRAW, -}; - extern bool benchmark; -extern const char *compare_prefix; -extern const char *snapshot_prefix; -extern enum frequency snapshot_frequency; extern unsigned dump_state; @@ -69,7 +59,6 @@ extern const retrace::Entry glx_callbacks[]; extern const retrace::Entry wgl_callbacks[]; extern const retrace::Entry egl_callbacks[]; -void snapshot(unsigned call_no); void frame_complete(trace::Call &call); void updateDrawable(int width, int height); diff --git a/glretrace.py b/glretrace.py index f3ae91b..7570ebd 100644 --- a/glretrace.py +++ b/glretrace.py @@ -197,21 +197,12 @@ class GlRetracer(Retracer): print ' GLint __pack_buffer = 0;' print ' glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &__pack_buffer);' print ' if (!__pack_buffer) {' - if function.name == 'glReadPixels': - print ' glFinish();' - print ' if (glretrace::snapshot_frequency == glretrace::FREQUENCY_FRAME ||' - print ' glretrace::snapshot_frequency == glretrace::FREQUENCY_FRAMEBUFFER) {' - print ' glretrace::snapshot(call.no);' - print ' }' print ' return;' print ' }' # Pre-snapshots if function.name in self.bind_framebuffer_function_names: print ' assert(call.flags & trace::CALL_FLAG_SWAP_RENDERTARGET);' - print ' if (glretrace::snapshot_frequency == glretrace::FREQUENCY_FRAMEBUFFER) {' - print ' glretrace::snapshot(call.no - 1);' - print ' }' if function.name == 'glFrameTerminatorGREMEDY': print ' glretrace::frame_complete(call);' return @@ -225,9 +216,6 @@ class GlRetracer(Retracer): print ' }' if is_draw_array or is_draw_elements or is_misc_draw: print ' assert(call.flags & trace::CALL_FLAG_RENDER);' - print ' if (glretrace::snapshot_frequency == glretrace::FREQUENCY_DRAW) {' - print ' glretrace::snapshot(call.no);' - print ' }' def invokeFunction(self, function): diff --git a/glretrace_main.cpp b/glretrace_main.cpp index 1208632..f9d7741 100644 --- a/glretrace_main.cpp +++ b/glretrace_main.cpp @@ -30,6 +30,7 @@ #include "os_time.hpp" #include "image.hpp" #include "retrace.hpp" +#include "trace_callset.hpp" #include "glproc.hpp" #include "glstate.hpp" #include "glretrace.hpp" @@ -50,9 +51,10 @@ long long startTime = 0; bool wait = false; bool benchmark = false; -const char *compare_prefix = NULL; -const char *snapshot_prefix = NULL; -enum frequency snapshot_frequency = FREQUENCY_NEVER; +static const char *compare_prefix = NULL; +static const char *snapshot_prefix = NULL; +static trace::CallSet snapshot_frequency; +static trace::CallSet compare_frequency; unsigned dump_state = ~0; @@ -138,9 +140,11 @@ updateDrawable(int width, int height) { } -void snapshot(unsigned call_no) { - if (!drawable || - (!snapshot_prefix && !compare_prefix)) { +static void +snapshot(unsigned call_no) { + assert(snapshot_prefix || compare_prefix); + + if (!drawable) { return; } @@ -194,11 +198,6 @@ void frame_complete(trace::Call &call) { if (!drawable->visible) { retrace::warning(call) << "could not infer drawable size (glViewport never called)\n"; } - - if (snapshot_frequency == FREQUENCY_FRAME || - snapshot_frequency == FREQUENCY_FRAMEBUFFER) { - snapshot(call.no); - } } @@ -215,8 +214,32 @@ static void display(void) { trace::Call *call; while ((call = parser.parse_call())) { + bool swapRenderTarget = call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET; + bool doSnapshot = + snapshot_frequency.contains(*call) || + compare_frequency.contains(*call) + ; + + // For calls which cause rendertargets to be swaped, we take the + // snapshot _before_ swapping the rendertargets. + if (doSnapshot && swapRenderTarget) { + if (call->flags & trace::CALL_FLAG_END_FRAME) { + // For swapbuffers/presents we still use this call number, + // spite not have been executed yet. + snapshot(call->no); + } else { + // Whereas for ordinate fbo/rendertarget changes we use the + // previous call's number. + snapshot(call->no - 1); + } + } + retracer.retrace(*call); + if (doSnapshot && !swapRenderTarget) { + snapshot(call->no); + } + if (!insideGlBeginEnd && drawable && context && call->no >= dump_state) { @@ -255,11 +278,12 @@ static void usage(void) { "\n" " -b benchmark mode (no error checking or warning messages)\n" " -c PREFIX compare against snapshots\n" + " -C CALLSET calls to compare (default is every frame)\n" " -core use core profile\n" " -db use a double buffer visual (default)\n" " -sb use a single buffer visual\n" " -s PREFIX take snapshots; `-` for PNM stdout output\n" - " -S FREQUENCY snapshot frequency: frame (default), framebuffer, or draw\n" + " -S CALLSET calls to snapshot (default is every frame)\n" " -v increase output verbosity\n" " -D CALLNO dump state at specific call no\n" " -w wait on final frame\n"; @@ -268,6 +292,8 @@ static void usage(void) { extern "C" int main(int argc, char **argv) { + assert(compare_frequency.empty()); + assert(snapshot_frequency.empty()); int i; for (i = 1; i < argc; ++i) { @@ -285,8 +311,13 @@ int main(int argc, char **argv) glws::debug = false; } else if (!strcmp(arg, "-c")) { compare_prefix = argv[++i]; - if (snapshot_frequency == FREQUENCY_NEVER) { - snapshot_frequency = FREQUENCY_FRAME; + if (compare_frequency.empty()) { + compare_frequency = trace::CallSet(trace::FREQUENCY_FRAME); + } + } else if (!strcmp(arg, "-C")) { + compare_frequency = trace::CallSet(argv[++i]); + if (compare_prefix == NULL) { + compare_prefix = ""; } } else if (!strcmp(arg, "-D")) { dump_state = atoi(argv[++i]); @@ -302,25 +333,14 @@ int main(int argc, char **argv) return 0; } else if (!strcmp(arg, "-s")) { snapshot_prefix = argv[++i]; - if (snapshot_frequency == FREQUENCY_NEVER) { - snapshot_frequency = FREQUENCY_FRAME; + if (snapshot_frequency.empty()) { + snapshot_frequency = trace::CallSet(trace::FREQUENCY_FRAME); } if (snapshot_prefix[0] == '-' && snapshot_prefix[1] == 0) { retrace::verbosity = -2; } } else if (!strcmp(arg, "-S")) { - arg = argv[++i]; - if (!strcmp(arg, "frame")) { - snapshot_frequency = FREQUENCY_FRAME; - } else if (!strcmp(arg, "framebuffer")) { - snapshot_frequency = FREQUENCY_FRAMEBUFFER; - } else if (!strcmp(arg, "draw")) { - snapshot_frequency = FREQUENCY_DRAW; - } else { - std::cerr << "error: unknown frequency " << arg << "\n"; - usage(); - return 1; - } + snapshot_frequency = trace::CallSet(argv[++i]); if (snapshot_prefix == NULL) { snapshot_prefix = ""; } diff --git a/scripts/retracediff.py b/scripts/retracediff.py index bed216c..3495975 100755 --- a/scripts/retracediff.py +++ b/scripts/retracediff.py @@ -156,9 +156,9 @@ def main(): type="float", dest="threshold", default=12.0, help="threshold precision [default: %default]") optparser.add_option( - '-S', '--snapshot-frequency', metavar='FREQUENCY', + '-S', '--snapshot-frequency', metavar='CALLSET', type="string", dest="snapshot_frequency", default='draw', - help="snapshot frequency: frame, framebuffer, or draw [default: %default]") + help="calls to compare [default: %default]") (options, args) = optparser.parse_args(sys.argv[1:]) ref_env = parse_env(optparser, options.ref_env)