Initial commit
This commit is contained in:
26
CMakeLists.txt
Normal file
26
CMakeLists.txt
Normal 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
10
build_options.h.in
Normal 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
106
dram_tracer.cpp
Normal 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
69
dram_tracer.h
Normal 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
5
dram_tracer_create.h
Normal 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
44
dram_tracer_launcher.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user