Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
report.hpp
Go to the documentation of this file.
1/**
2 * @file report.hpp
3 * @brief Profiling report formatters (human-readable and tagged)
4 *
5 * Formats AudioBudget + named SectionProfiles into log-friendly strings.
6 * Gated behind SBL_PROFILING_ENABLED — compiles to nothing when disabled.
7 *
8 * Two output modes:
9 *
10 * Human-readable (format_report):
11 * CPU: avg=36.9% peak=38.8% overruns=0
12 * osc=85119 svf=12928 verb=41100 dly=9344
13 *
14 * Tagged (format_tagged_report + format_tagged_histogram):
15 * `#PROF` cpu_avg=36.9 cpu_peak=38.8 cpu_min=34.2 overruns=0 blocks=48000
16 * `#PROF.OSC` ticks=85119
17 * `#PROF.SVF` ticks=12928
18 * `#HIST` b0_10=0 b10_20=0 b20_30=150 b30_40=47200 ...
19 *
20 * See docs/conventions/tagged-output.md for the tagged output spec.
21 *
22 * Usage:
23 * sbl::diagnostics::NamedSection sections[] = {
24 * {"osc", s_prof_osc.avg}, {"svf", s_prof_svf.avg},
25 * };
26 * char buf[256];
27 * sbl::diagnostics::format_tagged_report(buf, sizeof(buf), s_budget, sections, 2);
28 * uart_write(buf);
29 * sbl::diagnostics::format_tagged_histogram(buf, sizeof(buf), s_budget);
30 * uart_write(buf);
31 */
32#ifndef SBL_COMMON_DIAGNOSTICS_REPORT_HPP_
33#define SBL_COMMON_DIAGNOSTICS_REPORT_HPP_
34
35#include <cstdint>
36#include <cstddef>
39
40namespace sbl::diagnostics {
41
43 const char* name;
44 uint32_t avg;
45};
46
47/**
48 * @brief Format a profiling report into a buffer
49 *
50 * @param buf Output buffer
51 * @param size Buffer size
52 * @param budget AudioBudget instance
53 * @param sections Array of named section profiles
54 * @param n Number of sections
55 * @return Number of characters written
56 */
57inline int format_report(char* buf, size_t size, const AudioBudget& budget,
58 const NamedSection* sections, size_t n) {
59#ifdef SBL_PROFILING_ENABLED
60 int pos = sbl::log::format(buf, size,
61 "CPU: avg=%d.%d%% peak=%d.%d%% overruns=%u\r\n",
62 static_cast<int>(budget.avg_load() * 100.0f),
63 static_cast<int>(budget.avg_load() * 1000.0f) % 10,
64 static_cast<int>(budget.peak_load() * 100.0f),
65 static_cast<int>(budget.peak_load() * 1000.0f) % 10,
66 budget.overruns());
67
68 if (n > 0 && static_cast<size_t>(pos) < size - 1) {
69 // " name=val name=val ...\r\n"
70 size_t remaining = size - static_cast<size_t>(pos);
71 int spos = sbl::log::format(buf + pos, remaining, " ");
72 pos += spos;
73
74 for (size_t i = 0; i < n && static_cast<size_t>(pos) < size - 1; ++i) {
75 remaining = size - static_cast<size_t>(pos);
76 if (i > 0) {
77 spos = sbl::log::format(buf + pos, remaining, " ");
78 pos += spos;
79 remaining = size - static_cast<size_t>(pos);
80 }
81 spos = sbl::log::format(buf + pos, remaining, "%s=%u",
82 sections[i].name, sections[i].avg);
83 pos += spos;
84 }
85
86 remaining = size - static_cast<size_t>(pos);
87 spos = sbl::log::format(buf + pos, remaining, "\r\n");
88 pos += spos;
89 }
90
91 return pos;
92#else
93 (void)budget; (void)sections; (void)n;
94 if (size > 0) buf[0] = '\0';
95 return 0;
96#endif
97}
98
99// ─── Tagged output formatters (FDP-028) ─────────────────────────────
100
101/**
102 * @brief Format profiling data as tagged output lines
103 *
104 * Emits one `#PROF` summary line followed by one `#PROF.<NAME>` line per section.
105 * Section names are uppercased in the tag (e.g., "osc" → `#PROF.OSC`).
106 *
107 * @return Number of characters written
108 */
109inline int format_tagged_report(char* buf, size_t size, const AudioBudget& budget,
110 const NamedSection* sections, size_t n) {
111#ifdef SBL_PROFILING_ENABLED
112 int pos = sbl::log::format(buf, size,
113 "#PROF cpu_avg=%d.%d cpu_peak=%d.%d cpu_min=%d.%d overruns=%u blocks=%u\r\n",
114 static_cast<int>(budget.avg_load() * 100.0f),
115 static_cast<int>(budget.avg_load() * 1000.0f) % 10,
116 static_cast<int>(budget.peak_load() * 100.0f),
117 static_cast<int>(budget.peak_load() * 1000.0f) % 10,
118 static_cast<int>(budget.min_load() * 100.0f),
119 static_cast<int>(budget.min_load() * 1000.0f) % 10,
120 budget.overruns(),
121 budget.count());
122
123 for (size_t i = 0; i < n && static_cast<size_t>(pos) < size - 1; ++i) {
124 size_t remaining = size - static_cast<size_t>(pos);
125 pos += sbl::log::format(buf + pos, remaining,
126 "#PROF.%s ticks=%u\r\n",
127 sections[i].name, sections[i].avg);
128 }
129
130 return pos;
131#else
132 (void)budget; (void)sections; (void)n;
133 if (size > 0) buf[0] = '\0';
134 return 0;
135#endif
136}
137
138/**
139 * @brief Format CPU load histogram as a tagged output line
140 *
141 * Emits a single `#HIST` line with 11 bucket counts (10% each, last is >100%).
142 *
143 * @return Number of characters written
144 */
145inline int format_tagged_histogram(char* buf, size_t size, const AudioBudget& budget) {
146#ifdef SBL_PROFILING_ENABLED
147 static const char* labels[] = {
148 "b0_10", "b10_20", "b20_30", "b30_40", "b40_50",
149 "b50_60", "b60_70", "b70_80", "b80_90", "b90_100", "over"
150 };
151
152 int pos = sbl::log::format(buf, size, "#HIST");
153
154 for (size_t i = 0; i < budget.hist_size() && static_cast<size_t>(pos) < size - 1; ++i) {
155 size_t remaining = size - static_cast<size_t>(pos);
156 pos += sbl::log::format(buf + pos, remaining, " %s=%u",
157 labels[i], budget.hist_bucket(i));
158 }
159
160 if (static_cast<size_t>(pos) < size - 1) {
161 size_t remaining = size - static_cast<size_t>(pos);
162 pos += sbl::log::format(buf + pos, remaining, "\r\n");
163 }
164
165 return pos;
166#else
167 (void)budget;
168 if (size > 0) buf[0] = '\0';
169 return 0;
170#endif
171}
172
173} // namespace sbl::diagnostics
174
175#endif // SBL_COMMON_DIAGNOSTICS_REPORT_HPP_
Audio callback tick budget tracker.
uint32_t overruns() const
Number of callbacks that exceeded the tick budget.
static constexpr size_t hist_size()
Number of histogram buckets.
uint32_t count() const
Total callbacks measured.
float avg_load() const
Average load as fraction [0.0, 1.0+].
float min_load() const
Minimum load since last reset.
float peak_load() const
Peak (worst-case) load since last reset.
uint32_t hist_bucket(size_t i) const
Read histogram bucket count (0-indexed, last bucket is >100%)
Minimal safe string formatting for embedded systems.
Diagnostics and profiling.
int format_tagged_report(char *buf, size_t size, const AudioBudget &budget, const NamedSection *sections, size_t n)
Format profiling data as tagged output lines.
Definition report.hpp:109
int format_report(char *buf, size_t size, const AudioBudget &budget, const NamedSection *sections, size_t n)
Format a profiling report into a buffer.
Definition report.hpp:57
int format_tagged_histogram(char *buf, size_t size, const AudioBudget &budget)
Format CPU load histogram as a tagged output line.
Definition report.hpp:145
int format(char *buf, size_t size, const char *fmt,...)
Format a string into a buffer (snprintf-style)
Definition format.hpp:265