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.
 
 
 
 
 
 

242 lines
11 KiB

/* CPU-метрика: управляемые часы/ожидание и реальная нагрузка одного потока. */
#define _GNU_SOURCE
#include "u_async.h"
#include "debug_config.h"
#include "mem.h"
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <time.h>
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #c); exit(1); } } while (0)
#define NEAR(a, b) CHECK(fabs((a) - (b)) < 0.0001)
static int simulated, clock_failure, wait_error, last_timeout, worker_stop, force_poll;
static uint64_t wall_us, cpu_ns, wake_after_us;
int __real_epoll_create1(int flags);
int __wrap_epoll_create1(int flags) {
if (force_poll) { errno = ENOSYS; return -1; }
return __real_epoll_create1(flags);
}
int __real_clock_gettime(clockid_t id, struct timespec* ts);
int __wrap_clock_gettime(clockid_t id, struct timespec* ts) {
if (!simulated) return __real_clock_gettime(id, ts);
if (id == CLOCK_THREAD_CPUTIME_ID && clock_failure) { errno = EINVAL; return -1; }
uint64_t ns = id == CLOCK_THREAD_CPUTIME_ID ? cpu_ns : wall_us * 1000;
ts->tv_sec = ns / 1000000000; ts->tv_nsec = ns % 1000000000;
return 0;
}
static int simulate_wait(int timeout) {
last_timeout = timeout;
CHECK(timeout >= 0 || wake_after_us);
wall_us += timeout >= 0 ? (uint64_t)timeout * 1000 : wake_after_us;
wake_after_us = 0;
if (wait_error) { errno = wait_error; wait_error = 0; return -1; }
return 0;
}
int __real_epoll_wait(int fd, struct epoll_event* events, int count, int timeout);
int __wrap_epoll_wait(int fd, struct epoll_event* events, int count, int timeout) {
return simulated ? simulate_wait(timeout) : __real_epoll_wait(fd, events, count, timeout);
}
int __real_poll(struct pollfd* fds, nfds_t count, int timeout);
int __wrap_poll(struct pollfd* fds, nfds_t count, int timeout) {
return simulated ? simulate_wait(timeout) : __real_poll(fds, count, timeout);
}
static struct uasync_load snapshot(struct UASYNC* ua) {
struct uasync_load load;
CHECK(uasync_get_load(ua, &load) == ERR_OK);
return load;
}
static struct uasync_load advance(struct UASYNC* ua, uint64_t elapsed_us, uint64_t used_cpu_us) {
wall_us += elapsed_us; cpu_ns += used_cpu_us * 1000;
uasync_poll(ua, 0);
return snapshot(ua);
}
static struct UASYNC* start_fake(int fallback) {
simulated = 1; wall_us = 10000000; cpu_ns = 0; clock_failure = 0; wait_error = 0;
force_poll = fallback;
struct UASYNC* ua = uasync_create(); CHECK(ua);
CHECK(!snapshot(ua).valid);
uasync_poll(ua, 0);
CHECK(!snapshot(ua).valid);
return ua;
}
static void delayed_callback(void* arg) {
(void)arg;
wall_us += 350000; cpu_ns += 70000000;
}
static void deterministic(int fallback) {
struct UASYNC* ua = start_fake(fallback);
for (int i = 1; i <= 10; i++) {
uasync_poll(ua, 1000);
struct uasync_load load = snapshot(ua);
CHECK(last_timeout == 100); CHECK(load.valid); CHECK(load.window_us == (uint64_t)i * 100000);
NEAR(load.current_percent, 0); NEAR(load.average_percent, 0);
}
// Пять нагруженных отсчётов вытесняют половину секунды простоя.
struct uasync_load load;
for (int i = 1; i <= 5; i++) {
load = advance(ua, 100000, 100000);
NEAR(load.current_percent, 100); NEAR(load.average_percent, i * 10.0);
CHECK(load.window_us == 1000000);
}
for (int i = 0; i < 5; i++) load = advance(ua, 100000, 0);
NEAR(load.average_percent, 50);
for (int i = 1; i <= 5; i++) { load = advance(ua, 100000, 0); NEAR(load.average_percent, 50 - i * 10.0); }
// Нулевые проходы не создают отсчёты.
uint64_t sampled = load.sample_time_us;
for (int i = 0; i < 100; i++) uasync_poll(ua, 0);
CHECK(snapshot(ua).sample_time_us == sampled);
// EINTR на сроке измерения также публикует отсчёт.
wait_error = EINTR; uasync_poll(ua, 1000);
CHECK(snapshot(ua).sample_time_us == sampled + 100000);
uasync_destroy(ua, 0);
ua = start_fake(fallback);
load = advance(ua, 190000, 95000); NEAR(load.average_percent, 50);
load = advance(ua, 10000, 0); NEAR(load.current_percent, 0); NEAR(load.average_percent, 47.5);
for (int i = 0; i < 9; i++) load = advance(ua, 100000, 0);
// В окне осталось 90мс первого интервала с CPU 50% = 45мс / 1с.
CHECK(load.window_us == 1000000); NEAR(load.average_percent, 4.5);
load = advance(ua, 100000, 0); NEAR(load.average_percent, 0);
// Перерыв > окна: оценка по измеренному длинному интервалу, без выдуманных отсчётов.
load = advance(ua, 2500000, 625000);
CHECK(load.interval_us == 2500000); CHECK(load.window_us == 1000000);
NEAR(load.current_percent, 25); NEAR(load.average_percent, 25);
uasync_destroy(ua, 0);
ua = start_fake(fallback);
CHECK(uasync_call_soon(ua, NULL, delayed_callback));
uasync_poll(ua, 0); load = snapshot(ua);
CHECK(last_timeout == 0); CHECK(load.interval_us == 350000); NEAR(load.current_percent, 20);
uasync_poll(ua, 500); CHECK(last_timeout == 50); CHECK(snapshot(ua).sample_time_us == 10400000);
clock_failure = 1; advance(ua, 100000, 0); CHECK(!snapshot(ua).valid);
clock_failure = 0; uasync_poll(ua, 0); CHECK(!snapshot(ua).valid);
uasync_destroy(ua, 0);
ua = start_fake(fallback);
// Запросы без poll актуализируют нагрузку, частые запросы не вытесняют историю.
for (int i = 0; i < 20000; i++) {
wall_us += 50; cpu_ns += 25000;
load = snapshot(ua);
}
NEAR(load.current_percent, 50); NEAR(load.average_percent, 50); CHECK(load.window_us == 1000000);
// Бесконечный poll не получает таймаут от измерителя; пробуждение моделирует внешнее событие.
wake_after_us = 2000000; uasync_poll(ua, -1); CHECK(last_timeout == -1);
load = snapshot(ua); CHECK(load.valid); NEAR(load.current_percent, 0); NEAR(load.average_percent, 0);
CHECK(load.window_us == 1000000);
uasync_destroy(ua, 0);
printf("PASS deterministic %s: cadence, window, weighting, delay, EINTR, clock failure, request, sleep\n", fallback ? "poll" : "epoll");
}
static uint64_t real_time_ns(clockid_t id) {
struct timespec ts; CHECK(__real_clock_gettime(id, &ts) == 0);
return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
}
struct workload {
struct UASYNC* ua;
int mixed;
uint64_t next_us;
void* timer;
};
static void burn(void* arg) {
struct workload* work = arg;
work->timer = NULL;
uint64_t end = real_time_ns(CLOCK_MONOTONIC) + (work->mixed ? 25000000 : 3000000);
while (real_time_ns(CLOCK_MONOTONIC) < end) {}
if (work->mixed) {
work->next_us += 100000;
uint64_t now = get_time_us();
int delay_tb = now < work->next_us ? (int)((work->next_us - now + 99) / 100) : 0;
work->timer = uasync_set_timeout(work->ua, delay_tb, work, burn, "load 25%");
} else work->timer = uasync_call_soon(work->ua, work, burn);
CHECK(work->timer);
}
static void* other_thread(void* arg) {
(void)arg;
while (!__atomic_load_n(&worker_stop, __ATOMIC_ACQUIRE)) {}
return NULL;
}
static void real_phase(struct UASYNC* ua, const char* name, double minimum, double maximum) {
uint64_t start = get_time_us(), deadline = start + 3000000;
struct uasync_load load = snapshot(ua);
uint64_t last = load.sample_time_us;
uint64_t before_cpu = real_time_ns(CLOCK_THREAD_CPUTIME_ID);
unsigned int updates = 0;
// Полное реальное окно новой фазы и минимум десять отсчётов; лишние 400мс не требуются.
while ((load.sample_time_us < start + 1000000 || load.window_us < 1000000 || updates < 10) && get_time_us() < deadline) {
uasync_poll(ua, 1000);
load = snapshot(ua);
if (load.sample_time_us != last) { updates++; last = load.sample_time_us; }
}
double reference = (double)(real_time_ns(CLOCK_THREAD_CPUTIME_ID) - before_cpu) / (get_time_us() - start) / 10;
printf("LIVE %-18s current=%6.2f%% average=%6.2f%% reference=%6.2f%% updates=%u window=%lluus elapsed=%lluus\n",
name, load.current_percent, load.average_percent, reference, updates,
(unsigned long long)load.window_us, (unsigned long long)(get_time_us() - start));
CHECK(load.valid); CHECK(load.window_us == 1000000);
CHECK(load.sample_time_us >= start + 1000000);
CHECK(load.average_percent >= minimum && load.average_percent <= maximum);
CHECK(fabs(load.average_percent - reference) < 15);
CHECK(updates >= 10 && updates <= 16);
}
static void timer_fired(void* arg) { *(int*)arg = 1; }
static void real_load(int fallback) {
simulated = 0;
force_poll = fallback;
struct UASYNC* ua = uasync_create(); CHECK(ua);
printf("REAL %s\n", fallback ? "poll" : "epoll");
real_phase(ua, "idle", 0, 10);
struct workload work = {.ua = ua, .mixed = 1, .next_us = get_time_us()};
work.timer = uasync_call_soon(ua, &work, burn); CHECK(work.timer);
real_phase(ua, "25% wall workload", 10, 40);
CHECK(uasync_cancel_timeout(ua, work.timer) == ERR_OK);
work.mixed = 0; work.timer = uasync_call_soon(ua, &work, burn); CHECK(work.timer);
real_phase(ua, "busy", 40, 100);
CHECK(uasync_call_soon_cancel(ua, work.timer) == ERR_OK);
real_phase(ua, "recovered idle", 0, 10);
pthread_t worker; __atomic_store_n(&worker_stop, 0, __ATOMIC_RELEASE);
CHECK(pthread_create(&worker, NULL, other_thread, NULL) == 0);
real_phase(ua, "other thread busy", 0, 10);
__atomic_store_n(&worker_stop, 1, __ATOMIC_RELEASE); CHECK(pthread_join(worker, NULL) == 0);
// Один poll спит до прикладного таймера, без пробуждений для CPU-метрики.
int fired = 0; CHECK(uasync_set_timeout(ua, 12000, &fired, timer_fired, "long idle"));
uint64_t sleep_start = get_time_us();
uasync_poll(ua, -1);
uint64_t slept = get_time_us() - sleep_start;
struct uasync_load load = snapshot(ua);
CHECK(fired); CHECK(slept >= 1190000); NEAR(load.current_percent, 0); NEAR(load.average_percent, 0);
printf("LIVE long idle current=%.2f%% average=%.2f%% one_poll=%lluus\n",
load.current_percent, load.average_percent, (unsigned long long)slept);
uasync_destroy(ua, 0);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) debug_set_category_level(i, DEBUG_LEVEL_WARN);
if (getenv("UTUN_TEST_DEBUG")) debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_DEBUG);
size_t allocated = u_get_allocated_count();
deterministic(0); deterministic(1);
real_load(0); real_load(1);
CHECK(u_get_allocated_count() == allocated);
puts("PASS uasync CPU load");
return 0;
}