commit 9ea37507a7842c69f83c59cf3ca7e6575148e148 Author: Derek Christ Date: Wed Apr 6 20:49:26 2022 +0200 Initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..444b78c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required (VERSION 3.10) +project (dram_tracer) + +set(CMAKE_CXX_STANDARD 17) + +option(BINARY_OUTPUT "Whether the output trace is in binary or text form." OFF) +option(DEBUG_OUTPUT "Debug output on each data reference." OFF) + +find_package(DynamoRIO REQUIRED 9.0) +find_package(fmt REQUIRED) + +configure_file("build_options.h.in" "include/build_options.h") + +add_library(dram_tracer STATIC dram_tracer.cpp) + +target_include_directories(dram_tracer PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/include") +target_link_libraries(dram_tracer drmemtrace_analyzer fmt::fmt) +use_DynamoRIO_drmemtrace(dram_tracer) + +add_executable(dram_tracer_launcher dram_tracer_launcher.cpp) +target_link_libraries(dram_tracer_launcher dram_tracer drmemtrace_histogram z) + +use_DynamoRIO_extension(dram_tracer_launcher droption) + +# Needed for dr_frontend.h +configure_DynamoRIO_main_headers() diff --git a/build_options.h.in b/build_options.h.in new file mode 100644 index 0000000..a16b4b8 --- /dev/null +++ b/build_options.h.in @@ -0,0 +1,10 @@ +#pragma once + +#cmakedefine01 BINARY_OUTPUT +#cmakedefine01 DEBUG_OUTPUT + +struct BuildOptions { + constexpr static bool binary_output = static_cast(BINARY_OUTPUT); + constexpr static bool debug_output = static_cast(DEBUG_OUTPUT); +}; +inline constexpr BuildOptions build_options; diff --git a/dram_tracer.cpp b/dram_tracer.cpp new file mode 100644 index 0000000..97c577b --- /dev/null +++ b/dram_tracer.cpp @@ -0,0 +1,106 @@ +#include "dram_tracer.h" +#include "build_options.h" +#include "dram_tracer_create.h" +#include "drmemtrace/trace_entry.h" + +#include +#include +#include +#include +#include +#include + +analysis_tool_t *dram_tracer_create(const std::string &output_dir) +{ + return new dram_tracer(output_dir); +} + +dram_tracer::dram_tracer(std::string_view output_dir) : output_dir(output_dir) +{} + +bool dram_tracer::process_memref(const memref_t &memref) +{ + // For online tracing we do not know beforehand how many threads there will be, + // so we need to keep track of them dynamically. + if (memref.marker.type == TRACE_TYPE_MARKER && memref.marker.marker_type == TRACE_MARKER_TYPE_TIMESTAMP && + memref.marker.tid != 0) { + + tid_t tid = memref.marker.tid; + + if (thread_ids.find(tid) == thread_ids.end()) { + // Store all thread ids of the process in a set. + thread_ids.emplace(memref.marker.tid); + + // Create a new trace file for the thread. + if (output_dir.empty()) + output_dir = fmt::format("dramsys.trace.{}.dir", memref.marker.pid); + + std::filesystem::create_directory(output_dir); + + if constexpr (build_options.binary_output) { + trace_files.try_emplace( + tid, output_dir / fmt::format("dramsys.trace.{}.{}.bin", memref.marker.pid, tid), std::ios::binary); + } else { + trace_files.try_emplace( + tid, output_dir / fmt::format("dramsys.trace.{}.{}.trace", memref.marker.pid, tid), std::ios::out); + + // Print a header as comment + trace_files[tid] << "# instruction count,read/write,data size,data address\n" + << "# \n"; + } + } + } + + if (type_is_instr(memref.instr.type)) { + ++instruction_counts[memref.instr.tid]; + } else if (memref.data.type == TRACE_TYPE_READ || memref.data.type == TRACE_TYPE_WRITE) { + addr_t address = memref.data.addr; + size_t size = memref.data.size; + tid_t tid = memref.data.tid; + + // One instruction can issue multiple data accesses, for example a read and a write. + // Do not substract 1 in this case. + instr_count_t instruction_count = instruction_counts[memref.data.tid] == 0 + ? instruction_counts[memref.data.tid] + : instruction_counts[memref.data.tid] - 1; + + if constexpr (build_options.debug_output) { + std::string_view access_string = memref.data.type == TRACE_TYPE_READ ? "Read" : "Write"; + fmt::print("[{}] {} at address 0x{:x} with size {} after {} instructions.\n", memref.data.tid, + access_string, address, size, instruction_count); + } + + if constexpr (build_options.binary_output) { + trace_files[tid] << BinaryTraceEntry( + BinaryTraceEntry::DataRef(instruction_count, memref.data.type == TRACE_TYPE_WRITE, size, address)); + } else { + std::string_view access_type = memref.data.type == TRACE_TYPE_READ ? "r" : "w"; + trace_files[tid] << fmt::format("{},{},{},{:x}\n", instruction_count, access_type, size, address); + } + + instruction_counts[memref.data.tid] = 0; + ++data_references; + } else if (memref.marker.type == TRACE_TYPE_MARKER && memref.marker.marker_type == TRACE_MARKER_TYPE_TIMESTAMP) { + // Each 32KB block of thread data has a timestamp, allowing reconstructing + // the thread interleaving at that granularity. + // We place the thread specific timestamp into the trace so that the thread player + // can wait on other threads to progress at least to this point in time. + + uint64_t timestamp = memref.marker.marker_value; + tid_t tid = memref.marker.tid; + + if constexpr (build_options.binary_output) { + trace_files[tid] << BinaryTraceEntry(BinaryTraceEntry::Timestamp(timestamp)); + } else { + trace_files[tid] << fmt::format("<{}>\n", timestamp); + } + } + + return true; +} + +bool dram_tracer::print_results() +{ + fmt::print("Traced {} data references in {} threads.\n", data_references, thread_ids.size()); + return true; +} diff --git a/dram_tracer.h b/dram_tracer.h new file mode 100644 index 0000000..7b1f255 --- /dev/null +++ b/dram_tracer.h @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include + +class dram_tracer : public analysis_tool_t +{ +public: + dram_tracer(std::string_view output_dir = ""); + + bool process_memref(const memref_t &memref) override; + bool print_results() override; + +private: + using tid_t = uint64_t; + using instr_count_t = unsigned int; + + // Used for the binary format. + union BinaryTraceEntry + { + enum class Type + { + DataRef, + Timestamp + }; + + struct DataRef + { + const Type type = Type::DataRef; + instr_count_t instruction_count; + bool write; + size_t data_size; + uintptr_t data_address; + + DataRef(instr_count_t instruction_count, bool write, size_t data_size, uintptr_t data_address) + : instruction_count(instruction_count), write(write), data_size(data_size), data_address(data_address) + {} + } data_ref; + + struct Timestamp + { + const Type type = Type::Timestamp; + uint64_t timestamp; + + Timestamp(uint64_t timestamp) : timestamp(timestamp) + {} + } timestamp; + + friend std::ofstream &operator<<(std::ofstream &out, const BinaryTraceEntry &entry) + { + out.write((char *)&entry, sizeof entry); + return out; + } + + BinaryTraceEntry(DataRef data_ref) : data_ref(data_ref) + {} + BinaryTraceEntry(Timestamp timestamp) : timestamp(timestamp) + {} + }; + + uint64_t data_references = 0; + std::set thread_ids; + std::unordered_map instruction_counts; + std::unordered_map trace_files; + + std::filesystem::path output_dir; +}; \ No newline at end of file diff --git a/dram_tracer_create.h b/dram_tracer_create.h new file mode 100644 index 0000000..c1fba3c --- /dev/null +++ b/dram_tracer_create.h @@ -0,0 +1,5 @@ +#include + +class analysis_tool_t; + +analysis_tool_t *dram_tracer_create(const std::string &output_dir = ""); diff --git a/dram_tracer_launcher.cpp b/dram_tracer_launcher.cpp new file mode 100644 index 0000000..4896744 --- /dev/null +++ b/dram_tracer_launcher.cpp @@ -0,0 +1,44 @@ +#include "dram_tracer_create.h" + +#include +#include +#include +#include +#include +#include + +static droption_t + op_trace_dir(DROPTION_SCOPE_FRONTEND, "trace_dir", "", "[Required] Trace input directory", + "Specifies the directory containing the trace files to be analyzed."); + +static droption_t + op_output_dir(DROPTION_SCOPE_FRONTEND, "output_dir", "", "Trace output directory", + "Specifies the directory of the trace files to output."); + +int _tmain(int argc, const TCHAR *targv[]) +{ + // Convert to UTF-8 if necessary + char **argv; + drfront_status_t sc = drfront_convert_args(targv, &argv, argc); + if (sc != DRFRONT_SUCCESS) { + fmt::print("Failed to process args: {}", sc); + exit(1); + } + + std::string parse_err; + if (!droption_parser_t::parse_argv(DROPTION_SCOPE_FRONTEND, argc, (const char **)argv, &parse_err, NULL) || + op_trace_dir.get_value().empty()) { + fmt::print("Usage error: {}\nUsage:\n{}", parse_err.c_str(), + droption_parser_t::usage_short(DROPTION_SCOPE_ALL).c_str()); + exit(1); + } + + std::array tools{dram_tracer_create(op_output_dir.get_value())}; + + analyzer_t analyzer(op_trace_dir.get_value(), tools.data(), (int)tools.size()); + analyzer.run(); + analyzer.print_stats(); + + for (auto tool : tools) + delete tool; +}