Skip to content

Commit

Permalink
i#3995: tracing windows at irregular instruction intervals (#6927)
Browse files Browse the repository at this point in the history
Currently we can only perform window-tracing at regular intervals
on binaries using the -trace_after_instrs, -trace_for_instrs,
-retrace_every_instrs options.
Sometimes it's useful to trace windows of different sizes at irregular
instruction intervals (e.g., for tracing simpoints).

We do so introducing a new option:
```
-trace_instr_intervals_file path/to/instr/intervals.csv
```
which takes a CSV file where every line has <start,duration> pairs
representing intervals in terms of number of instructions.

The implementation relies on the same window-tracing mechanism
used by -trace_after_instrs, -trace_for_instrs, -retrace_every_instrs.
We add a level of indirection to obtain the values of these options
through
the `get_initial_no_trace_for_instrs_value()`,
`get_current_trace_for_instrs_value()`,
and `get_current_no_trace_for_instrs_value()` functions respectively.
This allows us to change the returned value of these options depending
on
the window we are tracing at that point.
We do so using a global, read-only vector `irregular_windows_list`
containing the
trace_for_instrs/no_trace_for_instrs window, and an atomic (also global)
index
`irregular_window_idx` that we increment every time we finish tracing a
window
to go to the next one in the vector.

We add a new end-to-end test: tool.drcachesim.irregular-windows-simple.

Issue #3995
  • Loading branch information
edeiana authored Sep 17, 2024
1 parent f1b2d54 commit ce56453
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 21 deletions.
3 changes: 3 additions & 0 deletions api/docs/release.dox
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ Further non-compatibility-affecting changes include:
input_workload_t::times_of_interest to the drmemtrace scheduler.
- Added v2p_reader_t to parse a virtual-to-physical mapping in textproto format and
v2p_info_t to hold that mapping in memory.
- Added -trace_instr_intervals_file option to the drmemtrace trace analysis tools
framework. The file must be in CSV format containing a <start,duration> tracing
interval per line where start and duration are expressed in number of instructions.

**************************************************
<hr>
Expand Down
7 changes: 7 additions & 0 deletions clients/drcachesim/common/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,13 @@ droption_t<bytesize_t> op_retrace_every_instrs(
"TRACE_MARKER_TYPE_WINDOW_ID markers. For -offline traces, each window is placed "
"into its own separate set of output files, unless -no_split_windows is set.");

droption_t<std::string> op_trace_instr_intervals_file(
DROPTION_SCOPE_CLIENT, "trace_instr_intervals_file", "",
"File containing instruction intervals to trace.",
"File containing instruction intervals to trace in csv format. "
"Intervals are specified as a <start, duration> pair per line. Example in: "
"clients/drcachesim/tests/instr_intervals_example.csv");

