Skip to content

Commit 91db2f4

Browse files
authored
[SWDEV-510820] Add missing goamdsmi documentation (#147)
* add API doc comments to goamdsmi.go * update README and usage * add sphinx directive to parse go doc * fix walrus operator typos * make docs more consistent * add Go docs to index.md --------- Signed-off-by: Arif, Maisam <Maisam.Arif@amd.com> [ROCm/amdsmi commit: 15c32f6]
1 parent d853f1d commit 91db2f4

File tree

10 files changed

+1032
-23
lines changed

10 files changed

+1032
-23
lines changed

projects/amdsmi/README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,23 @@ for more information.
2626
* [Install the AMD SMI library and CLI tool](https://rocm.docs.amd.com/projects/amdsmi/en/latest/install/install.html)
2727

2828
## Requirements
29-
The following are required to install and use the AMD SMI libraries and CLI tool.
3029

31-
* Python 3.6.8+ (64-bit)
30+
The following are required to install and use the AMD SMI library through its language interfaces and CLI.
31+
3232
* `amdgpu` driver must be loaded for [`amdsmi_init()`](./docs/how-to/amdsmi-cpp-lib#hello-amd-smi) to work.
33+
* Export `LD_LIBRARY_PATH` to the `amdsmi` installation directory.
34+
35+
```bash
36+
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/rocm/lib:/opt/rocm/lib64
37+
```
38+
39+
### Python interface and CLI tool prerequisites
40+
41+
* Python 3.6.8+ (64-bit)
42+
43+
### Go API prerequisites
44+
45+
* Go version 1.20 or greater
3346

3447
## Install amdgpu driver and AMD SMI with ROCm
3548

@@ -97,6 +110,17 @@ Refer to the [user guide](https://rocm.docs.amd.com/projects/amdsmi/en/latest/ho
97110
detailed [Python API reference](https://rocm.docs.amd.com/projects/amdsmi/en/latest/reference/amdsmi-py-api.html) in the
98111
ROCm documentation portal.
99112

113+
### Go library
114+
115+
The AMD SMI Go interface provides a simple
116+
[API](https://rocm.docs.amd.com/projects/amdsmi/en/latest/reference/amdsmi-go-lib.html)
117+
for AMD hardware management. It streamlines hardware monitoring and control
118+
while leveraging Golang's features.
119+
120+
Refer to the [user guide](https://rocm.docs.amd.com/projects/amdsmi/en/latest/how-to/amdsmi-go-lib.html) and the
121+
[Go API reference](https://rocm.docs.amd.com/projects/amdsmi/en/latest/reference/amdsmi-go-api.html) in the
122+
ROCm documentation portal.
123+
100124
### CLI tool
101125

102126
A versatile command line tool for managing and monitoring AMD hardware. You can use `amd-smi` for:
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import re
2+
import os
3+
from pathlib import Path
4+
5+
from docutils import nodes
6+
from docutils.parsers.rst import Directive, directives
7+
from sphinx.application import Sphinx
8+
from sphinx.util.typing import ExtensionMetadata
9+
10+
11+
class GoApiRefDirective(Directive):
12+
"""
13+
Directive for generating Go API reference documentation.
14+
15+
Usage:
16+
.. go-api-ref:: path/to/gofile.go
17+
:section: gpu
18+
"""
19+
20+
required_arguments = 1 # Requires one argument: the path to the Go file
21+
optional_arguments = 0
22+
has_content = False
23+
option_spec = {
24+
"section": directives.unchanged, # Optional section filter
25+
}
26+
27+
def run(self):
28+
# Get the path to the Go file
29+
go_file_path = self.arguments[0]
30+
env = self.state.document.settings.env
31+
32+
# Get the section filter if provided
33+
section_filter = self.options.get("section", None)
34+
35+
# Resolve the path relative to the document
36+
doc_dir = Path(env.doc2path(env.docname)).parent
37+
source_path = (doc_dir / go_file_path).resolve()
38+
39+
# Check if the file exists
40+
if not source_path.exists():
41+
msg = f"Go source file not found: {source_path}"
42+
return [nodes.warning("", nodes.paragraph("", msg))]
43+
44+
# Parse the Go file and generate documentation
45+
functions = parse_go_file(str(source_path))
46+
47+
# Create a container for the API documentation
48+
container = nodes.container()
49+
container["classes"].append("go-api-reference")
50+
51+
# Add the API documentation to the container
52+
content = generate_rst_content(functions, section_filter)
53+
self.state_machine.insert_input(content, source=str(source_path))
54+
55+
return [container]
56+
57+
58+
def parse_go_file(file_path):
59+
"""Parse a Go file and extract function documentation."""
60+
with open(file_path, "r") as f:
61+
content = f.read()
62+
63+
# Pattern to match function documentation and definition
64+
pattern = r"(\/\/[^\n]*(?:\n\/\/[^\n]*)*)\n\s*func\s+([A-Za-z0-9_]+)\s*\((.*?)\)\s*(\(.*?\)|\w+)\s*\{"
65+
matches = re.findall(pattern, content, re.DOTALL)
66+
67+
functions = []
68+
for match in matches:
69+
doc_comment = match[0]
70+
func_name = match[1]
71+
params = match[2].strip()
72+
return_type = match[3].strip()
73+
74+
# Process the comment lines
75+
doc_lines = []
76+
for line in doc_comment.split("\n"):
77+
if line.strip().startswith("//"):
78+
# Remove the comment marker and one space after it (if present)
79+
comment_text = line.strip()[2:]
80+
if comment_text.startswith(" "):
81+
comment_text = comment_text[1:]
82+
doc_lines.append(comment_text)
83+
84+
# Extract sections from the doc comment
85+
description = []
86+
input_params = []
87+
output_params = []
88+
example = []
89+
90+
current_section = "description"
91+
92+
for line in doc_lines:
93+
if line.startswith("Input parameter"):
94+
current_section = "input"
95+
input_params.append(line)
96+
elif line.startswith("Output:"):
97+
current_section = "output"
98+
output_params.append(line)
99+
elif line.startswith("Example:"):
100+
current_section = "example"
101+
example.append(line)
102+
elif current_section == "description":
103+
description.append(line)
104+
elif current_section == "input":
105+
input_params.append(line)
106+
elif current_section == "output":
107+
output_params.append(line)
108+
elif current_section == "example":
109+
example.append(line)
110+
111+
# Combine description lines into a single line
112+
desc_text = " ".join([line.strip() for line in description if line.strip()])
113+
114+
# Combine output lines into a single line
115+
output_text = " ".join([line.strip() for line in output_params if line.strip()])
116+
117+
# Determine the section based on function name
118+
parts = func_name.split("_")
119+
section = parts[1] if len(parts) > 1 else "other"
120+
121+
functions.append(
122+
{
123+
"name": func_name,
124+
"params": params,
125+
"return_type": return_type,
126+
"description": desc_text,
127+
"input_params": "\n".join(input_params).strip(),
128+
"output_params": output_text,
129+
"example": "\n".join(example).strip(),
130+
"section": section.lower(), # Store the section for filtering
131+
}
132+
)
133+
134+
return functions
135+
136+
137+
def generate_rst_content(functions, section_filter=None):
138+
"""Generate reStructuredText content from parsed functions."""
139+
lines = []
140+
141+
# Filter functions by section if a filter is provided
142+
if section_filter:
143+
section_filter = section_filter.lower()
144+
functions = [f for f in functions if f["section"] == section_filter]
145+
146+
if not functions:
147+
lines.append(f"No functions found in section: {section_filter}")
148+
return lines
149+
150+
# Group functions by prefix if no section filter is provided
151+
if not section_filter:
152+
# Group functions by prefix (e.g., GO_gpu_, GO_cpu_)
153+
function_groups = {}
154+
for func in functions:
155+
section = func["section"]
156+
if section not in function_groups:
157+
function_groups[section] = []
158+
function_groups[section].append(func)
159+
160+
# Define the order of sections (GPU first, then CPU, then others)
161+
section_order = []
162+
163+
# Add GPU section first if it exists
164+
if "gpu" in function_groups:
165+
section_order.append("gpu")
166+
167+
# Add CPU section next if it exists
168+
if "cpu" in function_groups:
169+
section_order.append("cpu")
170+
171+
# Add all other sections in alphabetical order
172+
for prefix in sorted(function_groups.keys()):
173+
if prefix not in ["gpu", "cpu"]:
174+
section_order.append(prefix)
175+
176+
# Write each group in the specified order
177+
for section in section_order:
178+
funcs = function_groups[section]
179+
lines.append(f"{section.upper()} Functions")
180+
lines.append("-" * len(f"{section.upper()} Functions"))
181+
lines.append("")
182+
183+
for func in funcs:
184+
add_function_documentation(lines, func)
185+
else:
186+
# If a section filter is provided, just document those functions without section headers
187+
for func in functions:
188+
add_function_documentation(lines, func)
189+
190+
return lines
191+
192+
193+
def add_function_documentation(lines, func):
194+
"""Add documentation for a single function to the lines list."""
195+
lines.append(func['name'])
196+
lines.append("~" * len(f"``{func['name']}``"))
197+
lines.append("")
198+
199+
# Function signature
200+
return_type = func["return_type"]
201+
if return_type.startswith("(") and return_type.endswith(")"):
202+
return_type = return_type[1:-1]
203+
204+
lines.append(".. code-block:: go")
205+
lines.append("")
206+
lines.append(f" func {func['name']}({func['params']}) {return_type}")
207+
lines.append("")
208+
209+
# Description
210+
if func["description"]:
211+
lines.append(func["description"])
212+
lines.append("")
213+
214+
# Input parameters
215+
if func["input_params"]:
216+
for input_line in func["input_params"].split("\n"):
217+
lines.append(input_line)
218+
lines.append("")
219+
220+
# Output parameters
221+
if func["output_params"]:
222+
lines.append(func["output_params"])
223+
lines.append("")
224+
225+
# Example
226+
if func["example"]:
227+
# Process the example to properly format code blocks
228+
example_lines = func["example"].split("\n")
229+
in_code_block = False
230+
231+
for i, line in enumerate(example_lines):
232+
stripped_line = line.strip()
233+
234+
# Check if this is the Example: line
235+
if stripped_line == "Example:":
236+
lines.append("Example:")
237+
continue
238+
239+
# Check if we're entering a code block
240+
if (
241+
not in_code_block
242+
and i > 0
243+
and (
244+
stripped_line.startswith("import")
245+
or stripped_line.startswith("if")
246+
or stripped_line.startswith("for")
247+
)
248+
):
249+
in_code_block = True
250+
lines.append("")
251+
lines.append(".. code-block:: go")
252+
lines.append("")
253+
254+
# Add the line to the formatted example
255+
if in_code_block:
256+
# For code blocks, add indentation
257+
lines.append(f" {line}")
258+
elif stripped_line: # Only add non-empty lines outside code blocks
259+
lines.append(line)
260+
261+
lines.append("")
262+
263+
264+
def setup(app):
265+
"""
266+
Setup function for Sphinx extension.
267+
This will be called by Sphinx when the extension is loaded.
268+
"""
269+
# Register the directive
270+
app.add_directive("go-api-ref", GoApiRefDirective)
271+
272+
return {
273+
"version": "0.1.0",
274+
"parallel_read_safe": True,
275+
"parallel_write_safe": True,
276+
}

projects/amdsmi/docs/conf.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
# https://www.sphinx-doc.org/en/master/usage/configuration.html
66

77
import re
8+
import sys
9+
from pathlib import Path
810

11+
sys.path.append(str(Path('_extension').resolve()))
912

1013
# get version number to print in docs
1114
def get_version_info(filepath):
@@ -49,7 +52,7 @@ def get_version_info(filepath):
4952
external_toc_path = "./sphinx/_toc.yml"
5053

5154
external_projects_current_project = "amdsmi"
52-
extensions = ["rocm_docs", "rocm_docs.doxygen"]
55+
extensions = ["rocm_docs", "rocm_docs.doxygen", "go_api_ref"]
5356

5457
doxygen_root = "doxygen"
5558
doxysphinx_enabled = True

0 commit comments

Comments
 (0)