Phase 29: add project benchmarks, endurance stress and callback safety checks

This commit is contained in:
Johannes Findeisen 2026-10-27 17:07:37 +01:00
commit afc049e2c8
14 changed files with 1032 additions and 43 deletions

View file

@ -6,6 +6,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/resource.h>
#include <inttypes.h>
#define REQUIRE(c) \
do { \
if (!(c)) { \
@ -19,6 +21,12 @@ static double now(void)
clock_gettime(CLOCK_MONOTONIC, &t);
return (double)t.tv_sec + (double)t.tv_nsec / 1e9;
}
static double cpu_now(void)
{
struct timespec t;
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t)) abort();
return (double)t.tv_sec + (double)t.tv_nsec / 1e9;
}
static int compare(const void *left, const void *right)
{
double a = *(const double *)left, b = *(const double *)right;
@ -32,39 +40,52 @@ int main(int argc, char **argv)
xtc_midi_plan *midi = NULL;
long blocks = 2000;
char *end;
size_t i, j, source, previous, node;
size_t i, j, source, previous, node, tracks = 16, frames = 128, misses = 0;
size_t point_count, note_count;
uint64_t position = 0, wraps = 0;
struct rusage usage;
uint64_t track, clip, note, future;
float left[128], right[128], out_left[128], out_right[128];
double *times = NULL, total = 0, begin, prepare_ms;
float left[1024], right[1024], out_left[1024], out_right[1024];
double *times = NULL, total = 0, begin, prepare_ms, cpu_begin, cpu;
int result = EXIT_FAILURE;
if (argc > 2)
if (argc != 1 && argc != 2 && argc != 4)
return EXIT_FAILURE;
if (argc == 2) {
if (argc >= 2) {
blocks = strtol(argv[1], &end, 10);
if (!*argv[1] || *end || blocks < 1 || blocks > 100000)
if (!*argv[1] || *end || blocks < 1 || blocks > 10000000)
return EXIT_FAILURE;
}
if (argc == 4) {
long value = strtol(argv[2], &end, 10);
if (!*argv[2] || *end || value < 1 || value > 64) return EXIT_FAILURE;
tracks = (size_t)value;
value = strtol(argv[3], &end, 10);
if (!*argv[3] || *end || value < 1 || value > 1024) return EXIT_FAILURE;
frames = (size_t)value;
}
times = calloc((size_t)blocks, sizeof(*times));
REQUIRE(times);
begin = now();
REQUIRE(xtc_project_create(&project) == XTC_OK);
for (i = 0; i < 16; ++i) {
for (i = 0; i < tracks; ++i) {
xtc_automation_point point;
REQUIRE(xtc_project_add_track(project, "Performance", &track) == XTC_OK);
REQUIRE(xtc_project_add_midi_clip(project, track, 0, 384000, &clip) == XTC_OK);
REQUIRE(xtc_project_set_mix(project, track, 0.1f, i % 2 ? 0.5f : -0.5f, 0, 0) == XTC_OK);
for (j = 0; j < 2; ++j)
REQUIRE(xtc_project_add_note(project, clip, 0, 384000, 48 + (unsigned int)i, 100, 0,
REQUIRE(xtc_project_add_note(project, clip, i < 16 ? 0 : 380000 + i * 2, i < 16 ? 370000 : 1, 48 + (unsigned int)i, 100, 0,
&note) == XTC_OK);
point = (xtc_automation_point){track, 0, XTC_AUTOMATION_GAIN, 0.1f,0};
REQUIRE(xtc_project_set_automation(project, &point) == XTC_OK);
point.tick = 384000;
point.value = 0.2f;
REQUIRE(xtc_project_set_automation(project, &point) == XTC_OK);
for (j = 1; j < 16; ++j) {
point.tick = j * 25600;
point.value = j % 2 ? 0.2f : 0.1f;
REQUIRE(xtc_project_set_automation(project, &point) == XTC_OK);
}
}
REQUIRE(xtc_project_add_midi_clip(project, track, 1000000, 10000, &future) == XTC_OK);
for (i = 32; i < XTC_PROJECT_MAX_NOTES; ++i)
REQUIRE(xtc_project_add_note(project, future, (i - 32) * 2, 1, 60, 100, 0, &note) ==
for (i = tracks * 2; i < tracks * 64; ++i)
REQUIRE(xtc_project_add_note(project, future, (i - tracks * 2) * 2, 1, 60, 100, 0, &note) ==
XTC_OK);
REQUIRE(xtc_graph_create(64, &graph) == XTC_OK &&
xtc_graph_add_instrument(graph, &source) == XTC_OK);
@ -79,26 +100,40 @@ int main(int argc, char **argv)
previous = node;
REQUIRE(xtc_graph_add_output(graph, &node) == XTC_OK &&
xtc_graph_connect(graph, previous, node) == XTC_OK);
REQUIRE(xtc_graph_compile(graph, 48000, 128, &plan) == XTC_OK &&
REQUIRE(xtc_graph_compile(graph, 48000, frames, &plan) == XTC_OK &&
xtc_midi_plan_create(project, 48000, &midi) == XTC_OK);
prepare_ms = (now() - begin) * 1000;
REQUIRE(xtc_project_note_count(project, &note_count) == XTC_OK);
REQUIRE(xtc_project_automation_count(project, &point_count) == XTC_OK);
cpu_begin = cpu_now();
for (i = 0; i < (size_t)blocks; ++i) {
begin = now();
REQUIRE(xtc_midi_plan_render_stereo(midi, i * 128, NULL, 0, left, right, 128) == XTC_OK);
REQUIRE(xtc_midi_plan_render_stereo(midi, position, NULL, 0, left, right, frames) == XTC_OK);
REQUIRE(xtc_plan_process_stereo(plan, NULL, NULL, NULL, left, right, out_left, out_right,
128) == XTC_OK);
frames) == XTC_OK);
times[i] = now() - begin;
total += times[i];
for (j = 0; j < 128; ++j)
if (times[i] > (double)frames / 48000) ++misses;
position += frames;
/* Revisit the active arrangement in long offline runs. Explicit sample
* positions exercise discontinuous seek/wrap handling in the MIDI plan. */
if (position >= 96000) { position = 0; ++wraps; }
for (j = 0; j < frames; ++j)
REQUIRE(isfinite(out_left[j]) && isfinite(out_right[j]));
}
cpu = cpu_now() - cpu_begin;
REQUIRE(getrusage(RUSAGE_SELF, &usage) == 0);
qsort(times, (size_t)blocks, sizeof(*times), compare);
printf("Offline 48kHz/128, 64 nodes, 4096 notes (32 active), 16 automated tracks; %ld blocks\n",
blocks);
printf("Prepare %.3f ms; block mean %.3f us, p99 %.3f us, max %.3f us; deadline 2666.667 us; "
"CPU/audio %.4f\n",
prepare_ms, total / (double)blocks * 1e6, times[(size_t)blocks * 99 / 100] * 1e6,
times[blocks - 1] * 1e6, total / ((double)blocks * 128 / 48000));
printf("{\"tracks\":%zu,\"notes\":%zu,\"points\":%zu,\"nodes\":64,"
"\"rate\":48000,\"frames\":%zu,\"blocks\":%ld,\"wraps\":%" PRIu64 ","
"\"prepare_ms\":%.6f,\"mean_us\":%.6f,\"p99_us\":%.6f,\"max_us\":%.6f,"
"\"deadline_us\":%.6f,\"deadline_misses\":%zu,\"process_cpu_ms\":%.6f,"
"\"cpu_audio_ratio\":%.6f,\"peak_rss_kib\":%ld}\n",
tracks, note_count, point_count, frames, blocks, wraps, prepare_ms,
total / (double)blocks * 1e6, times[(size_t)blocks * 99 / 100] * 1e6,
times[blocks - 1] * 1e6, (double)frames / 48000 * 1e6, misses, cpu * 1e3,
cpu / ((double)blocks * (double)frames / 48000), usage.ru_maxrss);
result = EXIT_SUCCESS;
cleanup:
free(times);