droption_t<bool> op_split_windows(
DROPTION_SCOPE_CLIENT, "split_windows", true,
"Whether -retrace_every_instrs should write separate files",
Expand Down
1 change: 1 addition & 0 deletions clients/drcachesim/common/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ extern dynamorio::droption::droption_t<dynamorio::droption::bytesize_t>
op_trace_for_instrs;
extern dynamorio::droption::droption_t<dynamorio::droption::bytesize_t>
op_retrace_every_instrs;
extern dynamorio::droption::droption_t<std::string> op_trace_instr_intervals_file;
extern dynamorio::droption::droption_t<bool> op_split_windows;
extern dynamorio::droption::droption_t<dynamorio::droption::bytesize_t>
op_exit_after_tracing;
Expand Down
3 changes: 3 additions & 0 deletions clients/drcachesim/tests/instr_intervals_example.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
100,10000,
40000,20000,
100000,1000,
17 changes: 17 additions & 0 deletions clients/drcachesim/tests/irregular-windows-simple.templatex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Hit delay threshold: enabling tracing.
Hit tracing window #0 limit: disabling tracing.
Hit retrace threshold: enabling tracing for window #1.
Hit tracing window #1 limit: disabling tracing.
Hit retrace threshold: enabling tracing for window #2.
Hit tracing window #2 limit: disabling tracing.
.*
---- <application exited with code 0> ----
Basic counts tool results:
.*
Total windows: [0-9]*
Window #0:
.*
Window #1:
.*
Window #2:
.*
253 changes: 243 additions & 10 deletions clients/drcachesim/tracer/instr_counter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@

#include "instr_counter.h"

#include <limits.h>
#include <limits>
#include <stddef.h>

#include <atomic>
#include <cstdint>

// These libraries are safe to use during initialization only.
// See api/docs/deployment.dox sec_static_DR.
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

#include "dr_api.h"
#include "dr_defines.h"
#include "drmgr.h"
#include "drreg.h"
#include "options.h"
Expand Down Expand Up @@ -72,28 +81,102 @@ static uint64 instr_count;
// but to avoid the complexity of different instrumentation we need a threshold.
#define DELAY_FOREVER_THRESHOLD (1024 * 1024 * 1024)

static std::atomic<bool> reached_trace_after_instrs;
static std::atomic<bool> reached_trace_after_instrs(false);
std::atomic<uint64> retrace_start_timestamp;

static std::atomic<uint> irregular_window_idx(0);
static drvector_t irregular_windows_list;
static uint num_irregular_windows = 0;

void
delete_instr_window_lists()
{
if (num_irregular_windows == 0)
return;
if (!drvector_delete(&irregular_windows_list))
FATAL("Fatal error: irregular_windows_list global vector was not deleted.");
}

void
maybe_increment_irregular_window_index()
{
if (irregular_window_idx.load(std::memory_order_acquire) < num_irregular_windows)
irregular_window_idx.fetch_add(1, std::memory_order_release);
}

struct irregular_window_t {
uint64 no_trace_for_instrs = 0;
uint64 trace_for_instrs = 0;
};

uint64
get_initial_no_trace_for_instrs_value()
{
if (op_trace_after_instrs.get_value() > 0)
return (uint64)op_trace_after_instrs.get_value();
if (num_irregular_windows > 0) {
void *irregular_window_ptr = drvector_get_entry(&irregular_windows_list, 0);
if (irregular_window_ptr == nullptr)
FATAL("Fatal error: irregular window not found at index 0.");
return ((irregular_window_t *)irregular_window_ptr)->no_trace_for_instrs;
}
return 0;
}

uint64
get_current_trace_for_instrs_value()
{
if (op_trace_for_instrs.get_value() > 0)
return (uint64)op_trace_for_instrs.get_value();
if (num_irregular_windows > 0) {
uint i = irregular_window_idx.load(std::memory_order_acquire);
void *irregular_window_ptr = drvector_get_entry(&irregular_windows_list, i);
if (irregular_window_ptr == nullptr)
FATAL("Fatal error: irregular window not found at index %d.", i);
return ((irregular_window_t *)irregular_window_ptr)->trace_for_instrs;
}
return 0;
}

// This function returns the no_trace interval for all windows except the first one.
// The no_trace interval for the first window is returned by
// get_initial_no_trace_for_instrs_value().
uint64
get_current_no_trace_for_instrs_value()
{
if (op_retrace_every_instrs.get_value() > 0)
return (uint64)op_retrace_every_instrs.get_value();
if (num_irregular_windows > 0) {
uint i = irregular_window_idx.load(std::memory_order_acquire);
void *irregular_window_ptr = drvector_get_entry(&irregular_windows_list, i);
if (irregular_window_ptr == nullptr)
FATAL("Fatal error: irregular window not found at index %d.", i);
return ((irregular_window_t *)irregular_window_ptr)->no_trace_for_instrs;
}
return 0;
}

static bool
has_instr_count_threshold_to_enable_tracing()
{
if (op_trace_after_instrs.get_value() > 0 &&
if (get_initial_no_trace_for_instrs_value() > 0 &&
!reached_trace_after_instrs.load(std::memory_order_acquire))
return true;
if (op_retrace_every_instrs.get_value() > 0)
if (get_current_no_trace_for_instrs_value() > 0)
return true;
return false;
}

static uint64
instr_count_threshold()
{
if (op_trace_after_instrs.get_value() > 0 &&
uint64 initial_no_trace_for_instrs = get_initial_no_trace_for_instrs_value();
if (initial_no_trace_for_instrs > 0 &&
!reached_trace_after_instrs.load(std::memory_order_acquire))
return op_trace_after_instrs.get_value();
if (op_retrace_every_instrs.get_value() > 0)
return op_retrace_every_instrs.get_value();
return initial_no_trace_for_instrs;
uint64 current_no_trace_for_instrs = get_current_no_trace_for_instrs_value();
if (current_no_trace_for_instrs > 0)
return current_no_trace_for_instrs;
return DELAY_FOREVER_THRESHOLD;
}

Expand Down Expand Up @@ -128,7 +211,7 @@ hit_instr_count_threshold(app_pc next_pc)
dr_mutex_unlock(mutex);
return;
}
if (op_trace_after_instrs.get_value() > 0 &&
if (get_initial_no_trace_for_instrs_value() > 0 &&
!reached_trace_after_instrs.load(std::memory_order_acquire)) {
NOTIFY(0, "Hit delay threshold: enabling tracing.\n");
retrace_start_timestamp.store(instru_t::get_timestamp());
Expand Down Expand Up @@ -347,9 +430,159 @@ event_inscount_thread_init(void *drcontext)
DELAY_COUNTDOWN_UNIT;
}

// Represents an interval as a <start,duration> pair in terms of number of instructions.
struct instr_interval_t {
instr_interval_t(uint64 start, uint64 duration)
: start(start)
, duration(duration)
{
}

uint64 start = 0;
uint64 duration = 0;
};

// Function to order instruction intervals by start time in ascending order.
static bool
cmp_start_instr(const instr_interval_t &l, const instr_interval_t &r)
{
return l.start < r.start;
}

static std::vector<instr_interval_t>
parse_instr_intervals_file(std::string path_to_file)
{
std::vector<instr_interval_t> instr_intervals;
std::ifstream file(path_to_file);
if (!file.is_open())
FATAL("Fatal error: failed to open file %s.\n", path_to_file.c_str());

std::string line;
while (std::getline(file, line)) {
// Ignore empty lines, if any.
if (line.empty())
continue;
std::stringstream ss(line);
std::string elem;
if (!std::getline(ss, elem, ','))
FATAL("Fatal error: start instruction not found.\n");
uint64 start = std::stoull(elem);
if (!std::getline(ss, elem, ','))
FATAL("Fatal error: instruction duration not found.\n");
uint64 duration = std::stoull(elem);
// Ignore the remaining comma-separated elements, if any.

instr_intervals.emplace_back(start, duration);
}
file.close();

if (instr_intervals.empty()) {
FATAL("Fatal error: -trace_instr_intervals_file %s contains no intervals.\n",
path_to_file.c_str());
}

// Enforcing constraints on intervals:
// 1) They need to be ordered by start time.
std::sort(instr_intervals.begin(), instr_intervals.end(), cmp_start_instr);

// 2) Overlapping intervals must be merged.
std::vector<instr_interval_t> instr_intervals_merged;
instr_intervals_merged.emplace_back(instr_intervals[0]);
for (instr_interval_t &interval : instr_intervals) {
uint64 end = interval.start + interval.duration;
instr_interval_t &last_interval = instr_intervals_merged.back();
uint64 last_end = last_interval.start + last_interval.duration;
if (interval.start <= last_end) {
uint64 max_end = last_end > end ? last_end : end;
last_interval.duration = max_end - last_interval.start;
} else {
instr_intervals_merged.emplace_back(interval);
}
}

return instr_intervals_merged;
}

static void
free_trace_window_entry(void *entry)
{
dr_global_free(entry, sizeof(irregular_window_t));
}

// Transforms instruction intervals from <start,duration> pairs to trace and no_trace
// number of instructions. Has the side effect of populating the read-only, global vector
// irregular_windows_list, and the global num_irregular_windows.
static void
compute_irregular_trace_windows(std::vector<instr_interval_t> &instr_intervals)
{
if (instr_intervals.empty())
return;

uint num_intervals = (uint)instr_intervals.size();
num_irregular_windows = num_intervals + 1;

// We don't need to synch accesses because this global vector is initialized here and
// then it's only read.
drvector_init(&irregular_windows_list, num_irregular_windows, /* synch = */ false,
free_trace_window_entry);

irregular_window_t *irregular_window_ptr =
(irregular_window_t *)dr_global_alloc(sizeof(irregular_window_t));
irregular_window_ptr->no_trace_for_instrs = instr_intervals[0].start;
irregular_window_ptr->trace_for_instrs = instr_intervals[0].duration;
drvector_set_entry(&irregular_windows_list, 0, irregular_window_ptr);

for (uint i = 1; i < num_intervals; ++i) {
uint64 no_trace_for_instrs = instr_intervals[i].start -
(instr_intervals[i - 1].start + instr_intervals[i - 1].duration);
uint64 trace_for_instrs = instr_intervals[i].duration;

irregular_window_ptr =
(irregular_window_t *)dr_global_alloc(sizeof(irregular_window_t));
irregular_window_ptr->no_trace_for_instrs = no_trace_for_instrs;
irregular_window_ptr->trace_for_instrs = trace_for_instrs;
drvector_set_entry(&irregular_windows_list, i, irregular_window_ptr);
}

// Last window. We are done setting all the irregular windows of the csv file. We
// generate one last non-tracing window in case the target program is still running.
// If the user wants to finish with a tracing window, the last window in the csv file
// must have a duration long enough to cover the end of the program.
irregular_window_ptr =
(irregular_window_t *)dr_global_alloc(sizeof(irregular_window_t));
// DELAY_FOREVER_THRESHOLD might be too small for long traces, but it doesn't matter
// because we trace_for_instrs = 0, so no window is created anyway.
irregular_window_ptr->no_trace_for_instrs = DELAY_FOREVER_THRESHOLD;
irregular_window_ptr->trace_for_instrs = 0;
drvector_set_entry(&irregular_windows_list, num_intervals, irregular_window_ptr);
}

static void
init_irregular_trace_windows()
{
std::string path_to_file = op_trace_instr_intervals_file.get_value();
if (!path_to_file.empty()) {
// Other instruction interval options (i.e., -trace_after_instrs,
// -trace_for_instrs, -retrace_every_instrs) are not compatible with
// -trace_instr_intervals_file. Check that they are not set.
if (op_trace_after_instrs.get_value() > 0 ||
op_trace_for_instrs.get_value() > 0 ||
op_retrace_every_instrs.get_value() > 0) {
FATAL("Fatal error: -trace_instr_intervals_file cannot be used with "
"-trace_after_instrs, -trace_for_instrs, or -retrace_every_instrs.\n");
}
// Parse intervals file.
std::vector<instr_interval_t> instr_intervals =
parse_instr_intervals_file(op_trace_instr_intervals_file.get_value());
// Populate the irregular_windows_list global vector and num_irregular_windows.
compute_irregular_trace_windows(instr_intervals);
}
}

void
event_inscount_init()
{
init_irregular_trace_windows();
DR_ASSERT(std::atomic_is_lock_free(&reached_trace_after_instrs));
}

Expand Down
15 changes: 15 additions & 0 deletions clients/drcachesim/tracer/instr_counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ namespace drmemtrace {

extern std::atomic<uint64> retrace_start_timestamp;

void
delete_instr_window_lists();

void
maybe_increment_irregular_window_index();

uint64
get_initial_no_trace_for_instrs_value();

uint64
get_current_trace_for_instrs_value();

uint64
get_current_no_trace_for_instrs_value();

dr_emit_flags_t
event_inscount_bb_analysis(void *drcontext, void *tag, instrlist_t *bb, bool for_trace,
bool translating, void **user_data);
Expand Down
Loading

0 comments on commit ce56453

Please sign in to comment.