redis-benchmarks-specification 0.1.274__py3-none-any.whl → 0.1.276__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of redis-benchmarks-specification might be problematic. Click here for more details.

Files changed (32) hide show
  1. redis_benchmarks_specification/__common__/timeseries.py +28 -6
  2. redis_benchmarks_specification/__runner__/args.py +43 -1
  3. redis_benchmarks_specification/__runner__/remote_profiling.py +329 -0
  4. redis_benchmarks_specification/__runner__/runner.py +603 -67
  5. redis_benchmarks_specification/test-suites/defaults.yml +3 -0
  6. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-string-get-10B-pipeline-100-nokeyprefix.yml +4 -0
  7. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-100B-expire-use-case.yml +2 -2
  8. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-expire-use-case.yml +2 -2
  9. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-psetex-expire-use-case.yml +2 -2
  10. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-10B-setex-expire-use-case.yml +2 -2
  11. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-1KiB-expire-use-case.yml +2 -2
  12. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-4KiB-expire-use-case.yml +2 -2
  13. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-get-10B-pipeline-100-nokeyprefix.yml +4 -0
  14. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-mixed-50-50-set-get-with-expiration-240B-400_conns.yml +4 -2
  15. redis_benchmarks_specification/test-suites/memtier_benchmark-1Mkeys-string-set-with-ex-100B-pipeline-10.yml +1 -1
  16. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-sscan-cursor-count-100.yml +1 -1
  17. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-set-1K-elements-sscan.yml +1 -1
  18. redis_benchmarks_specification/test-suites/memtier_benchmark-1key-zset-1K-elements-zscan.yml +1 -1
  19. redis_benchmarks_specification/test-suites/memtier_benchmark-3Mkeys-string-mixed-50-50-with-512B-values-with-expiration-pipeline-10-400_conns.yml +0 -2
  20. {redis_benchmarks_specification-0.1.274.dist-info → redis_benchmarks_specification-0.1.276.dist-info}/METADATA +1 -1
  21. {redis_benchmarks_specification-0.1.274.dist-info → redis_benchmarks_specification-0.1.276.dist-info}/RECORD +24 -31
  22. redis_benchmarks_specification/test-suites/memtier_benchmark-10Mkeys-string-set-update-del-ex-36000-pipeline-10.yml +0 -32
  23. redis_benchmarks_specification/test-suites/memtier_benchmark-150Mkeys-string-set-ex-20-pipeline-10.yml +0 -30
  24. redis_benchmarks_specification/test-suites/memtier_benchmark-50Mkeys-string-set-ex-10-with-precondition-pipeline-10.yml +0 -34
  25. redis_benchmarks_specification/test-suites/memtier_benchmark-50Mkeys-string-set-ex-10years-pipeline-10.yml +0 -30
  26. redis_benchmarks_specification/test-suites/memtier_benchmark-50Mkeys-string-set-ex-3-pipeline-10.yml +0 -30
  27. redis_benchmarks_specification/test-suites/memtier_benchmark-50Mkeys-string-set-ex-random-range-pipeline-10.yml +0 -30
  28. redis_benchmarks_specification/test-suites/memtier_benchmark-50Mkeys-string-set-update-del-ex-120-pipeline-10.yml +0 -32
  29. redis_benchmarks_specification/test-suites/memtier_benchmark-80Mkeys-string-set-ex-20-precodition-multiclient-pipeline-10.yml +0 -34
  30. {redis_benchmarks_specification-0.1.274.dist-info → redis_benchmarks_specification-0.1.276.dist-info}/LICENSE +0 -0
  31. {redis_benchmarks_specification-0.1.274.dist-info → redis_benchmarks_specification-0.1.276.dist-info}/WHEEL +0 -0
  32. {redis_benchmarks_specification-0.1.274.dist-info → redis_benchmarks_specification-0.1.276.dist-info}/entry_points.txt +0 -0
@@ -4,6 +4,7 @@ import logging
4
4
  import math
5
5
  import os
6
6
  import shutil
7
+ import subprocess
7
8
  import sys
8
9
  import tempfile
9
10
  import traceback
@@ -62,6 +63,84 @@ from redis_benchmarks_specification.__common__.spec import (
62
63
  extract_client_tools,
63
64
  )
64
65
  from redis_benchmarks_specification.__runner__.args import create_client_runner_args
