redis-benchmarks-specification 0.1.281__py3-none-any.whl → 0.1.282__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.
- redis_benchmarks_specification/__common__/timeseries.py +14 -3
- redis_benchmarks_specification/__compare__/args.py +24 -0
- redis_benchmarks_specification/__compare__/compare.py +161 -59
- redis_benchmarks_specification/__runner__/args.py +6 -0
- redis_benchmarks_specification/__runner__/remote_profiling.py +77 -50
- redis_benchmarks_specification/__runner__/runner.py +190 -76
- {redis_benchmarks_specification-0.1.281.dist-info → redis_benchmarks_specification-0.1.282.dist-info}/METADATA +1 -1
- {redis_benchmarks_specification-0.1.281.dist-info → redis_benchmarks_specification-0.1.282.dist-info}/RECORD +11 -11
- {redis_benchmarks_specification-0.1.281.dist-info → redis_benchmarks_specification-0.1.282.dist-info}/LICENSE +0 -0
- {redis_benchmarks_specification-0.1.281.dist-info → redis_benchmarks_specification-0.1.282.dist-info}/WHEEL +0 -0
- {redis_benchmarks_specification-0.1.281.dist-info → redis_benchmarks_specification-0.1.282.dist-info}/entry_points.txt +0 -0
|
@@ -19,10 +19,10 @@ import requests
|
|
|
19
19
|
def extract_redis_pid(redis_conn) -> Optional[int]:
|
|
20
20
|
"""
|
|
21
21
|
Extract Redis process ID from Redis INFO command.
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
Args:
|
|
24
24
|
redis_conn: Redis connection object
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
Returns:
|
|
27
27
|
Redis process ID as integer, or None if not found
|
|
28
28
|
"""
|
|
@@ -43,10 +43,10 @@ def extract_redis_pid(redis_conn) -> Optional[int]:
|
|
|
43
43
|
def extract_redis_metadata(redis_conn) -> Dict[str, Any]:
|
|
44
44
|
"""
|
|
45
45
|
Extract Redis metadata for profile comments.
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
Args:
|
|
48
48
|
redis_conn: Redis connection object
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
Returns:
|
|
51
51
|
Dictionary containing Redis metadata
|
|
52
52
|
"""
|
|
@@ -60,18 +60,20 @@ def extract_redis_metadata(redis_conn) -> Dict[str, Any]:
|
|
|
60
60
|
"process_id": redis_info.get("process_id", "unknown"),
|
|
61
61
|
"tcp_port": redis_info.get("tcp_port", "unknown"),
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
# Use build_id if git_sha1 is empty or 0
|
|
65
65
|
if metadata["redis_git_sha1"] in ("", 0, "0"):
|
|
66
66
|
metadata["redis_git_sha1"] = metadata["redis_build_id"]
|
|
67
|
-
|
|
68
|
-
logging.info(
|
|
67
|
+
|
|
68
|
+
logging.info(
|
|
69
|
+
f"Extracted Redis metadata: version={metadata['redis_version']}, sha={metadata['redis_git_sha1']}, pid={metadata['process_id']}"
|
|
70
|
+
)
|
|
69
71
|
return metadata
|
|
70
72
|
except Exception as e:
|
|
71
73
|
logging.error(f"Failed to extract Redis metadata: {e}")
|
|
72
74
|
return {
|
|
73
75
|
"redis_version": "unknown",
|
|
74
|
-
"redis_git_sha1": "unknown",
|
|
76
|
+
"redis_git_sha1": "unknown",
|
|
75
77
|
"redis_git_dirty": "unknown",
|
|
76
78
|
"redis_build_id": "unknown",
|
|
77
79
|
"process_id": "unknown",
|
|
@@ -82,16 +84,18 @@ def extract_redis_metadata(redis_conn) -> Dict[str, Any]:
|
|
|
82
84
|
def calculate_profile_duration(benchmark_duration_seconds: int) -> int:
|
|
83
85
|
"""
|
|
84
86
|
Calculate profiling duration based on benchmark duration.
|
|
85
|
-
|
|
87
|
+
|
|
86
88
|
Args:
|
|
87
89
|
benchmark_duration_seconds: Expected benchmark duration in seconds
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
Returns:
|
|
90
92
|
Profiling duration in seconds (minimum: benchmark duration, maximum: 30)
|
|
91
93
|
"""
|
|
92
94
|
# Minimum duration is the benchmark duration, maximum is 30 seconds
|
|
93
95
|
duration = min(max(benchmark_duration_seconds, 10), 30)
|
|
94
|
-
logging.info(
|
|
96
|
+
logging.info(
|
|
97
|
+
f"Calculated profile duration: {duration}s (benchmark: {benchmark_duration_seconds}s)"
|
|
98
|
+
)
|
|
95
99
|
return duration
|
|
96
100
|
|
|
97
101
|
|
|
@@ -102,7 +106,7 @@ def trigger_remote_profile(
|
|
|
102
106
|
duration: int,
|
|
103
107
|
timeout: int = 60,
|
|
104
108
|
username: Optional[str] = None,
|
|
105
|
-
password: Optional[str] = None
|
|
109
|
+
password: Optional[str] = None,
|
|
106
110
|
) -> Optional[bytes]:
|
|
107
111
|
"""
|
|
108
112
|
Trigger remote profiling via HTTP GET request using pprof endpoint.
|
|
@@ -120,10 +124,7 @@ def trigger_remote_profile(
|
|
|
120
124
|
Profile content in pprof binary format, or None if failed
|
|
121
125
|
"""
|
|
122
126
|
url = f"http://{host}:{port}/debug/pprof/profile"
|
|
123
|
-
params = {
|
|
124
|
-
"pid": pid,
|
|
125
|
-
"seconds": duration
|
|
126
|
-
}
|
|
127
|
+
params = {"pid": pid, "seconds": duration}
|
|
127
128
|
|
|
128
129
|
# Prepare authentication if provided
|
|
129
130
|
auth = None
|
|
@@ -132,7 +133,9 @@ def trigger_remote_profile(
|
|
|
132
133
|
logging.info(f"Using HTTP basic authentication with username: {username}")
|
|
133
134
|
|
|
134
135
|
try:
|
|
135
|
-
logging.info(
|
|
136
|
+
logging.info(
|
|
137
|
+
f"Triggering remote profile: {url} with PID={pid}, duration={duration}s"
|
|
138
|
+
)
|
|
136
139
|
response = requests.get(url, params=params, timeout=timeout, auth=auth)
|
|
137
140
|
response.raise_for_status()
|
|
138
141
|
|
|
@@ -161,7 +164,7 @@ def save_profile_with_metadata(
|
|
|
161
164
|
benchmark_name: str,
|
|
162
165
|
output_dir: str,
|
|
163
166
|
redis_metadata: Dict[str, Any],
|
|
164
|
-
duration: int
|
|
167
|
+
duration: int,
|
|
165
168
|
) -> Optional[str]:
|
|
166
169
|
"""
|
|
167
170
|
Save profile content to file in pprof binary format.
|
|
@@ -188,7 +191,7 @@ def save_profile_with_metadata(
|
|
|
188
191
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
189
192
|
|
|
190
193
|
# Write binary profile content directly
|
|
191
|
-
with open(filepath,
|
|
194
|
+
with open(filepath, "wb") as f:
|
|
192
195
|
f.write(profile_content)
|
|
193
196
|
|
|
194
197
|
# Create a separate metadata file
|
|
@@ -209,7 +212,7 @@ def save_profile_with_metadata(
|
|
|
209
212
|
f"duration_seconds={duration}\n"
|
|
210
213
|
)
|
|
211
214
|
|
|
212
|
-
with open(metadata_filepath,
|
|
215
|
+
with open(metadata_filepath, "w") as f:
|
|
213
216
|
f.write(metadata_content)
|
|
214
217
|
|
|
215
218
|
logging.info(f"Saved profile to: {filepath}")
|
|
@@ -226,7 +229,14 @@ class RemoteProfiler:
|
|
|
226
229
|
Remote profiler class to handle threaded profiling execution.
|
|
227
230
|
"""
|
|
228
231
|
|
|
229
|
-
def __init__(
|
|
232
|
+
def __init__(
|
|
233
|
+
self,
|
|
234
|
+
host: str,
|
|
235
|
+
port: int,
|
|
236
|
+
output_dir: str,
|
|
237
|
+
username: Optional[str] = None,
|
|
238
|
+
password: Optional[str] = None,
|
|
239
|
+
):
|
|
230
240
|
self.host = host
|
|
231
241
|
self.port = port
|
|
232
242
|
self.output_dir = output_dir
|
|
@@ -235,21 +245,18 @@ class RemoteProfiler:
|
|
|
235
245
|
self.profile_thread = None
|
|
236
246
|
self.profile_result = None
|
|
237
247
|
self.profile_error = None
|
|
238
|
-
|
|
248
|
+
|
|
239
249
|
def start_profiling(
|
|
240
|
-
self,
|
|
241
|
-
redis_conn,
|
|
242
|
-
benchmark_name: str,
|
|
243
|
-
benchmark_duration_seconds: int
|
|
250
|
+
self, redis_conn, benchmark_name: str, benchmark_duration_seconds: int
|
|
244
251
|
) -> bool:
|
|
245
252
|
"""
|
|
246
253
|
Start profiling in a separate thread.
|
|
247
|
-
|
|
254
|
+
|
|
248
255
|
Args:
|
|
249
256
|
redis_conn: Redis connection object
|
|
250
257
|
benchmark_name: Name of the benchmark
|
|
251
258
|
benchmark_duration_seconds: Expected benchmark duration
|
|
252
|
-
|
|
259
|
+
|
|
253
260
|
Returns:
|
|
254
261
|
True if profiling thread started successfully, False otherwise
|
|
255
262
|
"""
|
|
@@ -257,44 +264,60 @@ class RemoteProfiler:
|
|
|
257
264
|
# Extract Redis metadata and PID
|
|
258
265
|
redis_metadata = extract_redis_metadata(redis_conn)
|
|
259
266
|
pid = redis_metadata.get("process_id")
|
|
260
|
-
|
|
267
|
+
|
|
261
268
|
if pid == "unknown" or pid is None:
|
|
262
269
|
logging.error("Cannot start remote profiling: Redis PID not available")
|
|
263
270
|
return False
|
|
264
|
-
|
|
271
|
+
|
|
265
272
|
# Calculate profiling duration
|
|
266
273
|
duration = calculate_profile_duration(benchmark_duration_seconds)
|
|
267
|
-
|
|
274
|
+
|
|
268
275
|
# Start profiling thread
|
|
269
276
|
self.profile_thread = threading.Thread(
|
|
270
277
|
target=self._profile_worker,
|
|
271
278
|
args=(pid, duration, benchmark_name, redis_metadata),
|
|
272
|
-
daemon=True
|
|
279
|
+
daemon=True,
|
|
273
280
|
)
|
|
274
281
|
self.profile_thread.start()
|
|
275
|
-
|
|
276
|
-
logging.info(
|
|
282
|
+
|
|
283
|
+
logging.info(
|
|
284
|
+
f"Started remote profiling thread for benchmark: {benchmark_name}"
|
|
285
|
+
)
|
|
277
286
|
return True
|
|
278
|
-
|
|
287
|
+
|
|
279
288
|
except Exception as e:
|
|
280
289
|
logging.error(f"Failed to start remote profiling: {e}")
|
|
281
290
|
return False
|
|
282
|
-
|
|
283
|
-
def _profile_worker(
|
|
291
|
+
|
|
292
|
+
def _profile_worker(
|
|
293
|
+
self,
|
|
294
|
+
pid: int,
|
|
295
|
+
duration: int,
|
|
296
|
+
benchmark_name: str,
|
|
297
|
+
redis_metadata: Dict[str, Any],
|
|
298
|
+
):
|
|
284
299
|
"""
|
|
285
300
|
Worker function for profiling thread.
|
|
286
301
|
"""
|
|
287
302
|
try:
|
|
288
303
|
# Trigger remote profiling
|
|
289
304
|
profile_content = trigger_remote_profile(
|
|
290
|
-
self.host,
|
|
291
|
-
|
|
305
|
+
self.host,
|
|
306
|
+
self.port,
|
|
307
|
+
pid,
|
|
308
|
+
duration,
|
|
309
|
+
username=self.username,
|
|
310
|
+
password=self.password,
|
|
292
311
|
)
|
|
293
312
|
|
|
294
313
|
if profile_content is not None:
|
|
295
314
|
# Save profile with metadata
|
|
296
315
|
filepath = save_profile_with_metadata(
|
|
297
|
-
profile_content,
|
|
316
|
+
profile_content,
|
|
317
|
+
benchmark_name,
|
|
318
|
+
self.output_dir,
|
|
319
|
+
redis_metadata,
|
|
320
|
+
duration,
|
|
298
321
|
)
|
|
299
322
|
self.profile_result = filepath
|
|
300
323
|
else:
|
|
@@ -303,38 +326,42 @@ class RemoteProfiler:
|
|
|
303
326
|
except Exception as e:
|
|
304
327
|
self.profile_error = f"Profile worker error: {e}"
|
|
305
328
|
logging.error(self.profile_error)
|
|
306
|
-
|
|
329
|
+
|
|
307
330
|
def wait_for_completion(self, timeout: int = 60) -> bool:
|
|
308
331
|
"""
|
|
309
332
|
Wait for profiling thread to complete.
|
|
310
|
-
|
|
333
|
+
|
|
311
334
|
Args:
|
|
312
335
|
timeout: Maximum time to wait in seconds
|
|
313
|
-
|
|
336
|
+
|
|
314
337
|
Returns:
|
|
315
338
|
True if completed successfully, False if timed out or failed
|
|
316
339
|
"""
|
|
317
340
|
if self.profile_thread is None:
|
|
318
341
|
return False
|
|
319
|
-
|
|
342
|
+
|
|
320
343
|
try:
|
|
321
344
|
self.profile_thread.join(timeout=timeout)
|
|
322
|
-
|
|
345
|
+
|
|
323
346
|
if self.profile_thread.is_alive():
|
|
324
|
-
logging.warning(
|
|
347
|
+
logging.warning(
|
|
348
|
+
f"Remote profiling thread did not complete within {timeout}s"
|
|
349
|
+
)
|
|
325
350
|
return False
|
|
326
|
-
|
|
351
|
+
|
|
327
352
|
if self.profile_error:
|
|
328
353
|
logging.error(f"Remote profiling failed: {self.profile_error}")
|
|
329
354
|
return False
|
|
330
|
-
|
|
355
|
+
|
|
331
356
|
if self.profile_result:
|
|
332
|
-
logging.info(
|
|
357
|
+
logging.info(
|
|
358
|
+
f"Remote profiling completed successfully: {self.profile_result}"
|
|
359
|
+
)
|
|
333
360
|
return True
|
|
334
361
|
else:
|
|
335
362
|
logging.warning("Remote profiling completed but no result available")
|
|
336
363
|
return False
|
|
337
|
-
|
|
364
|
+
|
|
338
365
|
except Exception as e:
|
|
339
366
|
logging.error(f"Error waiting for remote profiling completion: {e}")
|
|
340
367
|
return False
|