Skip to content

Commit 762efd7

Browse files
committed
step
1 parent 5b88202 commit 762efd7

File tree

2 files changed

+129
-54
lines changed

2 files changed

+129
-54
lines changed

.github/workflows/package_core.yml

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
- build-env
6767
env:
6868
CCACHE_IGNOREOPTIONS: -specs=*
69-
OUTPUT_ARTIFACT: binaries-${{ matrix.board }}-${{ needs.build-env.outputs.CORE_HASH }}
69+
ARTIFACT_TAG: ${{ needs.build-env.outputs.CORE_HASH }}-${{ matrix.board }}
7070
strategy:
7171
matrix:
7272
include:
@@ -101,8 +101,8 @@ jobs:
101101
102102
# extract the memory usage table (from the header to the first non-% line)
103103
cat output.log | sed -n '/^Memory region/,/^[^%]*$/p' | head -n -1 \
104-
| awk 'BEGIN {split("B KB MB GB", u); for(i in u) m[u[i]]=1024^(i-1)} /:/ {print $1 " " $2*m[$3] " " $4*m[$5]}' \
105-
| sort > firmwares/zephyr-${{ matrix.variant }}.meminfo
104+
| awk 'BEGIN {split("B KB MB GB", u); for(i in u) m[u[i]]=1024^(i-1)} /:/ {print "[\"" $1 "\"," $2*m[$3] "," $4*m[$5] "]"}' \
105+
| sort | jq -s > firmwares/zephyr-${{ matrix.variant }}.meminfo
106106
107107
- name: Package board binaries
108108
if: ${{ !cancelled() }}
@@ -111,14 +111,23 @@ jobs:
111111
firmwares/*${{ matrix.variant }}* \
112112
variants/${{ matrix.variant }}/ \
113113
${{ (job.status == 'failure') && format('build/{0}/', matrix.variant) }} \
114-
| zstd > ${OUTPUT_ARTIFACT}.tar.zstd
114+
| zstd > binaries-${ARTIFACT_TAG}.tar.zstd
115115
116116
- name: Archive board binaries
117117
if: ${{ !cancelled() }}
118118
uses: actions/upload-artifact@v4
119119
with:
120-
name: ${{ format('{0}{1}', (job.status == 'failure') && 'failed-' || '', env.OUTPUT_ARTIFACT) }}
121-
path: ${{ env.OUTPUT_ARTIFACT }}.tar.zstd
120+
name: ${{ format('{0}binaries-{1}', (job.status == 'failure') && 'failed-' || '', env.ARTIFACT_TAG) }}
121+
path: binaries-${{ env.ARTIFACT_TAG }}.tar.zstd
122+
123+
- name: Archive board memory report
124+
if: ${{ !cancelled() }}
125+
uses: actions/upload-artifact@v4
126+
with:
127+
name: mem-report-${{ env.ARTIFACT_TAG }}
128+
path: |
129+
firmware/zephyr-${{ matrix.variant }}.meminfo
130+
firmware/zephyr-${{ matrix.variant }}.config
122131
123132
package-core:
124133
name: Package ${{ matrix.artifact }}
@@ -130,7 +139,6 @@ jobs:
130139
ALL_BOARD_DATA: ${{ needs.build-env.outputs.ALL_BOARD_DATA }}
131140
CORE_ARTIFACT: ArduinoCore-${{ matrix.artifact }}-${{ needs.build-env.outputs.CORE_HASH }}
132141
CORE_TAG: ${{ needs.build-env.outputs.CORE_TAG }}
133-
MEM_REPORT_ARTIFACT: mem-report-${{ needs.build-env.outputs.CORE_HASH }}-${{ matrix.artifact }}
134142
strategy:
135143
matrix:
136144
artifact: ${{ fromJSON( needs.build-env.outputs.ARTIFACTS ) }}
@@ -163,22 +171,13 @@ jobs:
163171
tar --use-compress-program=unzstd -xpf $f
164172
done
165173
./extra/package_core.sh ${{ matrix.artifact }} ${CORE_TAG} distrib/${CORE_ARTIFACT}.tar.bz2
166-
if [ ${{ matrix.artifact }} != zephyr ] ; then
167-
python ./extra/ci_mem_report.py ${{ matrix.artifact }} > mem-report-${{ matrix.artifact }}.md
168-
fi
169174
170175
- uses: actions/upload-artifact@v4
171176
if: ${{ success() || failure() }}
172177
with:
173178
name: ${{ env.CORE_ARTIFACT }}
174179
path: distrib/${{ env.CORE_ARTIFACT }}.tar.bz2
175180

176-
- uses: actions/upload-artifact@v4
177-
if : ${{ success() && matrix.artifact != 'zephyr' }}
178-
with:
179-
name: ${{ env.MEM_REPORT_ARTIFACT }}
180-
path: mem-report-${{ matrix.artifact }}.md
181-
182181
cleanup-build:
183182
name: Clean up intermediates
184183
runs-on: ubuntu-latest
@@ -209,6 +208,7 @@ jobs:
209208
PLAT: arduino:${{ matrix.subarch }}
210209
FQBN: arduino:${{ matrix.subarch }}:${{ matrix.board }}
211210
CORE_ARTIFACT: ArduinoCore-${{ matrix.artifact }}-${{ needs.build-env.outputs.CORE_HASH }}
211+
ARTIFACT_TAG: ${{ needs.build-env.outputs.CORE_HASH }}-${{ matrix.board }}
212212
if: ${{ !cancelled() && needs.build-env.result == 'success' }}
213213
steps:
214214
- uses: actions/checkout@v4
@@ -280,7 +280,7 @@ jobs:
280280
- uses: actions/upload-artifact@v4
281281
if: ${{ success() || failure() }}
282282
with:
283-
name: test-report-${{ needs.build-env.outputs.CORE_TAG }}-${{ matrix.board }}
283+
name: test-report-${{ env.ARTIFACT_TAG }}
284284
path: sketches-reports/*
285285

286286
inspect-logs:

extra/ci_inspect_logs.py

Lines changed: 112 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env python3
22

3-
import os
4-
import sys
3+
from collections import defaultdict
54
import json
5+
import os
66
import re
7+
import sys
78

89
SKIP = -1 # Test was not performed
910
PASS = 0 # (PASS) Compiled successfully
@@ -75,9 +76,9 @@ def track(self, test_entry):
7576
self.boards.add(test_entry.board)
7677
self.sketches.add(test_entry.sketch)
7778

78-
ARTIFACT_TESTS = {} # { artifact: TestGroup() }
79-
BOARD_TESTS = {} # { board: TestGroup() }
80-
SKETCH_TESTS = {} # { artifact: { sketch: TestGroup() } }
79+
ARTIFACT_TESTS = defaultdict(TestGroup) # { artifact: TestGroup() }
80+
BOARD_TESTS = defaultdict(TestGroup) # { board: TestGroup() }
81+
SKETCH_TESTS = defaultdict(defaultdict(TestGroup)) # { artifact: { sketch: TestGroup() } }
8182

8283
def log_test(artifact, board, sketch, exceptions, status, issues, job_link=None):
8384
"""
@@ -96,18 +97,8 @@ def log_test(artifact, board, sketch, exceptions, status, issues, job_link=None)
9697
test_entry = TestEntry(artifact, board, sketch, status, issues)
9798

9899
# Track in global structures
99-
if not artifact in ARTIFACT_TESTS:
100-
ARTIFACT_TESTS[artifact] = TestGroup()
101100
ARTIFACT_TESTS[artifact].track(test_entry)
102-
103-
if not board in BOARD_TESTS:
104-
BOARD_TESTS[board] = TestGroup(link=job_link)
105101
BOARD_TESTS[board].track(test_entry)
106-
107-
if not artifact in SKETCH_TESTS:
108-
SKETCH_TESTS[artifact] = {}
109-
if not sketch in SKETCH_TESTS[artifact]:
110-
SKETCH_TESTS[artifact][sketch] = TestGroup()
111102
SKETCH_TESTS[artifact][sketch].track(test_entry)
112103

113104
def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x: True):
@@ -136,7 +127,7 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
136127
if not sketch_filter(res):
137128
continue
138129

139-
# (1................) (2....) (3................) (4.)
130+
# (1.................) (2....) (3................) (4.)
140131
match = re.search(r'(libraries|examples)/([^/]+)/(examples/|extras/)?(.*)', sketch)
141132
if match:
142133
group = match.group(2)
@@ -212,6 +203,69 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
212203

213204
print("</table></blockquote></details>\n")
214205

206+
BOARD_MEM_REPORTS = defaultdict(dict) # { board: { region: [used, total] } }
207+
BOARD_CONFIGS = defaultdict(dict) # { board: { config_symbol: value } }
208+
REGIONS_FOR_SOC = defaultdict(set) # { soc: set(regions) }
209+
210+
def print_mem_report(artifact, artifact_boards):
211+
BASE_COLOR = 0x60
212+
DELTA_COLOR = 0xff-BASE_COLOR
213+
214+
def color_cmd(percent):
215+
color_amt = int(DELTA_COLOR * percent)
216+
return f"\\color{{#{BASE_COLOR + color_amt:02x}{0xff - color_amt:02x}{BASE_COLOR:02x}}}"
217+
218+
def color_entry(values):
219+
if not values:
220+
return ""
221+
222+
percent = values[0] / values[1]
223+
return f"${{{color_cmd(percent)}\\frac{{{values[0]}}}{{{values[1]}}}\\space({percent*100:0.1f}\\\\%)}}$"
224+
225+
print("<table><tr>", end='')
226+
print("<th rowspan='2' colspan='2'>Board</th>", end='')
227+
print("<th rowspan='2'>SoC</th>", end='')
228+
print("<th rowspan='2'>FLASH</th>", end='')
229+
print("<th rowspan='2'>RAM</th>", end='')
230+
print("<th colspan='2'>User heaps</th>", end='')
231+
print("</tr>")
232+
print("<tr><th>SYS</th><th>LIBC</th><th>LLEXT</th><th>MBEDTLS</th></tr>")
233+
234+
for soc, board in sorted((ALL_BOARD_DATA[board]['soc'], board) for board in artifact_boards):
235+
max_pct = max([ (BOARD_MEM_REPORTS[board][r][0] / BOARD_MEM_REPORTS[board][r][1]) for r in ('FLASH', 'RAM') ])
236+
icon = ':warning:' if max_pct > 0.90 else ''
237+
board_str = board.replace('_', '\\\\_')
238+
239+
row = [
240+
icon,
241+
f"${{{color_cmd(max_pct)}\\texttt{{{board_str}}}}}$",
242+
f"<code>{soc}</code>",
243+
color_entry(BOARD_MEM_REPORTS[board]['FLASH']),
244+
color_entry(BOARD_MEM_REPORTS[board]['RAM']),
245+
f"${{{ BOARD_CONFIGS[board].get('CONFIG_HEAP_MEM_POOL_SIZE', 0) }}}$",
246+
f"${{{ BOARD_CONFIGS[board]['CONFIG_SRAM_SIZE']*1024 - BOARD_MEM_REPORTS[board]['RAM'][0] }}}$",
247+
f"${{{ BOARD_CONFIGS[board]['CONFIG_LLEXT_HEAP_SIZE']*1024 }}}$",
248+
f"${{{ BOARD_CONFIGS[board].get('CONFIG_MBEDTLS_HEAP_SIZE', '-') }}}$"
249+
]
250+
251+
print("<tr>")
252+
col_aligns = ['center', 'left', 'center', 'right', 'right', 'right', 'right', 'right', 'right']
253+
for index, cell in enumerate(row):
254+
print(f"<td align='{col_aligns[index]}'>\n\n{cell}\n\n</td>")
255+
print("</tr>")
256+
print("</table>")
257+
258+
print("<details><summary>Raw data</summary><blockquote>\n")
259+
print("<table>")
260+
for soc, board in sorted((ALL_BOARD_DATA[board]['soc'], board) for board in artifact_boards):
261+
print(f"<td><code>{soc}</code></td>")
262+
print(f"<td><code>{board}</code></td>")
263+
print(f"<td><pre>")
264+
for r in REGIONS_BY_SOC[soc]:
265+
print(f"{r:>20} {data[board].get(r, ['',''])[0]:8} {data[board].get(r, ['',''])[1]:8}")
266+
print("</pre></td></tr>")
267+
print("</table></blockquote></details>")
268+
215269
# --- Main Logic ---
216270

217271
# Environment Variable Checks
@@ -224,13 +278,45 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
224278
sys.exit(0)
225279

226280
ALL_BOARD_DATA = json.loads(ALL_BOARD_DATA_STR)
281+
ALL_BOARD_DATA = { b['board']: b for b in ALL_BOARD_DATA }
227282

228-
for board_data in ALL_BOARD_DATA:
283+
for board_data in ALL_BOARD_DATA.values():
229284
# Extract common fields
230285
artifact = board_data['artifact']
231286
board = board_data['board']
232287
variant = board_data['variant']
233288
subarch = board_data['subarch']
289+
290+
# get board's config settings
291+
report_file = f"firmware/zephyr-{variant}.config"
292+
try:
293+
with open(report_file, 'r') as f:
294+
for line in f:
295+
if line.startswith('#') or '=' not in line:
296+
continue
297+
sym, val = line.split('=', 1)
298+
if val.startswith('"'):
299+
BOARD_CONFIGS[board][sym] = val.strip().strip('"')
300+
else:
301+
BOARD_CONFIGS[board][sym] = int(val)
302+
except Exception as e:
303+
log_test(artifact, board, 'CI test', [], FAILURE, f"Error reading config file: {e}")
304+
continue # Skip to the next board
305+
306+
soc = BOARD_CONFIGS[board]['CONFIG_SOC']
307+
board_data['soc'] = soc
308+
309+
# get board's memory report
310+
report_file = f"firmware/zephyr-{variant}.meminfo"
311+
try:
312+
with open(report_file, 'r') as f:
313+
report_data = json.load(f)
314+
except Exception as e:
315+
log_test(artifact, board, 'CI test', [], FAILURE, f"Error reading mem report file: {e}")
316+
continue # Skip to the next board
317+
318+
BOARD_MEM_REPORTS[board] = { region.replace(':',''): [used, total] for region, used, total in report_data }
319+
REGIONS_FOR_SOC[soc].update(BOARD_MEM_REPORTS[board].keys())
234320

235321
# Get list of expected errors for this board/variant
236322
exceptions = []
@@ -242,16 +328,16 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
242328
exceptions.append(re.compile(f"^(ArduinoCore-zephyr/)?{sketch_pattern}"))
243329

244330
# Get raw data from report file
245-
REPORT_FILE = f"arduino-{subarch}-{board}.json"
246-
if not os.path.exists(REPORT_FILE):
247-
log_test(artifact, board, 'CI test', exceptions, FAILURE, "Report file not found.")
331+
report_file = f"arduino-{subarch}-{board}.json"
332+
if not os.path.exists(report_file):
333+
log_test(artifact, board, 'CI test', [], FAILURE, "Report file not found.")
248334
continue # Skip to the next board
249335

250336
try:
251-
with open(REPORT_FILE, 'r') as f:
337+
with open(report_file, 'r') as f:
252338
report_data = json.load(f)
253339
except Exception as e:
254-
log_test(artifact, board, 'CI test', exceptions, FAILURE, f"Error reading report file: {e}")
340+
log_test(artifact, board, 'CI test', [], FAILURE, f"Error reading report file: {e}")
255341
continue # Skip to the next board
256342

257343
# Extract data from the report file
@@ -260,7 +346,7 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
260346

261347
reports = report_data.get('boards', [{}])[0].get('sketches', [])
262348
if not reports:
263-
log_test(artifact, board, 'CI test', exceptions, FAILURE, "Test report is empty, check CI log.", job_link)
349+
log_test(artifact, board, 'CI test', [], FAILURE, "Test report is empty, check CI log.", job_link)
264350
continue # Skip to the next board
265351

266352
# Iterate through individual sketch reports
@@ -283,15 +369,6 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
283369

284370
artifacts = ARTIFACT_TESTS.keys()
285371

286-
# Load memory usage reports if they exist
287-
ARTIFACT_MEM_REPORTS = {}
288-
for artifact in artifacts:
289-
try:
290-
lines = open(f'mem-report-{artifact}.md').readlines()
291-
ARTIFACT_MEM_REPORTS[artifact] = lines
292-
except:
293-
pass
294-
295372
# Begin output of the report
296373
# --------------------------
297374

@@ -374,11 +451,9 @@ def print_test_matrix(artifact, artifact_boards, title, sketch_filter=lambda x:
374451
print_test_matrix(artifact, artifact_boards, "tests", sketch_filter=lambda res: res.status in (PASS, WARNING))
375452
print("</blockquote></details>\n")
376453

377-
if artifact in ARTIFACT_MEM_REPORTS:
378-
print(f"<details><summary>Memory usage report for <code>{artifact}</code></summary><blockquote>\n")
379-
for line in ARTIFACT_MEM_REPORTS[artifact]:
380-
print(line.strip())
381-
print("\n</blockquote></details>\n")
454+
print(f"<details><summary>Memory usage report for <code>{artifact}</code></summary><blockquote>")
455+
print_mem_report(artifact, artifact_boards)
456+
print("</blockquote></details>")
382457

383458
if not ci_run_passed:
384459
sys.exit(1)

0 commit comments

Comments
 (0)