66
+ from redis_benchmarks_specification.__runner__.remote_profiling import RemoteProfiler
67
+
68
+
69
+ def run_local_command_with_timeout(command_str, timeout_seconds, description="command"):
70
+ """
71
+ Run a local command with timeout support.
72
+
73
+ Args:
74
+ command_str: The command string to execute
75
+ timeout_seconds: Timeout in seconds
76
+ description: Description for logging
77
+
78
+ Returns:
79
+ tuple: (success, stdout, stderr)
80
+ """
81
+ try:
82
+ logging.info(f"Running {description} with {timeout_seconds}s timeout: {command_str}")
83
+
84
+ # Use shell=True to support complex command strings with pipes, etc.
85
+ process = subprocess.Popen(
86
+ command_str,
87
+ shell=True,
88
+ stdout=subprocess.PIPE,
89
+ stderr=subprocess.PIPE,
90
+ text=True
91
+ )
92
+
93
+ try:
94
+ stdout, stderr = process.communicate(timeout=timeout_seconds)
95
+ return_code = process.returncode
96
+
97
+ if return_code == 0:
98
+ logging.info(f"{description} completed successfully")
99
+ return True, stdout, stderr
100
+ else:
101
+ logging.error(f"{description} failed with return code {return_code}")
102
+ logging.error(f"stderr: {stderr}")
103
+ return False, stdout, stderr
104
+
105
+ except subprocess.TimeoutExpired:
106
+ logging.error(f"{description} timed out after {timeout_seconds} seconds")
107
+ process.kill()
108
+ try:
109
+ stdout, stderr = process.communicate(timeout=5) # Give 5 seconds to cleanup
110
+ except subprocess.TimeoutExpired:
111
+ stdout, stderr = "", "Process killed due to timeout"
112
+ return False, stdout, f"Timeout after {timeout_seconds} seconds. {stderr}"
113
+
114
+ except Exception as e:
115
+ logging.error(f"Error running {description}: {e}")
116
+ return False, "", str(e)
117
+
118
+
119
+ def calculate_process_timeout(command_str, buffer_timeout):
120
+ """
121
+ Calculate timeout for a process based on test-time parameter and buffer.
122
+
123
+ Args:
124
+ command_str: The command string to analyze
125
+ buffer_timeout: Buffer time to add to test-time
126
+
127
+ Returns:
128
+ int: Timeout in seconds
129
+ """
130
+ default_timeout = 300 # 5 minutes default
131
+
132
+ if "test-time" in command_str:
133
+ # Try to extract test time and add buffer
134
+ # Handle both --test-time (memtier) and -test-time (pubsub-sub-bench)
135
+ test_time_match = re.search(r"--?test-time[=\s]+(\d+)", command_str)
136
+ if test_time_match:
137
+ test_time = int(test_time_match.group(1))
138
+ timeout = test_time + buffer_timeout
139
+ logging.info(f"Set process timeout to {timeout}s (test-time: {test_time}s + {buffer_timeout}s buffer)")
140
+ return timeout
141
+
142
+ logging.info(f"Using default process timeout: {default_timeout}s")
143
+ return default_timeout
65
144
 
66
145
 
67
146
  def parse_size(size):
@@ -91,6 +170,31 @@ def parse_size(size):
91
170
  return int(number * units[unit])
92
171
 
93
172
 
