- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
301 lines
11 KiB
C
301 lines
11 KiB
C
// test_media_async.c — стресс-тест примитивов media_async (многопоточность + корректность)
|
|
//
|
|
// 4 потока одновременно выполняют ma_* операции на файлах 1K/5K/10K.
|
|
// Тест длится 2 секунды, затем потоки останавливаются.
|
|
// Проверяется корректность каждого результата и отсутствие заклиниваний.
|
|
|
|
#include "../src/media_async/media_async.h"
|
|
#include "../lib/platform_compat.h"
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <openssl/evp.h>
|
|
|
|
#define NUM_THREADS 4
|
|
#define TEST_TIMEOUT_SEC 2
|
|
#define NUM_FILES 3
|
|
#define SIZE_1K 1024
|
|
#define SIZE_5K 5120
|
|
#define SIZE_10K 10240
|
|
#define NUM_OPS 5
|
|
|
|
static volatile int g_running = 1;
|
|
|
|
static const char* g_op_names[NUM_OPS] = {
|
|
"SHA256-file", "Sign-block", "Copy-file",
|
|
"UUID", "FileSize"
|
|
};
|
|
|
|
struct thread_stats {
|
|
int tid;
|
|
int op_count;
|
|
int errors;
|
|
int op_counts[NUM_OPS];
|
|
double max_time_us[NUM_OPS]; // максимальное время (микросекунды) по каждой операции
|
|
};
|
|
|
|
static char g_test_dir[256];
|
|
static char g_file_paths[NUM_FILES][512];
|
|
static uint8_t g_file_hashes[NUM_FILES][32];
|
|
static int g_file_sizes[NUM_FILES];
|
|
static uint8_t g_test_data[NUM_FILES][SIZE_10K];
|
|
static uint8_t g_ed25519_privkey[32];
|
|
static uint8_t g_expected_sigs[NUM_FILES][64];
|
|
|
|
static int64_t now_us(void) {
|
|
struct timeval tv; gettimeofday(&tv, NULL);
|
|
return (int64_t)tv.tv_sec * 1000000LL + tv.tv_usec;
|
|
}
|
|
|
|
static void fill_test_data(uint8_t* buf, int size, int seed) {
|
|
unsigned s = (unsigned)seed;
|
|
for (int i = 0; i < size; i++) {
|
|
s = s * 1103515245 + 12345;
|
|
buf[i] = (uint8_t)((s >> 16) & 0xFF);
|
|
}
|
|
}
|
|
|
|
static void* worker_thread(void* arg) {
|
|
struct thread_stats* st = (struct thread_stats*)arg;
|
|
unsigned s = (unsigned)(time(NULL) ^ (st->tid * 0x9E3779B9 << 8));
|
|
|
|
while (g_running) {
|
|
s = s * 1103515245 + 12345;
|
|
int op = (int)((s >> 16) % 4); // random: 0=SHA256, 1=Sign, 2=Copy, 3=UUID+FileSize
|
|
s = s * 1103515245 + 12345;
|
|
int fi = (int)((s >> 16) % NUM_FILES);
|
|
int size = g_file_sizes[fi];
|
|
|
|
int real_op = -1;
|
|
int64_t t0, elapsed;
|
|
|
|
switch (op) {
|
|
case 0: { // SHA256 file
|
|
real_op = 0;
|
|
uint8_t hash[32];
|
|
t0 = now_us();
|
|
int rc = ma_sha256_file(g_file_paths[fi], hash);
|
|
elapsed = now_us() - t0;
|
|
if (rc != 0 || memcmp(hash, g_file_hashes[fi], 32) != 0) st->errors++;
|
|
break;
|
|
}
|
|
case 1: { // Ed25519 sign
|
|
real_op = 1;
|
|
uint8_t sig[64];
|
|
t0 = now_us();
|
|
int rc = ma_sign_block(g_ed25519_privkey, g_test_data[fi], (size_t)size, sig);
|
|
elapsed = now_us() - t0;
|
|
if (rc != 0 || memcmp(sig, g_expected_sigs[fi], 64) != 0) st->errors++;
|
|
break;
|
|
}
|
|
case 2: { // copy file
|
|
real_op = 2;
|
|
char dst[512];
|
|
snprintf(dst, sizeof(dst), "%s/cp_t%d_n%d.bin", g_test_dir, st->tid, st->op_count);
|
|
t0 = now_us();
|
|
int rc = ma_copy_file(g_file_paths[fi], dst);
|
|
elapsed = now_us() - t0;
|
|
if (rc == 0) {
|
|
uint8_t hash[32];
|
|
if (ma_sha256_file(dst, hash) != 0 || memcmp(hash, g_file_hashes[fi], 32) != 0)
|
|
st->errors++;
|
|
unlink(dst);
|
|
} else st->errors++;
|
|
break;
|
|
}
|
|
case 3: { // UUID
|
|
real_op = 3;
|
|
uint8_t uuid[16];
|
|
t0 = now_us();
|
|
ma_uuid(uuid);
|
|
elapsed = now_us() - t0;
|
|
int zero = 1;
|
|
for (int i = 0; i < 16; i++) { if (uuid[i] != 0) { zero = 0; break; } }
|
|
if (zero) st->errors++;
|
|
|
|
// file_size отдельно с замером времени
|
|
t0 = now_us();
|
|
int64_t fsz = ma_file_size(g_file_paths[fi]);
|
|
int64_t et = now_us() - t0;
|
|
if (fsz != g_file_sizes[fi]) st->errors++;
|
|
st->op_counts[4]++;
|
|
if (et > st->max_time_us[4]) st->max_time_us[4] = (double)et;
|
|
break;
|
|
}
|
|
}
|
|
|
|
st->op_count++;
|
|
if (real_op >= 0) {
|
|
st->op_counts[real_op]++;
|
|
if (elapsed > st->max_time_us[real_op])
|
|
st->max_time_us[real_op] = (double)elapsed;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
int ret = 0;
|
|
|
|
snprintf(g_test_dir, sizeof(g_test_dir), "/tmp/test_media_async_%d", (int)getpid());
|
|
if (utun_mkdir(g_test_dir, 0755) != 0) {
|
|
perror("mkdir"); return 1;
|
|
}
|
|
|
|
g_file_sizes[0] = SIZE_1K;
|
|
g_file_sizes[1] = SIZE_5K;
|
|
g_file_sizes[2] = SIZE_10K;
|
|
|
|
for (int i = 0; i < NUM_FILES; i++) {
|
|
fill_test_data(g_test_data[i], g_file_sizes[i], 42 + i * 100);
|
|
snprintf(g_file_paths[i], sizeof(g_file_paths[i]), "%s/test_%dk.bin", g_test_dir, g_file_sizes[i] / 1024);
|
|
FILE* f = fopen(g_file_paths[i], "wb");
|
|
if (!f) { perror("fopen"); ret = 1; goto cleanup; }
|
|
fwrite(g_test_data[i], 1, (size_t)g_file_sizes[i], f);
|
|
fclose(f);
|
|
ma_sha256_file(g_file_paths[i], g_file_hashes[i]);
|
|
}
|
|
|
|
{
|
|
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
|
|
if (!pctx) { fprintf(stderr, "EVP_PKEY_CTX_new_id failed\n"); ret = 1; goto cleanup; }
|
|
EVP_PKEY_keygen_init(pctx);
|
|
EVP_PKEY* pkey = NULL;
|
|
if (EVP_PKEY_keygen(pctx, &pkey) != 1 || !pkey) {
|
|
fprintf(stderr, "EVP_PKEY_keygen failed\n"); EVP_PKEY_CTX_free(pctx); ret = 1; goto cleanup;
|
|
}
|
|
EVP_PKEY_CTX_free(pctx);
|
|
size_t keylen = 32;
|
|
EVP_PKEY_get_raw_private_key(pkey, g_ed25519_privkey, &keylen);
|
|
EVP_PKEY_free(pkey);
|
|
for (int i = 0; i < NUM_FILES; i++)
|
|
ma_sign_block(g_ed25519_privkey, g_test_data[i], (size_t)g_file_sizes[i], g_expected_sigs[i]);
|
|
}
|
|
|
|
pthread_t threads[NUM_THREADS];
|
|
struct thread_stats stats[NUM_THREADS];
|
|
memset(stats, 0, sizeof(stats));
|
|
for (int i = 0; i < NUM_THREADS; i++) {
|
|
stats[i].tid = i;
|
|
if (pthread_create(&threads[i], NULL, worker_thread, &stats[i]) != 0) {
|
|
fprintf(stderr, "pthread_create(%d) failed\n", i);
|
|
g_running = 0;
|
|
for (int j = 0; j < i; j++) pthread_join(threads[j], NULL);
|
|
ret = 1; goto cleanup;
|
|
}
|
|
}
|
|
|
|
sleep(TEST_TIMEOUT_SEC);
|
|
g_running = 0;
|
|
|
|
for (int i = 0; i < NUM_THREADS; i++)
|
|
pthread_join(threads[i], NULL);
|
|
|
|
printf("\n=== media_async stress test ===\n");
|
|
printf("Timeout: %d sec, Threads: %d, Files: 1K/5K/10K\n\n", TEST_TIMEOUT_SEC, NUM_THREADS);
|
|
|
|
int total_ops = 0, total_errors = 0;
|
|
int totals_op[NUM_OPS] = {0};
|
|
double max_time_all[NUM_OPS] = {0};
|
|
|
|
for (int i = 0; i < NUM_THREADS; i++) {
|
|
printf("Thread %d: %d ops (", i, stats[i].op_count);
|
|
for (int o = 0; o < NUM_OPS; o++) {
|
|
if (o > 0) printf(" ");
|
|
printf("%s:%d", g_op_names[o], stats[i].op_counts[o]);
|
|
totals_op[o] += stats[i].op_counts[o];
|
|
}
|
|
printf(") errors=%d\n", stats[i].errors);
|
|
|
|
printf(" max-time(us): ");
|
|
for (int o = 0; o < NUM_OPS; o++) {
|
|
if (o > 0) printf(" ");
|
|
printf("%s:%.0f", g_op_names[o], stats[i].max_time_us[o]);
|
|
if (stats[i].max_time_us[o] > max_time_all[o]) max_time_all[o] = stats[i].max_time_us[o];
|
|
}
|
|
printf("\n");
|
|
|
|
total_ops += stats[i].op_count;
|
|
total_errors += stats[i].errors;
|
|
}
|
|
|
|
printf("\nTotal: %d ops, %d errors\n", total_ops, total_errors);
|
|
printf("Op totals: ");
|
|
for (int o = 0; o < NUM_OPS; o++) printf("%s:%d ", g_op_names[o], totals_op[o]);
|
|
printf("\nMax time(us): ");
|
|
for (int o = 0; o < NUM_OPS; o++) printf("%s:%.0f ", g_op_names[o], max_time_all[o]);
|
|
printf("\n\n");
|
|
|
|
/* ─── sanity checks ─── */
|
|
|
|
// минимальный throughput: хотя бы 1000 ops на поток за 2 сек
|
|
int min_ops_per_thread = 1000;
|
|
// разбалансировка потоков: самый медленный поток не хуже 0.1× от самого быстрого
|
|
double balance_ratio = 0.1;
|
|
// макс допустимое время SHA256/Sign/Copy/USB операции (микросекунды)
|
|
double max_us_sha = 100000; // 100ms
|
|
double max_us_sign = 100000;
|
|
double max_us_copy = 100000;
|
|
double max_us_uuid = 5000; // 5ms
|
|
double max_us_filesize = 5000;
|
|
|
|
int min_ops = stats[0].op_count, max_ops = stats[0].op_count;
|
|
for (int i = 1; i < NUM_THREADS; i++) {
|
|
if (stats[i].op_count < min_ops) min_ops = stats[i].op_count;
|
|
if (stats[i].op_count > max_ops) max_ops = stats[i].op_count;
|
|
}
|
|
|
|
printf("Sanity checks:\n");
|
|
|
|
if (total_errors > 0) {
|
|
printf(" FAIL: %d errors\n", total_errors);
|
|
ret = 1;
|
|
}
|
|
|
|
if (total_ops < min_ops_per_thread * NUM_THREADS) {
|
|
printf(" FAIL: total ops %d < %d (throughput too low)\n",
|
|
total_ops, min_ops_per_thread * NUM_THREADS);
|
|
ret = 1;
|
|
} else {
|
|
printf(" OK: total ops=%d >= %d\n", total_ops, min_ops_per_thread * NUM_THREADS);
|
|
}
|
|
|
|
if (max_ops > 0 && min_ops < (int)(max_ops * balance_ratio)) {
|
|
printf(" FAIL: thread imbalance min=%d max=%d (ratio %.3f < %.1f)\n",
|
|
min_ops, max_ops, max_ops > 0 ? (double)min_ops / max_ops : 0, balance_ratio);
|
|
ret = 1;
|
|
} else {
|
|
printf(" OK: thread balance min=%d max=%d (ratio %.3f)\n",
|
|
min_ops, max_ops, max_ops > 0 ? (double)min_ops / max_ops : 0);
|
|
}
|
|
|
|
double limits[NUM_OPS] = { max_us_sha, max_us_sign, max_us_copy, max_us_uuid, max_us_filesize };
|
|
for (int o = 0; o < NUM_OPS; o++) {
|
|
if (max_time_all[o] > limits[o]) {
|
|
printf(" FAIL: %s max-time %.0fus > %.0fus\n", g_op_names[o], max_time_all[o], limits[o]);
|
|
ret = 1;
|
|
} else if (max_time_all[o] <= 0.0) {
|
|
printf(" WARN: %s max-time 0 (no measurements?)\n", g_op_names[o]);
|
|
} else {
|
|
printf(" OK: %s max-time %.0fus\n", g_op_names[o], max_time_all[o]);
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
printf("\nPASS: %d ops, 0 errors, all checks OK\n", total_ops);
|
|
} else {
|
|
printf("\nFAIL: sanity checks failed\n");
|
|
}
|
|
|
|
cleanup:
|
|
for (int i = 0; i < NUM_FILES; i++) unlink(g_file_paths[i]);
|
|
rmdir(g_test_dir);
|
|
return ret;
|
|
}
|