Skip to content

Commit fbe4a27

Browse files
Enable key placeholder to be combined with other data within the same command argument (#154)
* Enable key placeholder to be combined with other data within the same command argument * Ensured tests cover key format with prefix/suffix * Fixes per PR review: added KEY_BUFFER_STACK_SIZE instead of constexpr --------- Co-authored-by: fcostaoliveira <[email protected]>
1 parent 49652c0 commit fbe4a27

File tree

5 files changed

+124
-4
lines changed

5 files changed

+124
-4
lines changed

client.cpp

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@
4646

4747
#include <math.h>
4848
#include <algorithm>
49+
#include <sstream>
4950
#include <arpa/inet.h>
5051

5152
#include "client.h"
5253
#include "cluster_client.h"
54+
#include "config_types.h"
5355

5456

5557
bool client::setup_client(benchmark_config *config, abstract_protocol *protocol, object_generator *objgen)
@@ -286,7 +288,46 @@ bool client::create_arbitrary_request(unsigned int command_index, struct timeval
286288
get_key_response res = get_key_for_conn(command_index, conn_id, &key_index);
287289
/* If key not available for this connection, we have a bug of sending partial request */
288290
assert(res == available_for_conn);
289-
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg, m_obj_gen->get_key(), m_obj_gen->get_key_len());
291+
292+
//when we have static data mixed with the key placeholder
293+
if (arg->has_key_affixes) {
294+
// Pre-calculate total length to avoid reallocations
295+
const char* key = m_obj_gen->get_key();
296+
unsigned int key_len = m_obj_gen->get_key_len();
297+
size_t prefix_len = arg->data_prefix.length();
298+
size_t suffix_len = arg->data_suffix.length();
299+
size_t total_len = prefix_len + key_len + suffix_len;
300+
301+
// Optimization: use stack buffer for small keys to avoid heap allocation
302+
if (total_len < KEY_BUFFER_STACK_SIZE) {
303+
char stack_buffer[KEY_BUFFER_STACK_SIZE];
304+
char* pos = stack_buffer;
305+
306+
// Manual copy for better performance
307+
if (prefix_len > 0) {
308+
memcpy(pos, arg->data_prefix.data(), prefix_len);
309+
pos += prefix_len;
310+
}
311+
memcpy(pos, key, key_len);
312+
pos += key_len;
313+
if (suffix_len > 0) {
314+
memcpy(pos, arg->data_suffix.data(), suffix_len);
315+
}
316+
317+
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg, stack_buffer, total_len);
318+
} else {
319+
// Fallback to string for large keys
320+
std::string combined_key;
321+
combined_key.reserve(total_len);
322+
combined_key.append(arg->data_prefix);
323+
combined_key.append(key, key_len);
324+
combined_key.append(arg->data_suffix);
325+
326+
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg, combined_key.c_str(), combined_key.length());
327+
}
328+
} else{
329+
cmd_size += m_connections[conn_id]->send_arbitrary_command(arg, m_obj_gen->get_key(), m_obj_gen->get_key_len());
330+
}
290331
} else if (arg->type == data_type) {
291332
unsigned int value_len;
292333
const char *value = m_obj_gen->get_value(0, &value_len);

client.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class object_generator;
4949
#define SET_CMD_IDX 0
5050
#define GET_CMD_IDX 2
5151

52+
// Stack buffer size for key operations to avoid heap allocation
53+
#define KEY_BUFFER_STACK_SIZE 512
54+
5255
enum get_key_response { not_available, available_for_conn, available_for_other_conn };
5356

5457
class client : public connections_manager {

config_types.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,14 @@ enum command_arg_type {
115115
};
116116

117117
struct command_arg {
118-
command_arg(const char* arg, unsigned int arg_len) : type(undefined_type), data(arg, arg_len) {;}
118+
command_arg(const char* arg, unsigned int arg_len) : type(undefined_type), data(arg, arg_len), has_key_affixes(false) {;}
119119
command_arg_type type;
120120
std::string data;
121+
// the prefix and suffix strings are used for mixed key placeholder storing of substrings
122+
std::string data_prefix;
123+
std::string data_suffix;
124+
// optimization flag to avoid runtime checks
125+
bool has_key_affixes;
121126
};
122127

123128
struct arbitrary_command {

protocol.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,9 @@ bool redis_protocol::format_arbitrary_command(arbitrary_command &cmd) {
713713
// check arg type
714714
if (current_arg->data.find(KEY_PLACEHOLDER) != std::string::npos) {
715715
if (current_arg->data.length() != strlen(KEY_PLACEHOLDER)) {
716-
benchmark_error_log("error: key placeholder can't combined with other data\n");
717-
return false;
716+
current_arg->has_key_affixes = true;
717+
current_arg->data_prefix = current_arg->data.substr(0, current_arg->data.find(KEY_PLACEHOLDER));
718+
current_arg->data_suffix = current_arg->data.substr(current_arg->data.find(KEY_PLACEHOLDER) + strlen(KEY_PLACEHOLDER));
718719
}
719720
cmd.keys_count++;
720721
current_arg->type = key_type;

tests/tests_oss_simple_flow.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,76 @@ def test_default_arbitrary_command_hset_multi_data_placeholders(env):
615615
assert_minimum_memtier_outcomes(config, env, memtier_ok, overall_expected_request_count,
616616
overall_request_count)
617617

618+
619+
def test_key_placeholder(env):
620+
env.skipOnCluster()
621+
run_count = 1
622+
benchmark_specs = {"name": env.testName, "args": ['--command=HSET __key__ f __data__']}
623+
addTLSArgs(benchmark_specs, env)
624+
config = get_default_memtier_config()
625+
master_nodes_list = env.getMasterNodesList()
626+
overall_expected_request_count = get_expected_request_count(config) * run_count
627+
628+
add_required_env_arguments(benchmark_specs, config, env, master_nodes_list)
629+
630+
# Create a temporary directory
631+
test_dir = tempfile.mkdtemp()
632+
633+
config = RunConfig(test_dir, env.testName, config, {})
634+
ensure_clean_benchmark_folder(config.results_dir)
635+
636+
benchmark = Benchmark.from_json(config, benchmark_specs)
637+
638+
# benchmark.run() returns True if the return code of memtier_benchmark was 0
639+
memtier_ok = benchmark.run()
640+
debugPrintMemtierOnError(config, env)
641+
642+
master_nodes_connections = env.getOSSMasterNodesConnectionList()
643+
merged_command_stats = {'cmdstat_hset': {'calls': 0}}
644+
overall_request_count = agg_info_commandstats(master_nodes_connections, merged_command_stats)
645+
assert_minimum_memtier_outcomes(config, env, memtier_ok, overall_expected_request_count,
646+
overall_request_count)
647+
648+
649+
# key placeholder combined with other data
650+
def test_key_placeholder_togetherwithdata(env):
651+
env.skipOnCluster()
652+
run_count = 1
653+
benchmark_specs = {"name": env.testName, "args": ['--command=SET \"prefix:__key__:suffix\" \"__data__\"']}
654+
addTLSArgs(benchmark_specs, env)
655+
config = get_default_memtier_config(threads=4, clients=1,requests=50)
656+
master_nodes_list = env.getMasterNodesList()
657+
overall_expected_request_count = get_expected_request_count(config) * run_count
658+
659+
add_required_env_arguments(benchmark_specs, config, env, master_nodes_list)
660+
661+
# Create a temporary directory
662+
test_dir = tempfile.mkdtemp()
663+
664+
config = RunConfig(test_dir, env.testName, config, {})
665+
ensure_clean_benchmark_folder(config.results_dir)
666+
667+
benchmark = Benchmark.from_json(config, benchmark_specs)
668+
669+
# benchmark.run() returns True if the return code of memtier_benchmark was 0
670+
memtier_ok = benchmark.run()
671+
debugPrintMemtierOnError(config, env)
672+
673+
master_nodes_connections = env.getOSSMasterNodesConnectionList()
674+
merged_command_stats = {'cmdstat_set': {'calls': 0}}
675+
overall_request_count = agg_info_commandstats(master_nodes_connections, merged_command_stats)
676+
assert_minimum_memtier_outcomes(config, env, memtier_ok, overall_expected_request_count,
677+
overall_request_count)
678+
679+
# Ensure all keys have the correct prefix and suffix
680+
for conn in master_nodes_connections:
681+
for key in conn.scan_iter("*"):
682+
decoded_key = key.decode().split(":")
683+
env.assertEqual(decoded_key[0], "prefix")
684+
env.assertEqual(decoded_key[1].split("-")[0], "memtier")
685+
env.assertEqual(decoded_key[2], "suffix")
686+
687+
618688
def test_default_set_get_rate_limited(env):
619689
env.skipOnCluster()
620690
master_nodes_list = env.getMasterNodesList()

0 commit comments

Comments
 (0)