Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion clients/drcachesim/common/memtrace_stream.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2022-2024 Google, Inc. All rights reserved.
* Copyright (c) 2022-2025 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -112,6 +112,10 @@ class memtrace_stream_t {
* inputs from being scheduled onto an output.
*/
SCHED_STAT_HIT_OUTPUT_LIMIT,
/**
* Counts the instances when the context switch sequence was injected.
*/
SCHED_STAT_SWITCH_SEQUENCE_INJECTIONS,
/** Count of statistic types. */
SCHED_STAT_TYPE_COUNT,
};
Expand Down
9 changes: 6 additions & 3 deletions clients/drcachesim/scheduler/scheduler_dynamic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ scheduler_dynamic_tmpl_t<RecordType, ReaderType>::check_for_input_switch(
// boundaries so we live with those being before the switch.
// XXX: Once we insert kernel traces, we may have to try harder
// to stop before the post-syscall records.
if (this->record_type_is_instr_boundary(record, outputs_[output].last_record) &&
if (this->record_type_is_instr_boundary(record,
outputs_[output].last_record.record) &&
// We want to delay the context switch until after the injected syscall trace.
!outputs_[output].in_syscall_code) {
if (input->switch_to_input != sched_type_t::INVALID_INPUT_ORDINAL) {
Expand Down Expand Up @@ -507,7 +508,8 @@ scheduler_dynamic_tmpl_t<RecordType, ReaderType>::check_for_input_switch(
this->process_marker(*input, output, marker_type, marker_value);
}
if (options_.quantum_unit == sched_type_t::QUANTUM_INSTRUCTIONS &&
this->record_type_is_instr_boundary(record, outputs_[output].last_record) &&
this->record_type_is_instr_boundary(record,
outputs_[output].last_record.record) &&
!outputs_[output].in_context_switch_code) {
++input->instrs_in_quantum;
if (input->instrs_in_quantum > options_.quantum_duration_instrs) {
Expand Down Expand Up @@ -546,7 +548,8 @@ scheduler_dynamic_tmpl_t<RecordType, ReaderType>::check_for_input_switch(
// We only switch on instruction boundaries. We could possibly switch
// in between (e.g., scatter/gather long sequence of reads/writes) by
// setting input->switching_pre_instruction.
this->record_type_is_instr_boundary(record, outputs_[output].last_record)) {
this->record_type_is_instr_boundary(record,
outputs_[output].last_record.record)) {
if (outputs_[output].in_syscall_code) {
// XXX: Maybe this should be printed only once per-syscall-instance to
// reduce log spam.
Expand Down
194 changes: 124 additions & 70 deletions clients/drcachesim/scheduler/scheduler_impl.cpp

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions clients/drcachesim/scheduler/scheduler_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ template <typename RecordType, typename ReaderType> class scheduler_impl_tmpl_t
protected:
typedef speculator_tmpl_t<RecordType> spec_type_t;

struct cached_record_t {
cached_record_t(RecordType record, bool is_real)
: record(record)
, is_real(is_real)
{
}
RecordType record;
bool is_real = false;
};
struct input_info_t {
input_info_t()
: lock(new mutex_dbg_owned)
Expand Down Expand Up @@ -195,8 +204,11 @@ template <typename RecordType, typename ReaderType> class scheduler_impl_tmpl_t
// If non-empty these records should be returned before incrementing the reader.
// This is used for read-ahead and inserting synthetic records.
// We use a deque so we can iterate over it.
std::deque<RecordType> queue;

std::deque<cached_record_t> queue;
uint64_t real_records_in_queue = 0;
bool cur_from_queue;
bool is_cur_record_real;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an initial value. Ditto for line 210 (I think it's set in the code before read so no current bug I don't think, but safest to have an initial value).

std::set<output_ordinal_t> binding;
int priority = 0;
std::vector<range_t> regions_of_interest;
Expand Down Expand Up @@ -406,8 +418,8 @@ template <typename RecordType, typename ReaderType> class scheduler_impl_tmpl_t
scale_blocked_time(uint64_t initial_time) const;

void
update_switch_stats(output_ordinal_t output, input_ordinal_t prev_input,
input_ordinal_t new_input);
on_context_switch(output_ordinal_t output, input_ordinal_t prev_input,
input_ordinal_t new_input);

///
///////////////////////////////////////////////////////////////////////////
Expand All @@ -423,7 +435,7 @@ template <typename RecordType, typename ReaderType> class scheduler_impl_tmpl_t
output_info_t(scheduler_impl_tmpl_t<RecordType, ReaderType> *scheduler_impl,
output_ordinal_t ordinal,
typename spec_type_t::speculator_flags_t speculator_flags,
int rand_seed, RecordType last_record_init, int verbosity = 0)
int rand_seed, cached_record_t last_record_init, int verbosity = 0)
: self_stream(scheduler_impl, ordinal, verbosity)
, stream(&self_stream)
, ready_queue(rand_seed)
Expand Down Expand Up @@ -467,7 +479,7 @@ template <typename RecordType, typename ReaderType> class scheduler_impl_tmpl_t
// while this field holds the instruction's start PC. The use case is for
// queueing a read-ahead instruction record for start_speculation().
addr_t prev_speculate_pc = 0;
RecordType last_record; // Set to TRACE_TYPE_INVALID in constructor.
cached_record_t last_record; // Set to TRACE_TYPE_INVALID in constructor.
// A list of schedule segments. During replay, this is read by other threads,
// but it is only written at init time.
std::vector<schedule_record_t> record;
Expand Down Expand Up @@ -687,7 +699,7 @@ template <typename RecordType, typename ReaderType> class scheduler_impl_tmpl_t
// If STATUS_SKIPPED or STATUS_STOLE is returned, a new next record needs to be read.
stream_status_t
advance_region_of_interest(output_ordinal_t output, RecordType &record,
input_info_t &input);
input_info_t &input, bool &is_record_real);

// Discards the contents of the input queue. Meant to be used when skipping
// input records.
Expand Down
3 changes: 2 additions & 1 deletion clients/drcachesim/scheduler/scheduler_replay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ scheduler_replay_tmpl_t<RecordType, ReaderType>::pick_next_input_for_mode(
// a synthetic thread exit record. We need to first throw out the
// queued candidate record, if any.
this->clear_input_queue(inputs_[index]);
inputs_[index].queue.push_back(this->create_thread_exit(inputs_[index].tid));
inputs_[index].queue.emplace_back(this->create_thread_exit(inputs_[index].tid),
/*is_real=*/false);
VPRINT(this, 2, "early end for input %d\n", index);
// We're done with this entry but we need the queued record to be read,
// so we do not move past the entry.
Expand Down
1 change: 1 addition & 0 deletions clients/drcachesim/tests/core_serial.templatex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Total counts:
106490 instructions per context switch
6 voluntary context switches
0 direct context switches
0 context switch sequence injections
100.00% voluntary switches
0.00% direct switches
4 switches input-to-input
Expand Down
5 changes: 5 additions & 0 deletions clients/drcachesim/tests/schedule_stats_nopreempt.templatex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Total counts:
106490 instructions per context switch
6 voluntary context switches
0 direct context switches
0 context switch sequence injections
100\.00% voluntary switches
0\.00% direct switches
5 switches input-to-input
Expand Down Expand Up @@ -45,6 +46,7 @@ Core #0 counts:
*[0-9]* instructions per context switch
. voluntary context switches
0 direct context switches
0 context switch sequence injections
100\.00% voluntary switches
0\.00% direct switches
.*
Expand All @@ -56,6 +58,7 @@ Core #1 counts:
*[0-9]* instructions per context switch
. voluntary context switches
0 direct context switches
0 context switch sequence injections
100\.00% voluntary switches
0\.00% direct switches
.*
Expand All @@ -67,6 +70,7 @@ Core #2 counts:
*[0-9]* instructions per context switch
. voluntary context switches
0 direct context switches
0 context switch sequence injections
100\.00% voluntary switches
0\.00% direct switches
.*
Expand All @@ -78,6 +82,7 @@ Core #3 counts:
*[0-9]* instructions per context switch
. voluntary context switches
0 direct context switches
0 context switch sequence injections
100\.00% voluntary switches
0\.00% direct switches
.*
Expand Down
48 changes: 38 additions & 10 deletions clients/drcachesim/tests/scheduler_unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5770,9 +5770,11 @@ test_unscheduled()
}

static void
test_kernel_switch_sequences()
test_kernel_switch_sequences(bool use_input_ordinals)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment: I don't think we should bother trying to support USE_INPUT_ORDINALS with a dynamic schedule: no known use case wants that. I think we should just disallow it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dynamic schedule and USE_INPUT_ORDINALS seems orthogonal to each other. USE_INPUT_ORDINALS is supposed to affect only what the stream APIs return, not how the scheduler behaves. I don't see why we should disallow that combination.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no known use case wants that

Even if that is the case, the restriction is not intuitive. Are there other concerns in handling this combination?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This combination is adding complexity and causing extra work with no benefit: better to scope-limit than complicate the code for no benefit. USE_INPUT_ORDINALS was added solely for analyzer use.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the restriction is not intuitive.

I disagree: USE_INPUT_ORDINALS is all about ignoring the output streams and only looking at the inputs; which implies you don't care where the inputs are located -- you don't care about the schedule or whether they are interleaved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

I guess I need to first find some other real case that would hit this need of tracking real/synthetic record count in the queue. I can think of some possible cases but they're harder to write a test for to confirm:

  • missing correction for record/instr ordinal when some real record was put back in the queue
  • premature decrement of instr_pre_read causing get_instr_record to return a smaller value

I'll go ahead with #7277 now (which is where I started looking into this issue), because it doesn't look like it is affected: syscall sequences are never injected without seeing an instr on an input, which means the record/instr ordinal over-adjustment gating logic (get_instr_ordinal() == 0) would never be invoked when syscall injected instrs are in the queue. They may be some tricky interaction with instr_pre_read (since syscall injected instrs would also cause it to be decremented) but maybe that's not an issue either because the instr after the syscall would not even be read from the input reader until all syscall injected instrs are done.

{
std::cerr << "\n----------------\nTesting kernel switch sequences\n";
std::cerr
<< "\n----------------\nTesting kernel switch sequences for use_input_ordinals: "
<< use_input_ordinals << "\n";
static constexpr memref_tid_t TID_IN_SWITCHES = 1;
static constexpr addr_t PROCESS_SWITCH_PC_START = 0xfeed101;
static constexpr addr_t THREAD_SWITCH_PC_START = 0xcafe101;
Expand Down Expand Up @@ -5838,9 +5840,18 @@ test_kernel_switch_sequences()
}
sched_inputs.emplace_back(std::move(readers));
}
dynamorio::drmemtrace::scheduler_tmpl_t<
dynamorio::drmemtrace::_memref_t,
dynamorio::drmemtrace::reader_t>::scheduler_flags_t flags =
scheduler_t::SCHEDULER_DEFAULTS;
if (use_input_ordinals) {
flags = static_cast<dynamorio::drmemtrace::scheduler_tmpl_t<
dynamorio::drmemtrace::_memref_t,
dynamorio::drmemtrace::reader_t>::scheduler_flags_t>(
flags | scheduler_t::SCHEDULER_USE_INPUT_ORDINALS);
}
scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT,
scheduler_t::DEPENDENCY_TIMESTAMPS,
scheduler_t::SCHEDULER_DEFAULTS,
scheduler_t::DEPENDENCY_TIMESTAMPS, flags,
/*verbosity=*/3);
sched_ops.quantum_duration_instrs = INSTR_QUANTUM;
sched_ops.kernel_switch_reader = std::move(switch_reader);
Expand All @@ -5865,6 +5876,7 @@ test_kernel_switch_sequences()
std::vector<bool> in_switch(NUM_OUTPUTS, false);
std::vector<uint64> prev_in_ord(NUM_OUTPUTS, 0);
std::vector<uint64> prev_out_ord(NUM_OUTPUTS, 0);
std::vector<uint64> switch_seq_count(NUM_OUTPUTS, 0);
while (num_eof < NUM_OUTPUTS) {
for (int i = 0; i < NUM_OUTPUTS; i++) {
if (eof[i])
Expand All @@ -5888,9 +5900,12 @@ test_kernel_switch_sequences()
sched_as_string[i] +=
'A' + static_cast<char>(memref.instr.tid - TID_BASE);
}
bool now_switch = false;
if (memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type == TRACE_MARKER_TYPE_CONTEXT_SWITCH_START)
memref.marker.marker_type == TRACE_MARKER_TYPE_CONTEXT_SWITCH_START) {
now_switch = true;
in_switch[i] = true;
}
if (in_switch[i]) {
// Test that switch code is marked synthetic.
assert(outputs[i]->is_record_synthetic());
Expand All @@ -5899,10 +5914,14 @@ test_kernel_switch_sequences()
assert(outputs[i]->get_input_interface()->get_record_ordinal() ==
prev_in_ord[i] ||
// Won't match if we just switched inputs.
(memref.marker.type == TRACE_TYPE_MARKER &&
memref.marker.marker_type ==
TRACE_MARKER_TYPE_CONTEXT_SWITCH_START));
assert(outputs[i]->get_record_ordinal() > prev_out_ord[i]);
now_switch);
if (use_input_ordinals) {
assert(outputs[i]->get_record_ordinal() == prev_out_ord[i] ||
// Won't match if we just switched inputs.
now_switch);
} else {
assert(outputs[i]->get_record_ordinal() > prev_out_ord[i]);
}
} else
assert(!outputs[i]->is_record_synthetic());
if (type_is_instr(memref.instr.type))
Expand All @@ -5912,7 +5931,9 @@ test_kernel_switch_sequences()
case TRACE_MARKER_TYPE_VERSION: sched_as_string[i] += 'v'; break;
case TRACE_MARKER_TYPE_TIMESTAMP: sched_as_string[i] += '0'; break;
case TRACE_MARKER_TYPE_CONTEXT_SWITCH_END:
assert(in_switch[i]);
in_switch[i] = false;
++switch_seq_count[i];
ANNOTATE_FALLTHROUGH;
case TRACE_MARKER_TYPE_CONTEXT_SWITCH_START:
if (memref.marker.marker_value == scheduler_t::SWITCH_PROCESS)
Expand All @@ -5930,6 +5951,12 @@ test_kernel_switch_sequences()
prev_out_ord[i] = outputs[i]->get_record_ordinal();
}
}
for (int i = 0; i < NUM_OUTPUTS; i++) {
assert(switch_seq_count[i] > 0);
assert(switch_seq_count[i] ==
static_cast<uint64>(outputs[i]->get_schedule_statistic(
memtrace_stream_t::SCHED_STAT_SWITCH_SEQUENCE_INJECTIONS)));
}
// Check the high-level strings.
for (int i = 0; i < NUM_OUTPUTS; i++) {
std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n";
Expand Down Expand Up @@ -6730,7 +6757,8 @@ test_main(int argc, const char *argv[])
test_inactive();
test_direct_switch();
test_unscheduled();
test_kernel_switch_sequences();
test_kernel_switch_sequences(/*use_input_ordinals=*/true);
test_kernel_switch_sequences(/*use_input_ordinals=*/false);
test_random_schedule();
test_record_scheduler();
test_rebalancing();
Expand Down
32 changes: 26 additions & 6 deletions clients/drcachesim/tests/switch_insertion.templatex
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
Basic counts tool results:
Schedule stats tool results:
Total counts:
[1-9][0-9][0-9][0-9][0-9][0-9] total \(fetched\) instructions
5971 total unique \(fetched\) instructions
[1-9][0-9][0-9][0-9][0-9][0-9] total userspace instructions
[1-9][0-9][0-9] total kernel instructions
[1-9][0-9][0-9][0-9][0-9][0-9] total non-fetched instructions
4 cores
.*
638960 instructions
11 total context switches
.*
6 voluntary context switches
0 direct context switches
11 context switch sequence injections
.*
9 switches input-to-input
5 switches input-to-idle
2 switches idle-to-input
.*
Core #0 counts:
.*
117031 instructions
8 total context switches
.*
3 voluntary context switches
0 direct context switches
8 context switch sequence injections
.*
6 switches input-to-input
3 switches input-to-idle
2 switches idle-to-input
.*
7 changes: 6 additions & 1 deletion clients/drcachesim/tools/schedule_stats.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2017-2024 Google, Inc. All rights reserved.
* Copyright (c) 2017-2025 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -166,6 +166,9 @@ schedule_stats_t::get_scheduler_stats(memtrace_stream_t *stream, counters_t &cou
memtrace_stream_t::SCHED_STAT_RUNQUEUE_REBALANCES));
counters.at_output_limit = static_cast<int64_t>(
stream->get_schedule_statistic(memtrace_stream_t::SCHED_STAT_HIT_OUTPUT_LIMIT));
counters.switch_sequence_injections =
static_cast<int64_t>(stream->get_schedule_statistic(
memtrace_stream_t::SCHED_STAT_SWITCH_SEQUENCE_INJECTIONS));

// XXX: Currently, schedule_stats is measuring swap-ins to a real input. If we
// want to match what "perf" targeting this app would record, which is swap-outs,
Expand Down Expand Up @@ -420,6 +423,8 @@ schedule_stats_t::print_counters(const counters_t &counters)
<< counters.voluntary_switches << " voluntary context switches\n";
std::cerr << std::setw(12) << counters.direct_switches
<< " direct context switches\n";
std::cerr << std::setw(12) << counters.switch_sequence_injections
<< " context switch sequence injections\n";
print_percentage(static_cast<double>(counters.voluntary_switches),
static_cast<double>(counters.total_switches),
"% voluntary switches\n");
Expand Down
4 changes: 3 additions & 1 deletion clients/drcachesim/tools/schedule_stats.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* **********************************************************
* Copyright (c) 2023-2024 Google, Inc. All rights reserved.
* Copyright (c) 2023-2025 Google, Inc. All rights reserved.
* **********************************************************/

/*
Expand Down Expand Up @@ -216,6 +216,7 @@ class schedule_stats_t : public analysis_tool_t {
syscalls += rhs.syscalls;
maybe_blocking_syscalls += rhs.maybe_blocking_syscalls;
direct_switch_requests += rhs.direct_switch_requests;
switch_sequence_injections += rhs.switch_sequence_injections;
observed_migrations += rhs.observed_migrations;
waits += rhs.waits;
idles += rhs.idles;
Expand Down Expand Up @@ -252,6 +253,7 @@ class schedule_stats_t : public analysis_tool_t {
int64_t syscalls = 0;
int64_t maybe_blocking_syscalls = 0;
int64_t direct_switch_requests = 0;
int64_t switch_sequence_injections = 0;
// Our observed migrations will be <= the scheduler's reported migrations
// for a dynamic schedule as we don't know the initial runqueue allocation
// and so can't see the migration of an input that didn't execute in the
Expand Down
2 changes: 1 addition & 1 deletion suite/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4067,7 +4067,7 @@ if (BUILD_CLIENTS)
set(switch_file
"${PROJECT_SOURCE_DIR}/clients/drcachesim/tests/mock_switch_sequences.x64.zip")
torunonly_simtool(switch_insertion ${ci_shared_app}
"-indir ${thread_trace_dir} -tool basic_counts -core_sharded -sched_quantum 1000 -sched_switch_file ${switch_file}"
"-indir ${thread_trace_dir} -tool schedule_stats -core_sharded -sched_quantum 1000 -sched_switch_file ${switch_file}"
"")
set(tool.switch_insertion_rawtemp ON) # no preprocessor

Expand Down