Reject malformed audio diagnostic snapshots

This commit is contained in:
evgeny committed 2026-10-27 03:19:03 +01:00
1 parent 9fa58b4f83
commit dafd3a2adc
3 files changed
+83 -14

No files matched your search

+34 -1
View File
@@ -5,7 +5,7 @@
#include <vector>
int main() {
debug_config_init(); debug_enable_console(0);
debug_config_init(); debug_enable_console(1); debug_set_category_level(DEBUG_CATEGORY_CALL, DEBUG_LEVEL_DEBUG);
constexpr int producers = 4, perProducer = 20000;
std::vector<bool> seen(producers * perProducer);
std::atomic<int> done{0};
@@ -19,6 +19,22 @@ int main() {
assert(take(e)); assert(e.value == int64_t(i) && e.detail == 7);
}
assert(!take(e)); lost = 0;
// Повреждённый вызов не должен выдавать прежний снимок слота за новые часы.
audio_diag_capture_clock(nullptr, nullptr);
audio_diag_playback_clock(nullptr, nullptr);
audio_diag_wasapi_playback(nullptr, nullptr);
audio_diag_event(nullptr, AUDIO_DIAG_CAPTURE_CLOCK, AUDIO_DIAG_READ, 1, 0);
audio_diag_event(nullptr, AUDIO_DIAG_ENTER, AUDIO_DIAG_STAGE_COUNT, 1, 0);
audio_diag_event(nullptr, -1, AUDIO_DIAG_READ, 1, 0);
std::map<uintptr_t, Device> invalidDevices;
for (int i = 0; i < 6; ++i) {
assert(take(e)); assert(e.kind == AUDIO_DIAG_ERROR && e.value == 1 && e.text[0]);
consume(e, invalidDevices);
}
assert(!take(e));
assert(invalidDevices[0].faults[AUDIO_DIAG_ERROR] == 6);
assert(!invalidDevices[0].captureBlocks && !invalidDevices[0].playbackBlocks && !invalidDevices[0].wasapiSeen);
report(invalidDevices, wallNow(), 1.0);
// Граница секундного отчёта не обрывает карту времени; hole отделён от скачка оценки latency.
std::map<uintptr_t, Device> devices;
audio_capture_timing timing{1000000, 100000, 0, 480, 48000, 1, 0};
@@ -56,6 +72,23 @@ int main() {
assert(capture.playbackLatency.average() == 14500 && capture.playbackBlock.low == 20000);
report(devices, wallNow(), 1.0);
assert(capture.playbackBlocks == 0 && capture.playbackLatency.count == 0 && capture.playbackBlock.high == 0);
// Снимок принадлежит producer только до возврата; exclusive padding не является измеренной очередью.
audio_wasapi_timing wasapi{};
wasapi.frames = 480; wasapi.rate = 48000; wasapi.device_rate = 96000;
wasapi.shared = 1; wasapi.padding_frames = 960; wasapi.stream_latency_us = 5000; wasapi.converter_us = 1000;
audio_diag_wasapi_playback(nullptr, &wasapi);
wasapi.shared = 0; wasapi.padding_frames = 96000; wasapi.stream_latency_us = 8000;
assert(take(e)); consume(e, devices);
assert(capture.wasapiPadding.count == 1 && capture.wasapiPadding.high == 10000);
assert(capture.wasapiLatency.high == 5000 && capture.wasapi.shared == 1);
audio_diag_wasapi_playback(nullptr, &wasapi);
wasapi.padding_result = -1; wasapi.latency_result = -1;
audio_diag_wasapi_playback(nullptr, &wasapi);
while (take(e)) consume(e, devices);
assert(capture.wasapiBlocks == 3 && capture.wasapiFailures == 1 && capture.wasapiPadding.count == 1);
assert(capture.wasapiLatency.count == 2 && capture.wasapiLatency.average() == 6500);
report(devices, wallNow(), 1.0);
assert(!capture.wasapiBlocks && !capture.wasapiFailures && !capture.wasapiPadding.count);
std::vector<std::thread> workers;
for (int p = 0; p < producers; ++p) workers.emplace_back([p, &done]() {
for (int i = 0; i < perProducer; ++i)
+15 -3
View File
@@ -21,7 +21,7 @@
#include <fstream>
#include <time.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "../../../lib/platform_compat.h"
#endif
extern "C" {
@@ -253,10 +253,11 @@ void consume(const Event& e, std::map<uintptr_t, Device>& devices) {
// Первая аномалия за окно, остальные попадут в сводку.
if (d.faults[e.kind] == uint64_t(std::max<int64_t>(e.value, 1)))
DEBUG_WARN(DEBUG_CATEGORY_CALL,
"AudioDiag: fault dev=%p role=%s id=%016llx kind=%s stage=%s value=%lld detail=%lld tid=%ld mono_ms=%llu",
"AudioDiag: fault dev=%p role=%s id=%016llx kind=%s stage=%s value=%lld detail=%lld tid=%ld mono_ms=%llu%s%s",
(void*)e.device, d.label.c_str(), (unsigned long long)d.id, kFaults[e.kind - AUDIO_DIAG_HOLE],
e.stage >= 0 && e.stage < AUDIO_DIAG_STAGE_COUNT ? kStages[e.stage] : "none",
(long long)e.value, (long long)e.detail, e.tid, (unsigned long long)(e.wall / 1000000));
(long long)e.value, (long long)e.detail, e.tid, (unsigned long long)(e.wall / 1000000),
e.text[0] ? " reason=" : "", e.text);
}
}
@@ -388,6 +389,14 @@ void audio_diag_stop(void) {
DEBUG_INFO(DEBUG_CATEGORY_CALL, "AudioDiag: stopped");
}
void audio_diag_event(const void* device, int kind, int stage, int64_t value, int64_t detail) {
if (kind < AUDIO_DIAG_ENTER || kind > AUDIO_DIAG_ERROR) {
publish(device, AUDIO_DIAG_ERROR, stage, 1, kind, "invalid generic event kind");
return;
}
if ((kind == AUDIO_DIAG_ENTER || kind == AUDIO_DIAG_LEAVE) && (stage < 0 || stage >= AUDIO_DIAG_STAGE_COUNT)) {
publish(device, AUDIO_DIAG_ERROR, -1, 1, stage, "invalid stage for enter/leave");
return;
}
publish(device, kind, stage, value, detail, nullptr);
}
void audio_diag_label(const void* device, uint64_t id, const char* name) {
@@ -397,12 +406,15 @@ void audio_diag_log(const void* log, unsigned int level, const char* message) {
publish(log, AUDIO_DIAG_LOG, -1, level, 0, message);
}
void audio_diag_capture_clock(const void* device, const audio_capture_timing* timing) {
if (!timing) { publish(device, AUDIO_DIAG_ERROR, AUDIO_DIAG_READ, 1, 0, "NULL capture clock snapshot"); return; }
publish(device, AUDIO_DIAG_CAPTURE_CLOCK, AUDIO_DIAG_READ, 0, 0, nullptr, timing);
}
void audio_diag_playback_clock(const void* device, const audio_playback_timing* timing) {
if (!timing) { publish(device, AUDIO_DIAG_ERROR, AUDIO_DIAG_WRITE, 1, 0, "NULL playback clock snapshot"); return; }
publish(device, AUDIO_DIAG_PLAYBACK_CLOCK, AUDIO_DIAG_WRITE, 0, 0, nullptr, nullptr, timing);
}
void audio_diag_wasapi_playback(const void* device, const audio_wasapi_timing* timing) {
if (!timing) { publish(device, AUDIO_DIAG_ERROR, AUDIO_DIAG_WRITE, 1, 0, "NULL WASAPI snapshot"); return; }
publish(device, AUDIO_DIAG_WASAPI_PLAYBACK, AUDIO_DIAG_WRITE, 0, 0, nullptr, nullptr, nullptr, timing);
}
#else
+34 -10
View File
@@ -2,8 +2,15 @@
#include <stdint.h>
/* Desktop-аудиодиагностика: callbacks передают числовые снимки в ограниченную очередь.
* Отдельный reporter пишет сводки в категорию call; start/stop принадлежат владельцу устройств. */
/* Desktop-аудиодиагностика Linux/Windows; на других платформах API ничего не делает.
* После start несколько producers передают метаданные в MPSC-кольцо на 8192 события.
* Публикация без allocation/логирования/ожидания, не более четырёх попыток резервирования;
* перегрузка/конкуренция дают потерю события, которую reporter отмечает в сводке.
* Отдельный поток опрашивает очередь каждые 20 мс, пишет секундные сводки в call:
* штатные стадии TRACE, часы DEBUG, аномалии WARN (первая каждого типа за интервал + счётчики).
* Указатели device/log — только числовые ключи, могут быть NULL; reporter их не разыменовывает.
* Строки копируются до 191 байта плюс NUL, timing копируется целиком до возврата вызова.
* PCM и содержимое сообщений не принимаются. До start/после stop события игнорируются. */
#ifdef __cplusplus
extern "C" {
#endif
@@ -22,15 +29,18 @@ enum audio_diag_kind {
};
struct audio_capture_timing {
int64_t first_us, latency_us, converter_us;
unsigned frames, rate;
int measured, missing;
int64_t first_us; // оценка monotonic-времени первого кадра блока, микросекунды
int64_t latency_us; // знаковая backend latency, учитывается только при measured!=0
int64_t converter_us; // задержка converter/cache, микросекунды
unsigned frames, rate; // размер блока и его частота, Гц; rate=0 исключает расчёт длительности
int measured; // backend-оценка доступна; 0 означает оценочный блок без измеренной latency
int missing; // блок с hole: в missing_frames попадают все frames этого блока
};
struct audio_playback_timing {
int64_t latency_us, converter_us;
unsigned frames, rate;
int measured;
int64_t latency_us, converter_us; // backend latency и задержка converter/cache, микросекунды
unsigned frames, rate; // размер блока и частота, Гц; rate=0 исключает расчёт длительности
int measured; // ненулевой: latency_us участвует в диапазоне; отрицательная latency допустима
};
/* Снимок перед заполнением client PCM. Padding — текущая очередь, stream_latency — отдельное
@@ -44,15 +54,29 @@ struct audio_wasapi_timing {
int32_t padding_result, latency_result; // HRESULT каждого запроса; отрицательное значение — ошибка.
};
/* start/stop — управляющий поток; перед stop все producers должны быть остановлены. */
/* Один управляющий владелец сериализует start/stop. Повторный start активного reporter — no-op;
* отказ создания потока логируется, диагностика остаётся выключенной (возврата статуса нет).
* Перед stop остановить и дождаться всех producers, включая незавершённые публикации.
* stop дренирует очередь, пишет последнюю сводку и join; повторный stop допустим.
* Следующий start очищает статистику и очередь. Не вызывать stop из reporter. */
void audio_diag_start(void);
void audio_diag_stop(void);
/* kind — ENTER..ERROR; LABEL/LOG/CLOCK передавать специализированными функциями.
* ENTER/LEAVE требуют stage=0..STAGE_COUNT-1; остальные события могут использовать -1.
* value/detail — backend-метрики: ENTER value=frames, OPEN value=тип/detail=частота;
* для аномалий value даёт max(value,1) в счётчик. Неверные kind/stage дают ERROR-событие. */
void audio_diag_event(const void* device, int kind, int stage, int64_t value, int64_t detail);
/* Метка ресурса; id без интерпретации, NULL name означает пустую строку. */
void audio_diag_label(const void* device, uint64_t id, const char* name);
/* miniaudio level 1/2/3/остальные -> ERROR/WARN/DEBUG/TRACE; NULL message — пустая строка.
* Строки с Hole. подавляются: число hole-кадров собирает отдельное событие. */
void audio_diag_log(const void* log, unsigned int level, const char* message);
/* Снимки из потока устройства; timing обязателен, NULL даёт ERROR-событие без снимка. */
void audio_diag_capture_clock(const void* device, const struct audio_capture_timing* timing);
void audio_diag_playback_clock(const void* device, const struct audio_playback_timing* timing);
/* Вызывать из playback callback; снимок копируется, устройством reporter не пользуется. */
/* Снимок перед заполнением playback buffer; timing обязателен, NULL даёт ERROR-событие.
* Только shared padding с успешным HRESULT и device_rate>0 даёт оценку очереди;
* stream latency учитывается отдельно, даже в exclusive mode. */
void audio_diag_wasapi_playback(const void* device, const struct audio_wasapi_timing* timing);
#ifdef __cplusplus