Initial commit

This commit is contained in:
2022-04-06 20:49:26 +02:00
commit 9ea37507a7
6 changed files with 260 additions and 0 deletions

26
CMakeLists.txt Normal file
View File

@@ -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()

10
build_options.h.in Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#cmakedefine01 BINARY_OUTPUT
#cmakedefine01 DEBUG_OUTPUT
struct BuildOptions {
constexpr static bool binary_output = static_cast<bool>(BINARY_OUTPUT);
constexpr static bool debug_output = static_cast<bool>(DEBUG_OUTPUT);
};
inline constexpr BuildOptions build_options;

106
dram_tracer.cpp Normal file
View File

@@ -0,0 +1,106 @@
#include "dram_tracer.h"
#include "build_options.h"
#include "dram_tracer_create.h"
#include "drmemtrace/trace_entry.h"
#include <cstdint>
#include <filesystem>
#include <fmt/core.h>
#include <iostream>
#include <tuple>
#include <utility>
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"
<< "# <timestamp>\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;
}

69
dram_tracer.h Normal file
View File

@@ -0,0 +1,69 @@
#include <cstring>
#include <drmemtrace/analysis_tool.h>
#include <filesystem>
#include <fstream>
#include <set>
#include <unordered_map>
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<tid_t> thread_ids;
std::unordered_map<tid_t, instr_count_t> instruction_counts;
std::unordered_map<tid_t, std::ofstream> trace_files;
std::filesystem::path output_dir;
};

5
dram_tracer_create.h Normal file
View File

@@ -0,0 +1,5 @@
#include <string>
class analysis_tool_t;
analysis_tool_t *dram_tracer_create(const std::string &output_dir = "");

44
dram_tracer_launcher.cpp Normal file
View File

@@ -0,0 +1,44 @@
#include "dram_tracer_create.h"
#include <dr_frontend.h>
#include <drmemtrace/analyzer.h>
#include <drmemtrace/histogram_create.h>
#include <droption.h>
#include <fmt/core.h>
#include <iostream>
static droption_t<std::string>
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<std::string>
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<analysis_tool_t *, 1> 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;
}