You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
11 KiB
299 lines
11 KiB
// test_media_async.c — стресс-тест примитивов media_async (многопоточность + корректность) |
|
// |
|
// 4 потока одновременно выполняют ma_* операции на файлах 1K/5K/10K. |
|
// Тест длится 2 секунды, затем потоки останавливаются. |
|
// Проверяется корректность каждого результата и отсутствие заклиниваний. |
|
|
|
#include "../src/media_async/media_async.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 (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; |
|
}
|
|
|