// retoor #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STYLE_RESET "\033[0m" #define STYLE_BOLD "\033[1m" #define STYLE_RED "\033[31m" #define STYLE_GREEN "\033[32m" #define STYLE_YELLOW "\033[33m" #define STYLE_CYAN "\033[36m" #define MAX_HEADER_SIZE (16 * 1024) #define INITIAL_BUFFER_SIZE (64 * 1024) #define MAX_BUFFER_SIZE (16 * 1024 * 1024) #define REQUEST_TIMEOUT_MS 30000 #define URL_SCHEME_MAX 16 #define URL_HOSTNAME_MAX 256 #define URL_PATH_MAX 2048 typedef struct { char scheme[URL_SCHEME_MAX]; char hostname[URL_HOSTNAME_MAX]; int port; char path[URL_PATH_MAX]; } url_t; typedef struct { long status; double duration_ms; size_t body_size_bytes; size_t header_size_bytes; char server_software[128]; int failed; char error[256]; } RequestResult; typedef struct { int sock; SSL *ssl; SSL_CTX *ctx; bool is_https; bool in_use; bool chunked; bool chunked_done; size_t chunk_remaining; int request_index; char *send_buffer; size_t send_offset; size_t send_total; char *recv_buffer; size_t recv_offset; size_t recv_capacity; bool headers_complete; long content_length; struct timespec start_time; } Connection; static volatile sig_atomic_t g_shutdown_requested = 0; static long total_connections_made = 0; static Connection *connection_pool = NULL; static int pool_size = 0; static bool ssl_initialized = false; static void signal_handler(int sig) { (void)sig; g_shutdown_requested = 1; } static void setup_signal_handlers(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); signal(SIGPIPE, SIG_IGN); } static int parse_url(const char *url_str, url_t *url) { if (!url_str || !url) { return -1; } memset(url, 0, sizeof(url_t)); const char *p = strstr(url_str, "://"); if (p) { size_t scheme_len = (size_t)(p - url_str); if (scheme_len >= URL_SCHEME_MAX) { return -1; } memcpy(url->scheme, url_str, scheme_len); url->scheme[scheme_len] = '\0'; url_str = p + 3; } else { strncpy(url->scheme, "http", URL_SCHEME_MAX - 1); url->scheme[URL_SCHEME_MAX - 1] = '\0'; } p = strchr(url_str, '/'); char host_port[URL_HOSTNAME_MAX + 8]; if (p) { size_t hp_len = (size_t)(p - url_str); if (hp_len >= sizeof(host_port)) { return -1; } memcpy(host_port, url_str, hp_len); host_port[hp_len] = '\0'; size_t path_len = strlen(p); if (path_len >= URL_PATH_MAX) { return -1; } strncpy(url->path, p, URL_PATH_MAX - 1); url->path[URL_PATH_MAX - 1] = '\0'; } else { size_t hp_len = strlen(url_str); if (hp_len >= sizeof(host_port)) { return -1; } strncpy(host_port, url_str, sizeof(host_port) - 1); host_port[sizeof(host_port) - 1] = '\0'; strncpy(url->path, "/", URL_PATH_MAX - 1); url->path[URL_PATH_MAX - 1] = '\0'; } char *colon = strchr(host_port, ':'); if (colon) { size_t host_len = (size_t)(colon - host_port); if (host_len >= URL_HOSTNAME_MAX) { return -1; } memcpy(url->hostname, host_port, host_len); url->hostname[host_len] = '\0'; url->port = atoi(colon + 1); if (url->port <= 0 || url->port > 65535) { return -1; } } else { if (strlen(host_port) >= URL_HOSTNAME_MAX) { return -1; } strncpy(url->hostname, host_port, URL_HOSTNAME_MAX - 1); url->hostname[URL_HOSTNAME_MAX - 1] = '\0'; url->port = (strcmp(url->scheme, "https") == 0) ? 443 : 80; } if (strlen(url->hostname) == 0) { return -1; } return 0; } static void init_openssl(void) { if (!ssl_initialized) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); #else SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); #endif ssl_initialized = true; } } static void cleanup_openssl(void) { if (ssl_initialized) { #if OPENSSL_VERSION_NUMBER < 0x10100000L EVP_cleanup(); ERR_free_strings(); #endif ssl_initialized = false; } } static SSL_CTX *create_context(bool verify_peer) { const SSL_METHOD *method = TLS_client_method(); SSL_CTX *ctx = SSL_CTX_new(method); if (!ctx) { return NULL; } if (verify_peer) { SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); SSL_CTX_set_default_verify_paths(ctx); } else { SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); } return ctx; } static int create_socket(const char *hostname, int port) { struct addrinfo hints, *result, *rp; int sock = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; char port_str[8]; snprintf(port_str, sizeof(port_str), "%d", port); int ret = getaddrinfo(hostname, port_str, &hints, &result); if (ret != 0) { return -1; } for (rp = result; rp != NULL; rp = rp->ai_next) { sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock < 0) { continue; } int flags = fcntl(sock, F_GETFL, 0); if (flags < 0) { close(sock); sock = -1; continue; } if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { close(sock); sock = -1; continue; } ret = connect(sock, rp->ai_addr, rp->ai_addrlen); if (ret < 0 && errno != EINPROGRESS) { close(sock); sock = -1; continue; } break; } freeaddrinfo(result); if (sock >= 0) { total_connections_made++; } return sock; } static void release_connection(Connection *conn, bool keep_alive, int *active_connections); static Connection* get_or_create_connection(url_t *url, int *active_connections, int max_connections, bool keep_alive, bool verify_ssl) { if (keep_alive) { for (int i = 0; i < pool_size; i++) { if (!connection_pool[i].in_use && connection_pool[i].sock >= 0) { connection_pool[i].in_use = true; return &connection_pool[i]; } } } if (*active_connections >= max_connections) { return NULL; } Connection *conn = NULL; for (int i = 0; i < pool_size; i++) { if (connection_pool[i].sock < 0) { conn = &connection_pool[i]; break; } } if (!conn) { int new_size = pool_size + 1; Connection *new_pool = realloc(connection_pool, sizeof(Connection) * (size_t)new_size); if (!new_pool) { return NULL; } connection_pool = new_pool; pool_size = new_size; conn = &connection_pool[pool_size - 1]; } memset(conn, 0, sizeof(Connection)); conn->sock = create_socket(url->hostname, url->port); if (conn->sock < 0) { conn->sock = -1; return NULL; } conn->is_https = (strcmp(url->scheme, "https") == 0); if (conn->is_https) { init_openssl(); conn->ctx = create_context(verify_ssl); if (!conn->ctx) { close(conn->sock); conn->sock = -1; return NULL; } conn->ssl = SSL_new(conn->ctx); if (!conn->ssl) { SSL_CTX_free(conn->ctx); conn->ctx = NULL; close(conn->sock); conn->sock = -1; return NULL; } SSL_set_fd(conn->ssl, conn->sock); SSL_set_connect_state(conn->ssl); SSL_set_tlsext_host_name(conn->ssl, url->hostname); } conn->recv_buffer = malloc(INITIAL_BUFFER_SIZE); if (!conn->recv_buffer) { if (conn->ssl) { SSL_free(conn->ssl); conn->ssl = NULL; } if (conn->ctx) { SSL_CTX_free(conn->ctx); conn->ctx = NULL; } close(conn->sock); conn->sock = -1; return NULL; } conn->recv_capacity = INITIAL_BUFFER_SIZE; conn->in_use = true; conn->content_length = -1; (*active_connections)++; return conn; } static void release_connection(Connection *conn, bool keep_alive, int *active_connections) { if (!conn) return; if (keep_alive && conn->sock >= 0) { conn->in_use = false; conn->send_offset = 0; conn->recv_offset = 0; conn->headers_complete = false; conn->content_length = -1; conn->chunked = false; conn->chunked_done = false; conn->chunk_remaining = 0; if (conn->send_buffer) { free(conn->send_buffer); conn->send_buffer = NULL; } } else { if (conn->ssl) { SSL_shutdown(conn->ssl); SSL_free(conn->ssl); conn->ssl = NULL; } if (conn->ctx) { SSL_CTX_free(conn->ctx); conn->ctx = NULL; } if (conn->sock >= 0) { close(conn->sock); conn->sock = -1; } if (conn->send_buffer) { free(conn->send_buffer); conn->send_buffer = NULL; } if (conn->recv_buffer) { free(conn->recv_buffer); conn->recv_buffer = NULL; } conn->recv_capacity = 0; (*active_connections)--; } } static long parse_status_code(const char *headers) { const char *space = strchr(headers, ' '); if (!space) return 0; return atol(space + 1); } static char* get_header_value(const char *headers, const char *name) { size_t name_len = strlen(name); const char *p = headers; while ((p = strchr(p, '\n')) != NULL) { p++; if (strncasecmp(p, name, name_len) == 0 && p[name_len] == ':') { const char *value_start = p + name_len + 1; while (*value_start == ' ' || *value_start == '\t') { value_start++; } const char *value_end = strstr(value_start, "\r\n"); if (!value_end) { value_end = value_start + strlen(value_start); } size_t len = (size_t)(value_end - value_start); char *value = malloc(len + 1); if (!value) { return NULL; } memcpy(value, value_start, len); value[len] = '\0'; return value; } } return NULL; } static bool is_chunked_encoding(const char *headers) { char *te = get_header_value(headers, "Transfer-Encoding"); if (!te) { return false; } bool chunked = (strcasestr(te, "chunked") != NULL); free(te); return chunked; } static size_t parse_chunk_size(const char *data, size_t *chunk_header_len) { const char *end = strstr(data, "\r\n"); if (!end) { *chunk_header_len = 0; return 0; } *chunk_header_len = (size_t)(end - data) + 2; char hex[20]; size_t hex_len = (size_t)(end - data); if (hex_len >= sizeof(hex)) { hex_len = sizeof(hex) - 1; } memcpy(hex, data, hex_len); hex[hex_len] = '\0'; char *semicolon = strchr(hex, ';'); if (semicolon) { *semicolon = '\0'; } return (size_t)strtoul(hex, NULL, 16); } static const char* format_bytes(long long bytes) { static char buffer[4][128]; static int idx = 0; idx = (idx + 1) % 4; const char *units[] = {"bytes", "KB", "MB", "GB", "TB"}; double value = (double)bytes; int i = 0; if (bytes < 1024) { snprintf(buffer[idx], sizeof(buffer[idx]), "%lld bytes", bytes); return buffer[idx]; } while (value >= 1024.0 && i < 4) { value /= 1024.0; i++; } snprintf(buffer[idx], sizeof(buffer[idx]), "%.2f %s", value, units[i]); return buffer[idx]; } static int compare_doubles(const void *a, const void *b) { double da = *(const double *)a; double db = *(const double *)b; if (da < db) return -1; if (da > db) return 1; return 0; } static double get_mean(const double data[], int n) { if (n <= 0) return 0.0; double sum = 0.0; for (int i = 0; i < n; ++i) sum += data[i]; return sum / n; } static double get_stdev(const double data[], int n) { if (n < 2) return 0.0; double mean = get_mean(data, n); double sum_sq_diff = 0.0; for (int i = 0; i < n; ++i) { sum_sq_diff += (data[i] - mean) * (data[i] - mean); } return sqrt(sum_sq_diff / (n - 1)); } static void print_summary(RequestResult results[], int total_requests, double total_duration_s, const char *url, int concurrency, long total_connections) { double *request_durations_ms = malloc(sizeof(double) * (size_t)total_requests); if (!request_durations_ms) { fprintf(stderr, "Failed to allocate memory for statistics\n"); return; } int success_count = 0; long long total_html_transferred = 0; long long total_transferred = 0; for (int i = 0; i < total_requests; ++i) { if (!results[i].failed) { request_durations_ms[success_count++] = results[i].duration_ms; total_html_transferred += (long long)results[i].body_size_bytes; total_transferred += (long long)(results[i].body_size_bytes + results[i].header_size_bytes); } } int failed_count = total_requests - success_count; if (success_count == 0) { printf("%sAll requests failed. Cannot generate a detailed summary.%s\n", STYLE_RED, STYLE_RESET); printf("Total time: %.3f seconds\n", total_duration_s); printf("Failed requests: %d\n", failed_count); if (total_requests > 0 && results[0].error[0] != '\0') { printf("Sample error: %s\n", results[0].error); } free(request_durations_ms); return; } url_t parsed_url; if (parse_url(url, &parsed_url) != 0) { strncpy(parsed_url.hostname, "unknown", URL_HOSTNAME_MAX - 1); parsed_url.port = 0; strncpy(parsed_url.path, "/", URL_PATH_MAX - 1); } RequestResult first_result = {0}; for (int i = 0; i < total_requests; ++i) { if (!results[i].failed) { first_result = results[i]; break; } } double req_per_second = (double)total_requests / total_duration_s; double time_per_req_concurrent = (total_duration_s * 1000) / total_requests; double time_per_req_mean = (total_duration_s * 1000 * concurrency) / total_requests; double transfer_rate_kbytes_s = (total_transferred / 1024.0) / total_duration_s; qsort(request_durations_ms, (size_t)success_count, sizeof(double), compare_doubles); double min_time = request_durations_ms[0]; double mean_time = get_mean(request_durations_ms, success_count); double stdev_time = get_stdev(request_durations_ms, success_count); double median_time = success_count % 2 ? request_durations_ms[success_count / 2] : (request_durations_ms[success_count / 2 - 1] + request_durations_ms[success_count / 2]) / 2.0; double max_time = request_durations_ms[success_count - 1]; int percentile_points[] = {50, 66, 75, 80, 90, 95, 98, 99, 100}; double percentile_values[9]; for (int i = 0; i < 8; ++i) { int index = (int)(success_count * percentile_points[i] / 100.0) - 1; if (index < 0) index = 0; percentile_values[i] = request_durations_ms[index]; } percentile_values[8] = max_time; const char *fail_color = (failed_count == 0) ? STYLE_GREEN : STYLE_RED; printf("%sServer Software:%s %s\n", STYLE_YELLOW, STYLE_RESET, strlen(first_result.server_software) > 0 ? first_result.server_software : "N/A"); printf("%sServer Hostname:%s %s\n", STYLE_YELLOW, STYLE_RESET, parsed_url.hostname); printf("%sServer Port:%s %d\n\n", STYLE_YELLOW, STYLE_RESET, parsed_url.port); printf("%sDocument Path:%s %s\n", STYLE_YELLOW, STYLE_RESET, parsed_url.path); printf("%sDocument Length:%s %s\n\n", STYLE_YELLOW, STYLE_RESET, format_bytes((long long)first_result.body_size_bytes)); printf("%sConcurrency Level:%s %d\n", STYLE_YELLOW, STYLE_RESET, concurrency); printf("%sTime taken for tests:%s %.3f seconds\n", STYLE_YELLOW, STYLE_RESET, total_duration_s); printf("%sComplete requests:%s %d\n", STYLE_YELLOW, STYLE_RESET, total_requests); printf("%sFailed requests:%s %s%d%s\n", STYLE_YELLOW, STYLE_RESET, fail_color, failed_count, STYLE_RESET); printf("%sTotal connections made:%s %ld\n", STYLE_YELLOW, STYLE_RESET, total_connections); printf("%sTotal transferred:%s %s\n", STYLE_YELLOW, STYLE_RESET, format_bytes(total_transferred)); printf("%sHTML transferred:%s %s\n", STYLE_YELLOW, STYLE_RESET, format_bytes(total_html_transferred)); printf("%sRequests per second:%s %s%.2f%s [#/sec] (mean)\n", STYLE_YELLOW, STYLE_RESET, STYLE_GREEN, req_per_second, STYLE_RESET); printf("%sTime per request:%s %.3f [ms] (mean)\n", STYLE_YELLOW, STYLE_RESET, time_per_req_mean); printf("%sTime per request:%s %.3f [ms] (mean, across all concurrent requests)\n", STYLE_YELLOW, STYLE_RESET, time_per_req_concurrent); printf("%sTransfer rate:%s %s%.2f%s [Kbytes/sec] received\n\n", STYLE_YELLOW, STYLE_RESET, STYLE_GREEN, transfer_rate_kbytes_s, STYLE_RESET); printf("%s%sConnection Times (ms)%s\n", STYLE_CYAN, STYLE_BOLD, STYLE_RESET); printf("%s---------------------%s\n", STYLE_CYAN, STYLE_RESET); printf("%-10s%8.0f\n", "min:", min_time); printf("%-10s%8.0f\n", "mean:", mean_time); printf("%-10s%8.1f\n", "sd:", stdev_time); printf("%-10s%8.0f\n", "median:", median_time); printf("%-10s%8.0f\n\n", "max:", max_time); printf("%s%sPercentage of the requests served within a certain time (ms)%s\n", STYLE_CYAN, STYLE_BOLD, STYLE_RESET); for (int i = 0; i < 9; ++i) { printf(" %s%3d%%%s %.0f\n", STYLE_GREEN, percentile_points[i], STYLE_RESET, percentile_values[i]); } free(request_durations_ms); } static double get_elapsed_ms(struct timespec *start) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return ((now.tv_sec - start->tv_sec) * 1000.0) + ((now.tv_nsec - start->tv_nsec) / 1000000.0); } static void print_usage(const char *prog) { fprintf(stderr, "Usage: %s -n -c [-k] [-i] \n", prog); fprintf(stderr, " -n Total number of requests to perform\n"); fprintf(stderr, " -c Number of concurrent connections\n"); fprintf(stderr, " -k Use HTTP Keep-Alive\n"); fprintf(stderr, " -i Insecure mode (skip SSL certificate verification)\n"); } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); setup_signal_handlers(); int total_requests = 0; int concurrency = 0; int keep_alive = 0; int insecure = 0; char *url = NULL; int opt; while ((opt = getopt(argc, argv, "n:c:ki")) != -1) { switch (opt) { case 'n': { char *endptr; long val = strtol(optarg, &endptr, 10); if (*endptr != '\0' || val <= 0 || val > INT_MAX) { fprintf(stderr, "Error: Invalid value for -n: %s\n", optarg); return 1; } total_requests = (int)val; break; } case 'c': { char *endptr; long val = strtol(optarg, &endptr, 10); if (*endptr != '\0' || val <= 0 || val > 10000) { fprintf(stderr, "Error: Invalid value for -c: %s (max 10000)\n", optarg); return 1; } concurrency = (int)val; break; } case 'k': keep_alive = 1; break; case 'i': insecure = 1; break; default: print_usage(argv[0]); return 1; } } if (optind < argc) { url = argv[optind]; } if (total_requests <= 0 || concurrency <= 0 || url == NULL) { print_usage(argv[0]); return 1; } if (total_requests < concurrency) { fprintf(stderr, "Error: Number of requests (-n) cannot be less than the concurrency level (-c).\n"); return 1; } url_t parsed_url; if (parse_url(url, &parsed_url) != 0) { fprintf(stderr, "Error: Failed to parse URL: %s\n", url); return 1; } printf("abr, a C-based HTTP benchmark inspired by ApacheBench.\n"); printf("Benchmarking %s (be patient)...\n", parsed_url.hostname); if (insecure && strcmp(parsed_url.scheme, "https") == 0) { printf("%sWarning: SSL certificate verification disabled%s\n", STYLE_YELLOW, STYLE_RESET); } RequestResult *results = calloc((size_t)total_requests, sizeof(RequestResult)); if (!results) { fprintf(stderr, "Error: Failed to allocate memory for results\n"); return 1; } int requests_initiated = 0; int requests_completed = 0; int active_connections = 0; struct timespec benchmark_start_time, current_time; clock_gettime(CLOCK_MONOTONIC, &benchmark_start_time); connection_pool = calloc((size_t)concurrency, sizeof(Connection)); if (!connection_pool) { fprintf(stderr, "Error: Failed to allocate connection pool\n"); free(results); return 1; } pool_size = concurrency; for (int i = 0; i < concurrency; i++) { connection_pool[i].sock = -1; } struct pollfd *poll_fds = malloc(sizeof(struct pollfd) * (size_t)concurrency); int *poll_conn_map = malloc(sizeof(int) * (size_t)concurrency); if (!poll_fds || !poll_conn_map) { fprintf(stderr, "Error: Failed to allocate poll structures\n"); free(poll_fds); free(poll_conn_map); free(connection_pool); free(results); return 1; } while (requests_completed < total_requests && !g_shutdown_requested) { int nfds = 0; while (requests_initiated < total_requests && active_connections < concurrency && !g_shutdown_requested) { Connection *conn = get_or_create_connection(&parsed_url, &active_connections, concurrency, keep_alive, !insecure); if (!conn) break; conn->request_index = requests_initiated; clock_gettime(CLOCK_MONOTONIC, &conn->start_time); char request[4096]; int req_len = snprintf(request, sizeof(request), "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: abr/1.0\r\n" "Accept: */*\r\n" "Connection: %s\r\n" "\r\n", parsed_url.path, parsed_url.hostname, keep_alive ? "keep-alive" : "close"); if (req_len < 0 || req_len >= (int)sizeof(request)) { snprintf(results[requests_initiated].error, sizeof(results[requests_initiated].error), "Request too large"); results[requests_initiated].failed = 1; requests_completed++; release_connection(conn, false, &active_connections); requests_initiated++; continue; } conn->send_buffer = malloc((size_t)req_len + 1); if (!conn->send_buffer) { snprintf(results[requests_initiated].error, sizeof(results[requests_initiated].error), "Memory allocation failed"); results[requests_initiated].failed = 1; requests_completed++; release_connection(conn, false, &active_connections); requests_initiated++; continue; } memcpy(conn->send_buffer, request, (size_t)req_len + 1); conn->send_total = (size_t)req_len; conn->send_offset = 0; requests_initiated++; } for (int i = 0; i < pool_size; i++) { Connection *conn = &connection_pool[i]; if (conn->sock >= 0 && conn->in_use) { double elapsed = get_elapsed_ms(&conn->start_time); if (elapsed > REQUEST_TIMEOUT_MS) { snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Request timeout (%.0fms)", elapsed); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = elapsed; requests_completed++; release_connection(conn, false, &active_connections); continue; } poll_fds[nfds].fd = conn->sock; poll_fds[nfds].events = POLLERR | POLLHUP; if (conn->send_offset < conn->send_total) { poll_fds[nfds].events |= POLLOUT; } else { poll_fds[nfds].events |= POLLIN; } poll_fds[nfds].revents = 0; poll_conn_map[nfds] = i; nfds++; } } if (nfds == 0) { usleep(1000); continue; } int ready = poll(poll_fds, (nfds_t)nfds, 10); if (ready < 0) { if (errno == EINTR) continue; break; } if (ready == 0) continue; for (int p = 0; p < nfds; p++) { if (poll_fds[p].revents == 0) continue; int i = poll_conn_map[p]; Connection *conn = &connection_pool[i]; if (conn->sock < 0 || !conn->in_use) continue; if (poll_fds[p].revents & (POLLERR | POLLNVAL)) { int err = 0; socklen_t errlen = sizeof(err); getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, &err, &errlen); snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Connection error: %s", err ? strerror(err) : "Unknown error"); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); continue; } if ((poll_fds[p].revents & POLLOUT) && conn->send_offset < conn->send_total) { ssize_t sent; if (conn->is_https && conn->ssl) { sent = SSL_write(conn->ssl, conn->send_buffer + conn->send_offset, (int)(conn->send_total - conn->send_offset)); if (sent <= 0) { int ssl_err = SSL_get_error(conn->ssl, (int)sent); if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) { snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "SSL write error"); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); } continue; } } else { sent = send(conn->sock, conn->send_buffer + conn->send_offset, conn->send_total - conn->send_offset, MSG_NOSIGNAL); if (sent <= 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Send error: %s", strerror(errno)); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); } continue; } } if (sent > 0) { conn->send_offset += (size_t)sent; } } if (poll_fds[p].revents & POLLIN) { if (conn->recv_offset >= conn->recv_capacity - 1) { if (conn->recv_capacity >= MAX_BUFFER_SIZE) { snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Response too large"); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); continue; } size_t new_capacity = conn->recv_capacity * 2; if (new_capacity > MAX_BUFFER_SIZE) { new_capacity = MAX_BUFFER_SIZE; } char *new_buffer = realloc(conn->recv_buffer, new_capacity); if (!new_buffer) { snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Memory allocation failed"); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); continue; } conn->recv_buffer = new_buffer; conn->recv_capacity = new_capacity; } ssize_t received; size_t space = conn->recv_capacity - conn->recv_offset - 1; if (conn->is_https && conn->ssl) { received = SSL_read(conn->ssl, conn->recv_buffer + conn->recv_offset, (int)space); if (received <= 0) { int ssl_err = SSL_get_error(conn->ssl, (int)received); if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { continue; } if (ssl_err == SSL_ERROR_ZERO_RETURN || received == 0) { goto connection_closed; } snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "SSL read error"); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); continue; } } else { received = recv(conn->sock, conn->recv_buffer + conn->recv_offset, space, 0); if (received <= 0) { if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { continue; } if (received == 0) { goto connection_closed; } snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Recv error: %s", strerror(errno)); results[conn->request_index].failed = 1; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); continue; } } conn->recv_offset += (size_t)received; conn->recv_buffer[conn->recv_offset] = '\0'; if (!conn->headers_complete) { char *header_end = strstr(conn->recv_buffer, "\r\n\r\n"); if (header_end) { conn->headers_complete = true; size_t header_size = (size_t)(header_end - conn->recv_buffer) + 4; results[conn->request_index].header_size_bytes = header_size; results[conn->request_index].status = parse_status_code(conn->recv_buffer); char *server = get_header_value(conn->recv_buffer, "Server"); if (server) { strncpy(results[conn->request_index].server_software, server, sizeof(results[conn->request_index].server_software) - 1); results[conn->request_index].server_software[sizeof(results[conn->request_index].server_software) - 1] = '\0'; free(server); } conn->chunked = is_chunked_encoding(conn->recv_buffer); if (!conn->chunked) { char *content_length_str = get_header_value(conn->recv_buffer, "Content-Length"); if (content_length_str) { conn->content_length = atol(content_length_str); free(content_length_str); } else { conn->content_length = -1; } } } } if (conn->headers_complete) { char *header_end = strstr(conn->recv_buffer, "\r\n\r\n"); size_t header_size = (size_t)(header_end - conn->recv_buffer) + 4; size_t body_received = conn->recv_offset - header_size; bool complete = false; size_t body_size = 0; if (conn->chunked) { char *body_start = header_end + 4; size_t body_data_len = conn->recv_offset - header_size; size_t pos = 0; size_t decoded_size = 0; while (pos < body_data_len && !conn->chunked_done) { if (conn->chunk_remaining > 0) { size_t available = body_data_len - pos; size_t to_consume = (available < conn->chunk_remaining) ? available : conn->chunk_remaining; decoded_size += to_consume; pos += to_consume; conn->chunk_remaining -= to_consume; if (conn->chunk_remaining == 0) { if (pos + 2 <= body_data_len && body_start[pos] == '\r' && body_start[pos + 1] == '\n') { pos += 2; } else if (pos + 2 > body_data_len) { break; } } } else { size_t chunk_header_len; size_t chunk_size = parse_chunk_size(body_start + pos, &chunk_header_len); if (chunk_header_len == 0) { break; } if (chunk_size == 0) { conn->chunked_done = true; complete = true; body_size = decoded_size; break; } pos += chunk_header_len; conn->chunk_remaining = chunk_size; } } if (!complete && conn->chunked_done) { complete = true; body_size = decoded_size; } } else if (conn->content_length >= 0) { complete = (body_received >= (size_t)conn->content_length); if (complete) { body_size = (size_t)conn->content_length; } } else { continue; } if (complete) { results[conn->request_index].body_size_bytes = body_size; results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); results[conn->request_index].failed = (results[conn->request_index].status >= 400); requests_completed++; long long total_bytes_transferred = 0; double total_duration_ms = 0; int success_count = 0; int failed_count = 0; for (int j = 0; j < requests_completed; ++j) { total_bytes_transferred += (long long)(results[j].body_size_bytes + results[j].header_size_bytes); if (results[j].failed) { failed_count++; } else { success_count++; total_duration_ms += results[j].duration_ms; } } clock_gettime(CLOCK_MONOTONIC, ¤t_time); double elapsed_time = (current_time.tv_sec - benchmark_start_time.tv_sec) + (current_time.tv_nsec - benchmark_start_time.tv_nsec) / 1e9; double req_per_sec = elapsed_time > 0 ? requests_completed / elapsed_time : 0; double avg_latency_ms = success_count > 0 ? total_duration_ms / success_count : 0; double transfer_rate_kbs = elapsed_time > 0 ? (total_bytes_transferred / 1024.0) / elapsed_time : 0; const char *fail_color = (failed_count == 0) ? STYLE_GREEN : STYLE_RED; fprintf(stdout, "\r%sCompleted: %d/%d | Failed: %s%d%s%s | RPS: %s%.1f%s%s | Avg Latency: %.0fms | Rate: %.1f KB/s%s", STYLE_BOLD, requests_completed, total_requests, fail_color, failed_count, STYLE_RESET, STYLE_BOLD, STYLE_GREEN, req_per_sec, STYLE_RESET, STYLE_BOLD, avg_latency_ms, transfer_rate_kbs, STYLE_RESET); fflush(stdout); release_connection(conn, keep_alive, &active_connections); } } continue; connection_closed: if (!conn->headers_complete) { snprintf(results[conn->request_index].error, sizeof(results[conn->request_index].error), "Connection closed prematurely"); results[conn->request_index].failed = 1; } else if (conn->content_length < 0 && !conn->chunked) { char *header_end = strstr(conn->recv_buffer, "\r\n\r\n"); size_t header_size = (size_t)(header_end - conn->recv_buffer) + 4; results[conn->request_index].body_size_bytes = conn->recv_offset - header_size; results[conn->request_index].failed = (results[conn->request_index].status >= 400); } results[conn->request_index].duration_ms = get_elapsed_ms(&conn->start_time); requests_completed++; release_connection(conn, false, &active_connections); } } } if (g_shutdown_requested) { printf("\n%sShutdown requested, cleaning up...%s\n", STYLE_YELLOW, STYLE_RESET); } struct timespec benchmark_end_time; clock_gettime(CLOCK_MONOTONIC, &benchmark_end_time); double total_duration = (benchmark_end_time.tv_sec - benchmark_start_time.tv_sec) + (benchmark_end_time.tv_nsec - benchmark_start_time.tv_nsec) / 1e9; fprintf(stdout, "\n\n"); printf("%s%sFinished %d requests%s\n\n", STYLE_GREEN, STYLE_BOLD, requests_completed, STYLE_RESET); print_summary(results, requests_completed, total_duration, url, concurrency, total_connections_made); for (int i = 0; i < pool_size; i++) { if (connection_pool[i].sock >= 0) { release_connection(&connection_pool[i], false, &active_connections); } } free(poll_fds); free(poll_conn_map); free(connection_pool); connection_pool = NULL; pool_size = 0; free(results); cleanup_openssl(); return g_shutdown_requested ? 130 : 0; }