Skip to content

Commit fe67267

Browse files
Add support for using Redis MONITOR inputs files as the commands used during memtier execution
1 parent fbe4a27 commit fe67267

File tree

8 files changed

+684
-11
lines changed

8 files changed

+684
-11
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,27 @@ $ memtier_benchmark --help
9595

9696
for command line options.
9797

98+
### Using monitor input files
99+
100+
You can replay real command streams by pointing memtier_benchmark to a monitor log file with the `--monitor-input=/path/to/file` option. Special commands such as `__monitor_c1__` pick a specific entry from the file, while `__monitor_c@__` selects commands at runtime (optionally combined with `--monitor-pattern` and `--command-ratio`). For example, the following command replays the first command from the file on each request:
101+
102+
```
103+
$ memtier_benchmark --monitor-input=monitor.txt --command=__monitor_c1__
104+
```
105+
106+
This lets you mix synthetic workloads with realistic captured traffic in both standalone and Redis Cluster deployments.
107+
108+
To generate monitor logs, you can use the Redis `MONITOR` command from `redis-cli`, which prints all commands received by the server. For example:
109+
110+
```
111+
$ redis-cli MONITOR
112+
OK
113+
1460100081.165665 [0 127.0.0.1:51706] "set" "shipment:8000736522714:status" "sorting"
114+
1460100083.053365 [0 127.0.0.1:51707] "get" "shipment:8000736522714:status"
115+
```
116+
117+
You can pipe this output and filter specific patterns with tools such as `grep`, then save it to a file and use it as a `--monitor-input` source. For more details, see the official Redis documentation on [monitoring commands executed in Redis](https://redis.io/docs/latest/develop/tools/cli/#monitor-commands-executed-in-redis).
118+
98119
## Crash Reporting
99120

100121
memtier_benchmark includes built-in crash handling that automatically generates detailed bug reports when the program crashes. If you encounter a crash, the tool will print a comprehensive report including:

client.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,64 @@ bool client::create_arbitrary_request(unsigned int command_index, struct timeval
279279

280280
benchmark_debug_log("%s: %s:\n", m_connections[conn_id]->get_readable_id(), cmd.command.c_str());
281281

282+
// Check if this is a monitor command placeholder - handle it specially
283+
if (cmd.command_args.size() == 1 && cmd.command_args[0].type == monitor_random_type) {
284+
// Select a command from the monitor file at runtime based on the monitor pattern
285+
size_t selected_index = 0;
286+
const std::string* monitor_cmd_ptr = NULL;
287+
if (m_config->monitor_pattern == 'R') {
288+
monitor_cmd_ptr = &m_config->monitor_commands->get_random_command(&selected_index);
289+
benchmark_debug_log("%s: random monitor command selected (q%zu): %s\n",
290+
m_connections[conn_id]->get_readable_id(),
291+
selected_index + 1, // 1-based index for user display
292+
monitor_cmd_ptr->c_str());
293+
} else {
294+
monitor_cmd_ptr = &m_config->monitor_commands->get_next_sequential_command(&selected_index);
295+
benchmark_debug_log("%s: sequential monitor command selected (q%zu): %s\n",
296+
m_connections[conn_id]->get_readable_id(),
297+
selected_index + 1, // 1-based index for user display
298+
monitor_cmd_ptr->c_str());
299+
}
300+
301+
const std::string& monitor_cmd = *monitor_cmd_ptr;
302+
303+
// Parse and format the monitor command into a temporary arbitrary_command
304+
arbitrary_command temp_cmd(monitor_cmd.c_str());
305+
if (!temp_cmd.split_command_to_args()) {
306+
fprintf(stderr, "error: failed to parse random monitor command at runtime: %s\n", monitor_cmd.c_str());
307+
return false;
308+
}
309+
310+
// Format the command for the protocol (adds RESP headers)
311+
if (!m_connections[conn_id]->get_protocol()->format_arbitrary_command(temp_cmd)) {
312+
fprintf(stderr, "error: failed to format random monitor command at runtime: %s\n", monitor_cmd.c_str());
313+
return false;
314+
}
315+
316+
// Send the randomly selected command
317+
for (unsigned int i = 0; i < temp_cmd.command_args.size(); i++) {
318+
const command_arg* arg = &temp_cmd.command_args[i];
319+
if (arg->type == const_type) {
320+
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg);
321+
} else if (arg->type == key_type) {
322+
unsigned long long key_index;
323+
get_key_response res = get_key_for_conn(command_index, conn_id, &key_index);
324+
assert(res == available_for_conn);
325+
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg, m_obj_gen->get_key(), m_obj_gen->get_key_len());
326+
} else if (arg->type == data_type) {
327+
unsigned int value_len;
328+
const char *value = m_obj_gen->get_value(0, &value_len);
329+
assert(value != NULL);
330+
assert(value_len > 0);
331+
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg, value, value_len);
332+
}
333+
}
334+
335+
m_connections[conn_id]->send_arbitrary_command_end(command_index, &timestamp, cmd_size);
336+
return true;
337+
}
338+
339+
// Normal arbitrary command handling
282340
for (unsigned int i = 0; i < cmd.command_args.size(); i++) {
283341
const command_arg* arg = &cmd.command_args[i];
284342
if (arg->type == const_type) {

config_types.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,88 @@ bool arbitrary_command::split_command_to_args() {
489489
err:
490490
return false;
491491
}
492+
493+
// Monitor command list implementation
494+
bool monitor_command_list::load_from_file(const char* filename) {
495+
FILE* file = fopen(filename, "r");
496+
if (!file) {
497+
fprintf(stderr, "error: failed to open monitor input file: %s\n", filename);
498+
return false;
499+
}
500+
501+
char line[65536]; // Large buffer for monitor lines
502+
size_t total_lines = 0;
503+
while (fgets(line, sizeof(line), file)) {
504+
total_lines++;
505+
// Find the first quote - this is where the command starts
506+
char* first_quote = strchr(line, '"');
507+
if (!first_quote) {
508+
continue; // Skip lines without commands
509+
}
510+
511+
// Extract everything from first quote to end of line
512+
// We keep the quotes as-is to avoid re-parsing
513+
std::string command_str(first_quote);
514+
515+
// Remove trailing newline if present
516+
if (!command_str.empty() && command_str[command_str.length() - 1] == '\n') {
517+
command_str.erase(command_str.length() - 1);
518+
}
519+
if (!command_str.empty() && command_str[command_str.length() - 1] == '\r') {
520+
command_str.erase(command_str.length() - 1);
521+
}
522+
523+
commands.push_back(command_str);
524+
}
525+
526+
fclose(file);
527+
528+
if (commands.empty()) {
529+
fprintf(stderr, "error: no commands found in monitor input file: %s\n", filename);
530+
return false;
531+
}
532+
533+
fprintf(stderr, "Loaded %zu monitor commands from %zu total lines\n", commands.size(), total_lines);
534+
return true;
535+
}
536+
537+
const std::string& monitor_command_list::get_command(size_t index) const {
538+
if (index >= commands.size()) {
539+
static std::string empty;
540+
return empty;
541+
}
542+
return commands[index];
543+
}
544+
545+
const std::string& monitor_command_list::get_random_command() const {
546+
if (commands.empty()) {
547+
static std::string empty;
548+
return empty;
549+
}
550+
size_t random_index = rand() % commands.size();
551+
return commands[random_index];
552+
}
553+
554+
const std::string& monitor_command_list::get_random_command(size_t* out_index) const {
555+
if (commands.empty()) {
556+
static std::string empty;
557+
if (out_index) *out_index = 0;
558+
return empty;
559+
}
560+
size_t random_index = rand() % commands.size();
561+
if (out_index) *out_index = random_index;
562+
return commands[random_index];
563+
}
564+
565+
const std::string& monitor_command_list::get_next_sequential_command(size_t* out_index) {
566+
if (commands.empty()) {
567+
static std::string empty;
568+
if (out_index) *out_index = 0;
569+
return empty;
570+
}
571+
// Use a global sequential index across all clients/threads.
572+
size_t index = next_index.fetch_add(1, std::memory_order_relaxed);
573+
index = index % commands.size();
574+
if (out_index) *out_index = index;
575+
return commands[index];
576+
}

config_types.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,24 @@ struct server_addr {
106106

107107
#define KEY_PLACEHOLDER "__key__"
108108
#define DATA_PLACEHOLDER "__data__"
109+
#define MONITOR_PLACEHOLDER_PREFIX "__monitor_c"
110+
#define MONITOR_RANDOM_PLACEHOLDER "__monitor_c@__"
109111

110112
enum command_arg_type {
111113
const_type = 0,
112114
key_type = 1,
113115
data_type = 2,
114-
undefined_type = 3
116+
monitor_type = 3,
117+
monitor_random_type = 4,
118+
undefined_type = 5
115119
};
116120

117121
struct command_arg {
118-
command_arg(const char* arg, unsigned int arg_len) : type(undefined_type), data(arg, arg_len), has_key_affixes(false) {;}
122+
command_arg(const char* arg, unsigned int arg_len) : type(undefined_type), data(arg, arg_len), monitor_index(0), has_key_affixes(false) {;}
119123
command_arg_type type;
120124
std::string data;
125+
// For monitor_type, stores the index (1-based)
126+
size_t monitor_index;
121127
// the prefix and suffix strings are used for mixed key placeholder storing of substrings
122128
std::string data_prefix;
123129
std::string data_suffix;
@@ -183,4 +189,20 @@ struct arbitrary_command_list {
183189
}
184190
};
185191

192+
struct monitor_command_list {
193+
private:
194+
std::vector<std::string> commands;
195+
std::atomic<size_t> next_index;
196+
197+
public:
198+
monitor_command_list() : next_index(0) {;}
199+
200+
bool load_from_file(const char* filename);
201+
const std::string& get_command(size_t index) const;
202+
const std::string& get_random_command() const;
203+
const std::string& get_random_command(size_t* out_index) const;
204+
const std::string& get_next_sequential_command(size_t* out_index);
205+
size_t size() const { return commands.size(); }
206+
};
207+
186208
#endif /* _CONFIG_TYPES_H */

0 commit comments

Comments
 (0)