redis-benchmarks-specification 0.1.280__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.

@@ -3,7 +3,7 @@ Remote profiling utilities for Redis benchmark runner.
3
3
 
4
4
  This module provides functionality to trigger remote profiling of Redis processes
5
5
  via HTTP GET endpoints during benchmark execution. Profiles are collected in
6
- folded format for performance analysis.
6
+ pprof binary format for performance analysis.
7
7
  """
8
8
 
9
9
  import datetime
@@ -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(f"Extracted Redis metadata: version={metadata['redis_version']}, sha={metadata['redis_git_sha1']}, pid={metadata['process_id']}")
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(f"Calculated profile duration: {duration}s (benchmark: {benchmark_duration_seconds}s)")
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,10 +106,10 @@ def trigger_remote_profile(
102
106
  duration: int,
103
107
  timeout: int = 60,
104
108
  username: Optional[str] = None,
105
- password: Optional[str] = None
106
- ) -> Optional[str]:
109
+ password: Optional[str] = None,
110
+ ) -> Optional[bytes]:
107
111
  """
108
- Trigger remote profiling via HTTP GET request.
112
+ Trigger remote profiling via HTTP GET request using pprof endpoint.
109
113
 
110
114
  Args:
111
115
  host: Remote host address
@@ -117,13 +121,10 @@ def trigger_remote_profile(
117
121
  password: Optional password for HTTP basic authentication
118
122
 
119
123
  Returns:
120
- Profile content in folded format, or None if failed
124
+ Profile content in pprof binary format, or None if failed
121
125
  """
122
- url = f"http://{host}:{port}/debug/folded/profile"
123
- params = {
124
- "pid": pid,
125
- "seconds": duration
126
- }
126
+ url = f"http://{host}:{port}/debug/pprof/profile"
127
+ params = {"pid": pid, "seconds": duration}
127
128
 
128
129
  # Prepare authentication if provided
129
130
  auth = None
@@ -132,12 +133,14 @@ def trigger_remote_profile(
132
133
  logging.info(f"Using HTTP basic authentication with username: {username}")
133
134
 
134
135
  try:
135
- logging.info(f"Triggering remote profile: {url} with PID={pid}, duration={duration}s")
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
 
139
- profile_content = response.text
140
- logging.info(f"Successfully collected profile: {len(profile_content)} characters")
142
+ profile_content = response.content
143
+ logging.info(f"Successfully collected profile: {len(profile_content)} bytes")
141
144
  return profile_content
142
145
 
143
146
  except requests.exceptions.Timeout:
@@ -157,55 +160,65 @@ def trigger_remote_profile(
157
160
 
158
161
 
159
162
  def save_profile_with_metadata(
160
- profile_content: str,
163
+ profile_content: bytes,
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
- Save profile content to file with metadata comments.
168
-
170
+ Save profile content to file in pprof binary format.
171
+
169
172
  Args:
170
- profile_content: Profile data in folded format
173
+ profile_content: Profile data in pprof binary format
171
174
  benchmark_name: Name of the benchmark
172
175
  output_dir: Output directory path
173
176
  redis_metadata: Redis metadata dictionary
174
177
  duration: Profiling duration in seconds
175
-
178
+
176
179
  Returns:
177
180
  Path to saved file, or None if failed
178
181
  """
179
182
  try:
180
183
  # Create output directory if it doesn't exist
181
184
  Path(output_dir).mkdir(parents=True, exist_ok=True)
182
-
183
- # Generate filename
184
- filename = f"{benchmark_name}.folded"
185
+
186
+ # Generate filename with .pb.gz extension
187
+ filename = f"{benchmark_name}.pb.gz"
185
188
  filepath = os.path.join(output_dir, filename)
186
-
189
+
187
190
  # Generate timestamp
188
191
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
189
-
190
- # Create metadata comment
191
- metadata_comment = (
192
- f"# profile from redis sha = {redis_metadata['redis_git_sha1']} "
192
+
193
+ # Write binary profile content directly
194
+ with open(filepath, "wb") as f:
195
+ f.write(profile_content)
196
+
197
+ # Create a separate metadata file
198
+ metadata_filename = f"{benchmark_name}.metadata.txt"
199
+ metadata_filepath = os.path.join(output_dir, metadata_filename)
200
+
201
+ metadata_content = (
202
+ f"Profile from redis sha = {redis_metadata['redis_git_sha1']} "
193
203
  f"and pid {redis_metadata['process_id']} for duration of {duration}s. "
194
- f"collection in date {timestamp}\n"
195
- f"# benchmark_name={benchmark_name}\n"
196
- f"# redis_git_sha1={redis_metadata['redis_git_sha1']}\n"
197
- f"# redis_version={redis_metadata['redis_version']}\n"
198
- f"# redis_git_dirty={redis_metadata['redis_git_dirty']}\n"
204
+ f"Collection date: {timestamp}\n"
205
+ f"benchmark_name={benchmark_name}\n"
206
+ f"redis_git_sha1={redis_metadata['redis_git_sha1']}\n"
207
+ f"redis_version={redis_metadata['redis_version']}\n"
208
+ f"redis_git_dirty={redis_metadata['redis_git_dirty']}\n"
209
+ f"redis_build_id={redis_metadata['redis_build_id']}\n"
210
+ f"process_id={redis_metadata['process_id']}\n"
211
+ f"tcp_port={redis_metadata['tcp_port']}\n"
212
+ f"duration_seconds={duration}\n"
199
213
  )
200
-
201
- # Write file with metadata and profile content
202
- with open(filepath, 'w') as f:
203
- f.write(metadata_comment)
204
- f.write(profile_content)
205
-
214
+
215
+ with open(metadata_filepath, "w") as f:
216
+ f.write(metadata_content)
217
+
206
218
  logging.info(f"Saved profile to: {filepath}")
219
+ logging.info(f"Saved metadata to: {metadata_filepath}")
207
220
  return filepath
208
-
221
+
209
222
  except Exception as e:
210
223
  logging.error(f"Failed to save profile file: {e}")
211
224
  return None
@@ -216,7 +229,14 @@ class RemoteProfiler:
216
229
  Remote profiler class to handle threaded profiling execution.
217
230
  """
218
231
 
219
- def __init__(self, host: str, port: int, output_dir: str, username: Optional[str] = None, password: Optional[str] = None):
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
+ ):
220
240
  self.host = host
221
241
  self.port = port
222
242
  self.output_dir = output_dir
@@ -225,21 +245,18 @@ class RemoteProfiler:
225
245
  self.profile_thread = None
226
246
  self.profile_result = None
227
247
  self.profile_error = None
228
-
248
+
229
249
  def start_profiling(
230
- self,
231
- redis_conn,
232
- benchmark_name: str,
233
- benchmark_duration_seconds: int
250
+ self, redis_conn, benchmark_name: str, benchmark_duration_seconds: int
234
251
  ) -> bool:
235
252
  """
236
253
  Start profiling in a separate thread.
237
-
254
+
238
255
  Args:
239
256
  redis_conn: Redis connection object
240
257
  benchmark_name: Name of the benchmark
241
258
  benchmark_duration_seconds: Expected benchmark duration
242
-
259
+
243
260
  Returns:
244
261
  True if profiling thread started successfully, False otherwise
245
262
  """
@@ -247,44 +264,60 @@ class RemoteProfiler:
247
264
  # Extract Redis metadata and PID
248
265
  redis_metadata = extract_redis_metadata(redis_conn)
249
266
  pid = redis_metadata.get("process_id")
250
-
267
+
251
268
  if pid == "unknown" or pid is None:
252
269
  logging.error("Cannot start remote profiling: Redis PID not available")
253
270
  return False
254
-
271
+
255
272
  # Calculate profiling duration
256
273
  duration = calculate_profile_duration(benchmark_duration_seconds)
257
-
274
+
258
275
  # Start profiling thread
259
276
  self.profile_thread = threading.Thread(
260
277
  target=self._profile_worker,
261
278
  args=(pid, duration, benchmark_name, redis_metadata),
262
- daemon=True
279
+ daemon=True,
263
280
  )
264
281
  self.profile_thread.start()
265
-
266
- logging.info(f"Started remote profiling thread for benchmark: {benchmark_name}")
282
+
283
+ logging.info(
284
+ f"Started remote profiling thread for benchmark: {benchmark_name}"
285
+ )
267
286
  return True
268
-
287
+
269
288
  except Exception as e:
270
289
  logging.error(f"Failed to start remote profiling: {e}")
271
290
  return False
272
-
273
- def _profile_worker(self, pid: int, duration: int, benchmark_name: str, redis_metadata: Dict[str, Any]):
291
+
292
+ def _profile_worker(
293
+ self,
294
+ pid: int,
295
+ duration: int,
296
+ benchmark_name: str,
297
+ redis_metadata: Dict[str, Any],
298
+ ):
274
299
  """
275
300
  Worker function for profiling thread.
276
301
  """
277
302
  try:
278
303
  # Trigger remote profiling
279
304
  profile_content = trigger_remote_profile(
280
- self.host, self.port, pid, duration,
281
- username=self.username, password=self.password
305
+ self.host,
306
+ self.port,
307
+ pid,
308
+ duration,
309
+ username=self.username,
310
+ password=self.password,
282
311
  )
283
312
 
284
- if profile_content:
313
+ if profile_content is not None:
285
314
  # Save profile with metadata
286
315
  filepath = save_profile_with_metadata(
287
- profile_content, benchmark_name, self.output_dir, redis_metadata, duration
316
+ profile_content,
317
+ benchmark_name,
318
+ self.output_dir,
319
+ redis_metadata,
320
+ duration,
288
321
  )
289
322
  self.profile_result = filepath
290
323
  else:
@@ -293,38 +326,42 @@ class RemoteProfiler:
293
326
  except Exception as e:
294
327
  self.profile_error = f"Profile worker error: {e}"
295
328
  logging.error(self.profile_error)
296
-
329
+
297
330
  def wait_for_completion(self, timeout: int = 60) -> bool:
298
331
  """
299
332
  Wait for profiling thread to complete.
300
-
333
+
301
334
  Args:
302
335
  timeout: Maximum time to wait in seconds
303
-
336
+
304
337
  Returns:
305
338
  True if completed successfully, False if timed out or failed
306
339
  """
307
340
  if self.profile_thread is None:
308
341
  return False
309
-
342
+
310
343
  try:
311
344
  self.profile_thread.join(timeout=timeout)
312
-
345
+
313
346
  if self.profile_thread.is_alive():
314
- logging.warning(f"Remote profiling thread did not complete within {timeout}s")
347
+ logging.warning(
348
+ f"Remote profiling thread did not complete within {timeout}s"
349
+ )
315
350
  return False
316
-
351
+
317
352
  if self.profile_error:
318
353
  logging.error(f"Remote profiling failed: {self.profile_error}")
319
354
  return False
320
-
355
+
321
356
  if self.profile_result:
322
- logging.info(f"Remote profiling completed successfully: {self.profile_result}")
357
+ logging.info(
358
+ f"Remote profiling completed successfully: {self.profile_result}"
359
+ )
323
360
  return True
324
361
  else:
325
362
  logging.warning("Remote profiling completed but no result available")
326
363
  return False
327
-
364
+
328
365
  except Exception as e:
329
366
  logging.error(f"Error waiting for remote profiling completion: {e}")
330
367
  return False