173
+ def extract_expected_benchmark_duration(benchmark_command_str, override_memtier_test_time):
174
+ """
175
+ Extract expected benchmark duration from command string or override.
176
+
177
+ Args:
178
+ benchmark_command_str: The benchmark command string
179
+ override_memtier_test_time: Override test time value
180
+
181
+ Returns:
182
+ Expected duration in seconds, or 30 as default
183
+ """
184
+ if override_memtier_test_time > 0:
185
+ return override_memtier_test_time
186
+
187
+ # Try to extract test-time from command string
188
+ if "test-time" in benchmark_command_str:
189
+ # Handle both --test-time (memtier) and -test-time (pubsub-sub-bench)
190
+ test_time_match = re.search(r"--?test-time[=\s]+(\d+)", benchmark_command_str)
191
+ if test_time_match:
192
+ return int(test_time_match.group(1))
193
+
194
+ # Default duration if not found
195
+ return 30
196
+
197
+
94
198
  def run_multiple_clients(
95
199
  benchmark_config,
96
200
  docker_client,
@@ -181,6 +285,30 @@ def run_multiple_clients(
181
285
  unix_socket,
182
286
  None, # username
183
287
  )
288
+ elif "vector-db-benchmark" in client_tool:
289
+ (
290
+ _,
291
+ benchmark_command_str,
292
+ arbitrary_command,
293
+ client_env_vars,
294
+ ) = prepare_vector_db_benchmark_parameters(
295
+ client_config,
296
+ client_tool,
297
+ port,
298
+ host,
299
+ password,
300
+ local_benchmark_output_filename,
301
+ oss_cluster_api_enabled,
302
+ tls_enabled,
303
+ tls_skip_verify,
304
+ test_tls_cert,
305
+ test_tls_key,
306
+ test_tls_cacert,
307
+ resp_version,
308
+ override_memtier_test_time,
309
+ unix_socket,
310
+ None, # username
311
+ )
184
312
  else:
185
313
  # Handle other benchmark tools
186
314
  (
@@ -199,9 +327,8 @@ def run_multiple_clients(
199
327
 
200
328
  # Calculate container timeout
201
329
  container_timeout = 300 # 5 minutes default
202
- buffer_timeout = (
203
- args.container_timeout_buffer
204
- ) # Configurable buffer from command line
330
+ # Use new timeout_buffer argument, fallback to container_timeout_buffer for backward compatibility
331
+ buffer_timeout = getattr(args, 'timeout_buffer', getattr(args, 'container_timeout_buffer', 60))
205
332
  if "test-time" in benchmark_command_str:
206
333
  # Try to extract test time and add buffer
207
334
  import re
@@ -224,23 +351,51 @@ def run_multiple_clients(
224
351
  # Start container (detached)
225
352
  import os
226
353
 
227
- container = docker_client.containers.run(
228
- image=client_image,
229
- volumes={
230
- temporary_dir_client: {
231
- "bind": client_mnt_point,
232
- "mode": "rw",
233
- },
354
+ # Set working directory based on tool
355
+ working_dir = benchmark_tool_workdir
356
+ if "vector-db-benchmark" in client_tool:
357
+ working_dir = "/app" # vector-db-benchmark needs to run from /app
358
+
359
+ # Prepare container arguments
360
+ volumes = {
361
+ temporary_dir_client: {
362
+ "bind": client_mnt_point,
363
+ "mode": "rw",
234
364
  },
235
- auto_remove=False,
236
- privileged=True,
237
- working_dir=benchmark_tool_workdir,
238
- command=benchmark_command_str,
239
- network_mode="host",
240
- detach=True,
241
- cpuset_cpus=client_cpuset_cpus,
242
- user=f"{os.getuid()}:{os.getgid()}", # Run as current user to fix permissions
243
- )
365
+ }
366
+
367
+ # For vector-db-benchmark, also mount the results directory
368
+ if "vector-db-benchmark" in client_tool:
369
+ volumes[temporary_dir_client] = {
370
+ "bind": "/app/results",
371
+ "mode": "rw",
372
+ }
373
+
374
+ container_kwargs = {
375
+ "image": client_image,
376
+ "volumes": volumes,
377
+ "auto_remove": False,
378
+ "privileged": True,
379
+ "working_dir": working_dir,
380
+ "command": benchmark_command_str,
381
+ "network_mode": "host",
382
+ "detach": True,
383
+ "cpuset_cpus": client_cpuset_cpus,
384
+ }
385
+
386
+ # Only add user for non-vector-db-benchmark tools to avoid permission issues
387
+ if "vector-db-benchmark" not in client_tool:
388
+ container_kwargs["user"] = f"{os.getuid()}:{os.getgid()}"
389
+
390
+ # Add environment variables for vector-db-benchmark
391
+ if "vector-db-benchmark" in client_tool:
392
+ try:
393
+ container_kwargs["environment"] = client_env_vars
394
+ except NameError:
395
+ # client_env_vars not defined, skip environment variables
396
+ pass
397
+
398
+ container = docker_client.containers.run(**container_kwargs)
244
399
 
245
400
  containers.append(
246
401
  {
@@ -334,6 +489,7 @@ def run_multiple_clients(
334
489
  aggregated_json = {}
335
490
  memtier_json = None
336
491
  pubsub_json = None
492
+ vector_json = None
337
493
 
338
494
  for result in successful_results:
339
495
  client_index = result["client_index"]
@@ -360,6 +516,19 @@ def run_multiple_clients(
360
516
  logging.info(
361
517
  f"Successfully read pubsub-sub-bench JSON output from client {client_index}"
362
518
  )
519
+ elif "vector-db-benchmark" in tool:
520
+ # For vector-db-benchmark, look for summary JSON file
521
+ summary_files = [f for f in os.listdir(temporary_dir_client) if f.endswith("-summary.json")]
522
+ if summary_files:
523
+ summary_filepath = os.path.join(temporary_dir_client, summary_files[0])
524
+ try:
525
+ with open(summary_filepath, 'r') as f:
526
+ vector_json = json.load(f)
527
+ logging.info(f"Successfully read vector-db-benchmark JSON output from {summary_files[0]}")
528
+ except Exception as e:
529
+ logging.warning(f"Failed to read vector-db-benchmark JSON from {summary_files[0]}: {e}")
530
+ else:
531
+ logging.warning(f"No vector-db-benchmark summary JSON file found for client {client_index}")
363
532
 
364
533
  logging.info(
365
534
  f"Successfully read JSON output from client {client_index} ({tool})"
@@ -376,16 +545,32 @@ def run_multiple_clients(
376
545
  f"JSON output file not found for client {client_index}: {json_filepath}"
377
546
  )
378
547
 
379
- # Merge JSON outputs from both tools
380
- if memtier_json and pubsub_json:
548
+ # Merge JSON outputs from all tools
549
+ if memtier_json and pubsub_json and vector_json:
550
+ # Use memtier as base and add other metrics
551
+ aggregated_json = memtier_json.copy()
552
+ aggregated_json.update(pubsub_json)
553
+ aggregated_json.update(vector_json)
554
+ aggregated_stdout = json.dumps(aggregated_json, indent=2)
555
+ logging.info("Using merged JSON results from memtier, pubsub-sub-bench, and vector-db-benchmark clients")
556
+ elif memtier_json and pubsub_json:
381
557
  # Use memtier as base and add pubsub metrics
382
558
  aggregated_json = memtier_json.copy()
383
- # Add pubsub metrics to the aggregated result
384
559
  aggregated_json.update(pubsub_json)
385
560
  aggregated_stdout = json.dumps(aggregated_json, indent=2)
386
- logging.info(
387
- "Using merged JSON results from memtier and pubsub-sub-bench clients"
388
- )
561
+ logging.info("Using merged JSON results from memtier and pubsub-sub-bench clients")
562
+ elif memtier_json and vector_json:
563
+ # Use memtier as base and add vector metrics
564
+ aggregated_json = memtier_json.copy()
565
+ aggregated_json.update(vector_json)
566
+ aggregated_stdout = json.dumps(aggregated_json, indent=2)
567
+ logging.info("Using merged JSON results from memtier and vector-db-benchmark clients")
568
+ elif pubsub_json and vector_json:
569
+ # Use pubsub as base and add vector metrics
570
+ aggregated_json = pubsub_json.copy()
571
+ aggregated_json.update(vector_json)
572
+ aggregated_stdout = json.dumps(aggregated_json, indent=2)
573
+ logging.info("Using merged JSON results from pubsub-sub-bench and vector-db-benchmark clients")
389
574
  elif memtier_json:
390
575
  # Only memtier available
391
576
  aggregated_json = memtier_json
@@ -396,12 +581,15 @@ def run_multiple_clients(
396
581
  aggregated_json = pubsub_json
397
582
  aggregated_stdout = json.dumps(aggregated_json, indent=2)
398
583
  logging.info("Using JSON results from pubsub-sub-bench client only")
584
+ elif vector_json:
585
+ # Only vector-db-benchmark available
586
+ aggregated_json = vector_json
587
+ aggregated_stdout = json.dumps(aggregated_json, indent=2)
588
+ logging.info("Using JSON results from vector-db-benchmark client only")
399
589
  else:
400
590
  # Fall back to concatenated stdout
401
591
  aggregated_stdout = "\n".join([r["stdout"] for r in successful_results])
402
- logging.warning(
403
- "No JSON results found, falling back to concatenated stdout"
404
- )
592
+ logging.warning("No JSON results found, falling back to concatenated stdout")
405
593
 
406
594
  return aggregated_stdout, results
407
595
 
@@ -665,6 +853,71 @@ def prepare_memtier_benchmark_parameters(
665
853
  return None, benchmark_command_str, arbitrary_command
666
854
 
667
855
 
856
+ def prepare_vector_db_benchmark_parameters(
857
+ clientconfig,
858
+ full_benchmark_path,
859
+ port,
860
+ server,
861
+ password,
862
+ local_benchmark_output_filename,
863
+ oss_cluster_api_enabled=False,
864
+ tls_enabled=False,
865
+ tls_skip_verify=False,
866
+ tls_cert=None,
867
+ tls_key=None,
868
+ tls_cacert=None,
869
+ resp_version=None,
870
+ override_test_time=0,
871
+ unix_socket="",
872
+ username=None,
873
+ ):
874
+ """
875
+ Prepare vector-db-benchmark command parameters
876
+ """
877
+ arbitrary_command = False
878
+
879
+ benchmark_command = [
880
+ "/app/run.py",
881
+ "--host",
882
+ f"{server}",
883
+ ]
884
+
885
+ # Add port as environment variable (vector-db-benchmark uses env vars)
886
+ env_vars = {}
887
+ if port is not None:
888
+ env_vars["REDIS_PORT"] = str(port)
889
+ if password is not None:
890
+ env_vars["REDIS_AUTH"] = password
891
+ if username is not None:
892
+ env_vars["REDIS_USER"] = username
893
+
894
+ # Add engines parameter
895
+ engines = clientconfig.get("engines", "vectorsets-fp32-default")
896
+ benchmark_command.extend(["--engines", engines])
897
+
898
+ # Add datasets parameter
899
+ datasets = clientconfig.get("datasets", "random-100")
900
+ benchmark_command.extend(["--datasets", datasets])
901
+
902
+ # Add other optional parameters
903
+ if "parallels" in clientconfig:
904
+ benchmark_command.extend(["--parallels", str(clientconfig["parallels"])])
905
+
906
+ if "queries" in clientconfig:
907
+ benchmark_command.extend(["--queries", str(clientconfig["queries"])])
908
+
909
+ if "timeout" in clientconfig:
910
+ benchmark_command.extend(["--timeout", str(clientconfig["timeout"])])
911
+
912
+ # Add custom arguments if specified
913
+ if "arguments" in clientconfig:
914
+ benchmark_command_str = " ".join(benchmark_command) + " " + clientconfig["arguments"]
915
+ else:
916
+ benchmark_command_str = " ".join(benchmark_command)
917
+
918
+ return benchmark_command, benchmark_command_str, arbitrary_command, env_vars
919
+
920
+
668
921
  def prepare_pubsub_sub_bench_parameters(
669
922
  clientconfig,
670
923
  full_benchmark_path,
@@ -899,6 +1152,23 @@ def process_self_contained_coordinator_stream(
899
1152
  redis_pid = conn.info()["process_id"]
900
1153
  redis_pids.append(redis_pid)
901
1154
 
1155
+ # Check if all tested commands are supported by this Redis instance
1156
+ supported_commands = get_supported_redis_commands(redis_conns)
1157
+ commands_supported, unsupported_commands = check_test_command_support(
1158
+ benchmark_config, supported_commands
1159
+ )
1160
+
1161
+ if not commands_supported:
1162
+ logging.warning(
1163
+ f"Skipping test {test_name} due to unsupported commands: {unsupported_commands}"
1164
+ )
1165
+ delete_temporary_files(
1166
+ temporary_dir_client=temporary_dir_client,
1167
+ full_result_path=None,
1168
+ benchmark_tool_global=benchmark_tool_global,
1169
+ )
1170
+ continue
1171
+
902
1172
  github_actor = f"{tf_triggering_env}-{running_platform}"
903
1173
  dso = "redis-server"
904
1174
  profilers_artifacts_matrix = []
@@ -1047,6 +1317,9 @@ def process_self_contained_coordinator_stream(
1047
1317
  continue
1048
1318
  if "dbconfig" in benchmark_config:
1049
1319
  if "preload_tool" in benchmark_config["dbconfig"]:
1320
+ # Get timeout buffer for preload
1321
+ buffer_timeout = getattr(args, 'timeout_buffer', getattr(args, 'container_timeout_buffer', 60))
1322
+
1050
1323
  res = data_prepopulation_step(
1051
1324
  benchmark_config,
1052
1325
  benchmark_tool_workdir,
@@ -1067,6 +1340,7 @@ def process_self_contained_coordinator_stream(
1067
1340
  password,
1068
1341
  oss_cluster_api_enabled,
1069
1342
  unix_socket,
1343
+ buffer_timeout,
1070
1344
  )
1071
1345
  if res is False:
1072
1346
  logging.warning(
@@ -1208,6 +1482,30 @@ def process_self_contained_coordinator_stream(
1208
1482
  unix_socket,
1209
1483
  None, # username
1210
1484
  )
1485
+ elif "vector-db-benchmark" in benchmark_tool:
1486
+ (
1487
+ _,
1488
+ benchmark_command_str,
1489
+ arbitrary_command,
1490
+ env_vars,
1491
+ ) = prepare_vector_db_benchmark_parameters(
1492
+ benchmark_config["clientconfig"],
1493
+ full_benchmark_path,
1494
+ port,
1495
+ host,
1496
+ password,
1497
+ local_benchmark_output_filename,
1498
+ oss_cluster_api_enabled,
1499
+ tls_enabled,
1500
+ tls_skip_verify,
1501
+ test_tls_cert,
1502
+ test_tls_key,
1503
+ test_tls_cacert,
1504
+ resp_version,
1505
+ override_memtier_test_time,
1506
+ unix_socket,
1507
+ None, # username
1508
+ )
1211
1509
  else:
1212
1510
  # prepare the benchmark command for other tools
1213
1511
  (
@@ -1241,6 +1539,40 @@ def process_self_contained_coordinator_stream(
1241
1539
  profiler_call_graph_mode,
1242
1540
  )
1243
1541
 
1542
+ # start remote profiling if enabled
1543
+ remote_profiler = None
1544
+ if args.enable_remote_profiling:
1545
+ try:
1546
+ remote_profiler = RemoteProfiler(
1547
+ args.remote_profile_host,
1548
+ args.remote_profile_port,
1549
+ args.remote_profile_output_dir,
1550
+ args.remote_profile_username,
1551
+ args.remote_profile_password
1552
+ )
1553
+
1554
+ # Extract expected benchmark duration
1555
+ expected_duration = extract_expected_benchmark_duration(
1556
+ benchmark_command_str, override_memtier_test_time
1557
+ )
1558
+
1559
+ # Start remote profiling
1560
+ profiling_started = remote_profiler.start_profiling(
1561
+ redis_conns[0] if redis_conns else None,
1562
+ test_name,
1563
+ expected_duration
1564
+ )
1565
+
1566
+ if profiling_started:
1567
+ logging.info(f"Started remote profiling for test: {test_name}")
1568
+ else:
1569
+ logging.warning(f"Failed to start remote profiling for test: {test_name}")
1570
+ remote_profiler = None
1571
+
1572
+ except Exception as e:
1573
+ logging.error(f"Error starting remote profiling: {e}")
1574
+ remote_profiler = None
1575
+
1244
1576
  # run the benchmark
1245
1577
  benchmark_start_time = datetime.datetime.now()
1246
1578
 
@@ -1284,13 +1616,23 @@ def process_self_contained_coordinator_stream(
1284
1616
  + " "
1285
1617
  + benchmark_command_str
1286
1618
  )
1287
- logging.info(
1288
- "Running memtier benchmark command {}".format(
1289
- benchmark_command_str
1290
- )
1619
+
1620
+ # Calculate timeout for local process
1621
+ buffer_timeout = getattr(args, 'timeout_buffer', getattr(args, 'container_timeout_buffer', 60))
1622
+ process_timeout = calculate_process_timeout(benchmark_command_str, buffer_timeout)
1623
+
1624
+ # Run with timeout
1625
+ success, client_container_stdout, stderr = run_local_command_with_timeout(
1626
+ benchmark_command_str,
1627
+ process_timeout,
1628
+ "memtier benchmark"
1291
1629
  )
1292
- stream = os.popen(benchmark_command_str)
1293
- client_container_stdout = stream.read()
1630
+
1631
+ if not success:
1632
+ logging.error(f"Memtier benchmark failed: {stderr}")
1633
+ # Continue with the test but log the failure
1634
+ client_container_stdout = f"ERROR: {stderr}"
1635
+
1294
1636
  move_command = "mv {} {}".format(
1295
1637
  local_benchmark_output_filename, temporary_dir_client
1296
1638
  )
@@ -1305,22 +1647,53 @@ def process_self_contained_coordinator_stream(
1305
1647
  )
1306
1648
 
1307
1649
  # Use explicit container management for single client
1308
- container = docker_client.containers.run(
1309
- image=client_container_image,
1310
- volumes={
1311
- temporary_dir_client: {
1312
- "bind": client_mnt_point,
1313
- "mode": "rw",
1314
- },
1650
+ import os
1651
+
1652
+ # Set working directory based on tool
1653
+ working_dir = benchmark_tool_workdir
1654
+ if "vector-db-benchmark" in benchmark_tool:
1655
+ working_dir = "/app" # vector-db-benchmark needs to run from /app
1656
+
1657
+ # Prepare volumes
1658
+ volumes = {
1659
+ temporary_dir_client: {
1660
+ "bind": client_mnt_point,
1661
+ "mode": "rw",
1315
1662
  },
1316
- auto_remove=False,
1317
- privileged=True,
1318
- working_dir=benchmark_tool_workdir,
1319
- command=benchmark_command_str,
1320
- network_mode="host",
1321
- detach=True,
1322
- cpuset_cpus=client_cpuset_cpus,
1323
- )
1663
+ }
1664
+
1665
+ # For vector-db-benchmark, also mount the results directory
1666
+ if "vector-db-benchmark" in benchmark_tool:
1667
+ volumes[temporary_dir_client] = {
1668
+ "bind": "/app/results",
1669
+ "mode": "rw",
1670
+ }
1671
+
1672
+ container_kwargs = {
1673
+ "image": client_container_image,
1674
+ "volumes": volumes,
1675
+ "auto_remove": False,
1676
+ "privileged": True,
1677
+ "working_dir": working_dir,
1678
+ "command": benchmark_command_str,
1679
+ "network_mode": "host",
1680
+ "detach": True,
1681
+ "cpuset_cpus": client_cpuset_cpus,
1682
+ }
1683
+
1684
+ # Only add user for non-vector-db-benchmark tools to avoid permission issues
1685
+ if "vector-db-benchmark" not in benchmark_tool:
1686
+ container_kwargs["user"] = f"{os.getuid()}:{os.getgid()}"
1687
+
1688
+ # Add environment variables for vector-db-benchmark
1689
+ if "vector-db-benchmark" in benchmark_tool:
1690
+ try:
1691
+ container_kwargs["environment"] = env_vars
1692
+ except NameError:
1693
+ # env_vars not defined, skip environment variables
1694
+ pass
1695
+
1696
+ container = docker_client.containers.run(**container_kwargs)
1324
1697
 
1325
1698
  # Wait for container and get output
1326
1699
  try:
@@ -1370,7 +1743,25 @@ def process_self_contained_coordinator_stream(
1370
1743
  test_name,
1371
1744
  )
1372
1745
 
1746
+ # wait for remote profiling completion
1747
+ if remote_profiler is not None:
1748
+ try:
1749
+ logging.info("Waiting for remote profiling to complete...")
1750
+ profiling_success = remote_profiler.wait_for_completion(timeout=60)
1751
+ if profiling_success:
1752
+ logging.info("Remote profiling completed successfully")
1753
+ else:
1754
+ logging.warning("Remote profiling did not complete successfully")
1755
+ except Exception as e:
1756
+ logging.error(f"Error waiting for remote profiling completion: {e}")
1757
+
1373
1758
  logging.info("Printing client tool stdout output")
1759
+ if client_container_stdout:
1760
+ print("=== Container Output ===")
1761
+ print(client_container_stdout)
1762
+ print("=== End Container Output ===")
1763
+ else:
1764
+ logging.warning("No container output captured")
1374
1765
 
1375
1766
  used_memory_check(
1376
1767
  test_name,
@@ -1428,13 +1819,30 @@ def process_self_contained_coordinator_stream(
1428
1819
  full_result_path = "{}/{}".format(
1429
1820
  temporary_dir_client, local_benchmark_output_filename
1430
1821
  )
1822
+ elif "vector-db-benchmark" in benchmark_tool:
1823
+ # For vector-db-benchmark, look for summary JSON file
1824
+ import os
1825
+ summary_files = [f for f in os.listdir(temporary_dir_client) if f.endswith("-summary.json")]
1826
+ if summary_files:
1827
+ full_result_path = os.path.join(temporary_dir_client, summary_files[0])
1828
+ logging.info(f"Found vector-db-benchmark summary file: {summary_files[0]}")
1829
+ else:
1830
+ logging.warning("No vector-db-benchmark summary JSON file found")
1831
+ # Create empty results dict to avoid crash
1832
+ results_dict = {}
1833
+
1431
1834
  logging.info(f"Reading results json from {full_result_path}")
1432
1835
 
1433
- with open(
1434
- full_result_path,
1435
- "r",
1436
- ) as json_file:
1437
- results_dict = json.load(json_file)
1836
+ if "vector-db-benchmark" in benchmark_tool and not os.path.exists(full_result_path):
1837
+ # Handle case where vector-db-benchmark didn't produce results
1838
+ results_dict = {}
1839
+ logging.warning("Vector-db-benchmark did not produce results file")
1840
+ else:
1841
+ with open(
1842
+ full_result_path,
1843
+ "r",
1844
+ ) as json_file:
1845
+ results_dict = json.load(json_file)
1438
1846
  print_results_table_stdout(
1439
1847
  benchmark_config,
1440
1848
  default_metrics,
@@ -1654,7 +2062,32 @@ def print_results_table_stdout(
1654
2062
  ]
1655
2063
  results_matrix = extract_results_table(metrics, results_dict)
1656
2064
 
1657
- results_matrix = [[x[0], f"{x[3]:.3f}"] for x in results_matrix]
2065
+ # Use resolved metric name for precision_summary metrics, otherwise use original path
2066
+ def get_display_name(x):
2067
+ # For precision_summary metrics with wildcards, construct the resolved path
2068
+ if (len(x) > 1 and
2069
+ isinstance(x[0], str) and
2070
+ "precision_summary" in x[0] and
2071
+ "*" in x[0]):
2072
+
2073
+ # Look for the precision level in the cleaned metrics logs
2074
+ # We need to find the corresponding cleaned metric to get the precision level
2075
+ # For now, let's extract it from the time series logs that we know are working
2076
+ # The pattern is: replace "*" with the actual precision level
2077
+
2078
+ # Since we know from logs that the precision level is available,
2079
+ # let's reconstruct it from the metric context path (x[1]) if available
2080
+ if len(x) > 1 and isinstance(x[1], str) and x[1].startswith("'") and x[1].endswith("'"):
2081
+ precision_level = x[1] # This should be something like "'1.0000'"
2082
+ resolved_path = x[0].replace("*", precision_level)
2083
+ return resolved_path
2084
+
2085
+ return x[0] # Use original path
2086
+
2087
+ results_matrix = [
2088
+ [get_display_name(x), f"{x[3]:.3f}"]
2089
+ for x in results_matrix
2090
+ ]
1658
2091
  writer = MarkdownTableWriter(
1659
2092
  table_name=table_name,
1660
2093
  headers=results_matrix_headers,
@@ -1668,14 +2101,19 @@ def print_redis_info_section(redis_conns):
1668
2101
  if redis_conns is not None and len(redis_conns) > 0:
1669
2102
  try:
1670
2103
  redis_info = redis_conns[0].info()
2104
+ server_name = "redis"
2105
+ if "server_name" in redis_info:
2106
+ server_name = redis_info['server_name']
1671
2107
 
1672
2108
  print("\n# Redis Server Information")
1673
2109
  redis_info_data = [
1674
- ["Redis Version", redis_info.get("redis_version", "unknown")],
1675
- ["Redis Git SHA1", redis_info.get("redis_git_sha1", "unknown")],
1676
- ["Redis Git Dirty", str(redis_info.get("redis_git_dirty", "unknown"))],
1677
- ["Redis Build ID", redis_info.get("redis_build_id", "unknown")],
1678
- ["Redis Mode", redis_info.get("redis_mode", "unknown")],
2110
+ [f"{server_name} version", redis_info.get(f"{server_name}_version", "unknown")],
2111
+ ["redis version", redis_info.get("redis_version", "unknown")],
2112
+ ["io_threads_active", redis_info.get("io_threads_active", "unknown")],
2113
+ [f"{server_name} Git SHA1", redis_info.get("redis_git_sha1", "unknown")],
2114
+ [f"{server_name} Git Dirty", str(redis_info.get("redis_git_dirty", "unknown"))],
2115
+ [f"{server_name} Build ID", redis_info.get("redis_build_id", "unknown")],
2116
+ [f"{server_name} Mode", redis_info.get("redis_mode", "unknown")],
1679
2117
  ["OS", redis_info.get("os", "unknown")],
1680
2118
  ["Arch Bits", str(redis_info.get("arch_bits", "unknown"))],
1681
2119
  ["GCC Version", redis_info.get("gcc_version", "unknown")],
@@ -1703,6 +2141,78 @@ def print_redis_info_section(redis_conns):
1703
2141
  logging.warning(f"Failed to collect Redis server information: {e}")
1704
2142
 
1705
2143
 
2144
+ def get_supported_redis_commands(redis_conns):
2145
+ """Get list of supported Redis commands from the server"""
2146
+ if redis_conns is not None and len(redis_conns) > 0:
2147
+ try:
2148
+ # Execute COMMAND to get all supported commands
2149
+ commands_info = redis_conns[0].execute_command("COMMAND")
2150
+ logging.info(f"COMMAND response type: {type(commands_info)}, length: {len(commands_info) if hasattr(commands_info, '__len__') else 'N/A'}")
2151
+
2152
+ # Extract command names
2153
+ supported_commands = set()
2154
+
2155
+ if isinstance(commands_info, dict):
2156
+ # COMMAND response is a dict with command names as keys
2157
+ for cmd_name in commands_info.keys():
2158
+ if isinstance(cmd_name, bytes):
2159
+ cmd_name = cmd_name.decode('utf-8')
2160
+ supported_commands.add(str(cmd_name).upper())
2161
+ elif isinstance(commands_info, (list, tuple)):
2162
+ # Fallback for list format (first element of each command info array)
2163
+ for cmd_info in commands_info:
2164
+ if isinstance(cmd_info, (list, tuple)) and len(cmd_info) > 0:
2165
+ cmd_name = cmd_info[0]
2166
+ if isinstance(cmd_name, bytes):
2167
+ cmd_name = cmd_name.decode('utf-8')
2168
+ supported_commands.add(str(cmd_name).upper())
2169
+
2170
+ logging.info(f"Retrieved {len(supported_commands)} supported Redis commands")
2171
+
2172
+ # Log some sample commands for debugging
2173
+ if supported_commands:
2174
+ sample_commands = sorted(list(supported_commands))[:10]
2175
+ logging.info(f"Sample commands: {sample_commands}")
2176
+
2177
+ # Check specifically for vector commands
2178
+ vector_commands = [cmd for cmd in supported_commands if cmd.startswith('V')]
2179
+ if vector_commands:
2180
+ logging.info(f"Vector commands found: {sorted(vector_commands)}")
2181
+
2182
+ return supported_commands
2183
+ except Exception as e:
2184
+ logging.warning(f"Failed to get supported Redis commands: {e}")
2185
+ logging.warning("Proceeding without command validation")
2186
+ return None
2187
+ return None
2188
+
2189
+
2190
+ def check_test_command_support(benchmark_config, supported_commands):
2191
+ """Check if all tested-commands in the benchmark config are supported"""
2192
+ if supported_commands is None:
2193
+ logging.warning("No supported commands list available, skipping command check")
2194
+ return True, []
2195
+
2196
+ if "tested-commands" not in benchmark_config:
2197
+ logging.info("No tested-commands specified in benchmark config")
2198
+ return True, []
2199
+
2200
+ tested_commands = benchmark_config["tested-commands"]
2201
+ unsupported_commands = []
2202
+
2203
+ for cmd in tested_commands:
2204
+ cmd_upper = cmd.upper()
2205
+ if cmd_upper not in supported_commands:
2206
+ unsupported_commands.append(cmd)
2207
+
2208
+ if unsupported_commands:
2209
+ logging.warning(f"Unsupported commands found: {unsupported_commands}")
2210
+ return False, unsupported_commands
2211
+ else:
2212
+ logging.info(f"All tested commands are supported: {tested_commands}")
2213
+ return True, []
2214
+
2215
+
1706
2216
  def prepare_overall_total_test_results(
1707
2217
  benchmark_config,
1708
2218
  default_metrics,
@@ -1721,8 +2231,25 @@ def prepare_overall_total_test_results(
1721
2231
  None,
1722
2232
  )
1723
2233
  current_test_results_matrix = extract_results_table(metrics, results_dict)
2234
+
2235
+ # Use the same display name logic as in the individual test results
2236
+ def get_overall_display_name(x):
2237
+ # For precision_summary metrics with wildcards, construct the resolved path
2238
+ if (len(x) > 1 and
2239
+ isinstance(x[0], str) and
2240
+ "precision_summary" in x[0] and
2241
+ "*" in x[0]):
2242
+
2243
+ # Reconstruct resolved path from metric context path (x[1]) if available
2244
+ if len(x) > 1 and isinstance(x[1], str) and x[1].startswith("'") and x[1].endswith("'"):
2245
+ precision_level = x[1] # This should be something like "'1.0000'"
2246
+ resolved_path = x[0].replace("*", precision_level)
2247
+ return resolved_path
2248
+
2249
+ return x[0] # Use original path
2250
+
1724
2251
  current_test_results_matrix = [
1725
- [test_name, x[0], f"{x[3]:.3f}"] for x in current_test_results_matrix
2252
+ [test_name, get_overall_display_name(x), f"{x[3]:.3f}"] for x in current_test_results_matrix
1726
2253
  ]
1727
2254
  overall_results_matrix.extend(current_test_results_matrix)
1728
2255
 
@@ -1747,6 +2274,7 @@ def data_prepopulation_step(
1747
2274
  password=None,
1748
2275
  oss_cluster_api_enabled=False,
1749
2276
  unix_socket="",
2277
+ timeout_buffer=60,
1750
2278
  ):
1751
2279
  result = True
1752
2280
  # setup the benchmark
@@ -1808,13 +2336,21 @@ def data_prepopulation_step(
1808
2336
  preload_command_str = (
1809
2337
  "taskset -c " + client_cpuset_cpus + " " + preload_command_str
1810
2338
  )
1811
- logging.info(
1812
- "Pre-loading using memtier benchmark command {}".format(
1813
- preload_command_str
1814
- )
2339
+
2340
+ # Calculate timeout for preload process
2341
+ process_timeout = calculate_process_timeout(preload_command_str, timeout_buffer)
2342
+
2343
+ # Run with timeout
2344
+ success, client_container_stdout, stderr = run_local_command_with_timeout(
2345
+ preload_command_str,
2346
+ process_timeout,
2347
+ "memtier preload"
1815
2348
  )
1816
- stream = os.popen(preload_command_str)
1817
- client_container_stdout = stream.read()
2349
+
2350
+ if not success:
2351
+ logging.error(f"Memtier preload failed: {stderr}")
2352
+ result = False
2353
+ return result
1818
2354
 
1819
2355
  move_command = "mv {} {}".format(
1820
2356
  local_benchmark_output_filename, temporary_dir