/
cska
/
master
Обзор
Документация
Войти
/
cska
/
master
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
load_tests.c
301 строка
11 KB
PR0GR4M1ST
Новый модуль нагрузочного тестирования + чутка измененная инструкция в README.md
12 авг 2025, 08:58
12 авг 2025, 08:58
1f82290
Код
Авторство
О чём код?
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <stdbool.h> #include <string.h> #include <time.h> #include <math.h> #include <pthread.h> #include <gattlib.h> #define HR_RING_CAP 2048 typedef struct { double t; uint16_t hr; } HrSample; typedef struct { HrSample ring[HR_RING_CAP]; size_t head, size; pthread_mutex_t mtx; #ifdef GATTLIB_NEW_API gattlib_connection_t *conn; gattlib_adapter_t *adapter; #else gatt_connection_t *conn; #endif int active; double last_rx; const char *mac; int timeout_sec; int retries_left; } HrCtx; typedef enum { MODE_ABROSIMOVA, MODE_KARPMAN } Mode; typedef struct { const char *mac, *csv_path, *json_path; Mode mode; double w, w1, w2; int watts; /* 1=Вт, 0=кгм/мин */ int rest_sec; /* перед тестом */ int stage1_sec; /* длит. ступени 1 */ int stage2_sec; /* длит. ступени 2 */ int pause_sec; /* пауза меж ступенями */ int ble_timeout; /* сек без пакетов до reconnect */ int ble_retries; /* число попыток reconnect */ } Args; /* ====== Чистые функции (формулы/конверсия) ====== */ static double to_kgmmin(double v, int watts) { double res = watts ? (v * 6.12) : v; return res; } static double pwc170_abrosimova(double W, double f0, double f1) { double res = NAN; if (f1 > f0 + 1.0) res = W * (170.0 - f0) / (f1 - f0); return res; } static double pwc170_karpman(double W1, double f1, double W2, double f2) { double res = NAN; if (f2 > f1 + 1.0) res = W1 + (W2 - W1) * (170.0 - f1) / (f2 - f1); return res; } /* ========================= BLE/HR поток ========================= */ static double now_sec(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } static void hr_ring_push(HrCtx *c, uint16_t hr) { pthread_mutex_lock(&c->mtx); c->ring[c->head].t = now_sec(); c->ring[c->head].hr = hr; c->head = (c->head + 1) % HR_RING_CAP; if (c->size < HR_RING_CAP) c->size++; c->last_rx = c->ring[(c->head + HR_RING_CAP - 1) % HR_RING_CAP].t; pthread_mutex_unlock(&c->mtx); } static double hr_avg_last(HrCtx *c, double win_sec, double t_end) { double sum = 0.0; size_t cnt = 0, i = 0; double t_start = t_end - win_sec; pthread_mutex_lock(&c->mtx); while (i < c->size) { size_t idx = (c->head + HR_RING_CAP - 1 - i) % HR_RING_CAP; HrSample s = c->ring[idx]; if (s.t < t_start) break; sum += s.hr; cnt++; i++; } pthread_mutex_unlock(&c->mtx); return cnt ? (sum / (double)cnt) : NAN; } static int hr_last(HrCtx *c, uint16_t *hr, double *ts) { int ok = 0; pthread_mutex_lock(&c->mtx); if (c->size) { size_t i = (c->head + HR_RING_CAP - 1) % HR_RING_CAP; *hr = c->ring[i].hr; *ts = c->ring[i].t; ok = 1; } pthread_mutex_unlock(&c->mtx); return ok; } static void hr_cb(const uuid_t *uuid, const uint8_t *data, size_t len, void *u){ (void)uuid; HrCtx *c = (HrCtx *)u; uint16_t hr = 0; size_t off = 1; if (len >= 2) { uint8_t flags = data[0]; if ((flags & 0x01) == 0) hr = data[off]; else if (len >= 3) hr = (uint16_t)data[off] | ((uint16_t)data[off + 1] << 8); } if (hr > 0 && hr < 260 && c->active) hr_ring_push(c, hr); } static int ble_connect_subscribe(HrCtx *c, const char *mac) { int ok = 1, rc = 0; uuid_t meas = CREATE_UUID16(0x2A37); c->conn = NULL; c->active = 0; c->mac = mac; c->last_rx = now_sec(); #ifdef GATTLIB_NEW_API c->adapter = NULL; #endif if (pthread_mutex_init(&c->mtx, NULL) != 0) ok = 0; #ifdef GATTLIB_NEW_API if (ok) rc = gattlib_adapter_open(NULL, &c->adapter); if (ok && rc) ok = 0; if (ok) rc = gattlib_connect(c->adapter, mac, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT, &c->conn); if (ok && rc) ok = 0; #else if (ok) c->conn = gattlib_connect(NULL, mac, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT); if (ok && c->conn == NULL) ok = 0; #endif if (ok) rc = gattlib_register_notification(c->conn, hr_cb, c); if (ok && rc) ok = 0; if (ok) rc = gattlib_notification_start(c->conn, &meas); if (ok && rc) ok = 0; if (ok) c->active = 1; if (!ok) { #ifdef GATTLIB_NEW_API if (c->conn) gattlib_disconnect(c->conn, true); if (c->adapter) gattlib_adapter_close(c->adapter); #else if (c->conn) gattlib_disconnect(c->conn); #endif pthread_mutex_destroy(&c->mtx); } return ok ? 0 : -1; } static void ble_close(HrCtx *c) { uuid_t meas = CREATE_UUID16(0x2A37); c->active = 0; if (c->conn) { gattlib_notification_stop(c->conn, &meas); #ifdef GATTLIB_NEW_API gattlib_disconnect(c->conn, true); c->conn = NULL; if (c->adapter) { gattlib_adapter_close(c->adapter); c->adapter = NULL; } #else gattlib_disconnect(c->conn); c->conn = NULL; #endif } pthread_mutex_destroy(&c->mtx); } static int ble_reconnect_if_idle(HrCtx *c) { int ok = 1; if (c->timeout_sec > 0 && (now_sec() - c->last_rx) > c->timeout_sec) { ble_close(c); if (c->retries_left <= 0) ok = 0; else { c->retries_left--; ok = (ble_connect_subscribe(c, c->mac) == 0); } } return ok ? 0 : -1; } /* ========================= Логирование/этапы ========================= */ static int log_open_csv(const char *path, FILE **f) { int ok = 1; *f = NULL; if (path && *path) { *f = fopen(path, "w"); if (!*f) ok = 0; if (ok) fprintf(*f, "ts_epoch,stage,hr,connected\n"); } return ok ? 0 : -1; } static void log_row(FILE *f, double ts, const char *stage, int hr, int conn) { if (f) fprintf(f, "%.3f,%s,%d,%d\n", ts, stage, hr, conn); } static double run_stage(HrCtx *c, int dur_sec, const char *label, FILE *csv) { double t0 = now_sec(), tend = t0 + (double)dur_sec, next_log = floor(t0) + 1.0; struct timespec req = {.tv_sec = 0, .tv_nsec = 200 * 1000000}; while (now_sec() < tend && c->active) { (void)ble_reconnect_if_idle(c); if (csv && now_sec() >= next_log) { uint16_t hr = 0; double ts = 0.0; if (hr_last(c, &hr, &ts)) log_row(csv, now_sec(), label, (int)hr, c->active); next_log += 1.0; } nanosleep(&req, NULL); } return hr_avg_last(c, 30.0, now_sec()); } /* ========================= Параметры/CLI ========================= */ static int parse_args(int ac, char **av, Args *a) { int i = 1, ok = 1; memset(a, 0, sizeof(*a)); a->rest_sec = 60; a->stage1_sec = 300; a->stage2_sec = 300; a->pause_sec = 180; a->ble_timeout = 5; a->ble_retries = 3; while (i < ac && ok) { if (!strcmp(av[i], "--device") && i + 1 < ac) a->mac = av[++i]; else if (!strcmp(av[i], "--mode") && i + 1 < ac) { const char *m = av[++i]; if (!strcmp(m, "abrosimova")) a->mode = MODE_ABROSIMOVA; else if (!strcmp(m, "karpman")) a->mode = MODE_KARPMAN; else ok = 0; } else if (!strcmp(av[i], "--w") && i + 1 < ac) a->w = atof(av[++i]); else if (!strcmp(av[i], "--w1") && i + 1 < ac) a->w1 = atof(av[++i]); else if (!strcmp(av[i], "--w2") && i + 1 < ac) a->w2 = atof(av[++i]); else if (!strcmp(av[i], "--unit") && i + 1 < ac) { const char *u = av[++i]; a->watts = !strcmp(u, "watts") ? 1 : (!strcmp(u, "kgmmin") ? 0 : -1); if (a->watts < 0) ok = 0; } else if (!strcmp(av[i], "--csv") && i + 1 < ac) a->csv_path = av[++i]; else if (!strcmp(av[i], "--json") && i + 1 < ac) a->json_path = av[++i]; else if (!strcmp(av[i], "--rest") && i + 1 < ac) a->rest_sec = atoi(av[++i]); else if (!strcmp(av[i], "--stage1") && i + 1 < ac) a->stage1_sec = atoi(av[++i]); else if (!strcmp(av[i], "--stage2") && i + 1 < ac) a->stage2_sec = atoi(av[++i]); else if (!strcmp(av[i], "--pause") && i + 1 < ac) a->pause_sec = atoi(av[++i]); else if (!strcmp(av[i], "--ble-timeout") && i + 1 < ac) a->ble_timeout = atoi(av[++i]); else if (!strcmp(av[i], "--ble-retries") && i + 1 < ac) a->ble_retries = atoi(av[++i]); else ok = 0; i++; } if (!a->mac) ok = 0; if (a->mode == MODE_ABROSIMOVA && a->w <= 0) ok = 0; if (a->mode == MODE_KARPMAN && (a->w1 <= 0 || a->w2 <= 0)) ok = 0; if (!ok) fprintf(stderr, "Usage:\n" " --device MAC --mode abrosimova --w <val> --unit watts|kgmmin [--csv f] [--json f] [--rest s] [--stage1 s]\n" " --device MAC --mode karpman --w1 <v1> --w2 <v2> --unit watts|kgmmin [--csv f] [--json f]\n" " [--rest s] [--stage1 s] [--pause s] [--stage2 s] [--ble-timeout s] [--ble-retries n]\n"); return ok ? 0 : -1; } /* ========================= Запуск теста/вывод ========================= */ static int run_and_report(HrCtx *c, Args *a, FILE *csv) { int rc = 0; double f0 = NAN, f1 = NAN, f2 = NAN, pwc = NAN; fprintf(stderr, "Rest %d s...\n", a->rest_sec); (void)run_stage(c, a->rest_sec, "rest", csv); f0 = hr_avg_last(c, 30.0, now_sec()); if (isnan(f0)) rc = 4; if (rc == 0 && a->mode == MODE_ABROSIMOVA) { fprintf(stderr, "Stage1 %d s.\n", a->stage1_sec); f1 = run_stage(c, a->stage1_sec, "stage1", csv); pwc = pwc170_abrosimova(to_kgmmin(a->w, a->watts), f0, f1); } if (rc == 0 && a->mode == MODE_KARPMAN) { fprintf(stderr, "Stage1 %d s.\n", a->stage1_sec); f1 = run_stage(c, a->stage1_sec, "stage1", csv); fprintf(stderr, "Pause %d s.\n", a->pause_sec); (void)run_stage(c, a->pause_sec, "pause", csv); fprintf(stderr, "Stage2 %d s.\n", a->stage2_sec); f2 = run_stage(c, a->stage2_sec, "stage2", csv); pwc = pwc170_karpman(to_kgmmin(a->w1, a->watts), f1, to_kgmmin(a->w2, a->watts), f2); if (f2 - f1 < 40.0) fprintf(stderr, "WARN: ΔHR=%.1f<40 bpm — по методике результат сомнителен.\n", f2 - f1); } if (rc == 0) { FILE *jf = NULL; if (a->json_path && *a->json_path) jf = fopen(a->json_path, "w"); fprintf(stdout, "{\"mode\":\"%s\",\"f0\":%.1f,\"f1\":%.1f,\"f2\":%.1f,\"PWC170(kgm/min)\":%.1f}\n", a->mode == MODE_ABROSIMOVA ? "abrosimova" : "karpman", f0, f1, isnan(f2) ? 0.0 : f2, pwc); if (jf) { fprintf(jf, "{\"f0\":%.1f,\"f1\":%.1f,\"f2\":%.1f,\"pwc170\":%.1f}\n", f0, f1, isnan(f2)?0.0:f2, pwc); fclose(jf); } } return rc; } int main(int argc, char **argv) { int rc = 0; Args a; HrCtx c; FILE *csv = NULL; if (parse_args(argc, argv, &a) != 0) rc = 1; if (rc == 0 && log_open_csv(a.csv_path, &csv) != 0) rc = 2; if (rc == 0) { c.timeout_sec = a.ble_timeout; c.retries_left = a.ble_retries; if (ble_connect_subscribe(&c, a.mac) != 0) rc = 3; } if (rc == 0) rc = run_and_report(&c, &a, csv); if (csv) fclose(csv); ble_close(&c); return rc; }