diff --git a/api/docs/release.dox b/api/docs/release.dox
index d9cc6aefb0f..759ecade1ef 100644
--- a/api/docs/release.dox
+++ b/api/docs/release.dox
@@ -129,7 +129,8 @@ changes:
- No compatibility changes yet.
Further non-compatibility-affecting changes include:
- - Nothing yet.
+ - Added support for reading a single drmemtrace trace file from stdin
+ via "-infile -".
**************************************************
diff --git a/clients/drcachesim/common/options.cpp b/clients/drcachesim/common/options.cpp
index 58047dd40ad..cebeca16504 100644
--- a/clients/drcachesim/common/options.cpp
+++ b/clients/drcachesim/common/options.cpp
@@ -1,5 +1,5 @@
/* **********************************************************
- * Copyright (c) 2015-2024 Google, Inc. All rights reserved.
+ * Copyright (c) 2015-2025 Google, Inc. All rights reserved.
* **********************************************************/
/*
@@ -95,9 +95,14 @@ droption_t op_indir(
"delete files here.");
droption_t op_infile(
- DROPTION_SCOPE_ALL, "infile", "", "Offline legacy file for input to the simulator",
- "Directs the simulator to use a single all-threads-interleaved-into-one trace file. "
- "This is a legacy file format that is no longer produced.");
+ DROPTION_SCOPE_ALL, "infile", "", "Single offline trace input file",
+ "Directs the framework to use a single trace file. This could be a legacy "
+ "all-software-threads-interleaved-into-one trace file, a core-sharded single "
+ "hardware thread file mixing multiple software threads, or a single software "
+ "thread selected from a directory (though in that case it is better to use "
+ "-only_thread, -only_threads, or -only_shards). This method of input does "
+ "not support any features that require auxiliary metadata files. "
+ "Passing '-' will read from stdin as plain or gzip-compressed data.");
droption_t op_jobs(
DROPTION_SCOPE_ALL, "jobs", -1, "Number of parallel jobs",
diff --git a/clients/drcachesim/reader/compressed_file_reader.cpp b/clients/drcachesim/reader/compressed_file_reader.cpp
index 5007f558631..9e441c1adab 100644
--- a/clients/drcachesim/reader/compressed_file_reader.cpp
+++ b/clients/drcachesim/reader/compressed_file_reader.cpp
@@ -1,5 +1,5 @@
/* **********************************************************
- * Copyright (c) 2017-2023 Google, Inc. All rights reserved.
+ * Copyright (c) 2017-2025 Google, Inc. All rights reserved.
* **********************************************************/
/*
@@ -52,7 +52,14 @@ namespace drmemtrace {
bool
open_single_file_common(const std::string &path, gzFile &out)
{
- out = gzopen(path.c_str(), "rb");
+ if (path == "-") {
+ // We assume stdin is 0.
+ // We do not use the glibc-specific "stdin->_fileno" method of finding the
+ // stdin descriptor number as that is not portable to musl or other libc.
+ constexpr int STDIN_FD = 0;
+ out = gzdopen(STDIN_FD, "rb");
+ } else
+ out = gzopen(path.c_str(), "rb");
return out != nullptr;
}
diff --git a/clients/drcachesim/tests/offline-stdin.expect b/clients/drcachesim/tests/offline-stdin.expect
new file mode 100644
index 00000000000..0ada73ad6e0
--- /dev/null
+++ b/clients/drcachesim/tests/offline-stdin.expect
@@ -0,0 +1,16 @@
+Output format:
+<--record#-> <--instr#->: <---tid--->
+------------------------------------------------------------
+ 1 0: 1257596
+ 2 0: 1257596
+ 3 0: 1257596
+ 4 0: 1257596
+ 5 0: 1257596
+ 6 0: 1257596
+ 7 0: 1257596
+ 8 1: 1257596 ifetch 4 byte\(s\) @ 0x00007f3e429fce35 48 8b 45 f8 mov -0x08\(%rbp\), %rax
+ 9 1: 1257596 read 8 byte\(s\) @ 0x00007ffefb03e128 by PC 0x00007f3e429fce35
+ 10 2: 1257596 ifetch 8 byte\(s\) @ 0x00007f3e429fce39 48 8d 14 c5 00 00 00 lea 0x00\(,%rax,8\), %rdx
+ 10 2: 1257596 00
+View tool results:
+ 2 : total instructions
diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt
index 357f60c3f82..9b92963b0df 100644
--- a/suite/tests/CMakeLists.txt
+++ b/suite/tests/CMakeLists.txt
@@ -4006,6 +4006,28 @@ if (BUILD_CLIENTS)
# Avoid the default core-sharded from re-scheduling the trace.
"-indir ${core_sharded_dir} -tool schedule_stats -only_shards 2,3 -no_core_sharded" "")
set(tool.core_on_disk_rawtemp ON) # no preprocessor
+
+ # Test reading a file over stdin.
+ # We rely on there being just one subfile in this zip file: gunzip only
+ # extracts the first one (bsdtar can extract all but is not likely installed).
+ # This is UNIX only so we rely on cat and gunzip being available.
+ find_program(CAT_EXE cat DOC "path to cat")
+ find_program(GUNZIP_EXE gunzip DOC "path to gunzip")
+ find_program(BASH_EXE bash DOC "path to bash")
+ if (CAT_EXE AND GUNZIP_EXE AND BASH_EXE)
+ # Easiest to make our pipeline in bash with a direct add_test()
+ # (although this bypasses the torun() features used by all other tests).
+ set(infile "${core_sharded_dir}/drmemtrace.core.0.trace.zip")
+ add_test(tool.drcacheoff.stdin
+ ${BASH_EXE} -c
+ "${CAT_EXE} ${infile} | ${GUNZIP_EXE} | ${drrun_path} -t drmemtrace -tool view -sim_refs 10 -infile -")
+ file(READ "${PROJECT_SOURCE_DIR}/clients/drcachesim/tests/offline-stdin.expect"
+ stdin_expect)
+ set_tests_properties(tool.drcacheoff.stdin PROPERTIES
+ PASS_REGULAR_EXPRESSION "${stdin_expect}")
+ else ()
+ message("Failed to find cat, gunzip, and bash: not running stdin test")
+ endif ()
endif ()
endif ()