diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/CMakeLists.txt | 27 | ||||
-rw-r--r-- | cli/cli.hpp | 6 | ||||
-rw-r--r-- | cli/cli_diff.cpp | 10 | ||||
-rw-r--r-- | cli/cli_diff_images.cpp | 10 | ||||
-rw-r--r-- | cli/cli_diff_state.cpp | 71 | ||||
-rw-r--r-- | cli/cli_leaks.cpp | 85 | ||||
-rw-r--r-- | cli/cli_main.cpp | 2 | ||||
-rw-r--r-- | cli/cli_pager.cpp | 47 | ||||
-rw-r--r-- | cli/cli_pager.hpp | 4 | ||||
-rw-r--r-- | cli/cli_pickle.cpp | 30 | ||||
-rw-r--r-- | cli/cli_repack.cpp | 30 | ||||
-rw-r--r-- | cli/cli_resources.cpp | 7 | ||||
-rw-r--r-- | cli/cli_resources.hpp | 4 | ||||
-rw-r--r-- | cli/cli_retrace.hpp | 4 | ||||
-rw-r--r-- | cli/cli_sed.cpp | 59 | ||||
-rw-r--r-- | cli/cli_trace.cpp | 99 | ||||
-rw-r--r-- | cli/cli_trim.cpp | 284 | ||||
-rw-r--r-- | cli/cli_trim_auto.cpp | 432 | ||||
-rw-r--r-- | cli/cli_trim_auto_analyzer.cpp (renamed from cli/trace_analyzer.cpp) | 2 | ||||
-rw-r--r-- | cli/cli_trim_auto_analyzer.hpp (renamed from cli/trace_analyzer.hpp) | 0 | ||||
-rw-r--r-- | cli/pickle.hpp | 62 |
21 files changed, 841 insertions, 434 deletions
diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 727cd7f8..31d7fdf2 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -8,12 +8,18 @@ add_definitions( -DAPITRACE_SCRIPTS_INSTALL_DIR="${CMAKE_INSTALL_PREFIX}/${SCRIPTS_INSTALL_DIR}" -DAPITRACE_WRAPPERS_INSTALL_DIR="${CMAKE_INSTALL_PREFIX}/${WRAPPER_INSTALL_DIR}" ) +if (WIN32) + add_definitions (-DAPITRACE_PYTHON_EXECUTABLE="python") +else () + add_definitions (-DAPITRACE_PYTHON_EXECUTABLE="${PYTHON_EXECUTABLE}") +endif () add_executable (apitrace cli_main.cpp cli_diff.cpp cli_diff_state.cpp cli_diff_images.cpp + cli_leaks.cpp cli_dump.cpp cli_dump_images.cpp cli_pager.cpp @@ -23,8 +29,9 @@ add_executable (apitrace cli_sed.cpp cli_trace.cpp cli_trim.cpp + cli_trim_auto.cpp + cli_trim_auto_analyzer.cpp cli_resources.cpp - trace_analyzer.cpp ) target_link_libraries (apitrace @@ -36,22 +43,12 @@ target_link_libraries (apitrace ) if (NOT CMAKE_CROSSCOMPILING) + # On debug builds tell where the source is so that scripts can be found + # http://www.cmake.org/cmake/help/v3.0/policy/CMP0043.html set_target_properties (apitrace PROPERTIES - # On debug builds tell where the source is so that scripts can be found - COMPILE_DEFINITIONS_DEBUG APITRACE_SOURCE_DIR="${CMAKE_SOURCE_DIR}" - ) -endif () - -if (MSVC AND NOT CMAKE_GENERATOR STREQUAL "Ninja") - # On MSVC builds tell which subdirectory the binaries with be (for each - # configuration) - set_target_properties (apitrace PROPERTIES - COMPILE_DEFINITIONS_DEBUG APITRACE_CONFIGURATION_SUBDIR="Debug" - COMPILE_DEFINITIONS_RELEASE APITRACE_CONFIGURATION_SUBDIR="Release" - COMPILE_DEFINITIONS_MINSIZEREL APITRACE_CONFIGURATION_SUBDIR="MinSizeRel" - COMPILE_DEFINITIONS_RELWITHDEBINFO APITRACE_CONFIGURATION_SUBDIR="RelWithDebInfo" + COMPILE_DEFINITIONS $<$<CONFIG:Debug>:APITRACE_SOURCE_DIR="${CMAKE_SOURCE_DIR}"> ) endif () - install (TARGETS apitrace RUNTIME DESTINATION bin) +install_pdb (apitrace RUNTIME DESTINATION bin) diff --git a/cli/cli.hpp b/cli/cli.hpp index 088f0e8c..f777122c 100644 --- a/cli/cli.hpp +++ b/cli/cli.hpp @@ -25,8 +25,7 @@ * *********************************************************************/ -#ifndef _APITRACE_CLI_HPP_ -#define _APITRACE_CLI_HPP_ +#pragma once struct Command { @@ -45,11 +44,12 @@ extern const Command diff_state_command; extern const Command diff_images_command; extern const Command dump_command; extern const Command dump_images_command; +extern const Command leaks_command; extern const Command pickle_command; extern const Command repack_command; extern const Command retrace_command; extern const Command sed_command; extern const Command trace_command; extern const Command trim_command; +extern const Command trim_auto_command; -#endif /* _APITRACE_CLI_HPP_ */ diff --git a/cli/cli_diff.cpp b/cli/cli_diff.cpp index 76cdce1d..daeccd3e 100644 --- a/cli/cli_diff.cpp +++ b/cli/cli_diff.cpp @@ -45,12 +45,9 @@ static void usage(void) { os::String command = find_command(); - if (!command.length()) { - exit(1); - } char *args[4]; - args[0] = (char *) "python"; + args[0] = (char *) APITRACE_PYTHON_EXECUTABLE; args[1] = (char *) command.str(); args[2] = (char *) "--help"; args[3] = NULL; @@ -64,14 +61,11 @@ command(int argc, char *argv[]) int i; os::String command = find_command(); - if (!command.length()) { - return 1; - } os::String apitracePath = os::getProcessName(); std::vector<const char *> args; - args.push_back("python"); + args.push_back(APITRACE_PYTHON_EXECUTABLE); args.push_back(command.str()); args.push_back("--apitrace"); args.push_back(apitracePath.str()); diff --git a/cli/cli_diff_images.cpp b/cli/cli_diff_images.cpp index ba8df3e9..54523cd3 100644 --- a/cli/cli_diff_images.cpp +++ b/cli/cli_diff_images.cpp @@ -45,12 +45,9 @@ static void usage(void) { os::String command = find_command(); - if (!command.length()) { - exit(1); - } char *args[4]; - args[0] = (char *) "python"; + args[0] = (char *) APITRACE_PYTHON_EXECUTABLE; args[1] = (char *) command.str(); args[2] = (char *) "--help"; args[3] = NULL; @@ -64,12 +61,9 @@ command(int argc, char *argv[]) int i; os::String command = find_command(); - if (!command.length()) { - return 1; - } std::vector<const char *> args; - args.push_back("python"); + args.push_back(APITRACE_PYTHON_EXECUTABLE); args.push_back(command.str()); for (i = 1; i < argc; i++) { args.push_back(argv[i]); diff --git a/cli/cli_diff_state.cpp b/cli/cli_diff_state.cpp index a802b12a..0488e77f 100644 --- a/cli/cli_diff_state.cpp +++ b/cli/cli_diff_state.cpp @@ -26,8 +26,6 @@ *********************************************************************/ #include <string.h> -#include <getopt.h> - #include <iostream> #include "cli.hpp" @@ -37,63 +35,42 @@ static const char *synopsis = "Identify differences between two state dumps."; +static os::String +find_command(void) +{ + return findScript("jsondiff.py"); +} + static void usage(void) { - std::cout - << "usage: apitrace diff-state <state-1> <state-2>\n" - << synopsis << "\n" - "\n" - " Both input files should be the result of running 'glretrace -D XYZ <trace>'.\n"; -} + os::String command = find_command(); -const static char * -shortOptions = "h"; + char *args[4]; + args[0] = (char *) APITRACE_PYTHON_EXECUTABLE; + args[1] = (char *) command.str(); + args[2] = (char *) "--help"; + args[3] = NULL; -const static struct option -longOptions[] = { - {"help", no_argument, 0, 'h'}, - {0, 0, 0, 0} -}; + os::execute(args); +} static int command(int argc, char *argv[]) { - int opt; - while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { - switch (opt) { - case 'h': - usage(); - return 0; - default: - std::cerr << "error: unexpected option `" << (char)opt << "`\n"; - usage(); - return 1; - } - } - - if (argc != optind + 2) { - std::cerr << "Error: diff-state requires exactly two state-dump files as arguments.\n"; - usage(); - return 1; - } - - char *file1, *file2; + int i; - file1 = argv[optind]; - file2 = argv[optind + 1]; + os::String command = find_command(); - os::String command = findScript("jsondiff.py"); - - char *args[5]; - - args[0] = const_cast<char *>("python"); - args[1] = const_cast<char *>(command.str()); - args[2] = file1; - args[3] = file2; - args[4] = NULL; + std::vector<const char *> args; + args.push_back(APITRACE_PYTHON_EXECUTABLE); + args.push_back(command.str()); + for (i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + args.push_back(NULL); - return os::execute(args); + return os::execute((char * const *)&args[0]); } const Command diff_state_command = { diff --git a/cli/cli_leaks.cpp b/cli/cli_leaks.cpp new file mode 100644 index 00000000..1d28ec58 --- /dev/null +++ b/cli/cli_leaks.cpp @@ -0,0 +1,85 @@ +/********************************************************************* + * + * Copyright 2016 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 <string.h> +#include <iostream> + +#include "cli.hpp" +#include "os_string.hpp" +#include "os_process.hpp" +#include "cli_resources.hpp" + +static const char *synopsis = "Check trace for object leaks."; + +static os::String +find_command(void) +{ + return findScript("leaks.py"); +} + +static void +usage(void) +{ + os::String command = find_command(); + + char *args[4]; + args[0] = (char *) APITRACE_PYTHON_EXECUTABLE; + args[1] = (char *) command.str(); + args[2] = (char *) "--help"; + args[3] = NULL; + + os::execute(args); +} + +static int +command(int argc, char *argv[]) +{ + int i; + + os::String command = find_command(); + + os::String apitracePath = os::getProcessName(); + + std::vector<const char *> args; + args.push_back("python"); + args.push_back(command.str()); + args.push_back("--apitrace"); + args.push_back(apitracePath.str()); + for (i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + args.push_back(NULL); + + return os::execute((char * const *)&args[0]); +} + +const Command leaks_command = { + "leaks", + synopsis, + usage, + command +}; diff --git a/cli/cli_main.cpp b/cli/cli_main.cpp index 1e278e41..b475bd3b 100644 --- a/cli/cli_main.cpp +++ b/cli/cli_main.cpp @@ -71,12 +71,14 @@ static const Command * commands[] = { &diff_images_command, &dump_command, &dump_images_command, + &leaks_command, &pickle_command, &sed_command, &repack_command, &retrace_command, &trace_command, &trim_command, + &trim_auto_command, &help_command }; diff --git a/cli/cli_pager.cpp b/cli/cli_pager.cpp index 579b19f3..4a3b451b 100644 --- a/cli/cli_pager.cpp +++ b/cli/cli_pager.cpp @@ -64,8 +64,8 @@ static pid_t pid = -1; static void on_exit(void) { - fflush(stdout); - fflush(stderr); + fflush(stdout); + fflush(stderr); close(STDOUT_FILENO); close(STDERR_FILENO); waitpid(pid, NULL, 0); @@ -78,6 +78,7 @@ on_exit(void) static void on_signal(int sig) { + fprintf(stderr, "on_signal\n"); on_exit(); signal(sig, SIG_DFL); raise(sig); @@ -93,37 +94,45 @@ pipepager(void) { return; } - enum { - READ_FD = 0, - WRITE_FD = 1 - }; + union { + int pipe[2]; + struct { + int read; + int write; + }; + } fd; - int parentToChild[2]; int ret; const char *pager; - ret = pipe(parentToChild); + ret = pipe(fd.pipe); assert(ret == 0); + if (ret != 0) { + return; + } pid = fork(); switch (pid) { case -1: // failed to fork + close(fd.read); + close(fd.write); return; case 0: // child - ret = dup2(parentToChild[READ_FD], STDIN_FILENO); - assert(ret != -1); - ret = close(parentToChild[WRITE_FD]); - assert(ret == 0); + close(fd.write); + + dup2(fd.read, STDIN_FILENO); pager = getenv("PAGER"); if (!pager) { pager = "less"; } - setenv("LESS", "FRXn", 0); + if (!getenv("PAGER")) { + putenv((char *)"LESS=FRXn"); + } execlp(pager, pager, NULL); @@ -132,13 +141,13 @@ pipepager(void) { default: // parent - ret = close(parentToChild[READ_FD]); - assert(ret == 0); + close(fd.read); - dup2(parentToChild[WRITE_FD], STDOUT_FILENO); - if (isatty(STDERR_FILENO)) - dup2(parentToChild[WRITE_FD], STDERR_FILENO); - close(parentToChild[WRITE_FD]); + dup2(fd.write, STDOUT_FILENO); + if (isatty(STDERR_FILENO)) { + dup2(fd.write, STDERR_FILENO); + } + close(fd.write); // Ensure we wait for the pager before terminating signal(SIGINT, on_signal); diff --git a/cli/cli_pager.hpp b/cli/cli_pager.hpp index 95d98781..197d712f 100644 --- a/cli/cli_pager.hpp +++ b/cli/cli_pager.hpp @@ -24,12 +24,10 @@ **************************************************************************/ -#ifndef _CLI_PAGER_HPP_ -#define _CLI_PAGER_HPP_ +#pragma once void pipepager(void); -#endif /* _CLI_PAGER_HPP_ */ diff --git a/cli/cli_pickle.cpp b/cli/cli_pickle.cpp index 5c267e0e..286716be 100644 --- a/cli/cli_pickle.cpp +++ b/cli/cli_pickle.cpp @@ -83,6 +83,10 @@ public: writer.writeString(node->value); } + void visit(WString *node) { + writer.writeWString(node->value); + } + void visit(Enum *node) { if (symbolic) { const EnumValue *it = node->lookup(); @@ -98,7 +102,7 @@ public: if (symbolic) { unsigned long long value = node->value; const BitmaskSig *sig = node->sig; - writer.beginList(); + writer.beginTuple(); for (const BitmaskFlag *it = sig->flags; it != sig->flags + sig->num_flags; ++it) { if ((it->value && (value & it->value) == it->value) || (!it->value && value == 0)) { @@ -112,14 +116,15 @@ public: if (value) { writer.writeInt(value); } - writer.endList(); + writer.endTuple(); } else { writer.writeInt(node->value); } } void visit(Struct *node) { - if (false) { + if (true) { + // Structures as dictionaries writer.beginDict(); for (unsigned i = 0; i < node->sig->num_members; ++i) { writer.beginItem(node->sig->member_names[i]); @@ -128,11 +133,13 @@ public: } writer.endDict(); } else { - writer.beginTuple(); - for (unsigned i = 0; i < node->sig->num_members; ++i) { + // Structures as tuples + unsigned num_members = node->sig->num_members; + writer.beginTuple(num_members); + for (unsigned i = 0; i < num_members; ++i) { _visit(node->members[i]); } - writer.endTuple(); + writer.endTuple(num_members); } } @@ -149,7 +156,7 @@ public: } void visit(Pointer *node) { - writer.writeInt(node->value); + writer.writePointer(node->value); } void visit(Repr *r) { @@ -169,11 +176,18 @@ public: writer.beginList(); for (unsigned i = 0; i < call->args.size(); ++i) { + writer.beginTuple(2); + if (i < call->sig->num_args) { + writer.writeString(call->sig->arg_names[i]); + } else { + writer.writeNone(); + } if (call->args[i].value) { _visit(call->args[i].value); } else { writer.writeNone(); } + writer.endTuple(2); } writer.endList(); @@ -183,6 +197,8 @@ public: writer.writeNone(); } + writer.writeInt(call->flags); + writer.endTuple(); } }; diff --git a/cli/cli_repack.cpp b/cli/cli_repack.cpp index 5d122fa6..291f0d96 100644 --- a/cli/cli_repack.cpp +++ b/cli/cli_repack.cpp @@ -32,40 +32,54 @@ #include "cli.hpp" #include "trace_file.hpp" +#include "trace_ostream.hpp" -static const char *synopsis = "Repack a trace file with Snappy compression."; +static const char *synopsis = "Repack a trace file with different compression."; static void usage(void) { std::cout - << "usage: apitrace repack <in-trace-file> <out-trace-file>\n" + << "usage: apitrace repack [options] <in-trace-file> <out-trace-file>\n" << synopsis << "\n" << "\n" << "Snappy compression allows for faster replay and smaller memory footprint,\n" << "at the expense of a slightly smaller compression ratio than zlib\n" + << "\n" + << " -z,--zlib Use ZLib compression instead\n" << "\n"; } const static char * -shortOptions = "h"; +shortOptions = "hz"; const static struct option longOptions[] = { {"help", no_argument, 0, 'h'}, + {"zlib", no_argument, 0, 'z'}, {0, 0, 0, 0} }; +enum Format { + FORMAT_SNAPPY = 0, + FORMAT_ZLIB, +}; + static int -repack(const char *inFileName, const char *outFileName) +repack(const char *inFileName, const char *outFileName, Format format) { trace::File *inFile = trace::File::createForRead(inFileName); if (!inFile) { return 1; } - trace::File *outFile = trace::File::createForWrite(outFileName); + trace::OutStream *outFile; + if (format == FORMAT_SNAPPY) { + outFile = trace::createSnappyStream(outFileName); + } else { + outFile = trace::createZLibStream(outFileName); + } if (!outFile) { delete inFile; return 1; @@ -89,12 +103,16 @@ repack(const char *inFileName, const char *outFileName) static int command(int argc, char *argv[]) { + Format format = FORMAT_SNAPPY; int opt; while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { switch (opt) { case 'h': usage(); return 0; + case 'z': + format = FORMAT_ZLIB; + break; default: std::cerr << "error: unexpected option `" << (char)opt << "`\n"; usage(); @@ -108,7 +126,7 @@ command(int argc, char *argv[]) return 1; } - return repack(argv[optind], argv[optind + 1]); + return repack(argv[optind], argv[optind + 1], format); } const Command repack_command = { diff --git a/cli/cli_resources.cpp b/cli/cli_resources.cpp index d6d0a55d..4186bd60 100644 --- a/cli/cli_resources.cpp +++ b/cli/cli_resources.cpp @@ -101,11 +101,11 @@ findWrapper(const char *wrapperFilename, bool verbose) // Try relative build directory // XXX: Just make build and install directory layout match wrapperPath = processDir; -#if defined(APITRACE_CONFIGURATION_SUBDIR) +#if defined(CMAKE_INTDIR) // Go from `Debug\apitrace.exe` to `wrappers\Debug\foo.dll` on MSVC builds. wrapperPath.join(".."); wrapperPath.join("wrappers"); - wrapperPath.join(APITRACE_CONFIGURATION_SUBDIR); + wrapperPath.join(CMAKE_INTDIR); #else wrapperPath.join("wrappers"); #endif @@ -194,6 +194,5 @@ findScript(const char *scriptFilename, bool verbose) #endif std::cerr << "error: cannot find " << scriptFilename << " script\n"; - - return ""; + exit(1); } diff --git a/cli/cli_resources.hpp b/cli/cli_resources.hpp index ee3882be..27b883c3 100644 --- a/cli/cli_resources.hpp +++ b/cli/cli_resources.hpp @@ -23,8 +23,7 @@ * **************************************************************************/ -#ifndef _CLI_RESOURCES_HPP_ -#define _CLI_RESOURCES_HPP_ +#pragma once #include <stdlib.h> @@ -43,4 +42,3 @@ os::String findWrapper(const char *wrapperFilename, bool verbose = false); -#endif /* _CLI_RESOURCES_HPP_ */ diff --git a/cli/cli_retrace.hpp b/cli/cli_retrace.hpp index 0dfa823e..6df0fbe6 100644 --- a/cli/cli_retrace.hpp +++ b/cli/cli_retrace.hpp @@ -25,8 +25,7 @@ * *********************************************************************/ -#ifndef _CLI_RETRACE_HPP_ -#define _CLI_RETRACE_HPP_ +#pragma once #include <vector> @@ -43,4 +42,3 @@ executeRetrace(const std::vector<const char *> & opts, const char *traceName); -#endif /* _CLI_RETRACE_HPP_ */ diff --git a/cli/cli_sed.cpp b/cli/cli_sed.cpp index afec1257..401b7a63 100644 --- a/cli/cli_sed.cpp +++ b/cli/cli_sed.cpp @@ -46,8 +46,11 @@ usage(void) << synopsis << "\n" "\n" " -h, --help Show detailed help for sed options and exit\n" - " -e s/SEARCH/REPLACE/ Search and replace a symbol.\n" - " XXX: Only works for enums.\n" + " -e s/SEARCH/REPLACE/ Search and replace a symbol. Use @file(<path>)\n" + " to read SEARCH or REPLACE from a file.\n" + " Any character (not just /) can be used as \n" + " separator.\n" + " XXX: Only works for enums and strings.\n" " -o, --output=TRACE_FILE Output trace file\n" ; } @@ -104,6 +107,16 @@ public: } void visit(String *node) { + if (!searchName.compare(node->value)) { + size_t len = replaceName.length() + 1; + delete [] node->value; + char *str = new char [len]; + memcpy(str, replaceName.c_str(), len); + node->value = str; + } + } + + void visit(WString *node) { } void visit(Enum *node) { @@ -212,17 +225,17 @@ sed_trace(Replacements &replacements, const char *inFileName, std::string &outFi static bool parseSubstOpt(Replacements &replacements, const char *opt) { + char separator; + if (*opt++ != 's') { return false; } - if (*opt++ != '/') { - return false; - } + separator = *opt++; // Parse the search pattern const char *search_begin = opt; - while (*opt != '/') { + while (*opt != separator) { if (*opt == 0) { return false; } @@ -232,7 +245,7 @@ parseSubstOpt(Replacements &replacements, const char *opt) // Parse the replace pattern const char *replace_begin = opt; - while (*opt != '/') { + while (*opt != separator) { if (*opt == 0) { return false; } @@ -247,6 +260,36 @@ parseSubstOpt(Replacements &replacements, const char *opt) std::string search(search_begin, search_end); std::string replace(replace_begin, replace_end); + // If search or replace strings are taken from a file, read the file + std::string file_subst = "@file("; + + for (int i = 0; i < 2; i++) { + std::string *str = i ? &search : &replace; + + if (!str->compare(0, file_subst.length(), file_subst)) { + if ((*str)[str->length()-1] != ')') { + return false; + } + + std::string fname = str->substr(file_subst.length()); + fname[fname.length()-1] = 0; + FILE *f = fopen(fname.c_str(), "rt"); + if (!f) { + std::cerr << "error: cannot open file " << fname << "\n"; + return false; + } + char buf[1024]; + (*str) = ""; + while (!feof(f)) { + if (fgets(buf, 1024, f)) { + str->append(buf); + } + } + fclose(f); + } + } + + replacements.push_back(Replacer(search, replace)); return true; @@ -270,7 +313,7 @@ command(int argc, char *argv[]) break; case 'e': if (!parseSubstOpt(replacements, optarg)) { - std::cerr << "error: invalid replacement patter `" << optarg << "`\n"; + std::cerr << "error: invalid replacement pattern `" << optarg << "`\n"; } break; default: diff --git a/cli/cli_trace.cpp b/cli/cli_trace.cpp index 54c20df2..adca8af3 100644 --- a/cli/cli_trace.cpp +++ b/cli/cli_trace.cpp @@ -32,9 +32,11 @@ #include <getopt.h> #include <iostream> +#include <fstream> #include "os_string.hpp" #include "os_process.hpp" +#include "os_version.hpp" #include "cli.hpp" #include "cli_resources.hpp" @@ -52,6 +54,8 @@ #endif +#ifdef _WIN32 + static inline bool copyWrapper(const os::String & wrapperPath, const char *programPath, @@ -81,12 +85,14 @@ copyWrapper(const os::String & wrapperPath, return true; } +#endif /* _WIN32 */ + static int traceProgram(trace::API api, char * const *argv, const char *output, - bool verbose, + int verbose, bool debug) { const char *wrapperFilename; @@ -121,6 +127,10 @@ traceProgram(trace::API api, wrapperFilename = "dxgitrace.dll"; useInject = true; break; + case trace::API_D2D1: + wrapperFilename = "d2d1trace.dll"; + useInject = true; + break; #endif default: std::cerr << "error: unsupported API\n"; @@ -136,27 +146,21 @@ traceProgram(trace::API api, #if defined(_WIN32) /* * Use DLL injection method on Windows, even for APIs that don't stricly - * need it. Except when tracing OpenGL on Windows 8, as the injection - * method seems to have troubles tracing the internal - * gdi32.dll!SwapBuffers -> opengl32.dll!wglSwapBuffer calls, per github - * issue #172. + * need it. */ - { - OSVERSIONINFO osvi; - ZeroMemory(&osvi, sizeof osvi); - osvi.dwOSVersionInfoSize = sizeof osvi; - GetVersionEx(&osvi); - BOOL bIsWindows8orLater = - osvi.dwMajorVersion > 6 || - (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion >= 2); - if (api != trace::API_GL || !bIsWindows8orLater) { - useInject = true; - } - } + useInject = true; if (useInject) { args.push_back("inject"); + if (debug) { + args.push_back("-d"); + } + for (int i = 1; i < verbose; ++i) { + args.push_back("-v"); + } + args.push_back("-D"); args.push_back(wrapperPath); + args.push_back("--"); } else { /* On Windows copy the wrapper to the program directory. */ @@ -187,14 +191,50 @@ traceProgram(trace::API api, wrapperPath.append(oldEnvVarValue); } + std::string ex; if (debug) { - std::string ex("set exec-wrapper env " TRACE_VARIABLE "="); - ex.append(wrapperPath.str()); +#if defined(__APPLE__) + bool lldb = true; +#else + bool lldb = false; +#endif - args.push_back("gdb"); - args.push_back("--ex"); - args.push_back(ex.c_str()); - args.push_back("--args"); + if (lldb) { + /* + * Debug with LLDB. + * + * See also http://lldb.llvm.org/lldb-gdb.html + */ + + char scriptFileName[] = "/tmp/apitrace.XXXXXX"; + int scriptFD = mkstemp(scriptFileName); + if (scriptFD < 0) { + std::cerr << "error: failed to create temporary lldb script file\n"; + exit(1); + } + + FILE *scriptStream = fdopen(scriptFD, "w"); + fprintf(scriptStream, "env " TRACE_VARIABLE "='%s'\n", wrapperPath.str()); + fclose(scriptStream); + + args.push_back("lldb"); + args.push_back("-s"); + args.push_back(scriptFileName); + args.push_back("--"); + } else { + /* + * Debug with GDB. + */ + + ex = "set exec-wrapper env " TRACE_VARIABLE "='"; + ex.append(wrapperPath.str()); + ex.append("'"); + + args.push_back("gdb"); + args.push_back("--ex"); + args.push_back(ex.c_str()); + args.push_back("--args"); + } os::unsetEnvironment(TRACE_VARIABLE); } else { @@ -287,7 +327,7 @@ usage(void) " -o, --output=TRACE specify output trace file;\n" " default is `PROGRAM.trace`\n" #ifdef TRACE_VARIABLE - " -d, --debug debug with gdb\n" + " -d, --debug run inside debugger (gdb/lldb)\n" #endif ; } @@ -308,7 +348,7 @@ longOptions[] = { static int command(int argc, char *argv[]) { - bool verbose = false; + int verbose = 0; trace::API api = trace::API_GL; const char *output = NULL; bool debug = false; @@ -320,14 +360,16 @@ command(int argc, char *argv[]) usage(); return 0; case 'v': - verbose = true; + ++verbose; break; case 'a': if (strcmp(optarg, "gl") == 0) { api = trace::API_GL; } else if (strcmp(optarg, "egl") == 0) { api = trace::API_EGL; - } else if (strcmp(optarg, "d3d7") == 0) { + } else if (strcmp(optarg, "ddraw") == 0 || + strcmp(optarg, "d3d6") == 0 || + strcmp(optarg, "d3d7") == 0) { api = trace::API_D3D7; } else if (strcmp(optarg, "d3d8") == 0) { api = trace::API_D3D8; @@ -339,6 +381,9 @@ command(int argc, char *argv[]) strcmp(optarg, "d3d11") == 0 || strcmp(optarg, "d3d11_1") == 0) { api = trace::API_DXGI; + } else if (strcmp(optarg, "d2d") == 0 || + strcmp(optarg, "d2d1") == 0) { + api = trace::API_D2D1; } else { std::cerr << "error: unknown API `" << optarg << "`\n"; usage(); diff --git a/cli/cli_trim.cpp b/cli/cli_trim.cpp index ed950e5c..b1e6b485 100644 --- a/cli/cli_trim.cpp +++ b/cli/cli_trim.cpp @@ -29,13 +29,10 @@ #include <limits.h> // for CHAR_MAX #include <getopt.h> -#include <set> - #include "cli.hpp" #include "os_string.hpp" -#include "trace_analyzer.hpp" #include "trace_callset.hpp" #include "trace_parser.hpp" #include "trace_writer.hpp" @@ -52,98 +49,15 @@ usage(void) " -h, --help Show detailed help for trim options and exit\n" " --calls=CALLSET Include specified calls in the trimmed output.\n" " --frames=FRAMESET Include specified frames in the trimmed output.\n" - " --deps Include additional calls to satisfy dependencies\n" - " --no-deps Do not include any more calls than requestd\n" - " --prune Omit calls without side effects from the output\n" - " --no-prune Do not omit any requested calls\n" - " -a, --auto Trim automatically to calls specified in --calls/--frames\n" - " Equivalent to both --deps and --prune\n" - " --exact Trim to exactly the calls specified in --calls/--frames\n" - " Equivalent to both --no-deps and --no-prune\n" - " --print-callset Print the final set of calls included in output\n" - " --trim-spec=SPEC Only performing trimming as described in SPEC\n" - " --thread=THREAD_ID Only retain calls from specified thread\n" - " -o, --output=TRACE_FILE Output trace file\n" - ; -} - -static void -help() -{ - std::cout - << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n" - << synopsis << "\n" - "\n" - " -h, --help Show this help message and exit\n" - "\n" - " --calls=CALLSET Include specified calls in the trimmed output.\n" - " --frames=FRAMESET Include specified frames in the trimmed output.\n" - "\n" - " --deps Perform dependency analysis and include dependent\n" - " calls as needed, (even if those calls were not\n" - " explicitly requested with --calls or --frames).\n" - " (On by default. See --no-deps or --exact)\n" - "\n" - " --no-deps Do not perform dependency analysis. Output will\n" - " not include any additional calls beyond those\n" - " explicitly requested with --calls or --frames).\n" - "\n" - " --prune Omit calls with no side effects, even if the call\n" - " is within the range specified by --calls/--frames.\n" - " (On by default. See --no-prune or --exact)\n" - "\n" - " --no-prune Never omit any calls from the range specified\n" - " --calls/--frames.\n" - "\n" - " -a, --auto Use dependency analysis and pruning\n" - " of uninteresting calls the resulting trace may\n" - " include more and less calls than specified.\n" - " This option is equivalent\n" - " to passing both --deps and --prune and is on by\n" - " default (see --no-deps, --no-prune and --exact)\n" - "\n" - " --exact Trim output to exact the calls or frames\n" - " specified with --calls or --frames.\n" - " This option is equivalent\n" - " to passing both --no-deps and --no-prune.\n" - "\n" - " --print-callset Print to stdout the final set of calls included\n" - " in the trim output. This can be useful for\n" - " tweaking the trimmed callset from --auto on the\n" - " command-line.\n" - " Use --calls=@FILE to read callset from a file.\n" - "\n" - " --trim-spec=SPEC Specifies which classes of calls will be trimmed.\n" - " This option only has an effect if dependency\n" - " analysis is enabled. The argument is a comma-\n" - " separated list of names from the following:\n" - "\n" - " no-side-effects Calls with no side effects\n" - " textures Calls to setup unused textures\n" - " shaders Calls to setup unused shaders\n" - " drawing Calls that draw\n" - "\n" - " The default trim specification includes all of\n" - " the above, (as much as possible will be trimmed).\n" - "\n" " --thread=THREAD_ID Only retain calls from specified thread\n" - "\n" " -o, --output=TRACE_FILE Output trace file\n" - "\n" ; } enum { CALLS_OPT = CHAR_MAX + 1, FRAMES_OPT, - DEPS_OPT, - NO_DEPS_OPT, - PRUNE_OPT, - NO_PRUNE_OPT, - THREAD_OPT, - PRINT_CALLSET_OPT, - TRIM_SPEC_OPT, - EXACT_OPT + THREAD_OPT }; const static char * @@ -154,16 +68,8 @@ longOptions[] = { {"help", no_argument, 0, 'h'}, {"calls", required_argument, 0, CALLS_OPT}, {"frames", required_argument, 0, FRAMES_OPT}, - {"deps", no_argument, 0, DEPS_OPT}, - {"no-deps", no_argument, 0, NO_DEPS_OPT}, - {"prune", no_argument, 0, PRUNE_OPT}, - {"no-prune", no_argument, 0, NO_PRUNE_OPT}, - {"auto", no_argument, 0, 'a'}, - {"exact", no_argument, 0, EXACT_OPT}, {"thread", required_argument, 0, THREAD_OPT}, {"output", required_argument, 0, 'o'}, - {"print-callset", no_argument, 0, PRINT_CALLSET_OPT}, - {"trim-spec", required_argument, 0, TRIM_SPEC_OPT}, {0, 0, 0, 0} }; @@ -180,91 +86,24 @@ struct trim_options { /* Frames to be included in trace. */ trace::CallSet frames; - /* Whether dependency analysis should be performed. */ - bool dependency_analysis; - - /* Whether uninteresting calls should be pruned.. */ - bool prune_uninteresting; - /* Output filename */ std::string output; /* Emit only calls from this thread (-1 == all threads) */ int thread; - - /* Print resulting callset */ - int print_callset; - - /* What kind of trimming to perform. */ - TrimFlags trim_flags; }; static int trim_trace(const char *filename, struct trim_options *options) { - trace::ParseBookmark beginning; trace::Parser p; - TraceAnalyzer analyzer(options->trim_flags); - trace::FastCallSet *required; unsigned frame; - int call_range_first, call_range_last; if (!p.open(filename)) { std::cerr << "error: failed to open " << filename << "\n"; return 1; } - /* Mark the beginning so we can return here for pass 2. */ - p.getBookmark(beginning); - - /* In pass 1, analyze which calls are needed. */ - frame = 0; - trace::Call *call; - while ((call = p.parse_call())) { - - /* There's no use doing any work past the last call and frame - * requested by the user. */ - if ((options->calls.empty() || call->no > options->calls.getLast()) && - (options->frames.empty() || frame > options->frames.getLast())) { - - delete call; - break; - } - - /* If requested, ignore all calls not belonging to the specified thread. */ - if (options->thread != -1 && call->thread_id != options->thread) { - goto NEXT; - } - - /* Also, prune if no side effects (unless the user asked for no pruning. */ - if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) { - goto NEXT; - } - - /* If this call is included in the user-specified call set, - * then require it (and all dependencies) in the trimmed - * output. */ - if (options->calls.contains(*call) || - options->frames.contains(frame, call->flags)) { - - analyzer.require(call); - } - - /* Regardless of whether we include this call or not, we do - * some dependency tracking (unless disabled by the user). We - * do this even for calls we have included in the output so - * that any state updates get performed. */ - if (options->dependency_analysis) { - analyzer.analyze(call); - } - - NEXT: - if (call->flags & trace::CALL_FLAG_END_FRAME) - frame++; - - delete call; - } - /* Prepare output file and writer for output. */ if (options->output.empty()) { os::String base(filename); @@ -279,15 +118,9 @@ trim_trace(const char *filename, struct trim_options *options) return 1; } - /* Reset bookmark for pass 2. */ - p.setBookmark(beginning); - - /* In pass 2, emit the calls that are required. */ - required = analyzer.get_required(); frame = 0; - call_range_first = -1; - call_range_last = -1; + trace::Call *call; while ((call = p.parse_call())) { /* There's no use doing any work past the last call and frame @@ -299,23 +132,21 @@ trim_trace(const char *filename, struct trim_options *options) break; } - if (required->contains(call->no)) { - writer.writeCall(call); + /* If requested, ignore all calls not belonging to the specified thread. */ + if (options->thread != -1 && call->thread_id != options->thread) { + goto NEXT; + } + + /* If this call is included in the user-specified call set, + * then require it (and all dependencies) in the trimmed + * output. */ + if (options->calls.contains(*call) || + options->frames.contains(frame, call->flags)) { - if (options->print_callset) { - if (call_range_first < 0) { - call_range_first = call->no; - printf ("%d", call_range_first); - } else if (call->no != call_range_last + 1) { - if (call_range_last != call_range_first) - printf ("-%d", call_range_last); - call_range_first = call->no; - printf (",%d", call_range_first); - } - call_range_last = call->no; - } + writer.writeCall(call); } + NEXT: if (call->flags & trace::CALL_FLAG_END_FRAME) { frame++; } @@ -323,71 +154,26 @@ trim_trace(const char *filename, struct trim_options *options) delete call; } - if (options->print_callset) { - if (call_range_last != call_range_first) - printf ("-%d\n", call_range_last); - } - std::cerr << "Trimmed trace is available as " << options->output << "\n"; return 0; } static int -parse_trim_spec(const char *trim_spec, TrimFlags *flags) -{ - std::string spec(trim_spec), word; - size_t start = 0, comma = 0; - *flags = 0; - - while (start < spec.size()) { - comma = spec.find(',', start); - - if (comma == std::string::npos) - word = std::string(spec, start); - else - word = std::string(spec, start, comma - start); - - if (strcmp(word.c_str(), "no-side-effects") == 0) - *flags |= TRIM_FLAG_NO_SIDE_EFFECTS; - else if (strcmp(word.c_str(), "textures") == 0) - *flags |= TRIM_FLAG_TEXTURES; - else if (strcmp(word.c_str(), "shaders") == 0) - *flags |= TRIM_FLAG_SHADERS; - else if (strcmp(word.c_str(), "drawing") == 0) - *flags |= TRIM_FLAG_DRAWING; - else { - return 1; - } - - if (comma == std::string::npos) - break; - - start = comma + 1; - } - - return 0; -} - -static int command(int argc, char *argv[]) { struct trim_options options; options.calls = trace::CallSet(trace::FREQUENCY_NONE); options.frames = trace::CallSet(trace::FREQUENCY_NONE); - options.dependency_analysis = true; - options.prune_uninteresting = true; options.output = ""; options.thread = -1; - options.print_callset = 0; - options.trim_flags = -1; int opt; while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { switch (opt) { case 'h': - help(); + usage(); return 0; case CALLS_OPT: options.calls.merge(optarg); @@ -395,42 +181,12 @@ command(int argc, char *argv[]) case FRAMES_OPT: options.frames.merge(optarg); break; - case DEPS_OPT: - options.dependency_analysis = true; - break; - case NO_DEPS_OPT: - options.dependency_analysis = false; - break; - case PRUNE_OPT: - options.prune_uninteresting = true; - break; - case NO_PRUNE_OPT: - options.prune_uninteresting = false; - break; - case 'a': - options.dependency_analysis = true; - options.prune_uninteresting = true; - break; - case EXACT_OPT: - options.dependency_analysis = false; - options.prune_uninteresting = false; - break; case THREAD_OPT: options.thread = atoi(optarg); break; case 'o': options.output = optarg; break; - case PRINT_CALLSET_OPT: - options.print_callset = 1; - break; - case TRIM_SPEC_OPT: - if (parse_trim_spec(optarg, &options.trim_flags)) { - std::cerr << "error: illegal value for trim-spec: " << optarg << "\n"; - std::cerr << "See \"apitrace help trim\" for help.\n"; - return 1; - } - break; default: std::cerr << "error: unexpected option `" << (char)opt << "`\n"; usage(); @@ -460,20 +216,12 @@ command(int argc, char *argv[]) return 1; } - if (options.dependency_analysis) { - std::cerr << - "Note: The dependency analysis in \"apitrace trim\" is still experimental.\n" - " We hope that it will be useful, but it may lead to incorrect results.\n" - " If you find a trace that misbehaves while trimming, please share that\n" - " by sending email to apitrace@lists.freedesktop.org, cworth@cworth.org\n"; - } - return trim_trace(argv[optind], &options); } const Command trim_command = { "trim", synopsis, - help, + usage, command }; diff --git a/cli/cli_trim_auto.cpp b/cli/cli_trim_auto.cpp new file mode 100644 index 00000000..685de21d --- /dev/null +++ b/cli/cli_trim_auto.cpp @@ -0,0 +1,432 @@ +/************************************************************************** + * + * Copyright 2010 VMware, Inc. + * Copyright 2011 Intel corporation + * 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 <sstream> +#include <string.h> +#include <limits.h> // for CHAR_MAX +#include <getopt.h> + +#include <set> + +#include "cli.hpp" + +#include "os_string.hpp" + +#include "trace_callset.hpp" +#include "trace_parser.hpp" +#include "trace_writer.hpp" + +#include "cli_trim_auto_analyzer.hpp" + +static const char *synopsis = "Create a new trace by automatically trimming unecessary calls from an existing trace."; + +static void +usage(void) +{ + std::cout + << "usage: apitrace trim-auto [OPTIONS] TRACE_FILE...\n" + << synopsis << "\n" + "\n" + " -h, --help Show this help message and exit\n" + "\n" + " --calls=CALLSET Include specified calls in the trimmed output.\n" + " --frames=FRAMESET Include specified frames in the trimmed output.\n" + "\n" + " --deps Perform dependency analysis and include dependent\n" + " calls as needed, (even if those calls were not\n" + " explicitly requested with --calls or --frames).\n" + " (On by default. See --no-deps or --exact)\n" + "\n" + " --no-deps Do not perform dependency analysis. Output will\n" + " not include any additional calls beyond those\n" + " explicitly requested with --calls or --frames).\n" + "\n" + " --prune Omit calls with no side effects, even if the call\n" + " is within the range specified by --calls/--frames.\n" + " (On by default. See --no-prune or --exact)\n" + "\n" + " --no-prune Never omit any calls from the range specified\n" + " --calls/--frames.\n" + "\n" + " --print-callset Print to stdout the final set of calls included\n" + " in the trim output. This can be useful for\n" + " tweaking the trimmed callset from --auto on the\n" + " command-line.\n" + " Use --calls=@FILE to read callset from a file.\n" + "\n" + " --trim-spec=SPEC Specifies which classes of calls will be trimmed.\n" + " This option only has an effect if dependency\n" + " analysis is enabled. The argument is a comma-\n" + " separated list of names from the following:\n" + "\n" + " no-side-effects Calls with no side effects\n" + " textures Calls to setup unused textures\n" + " shaders Calls to setup unused shaders\n" + " drawing Calls that draw\n" + "\n" + " The default trim specification includes all of\n" + " the above, (as much as possible will be trimmed).\n" + "\n" + " --thread=THREAD_ID Only retain calls from specified thread\n" + "\n" + " -o, --output=TRACE_FILE Output trace file\n" + "\n" + ; +} + +enum { + CALLS_OPT = CHAR_MAX + 1, + FRAMES_OPT, + DEPS_OPT, + NO_DEPS_OPT, + PRUNE_OPT, + NO_PRUNE_OPT, + THREAD_OPT, + PRINT_CALLSET_OPT, + TRIM_SPEC_OPT +}; + +const static char * +shortOptions = "ho:x"; + +const static struct option +longOptions[] = { + {"help", no_argument, 0, 'h'}, + {"calls", required_argument, 0, CALLS_OPT}, + {"frames", required_argument, 0, FRAMES_OPT}, + {"deps", no_argument, 0, DEPS_OPT}, + {"no-deps", no_argument, 0, NO_DEPS_OPT}, + {"prune", no_argument, 0, PRUNE_OPT}, + {"no-prune", no_argument, 0, NO_PRUNE_OPT}, + {"thread", required_argument, 0, THREAD_OPT}, + {"output", required_argument, 0, 'o'}, + {"print-callset", no_argument, 0, PRINT_CALLSET_OPT}, + {"trim-spec", required_argument, 0, TRIM_SPEC_OPT}, + {0, 0, 0, 0} +}; + +struct stringCompare { + bool operator() (const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +struct trim_auto_options { + /* Calls to be included in trace. */ + trace::CallSet calls; + + /* Frames to be included in trace. */ + trace::CallSet frames; + + /* Whether dependency analysis should be performed. */ + bool dependency_analysis; + + /* Whether uninteresting calls should be pruned.. */ + bool prune_uninteresting; + + /* Output filename */ + std::string output; + + /* Emit only calls from this thread (-1 == all threads) */ + int thread; + + /* Print resulting callset */ + int print_callset; + + /* What kind of trimming to perform. */ + TrimFlags trim_flags; +}; + +static int +trim_trace(const char *filename, struct trim_auto_options *options) +{ + trace::ParseBookmark beginning; + trace::Parser p; + TraceAnalyzer analyzer(options->trim_flags); + trace::FastCallSet *required; + unsigned frame; + int call_range_first, call_range_last; + + if (!p.open(filename)) { + std::cerr << "error: failed to open " << filename << "\n"; + return 1; + } + + /* Mark the beginning so we can return here for pass 2. */ + p.getBookmark(beginning); + + /* In pass 1, analyze which calls are needed. */ + frame = 0; + trace::Call *call; + while ((call = p.parse_call())) { + + /* There's no use doing any work past the last call and frame + * requested by the user. */ + if ((options->calls.empty() || call->no > options->calls.getLast()) && + (options->frames.empty() || frame > options->frames.getLast())) { + + delete call; + break; + } + + /* If requested, ignore all calls not belonging to the specified thread. */ + if (options->thread != -1 && call->thread_id != options->thread) { + goto NEXT; + } + + /* Also, prune if no side effects (unless the user asked for no pruning. */ + if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) { + goto NEXT; + } + + /* If this call is included in the user-specified call set, + * then require it (and all dependencies) in the trimmed + * output. */ + if (options->calls.contains(*call) || + options->frames.contains(frame, call->flags)) { + + analyzer.require(call); + } + + /* Regardless of whether we include this call or not, we do + * some dependency tracking (unless disabled by the user). We + * do this even for calls we have included in the output so + * that any state updates get performed. */ + if (options->dependency_analysis) { + analyzer.analyze(call); + } + + NEXT: + if (call->flags & trace::CALL_FLAG_END_FRAME) + frame++; + + delete call; + } + + /* Prepare output file and writer for output. */ + if (options->output.empty()) { + os::String base(filename); + base.trimExtension(); + + options->output = std::string(base.str()) + std::string("-trim.trace"); + } + + trace::Writer writer; + if (!writer.open(options->output.c_str())) { + std::cerr << "error: failed to create " << options->output << "\n"; + return 1; + } + + /* Reset bookmark for pass 2. */ + p.setBookmark(beginning); + + /* In pass 2, emit the calls that are required. */ + required = analyzer.get_required(); + + frame = 0; + call_range_first = -1; + call_range_last = -1; + while ((call = p.parse_call())) { + + /* There's no use doing any work past the last call and frame + * requested by the user. */ + if ((options->calls.empty() || call->no > options->calls.getLast()) && + (options->frames.empty() || frame > options->frames.getLast())) { + + delete call; + break; + } + + if (required->contains(call->no)) { + writer.writeCall(call); + + if (options->print_callset) { + if (call_range_first < 0) { + call_range_first = call->no; + printf ("%d", call_range_first); + } else if (call->no != call_range_last + 1) { + if (call_range_last != call_range_first) + printf ("-%d", call_range_last); + call_range_first = call->no; + printf (",%d", call_range_first); + } + call_range_last = call->no; + } + } + + if (call->flags & trace::CALL_FLAG_END_FRAME) { + frame++; + } + + delete call; + } + + if (options->print_callset) { + if (call_range_last != call_range_first) + printf ("-%d\n", call_range_last); + } + + std::cerr << "Trimmed trace is available as " << options->output << "\n"; + + return 0; +} + +static int +parse_trim_spec(const char *trim_spec, TrimFlags *flags) +{ + std::string spec(trim_spec), word; + size_t start = 0, comma = 0; + *flags = 0; + + while (start < spec.size()) { + comma = spec.find(',', start); + + if (comma == std::string::npos) + word = std::string(spec, start); + else + word = std::string(spec, start, comma - start); + + if (strcmp(word.c_str(), "no-side-effects") == 0) + *flags |= TRIM_FLAG_NO_SIDE_EFFECTS; + else if (strcmp(word.c_str(), "textures") == 0) + *flags |= TRIM_FLAG_TEXTURES; + else if (strcmp(word.c_str(), "shaders") == 0) + *flags |= TRIM_FLAG_SHADERS; + else if (strcmp(word.c_str(), "drawing") == 0) + *flags |= TRIM_FLAG_DRAWING; + else { + return 1; + } + + if (comma == std::string::npos) + break; + + start = comma + 1; + } + + return 0; +} + +static int +command(int argc, char *argv[]) +{ + struct trim_auto_options options; + + options.calls = trace::CallSet(trace::FREQUENCY_NONE); + options.frames = trace::CallSet(trace::FREQUENCY_NONE); + options.dependency_analysis = true; + options.prune_uninteresting = true; + options.output = ""; + options.thread = -1; + options.print_callset = 0; + options.trim_flags = -1; + + int opt; + while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + return 0; + case CALLS_OPT: + options.calls.merge(optarg); + break; + case FRAMES_OPT: + options.frames.merge(optarg); + break; + case DEPS_OPT: + options.dependency_analysis = true; + break; + case NO_DEPS_OPT: + options.dependency_analysis = false; + break; + case PRUNE_OPT: + options.prune_uninteresting = true; + break; + case NO_PRUNE_OPT: + options.prune_uninteresting = false; + break; + case THREAD_OPT: + options.thread = atoi(optarg); + break; + case 'o': + options.output = optarg; + break; + case PRINT_CALLSET_OPT: + options.print_callset = 1; + break; + case TRIM_SPEC_OPT: + if (parse_trim_spec(optarg, &options.trim_flags)) { + std::cerr << "error: illegal value for trim-spec: " << optarg << "\n"; + std::cerr << "See \"apitrace help trim\" for help.\n"; + return 1; + } + break; + default: + std::cerr << "error: unexpected option `" << (char)opt << "`\n"; + usage(); + return 1; + } + } + + /* If neither of --calls nor --frames was set, default to the + * entire set of calls. */ + if (options.calls.empty() && options.frames.empty()) { + options.calls = trace::CallSet(trace::FREQUENCY_ALL); + } + + if (optind >= argc) { + std::cerr << "error: apitrace trim requires a trace file as an argument.\n"; + usage(); + return 1; + } + + if (argc > optind + 1) { + std::cerr << "error: extraneous arguments:"; + for (int i = optind + 1; i < argc; i++) { + std::cerr << " " << argv[i]; + } + std::cerr << "\n"; + usage(); + return 1; + } + + if (options.dependency_analysis) { + std::cerr << + "Note: The dependency analysis in \"apitrace trim-auto\" is still experimental.\n" + " We hope that it will be useful, but it may lead to incorrect results.\n" + " If you find a trace that misbehaves while trimming, please share that\n" + " by sending email to apitrace@lists.freedesktop.org, cworth@cworth.org\n"; + } + + return trim_trace(argv[optind], &options); +} + +const Command trim_auto_command = { + "trim-auto", + synopsis, + usage, + command +}; diff --git a/cli/trace_analyzer.cpp b/cli/cli_trim_auto_analyzer.cpp index 79f95579..41d90180 100644 --- a/cli/trace_analyzer.cpp +++ b/cli/cli_trim_auto_analyzer.cpp @@ -25,7 +25,7 @@ #include <sstream> -#include "trace_analyzer.hpp" +#include "cli_trim_auto_analyzer.hpp" #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define STRNCMP_LITERAL(var, literal) strncmp((var), (literal), sizeof (literal) -1) diff --git a/cli/trace_analyzer.hpp b/cli/cli_trim_auto_analyzer.hpp index c344fb6a..c344fb6a 100644 --- a/cli/trace_analyzer.hpp +++ b/cli/cli_trim_auto_analyzer.hpp diff --git a/cli/pickle.hpp b/cli/pickle.hpp index 59af6a7b..eff36e0d 100644 --- a/cli/pickle.hpp +++ b/cli/pickle.hpp @@ -27,12 +27,12 @@ * Python pickle writer */ -#ifndef _PICKLE_HPP_ -#define _PICKLE_HPP_ +#pragma once #include <assert.h> #include <stddef.h> #include <stdint.h> +#include <wchar.h> #include <ostream> #include <string> @@ -162,6 +162,23 @@ public: os.put(TUPLE); } + inline void beginTuple(unsigned length) { + if (length >= 4) { + os.put(MARK); + } + } + + inline void endTuple(unsigned length) { + static const Opcode ops[4] = { + EMPTY_TUPLE, + TUPLE1, + TUPLE2, + TUPLE3, + }; + Opcode op = length < 4 ? ops[length] : TUPLE; + os.put(op); + } + inline void writeString(const char *s, size_t length) { if (!s) { writeNone(); @@ -194,6 +211,34 @@ public: writeString(s.c_str(), s.size()); } + inline void writeWString(const wchar_t *s, size_t length) { + if (!s) { + writeNone(); + return; + } + + /* FIXME: emit UTF-8 */ + os.put(BINUNICODE); + putInt32(length); + for (size_t i = 0; i < length; ++i) { + wchar_t wc = s[i]; + char c = wc >= 0 && wc < 0x80 ? (char)wc : '?'; + os.put(c); + } + + os.put(BINPUT); + os.put(1); + } + + inline void writeWString(const wchar_t *s) { + if (!s) { + writeNone(); + return; + } + + writeWString(s, wcslen(s)); + } + inline void writeNone(void) { os.put(NONE); } @@ -255,7 +300,7 @@ public: char c[8]; } u; - assert(sizeof u.f == sizeof u.c); + static_assert(sizeof u.f == sizeof u.c, "double is not 8 bytes"); u.f = f; os.put(BINFLOAT); @@ -279,6 +324,16 @@ public: os.put(REDUCE); } + inline void writePointer(unsigned long long addr) { + os.put(GLOBAL); + os << "unpickle\nPointer\n"; + os.put(BINPUT); + os.put(1); + writeInt(addr); + os.put(TUPLE1); + os.put(REDUCE); + } + protected: inline void putInt16(uint16_t i) { os.put( i & 0xff); @@ -330,4 +385,3 @@ protected: } }; -#endif /* _Pickle_HPP_ */ |