Skip to content

Commit e9a6520

Browse files
Add monitor placeholder validation and remove unused function
- Remove unused get_random_command() overload (parameterless version) - Add strict validation for monitor placeholders (__monitor_cN__): - Reject malformed placeholders like __monitor_c1, __monitor_c1_, __monitor_c1__garbage, __monitor_c1abc__, __monitor_c__ - Validation only applies when --monitor-input is provided - Add tests for malformed placeholder rejection - Add test verifying placeholder-like strings are literal without --monitor-input
1 parent a39004e commit e9a6520

File tree

4 files changed

+159
-19
lines changed

4 files changed

+159
-19
lines changed

config_types.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -530,16 +530,6 @@ const std::string &monitor_command_list::get_command(size_t index) const
530530
return commands[index];
531531
}
532532

533-
const std::string &monitor_command_list::get_random_command() const
534-
{
535-
if (commands.empty()) {
536-
static std::string empty;
537-
return empty;
538-
}
539-
size_t random_index = rand() % commands.size();
540-
return commands[random_index];
541-
}
542-
543533
const std::string &monitor_command_list::get_random_command(size_t *out_index) const
544534
{
545535
if (commands.empty()) {

config_types.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ struct monitor_command_list
209209

210210
bool load_from_file(const char *filename);
211211
const std::string &get_command(size_t index) const;
212-
const std::string &get_random_command() const;
213212
const std::string &get_random_command(size_t *out_index) const;
214213
const std::string &get_next_sequential_command(size_t *out_index);
215214
size_t size() const { return commands.size(); }

memtier_benchmark.cpp

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,14 +1987,35 @@ int main(int argc, char *argv[])
19871987
}
19881988

19891989
// Check if command is a specific monitor placeholder
1990-
if (strncmp(cmd.command.c_str(), MONITOR_PLACEHOLDER_PREFIX, strlen(MONITOR_PLACEHOLDER_PREFIX)) == 0) {
1991-
// Extract the index from __monitor_cN__
1992-
const char *num_start = cmd.command.c_str() + strlen(MONITOR_PLACEHOLDER_PREFIX);
1993-
char *endptr;
1994-
long index = strtol(num_start, &endptr, 10);
1995-
1996-
if (endptr == num_start || index < 1 || (size_t) index > cfg.monitor_commands->size()) {
1997-
fprintf(stderr, "error: invalid monitor placeholder '%s' (valid range: q1-q%zu or q@)\n",
1990+
// Expected format: __monitor_cN__ where N is a 1-based index
1991+
const size_t prefix_len = sizeof(MONITOR_PLACEHOLDER_PREFIX) - 1; // compile-time constant
1992+
const size_t suffix_len = 2; // "__"
1993+
size_t cmd_len = cmd.command.length();
1994+
1995+
// Check if command starts with monitor prefix
1996+
if (cmd_len >= prefix_len && strncmp(cmd.command.c_str(), MONITOR_PLACEHOLDER_PREFIX, prefix_len) == 0) {
1997+
// Validate full format: must end with __, have digits between prefix and suffix
1998+
bool valid = false;
1999+
long index = 0;
2000+
2001+
if (cmd_len > prefix_len + suffix_len && cmd.command[cmd_len - 2] == '_' &&
2002+
cmd.command[cmd_len - 1] == '_') {
2003+
// Extract the index from __monitor_cN__
2004+
const char *num_start = cmd.command.c_str() + prefix_len;
2005+
char *endptr;
2006+
index = strtol(num_start, &endptr, 10);
2007+
2008+
// Valid if: consumed at least one digit, ends exactly at "__", index in range
2009+
if (endptr != num_start && endptr == cmd.command.c_str() + cmd_len - suffix_len && index >= 1 &&
2010+
(size_t) index <= cfg.monitor_commands->size()) {
2011+
valid = true;
2012+
}
2013+
}
2014+
2015+
if (!valid) {
2016+
fprintf(stderr,
2017+
"error: invalid monitor placeholder '%s' (valid range: __monitor_c1__ to __monitor_c%zu__ "
2018+
"or __monitor_c@__)\n",
19982019
cmd.command.c_str(), cfg.monitor_commands->size());
19992020
exit(1);
20002021
}

tests/test_monitor_input.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,133 @@ def test_monitor_input_mixed_commands(env):
325325
found = True
326326
break
327327
env.assertTrue(found)
328+
329+
330+
def test_monitor_input_malformed_placeholder_rejected(env):
331+
"""
332+
Test that malformed monitor placeholders are rejected with an error.
333+
334+
This test verifies that placeholders like:
335+
- __monitor_c1 (missing trailing __)
336+
- __monitor_c1_ (only one trailing underscore)
337+
- __monitor_c1__garbage (trailing characters)
338+
- __monitor_c1abc__ (non-numeric characters before __)
339+
are properly rejected when --monitor-input is provided.
340+
"""
341+
# Create monitor input file
342+
test_dir = tempfile.mkdtemp()
343+
monitor_file = os.path.join(test_dir, "monitor.txt")
344+
with open(monitor_file, "w") as f:
345+
f.write(
346+
'[ proxy49 ] 1764031576.604009 [0 172.16.10.147:51682] "SET" "key1" "value1"\n'
347+
)
348+
349+
malformed_placeholders = [
350+
"__monitor_c1", # missing trailing __
351+
"__monitor_c1_", # only one trailing underscore
352+
"__monitor_c1__garbage", # trailing characters after valid placeholder
353+
"__monitor_c1abc__", # non-numeric characters before __
354+
"__monitor_c__", # missing index number
355+
]
356+
357+
config = get_default_memtier_config(threads=1, clients=1, requests=10)
358+
master_nodes_list = env.getMasterNodesList()
359+
360+
for placeholder in malformed_placeholders:
361+
benchmark_specs = {
362+
"name": env.testName,
363+
"args": [
364+
"--monitor-input={}".format(monitor_file),
365+
"--command={}".format(placeholder),
366+
],
367+
}
368+
addTLSArgs(benchmark_specs, env)
369+
370+
add_required_env_arguments(benchmark_specs, config, env, master_nodes_list)
371+
372+
run_config = RunConfig(test_dir, env.testName, config, {})
373+
ensure_clean_benchmark_folder(run_config.results_dir)
374+
375+
benchmark = Benchmark.from_json(run_config, benchmark_specs)
376+
377+
# Run memtier_benchmark - should fail
378+
memtier_ok = benchmark.run()
379+
380+
# Verify failure
381+
if memtier_ok:
382+
env.debugPrint(
383+
"Expected failure for malformed placeholder '{}' but it succeeded".format(
384+
placeholder
385+
),
386+
True,
387+
)
388+
env.assertFalse(memtier_ok)
389+
390+
# Check stderr for error message
391+
stderr_file = "{0}/mb.stderr".format(run_config.results_dir)
392+
if os.path.isfile(stderr_file):
393+
with open(stderr_file) as stderr:
394+
stderr_content = stderr.read()
395+
# The placeholder should either be rejected as invalid monitor placeholder
396+
# or treated as unknown command by Redis
397+
has_error = (
398+
"invalid monitor placeholder" in stderr_content
399+
or "error" in stderr_content.lower()
400+
)
401+
if not has_error:
402+
env.debugPrint(
403+
"Expected error message for '{}', got: {}".format(
404+
placeholder, stderr_content
405+
),
406+
True,
407+
)
408+
env.assertTrue(has_error)
409+
410+
411+
def test_monitor_placeholder_literal_without_monitor_input(env):
412+
"""
413+
Test that __monitor_c1__ is treated as a literal command when --monitor-input is not provided.
414+
415+
When monitor input is not configured, placeholder-like strings should be sent
416+
to Redis as-is (which will result in an unknown command error from Redis).
417+
This verifies that the placeholder validation only applies when monitor is in use.
418+
"""
419+
test_dir = tempfile.mkdtemp()
420+
421+
# Configure memtier WITHOUT --monitor-input but WITH a monitor-like placeholder
422+
benchmark_specs = {
423+
"name": env.testName,
424+
"args": [
425+
"--command=__monitor_c1__", # This should be sent literally to Redis
426+
],
427+
}
428+
addTLSArgs(benchmark_specs, env)
429+
430+
config = get_default_memtier_config(threads=1, clients=1, requests=10)
431+
master_nodes_list = env.getMasterNodesList()
432+
433+
add_required_env_arguments(benchmark_specs, config, env, master_nodes_list)
434+
435+
config = RunConfig(test_dir, env.testName, config, {})
436+
ensure_clean_benchmark_folder(config.results_dir)
437+
438+
benchmark = Benchmark.from_json(config, benchmark_specs)
439+
440+
# Run memtier_benchmark
441+
# This should NOT fail with "invalid monitor placeholder" error
442+
# It will likely fail because Redis doesn't recognize __monitor_c1__ as a command,
443+
# but that's a Redis error, not a memtier validation error
444+
benchmark.run() # Result doesn't matter - we only care about the error type
445+
446+
# Check stderr - should NOT contain "invalid monitor placeholder"
447+
stderr_file = "{0}/mb.stderr".format(config.results_dir)
448+
if os.path.isfile(stderr_file):
449+
with open(stderr_file) as stderr:
450+
stderr_content = stderr.read()
451+
has_monitor_error = "invalid monitor placeholder" in stderr_content
452+
if has_monitor_error:
453+
env.debugPrint(
454+
"Placeholder validation should not apply without --monitor-input",
455+
True,
456+
)
457+
env.assertFalse(has_monitor_error)

0 commit comments

Comments
 (0)