wafer-cli 0.2.14__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.
@@ -0,0 +1,490 @@
1
+ """ROCprof-Compute - CLI wrapper for rocprof-compute tool.
2
+
3
+ This module provides the CLI wrapper for the `wafer rocprof-compute` command.
4
+ It supports multiple subcommands:
5
+ - check: Check rocprof-compute installation
6
+ - profile: Run profiling on a command
7
+ - analyze: Analyze existing workload data
8
+ - gui: Launch GUI viewer for analyzing results
9
+ - list-metrics: List available metrics for architecture
10
+
11
+ This follows the design in Wafer-391: ROCprofiler Tools Architecture.
12
+ Architecture follows similar patterns from the codebase.
13
+ """
14
+
15
+ import json
16
+ import subprocess
17
+ import sys
18
+ from dataclasses import asdict
19
+ from pathlib import Path
20
+
21
+
22
+ def print_usage() -> None:
23
+ """Print CLI usage information."""
24
+ print("Usage: wafer rocprof-compute <subcommand> [options]", file=sys.stderr)
25
+ print("", file=sys.stderr)
26
+ print("Subcommands:", file=sys.stderr)
27
+ print(" check Check rocprof-compute installation status", file=sys.stderr)
28
+ print(" profile COMMAND Profile a command", file=sys.stderr)
29
+ print(" analyze PATH Analyze existing workload", file=sys.stderr)
30
+ print(" gui <folder> Launch GUI viewer for profiling results", file=sys.stderr)
31
+ print(" list-metrics ARCH List metrics for architecture", file=sys.stderr)
32
+ print("", file=sys.stderr)
33
+ print("Profile Options:", file=sys.stderr)
34
+ print(" --name NAME Workload name (required)", file=sys.stderr)
35
+ print(" --path DIR Workload base path", file=sys.stderr)
36
+ print(" --kernel K1,K2 Kernel name filter", file=sys.stderr)
37
+ print(" --dispatch D1,D2 Dispatch ID filter", file=sys.stderr)
38
+ print(" --block B1,B2 Hardware block filter", file=sys.stderr)
39
+ print(" --no-roof Skip roofline data", file=sys.stderr)
40
+ print(" --roof-only Profile roofline only (fastest)", file=sys.stderr)
41
+ print(" --hip-trace Enable HIP trace", file=sys.stderr)
42
+ print("", file=sys.stderr)
43
+ print("Analyze Options:", file=sys.stderr)
44
+ print(" --list-stats List all detected kernels and dispatches", file=sys.stderr)
45
+ print("", file=sys.stderr)
46
+ print("GUI Options:", file=sys.stderr)
47
+ print(" --port PORT Port for GUI server (default: 8050)", file=sys.stderr)
48
+ print(" --json Output result as JSON", file=sys.stderr)
49
+ print(" --external Use external rocprof-compute binary (default: bundled)", file=sys.stderr)
50
+ print("", file=sys.stderr)
51
+ print("Examples:", file=sys.stderr)
52
+ print(" wafer rocprof-compute check", file=sys.stderr)
53
+ print(" wafer rocprof-compute profile --name vcopy -- './vcopy -n 1048576'", file=sys.stderr)
54
+ print(" wafer rocprof-compute profile --name vcopy --roof-only -- './vcopy -n 1048576'", file=sys.stderr)
55
+ print(" wafer rocprof-compute analyze ./workloads/vcopy", file=sys.stderr)
56
+ print(" wafer rocprof-compute analyze ./workloads/vcopy --list-stats", file=sys.stderr)
57
+ print(" wafer rocprof-compute gui ./workloads/vcopy", file=sys.stderr)
58
+ print(" wafer rocprof-compute list-metrics gfx90a", file=sys.stderr)
59
+
60
+
61
+ def check_command(json_output: bool = False) -> str:
62
+ """CLI wrapper for checking rocprof-compute installation.
63
+
64
+ Args:
65
+ json_output: If True, return JSON; otherwise print human-readable
66
+
67
+ Returns:
68
+ Status message or JSON string
69
+ """
70
+ from dataclasses import asdict
71
+
72
+ from wafer_core.lib.rocprofiler.compute import (
73
+ check_installation as core_check, # pragma: no cover
74
+ )
75
+
76
+ result = core_check()
77
+
78
+ if json_output:
79
+ result_dict = asdict(result) if hasattr(result, "__dataclass_fields__") else result
80
+ return json.dumps(result_dict, indent=2)
81
+ else:
82
+ if result.installed:
83
+ print("✓ rocprof-compute is installed", file=sys.stderr)
84
+ if result.path:
85
+ print(f" Path: {result.path}", file=sys.stderr)
86
+ if result.version:
87
+ print(f" Version: {result.version}", file=sys.stderr)
88
+ return "rocprof-compute is installed"
89
+ else:
90
+ print("✗ rocprof-compute is not installed", file=sys.stderr)
91
+ print("", file=sys.stderr)
92
+ print("rocprof-compute is required to use this feature.", file=sys.stderr)
93
+ print("", file=sys.stderr)
94
+ print("Installation options:", file=sys.stderr)
95
+ print(" 1. Install ROCm toolkit (includes rocprof-compute):", file=sys.stderr)
96
+ print(" sudo apt-get install rocm-dev", file=sys.stderr)
97
+ print("", file=sys.stderr)
98
+ print(" 2. Install rocprofiler-compute package:", file=sys.stderr)
99
+ print(" sudo apt-get install rocprofiler-compute", file=sys.stderr)
100
+ print("", file=sys.stderr)
101
+ print(" 3. Add ROCm to PATH if already installed:", file=sys.stderr)
102
+ print(" export PATH=/opt/rocm/bin:$PATH", file=sys.stderr)
103
+ print("", file=sys.stderr)
104
+ if result.install_command:
105
+ print(f"Suggested command: {result.install_command}", file=sys.stderr)
106
+ return "rocprof-compute is not installed"
107
+
108
+
109
+ def check_installation() -> dict:
110
+ """Legacy function for backward compatibility."""
111
+ from dataclasses import asdict
112
+
113
+ from wafer_core.lib.rocprofiler.compute import (
114
+ check_installation as core_check, # pragma: no cover
115
+ )
116
+
117
+ result = core_check()
118
+ if hasattr(result, "__dataclass_fields__"):
119
+ return asdict(result)
120
+ elif hasattr(result, "__dict__"):
121
+ return result.__dict__
122
+ return result
123
+
124
+
125
+ def gui_command(
126
+ folder_path: str,
127
+ port: int = 8050,
128
+ json_output: bool = False,
129
+ use_bundled: bool = True,
130
+ ) -> str:
131
+ """Launch rocprof-compute analyze GUI.
132
+
133
+ By default, uses the bundled GUI viewer (GPU-agnostic, works on any platform).
134
+ Can optionally use external rocprof-compute binary if installed.
135
+
136
+ Args:
137
+ folder_path: Path to folder containing ROCprofiler results
138
+ port: Port number for the GUI server (default: 8050)
139
+ json_output: If True, return JSON with status; otherwise launch the GUI
140
+ use_bundled: If True, use bundled GUI viewer (default: True)
141
+ If False, use external rocprof-compute binary
142
+
143
+ Returns:
144
+ Success message or JSON output
145
+
146
+ Raises:
147
+ FileNotFoundError: If folder doesn't exist
148
+ RuntimeError: If launch fails
149
+ """
150
+ from dataclasses import asdict
151
+
152
+ if use_bundled:
153
+ # Use bundled GUI viewer (no rocprof-compute needed)
154
+ from wafer_core.lib.rocprofiler.compute import launch_gui_server # pragma: no cover
155
+
156
+ # For JSON mode, run in background to return immediately
157
+ # For interactive mode, run in foreground (blocking)
158
+ launch_result = launch_gui_server(folder_path, port, background=json_output)
159
+
160
+ if not launch_result.success:
161
+ raise RuntimeError(launch_result.error or "Failed to launch bundled GUI")
162
+
163
+ result_dict = asdict(launch_result)
164
+ result_dict["command"] = "bundled-gui-viewer"
165
+
166
+ if json_output:
167
+ return json.dumps(result_dict, indent=2)
168
+ else:
169
+ print("Launching bundled rocprof-compute GUI viewer...", file=sys.stderr)
170
+ print(f"Folder: {launch_result.folder}", file=sys.stderr)
171
+ print(f"Port: {launch_result.port}", file=sys.stderr)
172
+ print(f"URL: {launch_result.url}", file=sys.stderr)
173
+ print("", file=sys.stderr)
174
+ print(f"Open {launch_result.url} in your browser", file=sys.stderr)
175
+ print("Press Ctrl+C to stop the server", file=sys.stderr)
176
+
177
+ # The launch_gui_server with background=False is blocking, so we never reach here
178
+ # unless there's an error
179
+ return "GUI server stopped."
180
+ else:
181
+ # Legacy: use external rocprof-compute binary
182
+ from wafer_core.lib.rocprofiler.compute import launch_gui as core_launch # pragma: no cover
183
+
184
+ launch_result = core_launch(folder_path, port)
185
+
186
+ if not launch_result.success:
187
+ raise RuntimeError(launch_result.error or "Failed to launch GUI")
188
+
189
+ result_dict = asdict(launch_result)
190
+ result_dict["command"] = " ".join(launch_result.command or [])
191
+
192
+ if json_output:
193
+ return json.dumps(result_dict, indent=2)
194
+ else:
195
+ print("Launching external rocprof-compute GUI...", file=sys.stderr)
196
+ print(f"Folder: {launch_result.folder}", file=sys.stderr)
197
+ print(f"Port: {launch_result.port}", file=sys.stderr)
198
+ print(f"URL: {launch_result.url}", file=sys.stderr)
199
+ print(f"Command: {result_dict['command']}", file=sys.stderr)
200
+ print("", file=sys.stderr)
201
+
202
+ try:
203
+ subprocess.run(launch_result.command, check=True)
204
+ except subprocess.CalledProcessError as e:
205
+ raise RuntimeError(f"rocprof-compute failed with exit code {e.returncode}")
206
+ except KeyboardInterrupt:
207
+ print("\nGUI server stopped.", file=sys.stderr)
208
+
209
+ return "GUI server stopped."
210
+
211
+
212
+ def launch_gui(
213
+ folder_path: str,
214
+ port: int = 8050,
215
+ json_output: bool = False,
216
+ use_bundled: bool = True,
217
+ ) -> str:
218
+ """Legacy function for backward compatibility. Use gui_command() instead."""
219
+ return gui_command(folder_path, port, json_output, use_bundled)
220
+
221
+
222
+ def profile_command(
223
+ command: str,
224
+ name: str,
225
+ path: str | None = None,
226
+ kernel: str | None = None,
227
+ dispatch: str | None = None,
228
+ block: str | None = None,
229
+ no_roof: bool = False,
230
+ roof_only: bool = False,
231
+ hip_trace: bool = False,
232
+ verbose: int = 0,
233
+ json_output: bool = False,
234
+ ) -> str:
235
+ """Run rocprof-compute profiling.
236
+
237
+ Args:
238
+ command: Shell command to profile
239
+ name: Workload name
240
+ path: Base path for workload directory
241
+ kernel: Comma-separated kernel name filters
242
+ dispatch: Comma-separated dispatch ID filters
243
+ block: Comma-separated hardware block filters
244
+ no_roof: Skip roofline data collection
245
+ roof_only: Profile roofline data only (fastest)
246
+ hip_trace: Enable HIP trace
247
+ verbose: Verbosity level (0-3)
248
+ json_output: Return JSON output
249
+
250
+ Returns:
251
+ Success message or JSON string
252
+
253
+ Raises:
254
+ RuntimeError: If profiling fails
255
+ """
256
+ import shlex
257
+
258
+ from wafer_core.lib.rocprofiler.compute import run_profile # pragma: no cover
259
+
260
+ # Parse command string
261
+ cmd_list = shlex.split(command)
262
+
263
+ # Parse filter lists
264
+ kernel_list = kernel.split(",") if kernel else None
265
+ dispatch_list = [int(d) for d in dispatch.split(",")] if dispatch else None
266
+ block_list = block.split(",") if block else None
267
+
268
+ result = run_profile(
269
+ target_command=cmd_list,
270
+ workload_name=name,
271
+ workload_path=Path(path) if path else None,
272
+ kernel_filter=kernel_list,
273
+ dispatch_filter=dispatch_list,
274
+ block_filter=block_list,
275
+ no_roof=no_roof,
276
+ roof_only=roof_only,
277
+ hip_trace=hip_trace,
278
+ verbose=verbose,
279
+ )
280
+
281
+ if json_output:
282
+ result_dict = asdict(result)
283
+ return json.dumps(result_dict, indent=2)
284
+ else:
285
+ if result.success:
286
+ print("✓ Profiling completed", file=sys.stderr)
287
+ if result.workload_path:
288
+ print(f" Workload: {result.workload_path}", file=sys.stderr)
289
+ if result.output_files:
290
+ print(f" Generated {len(result.output_files)} files", file=sys.stderr)
291
+ return f"Results in: {result.workload_path}"
292
+ else:
293
+ print("✗ Profiling failed", file=sys.stderr)
294
+ print("", file=sys.stderr)
295
+
296
+ # Show stderr output (contains actual error details)
297
+ # Note: rocprof-compute may write errors to stdout instead of stderr
298
+ error_output = result.stderr or result.stdout
299
+ if error_output and error_output.strip():
300
+ print("rocprof-compute output:", file=sys.stderr)
301
+ print("─" * 60, file=sys.stderr)
302
+ print(error_output.strip(), file=sys.stderr)
303
+ print("─" * 60, file=sys.stderr)
304
+ print("", file=sys.stderr)
305
+
306
+ # Show command that was run
307
+ if result.command:
308
+ print(f"Command: {' '.join(result.command)}", file=sys.stderr)
309
+ print("", file=sys.stderr)
310
+
311
+ # Show high-level error
312
+ if result.error:
313
+ print(f"Error: {result.error}", file=sys.stderr)
314
+
315
+ # Create helpful error message
316
+ # Check both stderr and stdout since rocprof-compute may use either
317
+ combined_output = (result.stderr or "") + (result.stdout or "")
318
+ error_msg = "Profiling failed"
319
+ if "error while loading shared libraries" in combined_output.lower():
320
+ error_msg += "\n\nThis looks like a missing dependency. Check the output above for the specific library."
321
+ elif "not found" in combined_output.lower() or "no such file" in combined_output.lower():
322
+ error_msg += "\n\nThe command or a required file was not found. Check the paths above."
323
+ elif "distribution does not meet version requirements" in combined_output.lower():
324
+ error_msg += "\n\nThis looks like a Python dependency version mismatch. Check the output above for the specific package."
325
+ elif result.error and "exit code" in result.error.lower():
326
+ error_msg += "\n\nThe profiling tool exited with an error. See output above for details."
327
+
328
+ raise RuntimeError(error_msg)
329
+
330
+
331
+ def analyze_command(
332
+ workload_path: str,
333
+ kernel: str | None = None,
334
+ dispatch: str | None = None,
335
+ block: str | None = None,
336
+ output: str | None = None,
337
+ list_stats: bool = False,
338
+ json_output: bool = False,
339
+ gui: bool = False,
340
+ port: int = 8050,
341
+ external: bool = False,
342
+ ) -> str:
343
+ """Analyze rocprof-compute workload.
344
+
345
+ This calls the native rocprof-compute analyze tool to generate comprehensive
346
+ analysis output matching what you get from running rocprof-compute directly.
347
+
348
+ For programmatic access to parsed data (without running the native tool),
349
+ use parse_workload() from the Python API instead.
350
+
351
+ Args:
352
+ workload_path: Path to workload directory
353
+ kernel: Comma-separated kernel filters
354
+ dispatch: Comma-separated dispatch filters
355
+ block: Comma-separated block filters
356
+ output: Output file path
357
+ list_stats: List all detected kernels and dispatches
358
+ json_output: Return JSON output (uses parse_workload for structured data)
359
+ gui: Launch GUI viewer instead of text analysis
360
+ port: Port for GUI server (default: 8050)
361
+ external: Use external rocprof-compute binary for GUI (default: bundled)
362
+
363
+ Returns:
364
+ Analysis output or JSON string
365
+
366
+ Raises:
367
+ RuntimeError: If analysis fails
368
+ """
369
+ from wafer_core.lib.rocprofiler.compute import parse_workload, run_analysis # pragma: no cover
370
+
371
+ # If GUI mode, delegate to GUI launch
372
+ if gui:
373
+ return gui_command(workload_path, port, json_output, use_bundled=not external)
374
+
375
+ # For JSON output, use parse_workload (fast CSV parsing)
376
+ if json_output:
377
+ result = parse_workload(workload_path)
378
+ result_dict = asdict(result)
379
+ # Convert dataclass lists to dicts
380
+ if result.kernels:
381
+ result_dict["kernels"] = [asdict(k) for k in result.kernels]
382
+ if result.roofline:
383
+ result_dict["roofline"] = [asdict(r) for r in result.roofline]
384
+ return json.dumps(result_dict, indent=2)
385
+
386
+ # For text output, call native rocprof-compute analyze for full output
387
+ # Parse filter lists
388
+ kernel_list = kernel.split(",") if kernel else None
389
+ dispatch_list = [int(d) for d in dispatch.split(",")] if dispatch else None
390
+ block_list = block.split(",") if block else None
391
+
392
+ result = run_analysis(
393
+ workload_path=workload_path,
394
+ kernel_filter=kernel_list,
395
+ dispatch_filter=dispatch_list,
396
+ block_filter=block_list,
397
+ output_file=output,
398
+ list_stats=list_stats,
399
+ )
400
+
401
+ if result.success:
402
+ # Output is already streamed in real-time by run_analysis
403
+ # Just return success message
404
+ return "Analysis completed"
405
+ else:
406
+ print("✗ Analysis failed", file=sys.stderr)
407
+ print("", file=sys.stderr)
408
+
409
+ # Show stderr output (contains actual error details)
410
+ # Note: rocprof-compute may write errors to stdout instead of stderr
411
+ error_output = result.stderr or result.stdout
412
+ if error_output and error_output.strip():
413
+ print("rocprof-compute output:", file=sys.stderr)
414
+ print("─" * 60, file=sys.stderr)
415
+ print(error_output.strip(), file=sys.stderr)
416
+ print("─" * 60, file=sys.stderr)
417
+ print("", file=sys.stderr)
418
+
419
+ # Show command that was run
420
+ if result.command:
421
+ print(f"Command: {' '.join(result.command)}", file=sys.stderr)
422
+ print("", file=sys.stderr)
423
+
424
+ # Show high-level error
425
+ if result.error:
426
+ print(f"Error: {result.error}", file=sys.stderr)
427
+
428
+ # Create helpful error message
429
+ # Check both stderr and stdout since rocprof-compute may use either
430
+ combined_output = (result.stderr or "") + (result.stdout or "")
431
+ error_msg = "Analysis failed"
432
+ if "error while loading shared libraries" in combined_output.lower():
433
+ error_msg += "\n\nThis looks like a missing dependency. Check the output above for the specific library."
434
+ elif "not found" in combined_output.lower() or "no such file" in combined_output.lower():
435
+ error_msg += "\n\nThe workload directory or required files were not found. Check the path above."
436
+ elif "distribution does not meet version requirements" in combined_output.lower():
437
+ error_msg += "\n\nThis looks like a Python dependency version mismatch. Check the output above for the specific package."
438
+ elif result.error and "exit code" in result.error.lower():
439
+ error_msg += "\n\nThe analysis tool exited with an error. See output above for details."
440
+
441
+ raise RuntimeError(error_msg)
442
+
443
+
444
+ def list_metrics_command(arch: str) -> str:
445
+ """List available metrics for architecture.
446
+
447
+ Args:
448
+ arch: Architecture name (e.g., "gfx90a", "gfx942")
449
+
450
+ Returns:
451
+ Metrics list output
452
+ """
453
+ import os
454
+ import shutil
455
+ import subprocess
456
+
457
+ from wafer_core.lib.rocprofiler.compute import find_rocprof_compute # pragma: no cover
458
+
459
+ rocprof_path = find_rocprof_compute()
460
+ if not rocprof_path:
461
+ raise RuntimeError("rocprof-compute not found. Install ROCm toolkit.")
462
+
463
+ # Build command: rocprof-compute profile --list-metrics <arch>
464
+ cmd = [rocprof_path, "profile", "--list-metrics", arch]
465
+
466
+ # Preserve terminal formatting
467
+ env = os.environ.copy()
468
+ env['TERM'] = 'xterm-256color'
469
+ env['FORCE_COLOR'] = '1'
470
+ env['COLUMNS'] = str(shutil.get_terminal_size().columns)
471
+ env['LINES'] = str(shutil.get_terminal_size().lines)
472
+
473
+ result = subprocess.run(
474
+ cmd,
475
+ capture_output=True,
476
+ text=True,
477
+ env=env
478
+ )
479
+
480
+ if result.returncode == 0:
481
+ print(result.stdout, end='')
482
+ return result.stdout
483
+ else:
484
+ print(f"✗ Failed to list metrics for {arch}", file=sys.stderr)
485
+ if result.stderr:
486
+ print("Error output:", file=sys.stderr)
487
+ print(result.stderr, file=sys.stderr)
488
+ if result.stdout:
489
+ print(result.stdout, file=sys.stderr)
490
+ raise RuntimeError(f"Failed to list metrics for {arch}")