Files
dram_tracer/dram_tracer.cpp
2022-04-29 08:43:45 +02:00

107 lines
4.3 KiB
C++

#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;
}