Skip to content

Commit 79e4273

Browse files
committed
doc: redesign the memory requirements visualization
Improve memory requirements tables to show all the memory slots and free space available for application. Also show the same imformation in bar charts. Signed-off-by: Eduardo Montoya <eduardo.montoya@nordicsemi.no>
1 parent 6b85272 commit 79e4273

16 files changed

Lines changed: 2957 additions & 308 deletions

docs/_extensions/memory_data.py

Lines changed: 625 additions & 0 deletions
Large diffs are not rendered by default.

docs/_extensions/memory_viz.py

Lines changed: 483 additions & 0 deletions
Large diffs are not rendered by default.

docs/_extensions/stack_viz.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"""
2+
Copyright (c) 2026 Nordic Semiconductor ASA
3+
4+
SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
6+
Sphinx extension for stack usage bar charts in Zigbee documentation.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import html
12+
from typing import Any
13+
14+
from docutils import nodes
15+
from docutils.parsers.rst import directives
16+
from docutils.statemachine import StringList
17+
from memory_data import stack_samples, stack_thread_sizes, load_board_data
18+
from sphinx.application import Sphinx
19+
from sphinx.util.docutils import SphinxDirective
20+
21+
__version__ = "0.1.0"
22+
23+
RESOURCES_DIR = __import__("pathlib").Path(__file__).parent / "static"
24+
25+
THREAD_COLORS = {
26+
"main": ("#2563eb", "#93c5fd"),
27+
"zboss": ("#0891b2", "#a5f3fc"),
28+
}
29+
30+
31+
def _b_label(value_b: int) -> str:
32+
return f"{value_b} B"
33+
34+
35+
def _render_thread_bar(thread_id: str, label: str, used_b: int, size_b: int) -> str:
36+
used_color, free_color = THREAD_COLORS[thread_id]
37+
used_pct = min(100.0, 100.0 * used_b / size_b) if size_b else 0
38+
free_pct = 100.0 - used_pct
39+
free_b = max(size_b - used_b, 0)
40+
title = (
41+
f"{label}: {_b_label(size_b)} "
42+
f"({_b_label(used_b)} used, {_b_label(free_b)} free)"
43+
)
44+
inner = (
45+
f'<span class="memory-viz-fill memory-viz-fill-used" '
46+
f'style="width:{used_pct:.2f}%;background:{used_color}"></span>'
47+
f'<span class="memory-viz-fill memory-viz-fill-free" '
48+
f'style="width:{free_pct:.2f}%;background:{free_color}"></span>'
49+
)
50+
return (
51+
f'<div class="memory-viz-bar" role="img" aria-label="{html.escape(label, quote=True)}">'
52+
f'<div class="memory-viz-segment memory-viz-segment-{thread_id}" '
53+
f'style="flex:1 1 100%" '
54+
f'data-tooltip="{html.escape(title, quote=True)}">'
55+
f"{inner}</div></div>"
56+
)
57+
58+
59+
def _parse_rst_label(directive: SphinxDirective, text: str) -> nodes.Element:
60+
label = nodes.paragraph()
61+
label["classes"] = ["memory-viz-sample-label"]
62+
content = StringList([text], "<stack_viz>")
63+
directive.state.nested_parse(content, 0, label)
64+
if len(label) == 1 and isinstance(label[0], nodes.paragraph):
65+
inner = label[0]
66+
label.remove(inner)
67+
label.extend(inner.children)
68+
return label
69+
70+
71+
def _legend_swatch(color: str) -> str:
72+
return f'<span class="memory-viz-swatch" style="background:{color}"></span>'
73+
74+
75+
def _legend_items_used_free(thread_id: str, label_base: str) -> str:
76+
used_color, free_color = THREAD_COLORS[thread_id]
77+
items: list[str] = []
78+
for suffix, color in (("used", used_color), ("free", free_color)):
79+
items.append(
80+
f'<span class="memory-viz-legend-item">'
81+
f"{_legend_swatch(color)}"
82+
f"{label_base} ({suffix})"
83+
f"</span>"
84+
)
85+
return "".join(items)
86+
87+
88+
def _render_board_header(main_size_b: int, zboss_size_b: int) -> str:
89+
return (
90+
f'<div class="stack-viz-chart-header">'
91+
f'<div class="memory-viz-panel-title memory-viz-panel-title-sample">Sample</div>'
92+
f'<div class="stack-viz-header-thread">'
93+
f'<div class="memory-viz-panel-title">'
94+
f'main thread ({_b_label(main_size_b)})</div>'
95+
f'<div class="memory-viz-legend">{_legend_items_used_free("main", "main")}</div>'
96+
f"</div>"
97+
f'<div class="stack-viz-header-thread">'
98+
f'<div class="memory-viz-panel-title">'
99+
f'zboss thread ({_b_label(zboss_size_b)})</div>'
100+
f'<div class="memory-viz-legend">{_legend_items_used_free("zboss", "zboss")}</div>'
101+
f"</div>"
102+
f"</div>"
103+
)
104+
105+
106+
def _grid_style(main_size_b: int, zboss_size_b: int) -> str:
107+
return (
108+
f"--stack-viz-main-fr:{main_size_b}fr;"
109+
f"--stack-viz-zboss-fr:{zboss_size_b}fr"
110+
)
111+
112+
113+
def _build_board_nodes(directive: SphinxDirective, board: str) -> nodes.Element:
114+
data = load_board_data(board)
115+
samples = stack_samples(data)
116+
if not samples:
117+
paragraph = nodes.paragraph()
118+
paragraph += nodes.Text(f"No stack measurements for board {board}.")
119+
return paragraph
120+
121+
main_size_b, zboss_size_b = stack_thread_sizes(samples)
122+
style = _grid_style(main_size_b, zboss_size_b)
123+
124+
wrapper = nodes.container()
125+
wrapper += nodes.raw(
126+
"",
127+
f'<div class="stack-viz-board" data-board="{board}" style="{style}">',
128+
format="html",
129+
)
130+
131+
board_node = nodes.container()
132+
board_node["classes"] = ["stack-viz-board-inner"]
133+
board_node += nodes.raw(
134+
"",
135+
_render_board_header(main_size_b, zboss_size_b),
136+
format="html",
137+
)
138+
139+
samples_node = nodes.container()
140+
samples_node["classes"] = ["stack-viz-samples"]
141+
142+
for sample in samples:
143+
stack = sample["stack"]
144+
row = nodes.container()
145+
row["classes"] = ["stack-viz-sample-row"]
146+
row += _parse_rst_label(directive, sample["label"])
147+
row += nodes.raw(
148+
"",
149+
'<div class="memory-viz-bar-wrap">'
150+
+ _render_thread_bar(
151+
"main",
152+
"main thread",
153+
int(stack["main"]["used_b"]),
154+
int(stack["main"]["size_b"]),
155+
)
156+
+ "</div>",
157+
format="html",
158+
)
159+
row += nodes.raw(
160+
"",
161+
'<div class="memory-viz-bar-wrap">'
162+
+ _render_thread_bar(
163+
"zboss",
164+
"zboss thread",
165+
int(stack["zboss"]["used_b"]),
166+
int(stack["zboss"]["size_b"]),
167+
)
168+
+ "</div>",
169+
format="html",
170+
)
171+
samples_node += row
172+
173+
board_node += samples_node
174+
wrapper += board_node
175+
wrapper += nodes.raw("", "</div>", format="html")
176+
return wrapper
177+
178+
179+
class StackBoard(SphinxDirective):
180+
"""Render stack usage charts for a board defined in docs/data/memory/."""
181+
182+
required_arguments = 0
183+
optional_arguments = 0
184+
final_argument_whitespace = True
185+
has_content = False
186+
option_spec = {
187+
"board": directives.unchanged_required,
188+
}
189+
190+
def run(self) -> list[nodes.Node]:
191+
return [_build_board_nodes(self, self.options["board"])]
192+
193+
194+
def add_stack_viz_resources(app: Sphinx) -> None:
195+
static_path = RESOURCES_DIR.as_posix()
196+
if static_path not in app.config.html_static_path:
197+
app.config.html_static_path.append(static_path)
198+
app.add_css_file("stack_viz.css")
199+
200+
201+
def setup(app: Sphinx) -> dict[str, Any]:
202+
app.add_directive("stack-board", StackBoard)
203+
app.connect("builder-inited", add_stack_viz_resources)
204+
return {
205+
"version": __version__,
206+
"parallel_read_safe": True,
207+
"parallel_write_safe": True,
208+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
.memory-req-table {
2+
width: 100%;
3+
margin: 1rem 0;
4+
font-size: 0.875rem;
5+
}
6+
7+
.memory-req-table th,
8+
.memory-req-table td {
9+
border: 1px solid var(--bs-border-color, #dee2e6);
10+
padding: 0.4rem 0.55rem;
11+
vertical-align: middle;
12+
}
13+
14+
.memory-req-table thead th {
15+
font-size: 0.75rem;
16+
font-weight: 600;
17+
text-transform: uppercase;
18+
letter-spacing: 0.03em;
19+
color: var(--bs-secondary-color, #6c757d);
20+
background: var(--bs-tertiary-bg, #f8f9fa);
21+
}
22+
23+
.memory-req-table .memory-req-group-nvm {
24+
text-align: center;
25+
}
26+
27+
.memory-req-table .memory-req-group-external {
28+
text-align: center;
29+
}
30+
31+
.memory-req-table .memory-req-group-ram {
32+
text-align: center;
33+
}
34+
35+
.memory-req-table .memory-req-subhead {
36+
text-transform: none;
37+
letter-spacing: normal;
38+
font-weight: 500;
39+
}
40+
41+
.memory-req-table .memory-req-group-main,
42+
.memory-req-table .memory-req-group-zboss {
43+
text-align: center;
44+
}
45+
46+
.memory-req-table td.memory-req-sample {
47+
min-width: 14rem;
48+
}
49+
50+
.memory-req-table td.memory-req-value {
51+
text-align: right;
52+
white-space: nowrap;
53+
}
54+
55+
.memory-req-table td.memory-req-empty {
56+
color: var(--bs-secondary-color, #6c757d);
57+
text-align: center;
58+
}
59+
60+
.memory-req-table p {
61+
margin: 0;
62+
}
63+
64+
@media (max-width: 900px) {
65+
.memory-req-table {
66+
display: block;
67+
overflow-x: auto;
68+
}
69+
}

0 commit comments

Comments
 (0)