|
| 1 | +/* |
| 2 | + * asyncweb 性能基线 (自包含, 无外部压测工具依赖)。 |
| 3 | + * |
| 4 | + * 在同一进程/同一 loop 里起 asyncweb HTTP server, 再用裸 async socket 客户端 |
| 5 | + * 压它, 量三个数: |
| 6 | + * 1. 顺序吞吐 (keep-alive 单连接连续 N 个请求) -> req/s + 平均延迟 |
| 7 | + * 2. 并发吞吐 (C 条连接各发 M 个请求, 全部并发) -> req/s |
| 8 | + * 3. 延迟分布 (顺序场景收集每请求耗时, 报 p50/p99) |
| 9 | + * |
| 10 | + * 计时用 libcoro loop_time_ms (毫秒精度)。内存 RSS 由外层 /usr/bin/time -v 量。 |
| 11 | + * |
| 12 | + * 用法: ./bench [seq_n] [conc_conns] [conc_per] |
| 13 | + * 默认 seq_n=20000, conc_conns=50, conc_per=400 |
| 14 | + */ |
| 15 | +#include <stdio.h> |
| 16 | +#include <stdlib.h> |
| 17 | +#include <string.h> |
| 18 | +#include <time.h> |
| 19 | +#include <libcoro/libcoro.h> |
| 20 | +#include <asyncweb/asyncweb.h> |
| 21 | + |
| 22 | +/* 微秒级单调时钟 (loop_time_ms 只有毫秒精度, 高吞吐下延迟多为 0)。 */ |
| 23 | +static uint64_t now_us(void) { |
| 24 | + struct timespec ts; |
| 25 | + clock_gettime(CLOCK_MONOTONIC, &ts); |
| 26 | + return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)(ts.tv_nsec / 1000ULL); |
| 27 | +} |
| 28 | + |
| 29 | +static uint16_t g_port; |
| 30 | +static anet_http_server_t *g_server; |
| 31 | + |
| 32 | +static int SEQ_N = 20000; |
| 33 | +static int CONC_CONNS = 50; |
| 34 | +static int CONC_PER = 400; |
| 35 | + |
| 36 | +/* --- 并发场景共享状态 --- */ |
| 37 | +static int g_conc_pending; |
| 38 | +static int g_conc_done_reqs; |
| 39 | + |
| 40 | +static void bench_handler(const anet_http_server_request_t *req, |
| 41 | + anet_http_server_response_t *resp, void *ud) { |
| 42 | + (void)ud; (void)req; |
| 43 | + resp->status_code = 200; |
| 44 | + resp->status_text = "OK"; |
| 45 | + resp->content_type = "text/plain"; |
| 46 | + resp->body = "pong"; |
| 47 | + resp->body_len = 4; |
| 48 | +} |
| 49 | + |
| 50 | +static anet_socket_t *conn_client(void) { |
| 51 | + anet_palsock_t cs = anet_palsock_create(AF_INET, SOCK_STREAM, 0, 1); |
| 52 | + if (!anet_palsock_is_valid(cs)) return NULL; |
| 53 | + return anet_socket_create(cs); |
| 54 | +} |
| 55 | + |
| 56 | +static int cmp_u64(const void *a, const void *b) { |
| 57 | + uint64_t x = *(const uint64_t*)a, y = *(const uint64_t*)b; |
| 58 | + return (x > y) - (x < y); |
| 59 | +} |
| 60 | + |
| 61 | +/* 顺序 keep-alive: 单连接连续 SEQ_N 个请求, 记录总耗时与每请求延迟(us 粗略)。 */ |
| 62 | +task_t* task(seq_bench_task) { |
| 63 | + gen_dec_vars( |
| 64 | + anet_socket_t *cli; |
| 65 | + future_t *fut; |
| 66 | + int i; |
| 67 | + char rbuf[4096]; |
| 68 | + uint64_t t0; |
| 69 | + uint64_t t1; |
| 70 | + uint64_t rt0; /* 本请求开始时刻 us (必须跨 yield 存活) */ |
| 71 | + uint64_t *lat; /* 每请求延迟 (us) */ |
| 72 | + int ok; |
| 73 | + ); |
| 74 | + gen_begin(ctx); |
| 75 | + |
| 76 | + gen_var(cli) = conn_client(); |
| 77 | + { |
| 78 | + struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); |
| 79 | + sin.sin_family=AF_INET; sin.sin_addr.s_addr=htonl(INADDR_LOOPBACK); sin.sin_port=htons(g_port); |
| 80 | + gen_var(fut) = anet_socket_connect(gen_var(cli),(struct sockaddr*)&sin,sizeof(sin)); |
| 81 | + } |
| 82 | + gen_yield(gen_var(fut)); |
| 83 | + if (future_is_rejected(gen_var(fut))) { printf("seq connect failed\n"); exit(1); } |
| 84 | + |
| 85 | + gen_var(lat) = malloc(sizeof(uint64_t) * SEQ_N); |
| 86 | + gen_var(ok) = 0; |
| 87 | + gen_var(t0) = now_us(); |
| 88 | + |
| 89 | + for (gen_var(i) = 0; gen_var(i) < SEQ_N; gen_var(i)++) { |
| 90 | + gen_var(rt0) = now_us(); |
| 91 | + static const char *REQ = "GET /p HTTP/1.1\r\nHost: x\r\n\r\n"; |
| 92 | + gen_var(fut) = anet_socket_send(gen_var(cli), REQ, strlen(REQ)); |
| 93 | + gen_yield(gen_var(fut)); |
| 94 | + if (future_is_rejected(gen_var(fut))) break; |
| 95 | + |
| 96 | + gen_var(fut) = anet_socket_recv(gen_var(cli), gen_var(rbuf), sizeof(gen_var(rbuf))-1); |
| 97 | + gen_yield(gen_var(fut)); |
| 98 | + if (future_is_rejected(gen_var(fut)) || anet_code_of(future_result(gen_var(fut))) <= 0) break; |
| 99 | + gen_var(lat)[gen_var(ok)] = now_us() - gen_var(rt0); |
| 100 | + gen_var(ok)++; |
| 101 | + } |
| 102 | + |
| 103 | + gen_var(t1) = now_us(); |
| 104 | + { |
| 105 | + uint64_t elapsed_us = gen_var(t1) - gen_var(t0); |
| 106 | + if (elapsed_us == 0) elapsed_us = 1; |
| 107 | + double rps = (double)gen_var(ok) * 1000000.0 / (double)elapsed_us; |
| 108 | + qsort(gen_var(lat), gen_var(ok), sizeof(uint64_t), cmp_u64); |
| 109 | + uint64_t p50 = gen_var(ok) ? gen_var(lat)[gen_var(ok)/2] : 0; |
| 110 | + uint64_t p99 = gen_var(ok) ? gen_var(lat)[(gen_var(ok)*99)/100] : 0; |
| 111 | + printf("[seq] reqs=%d elapsed=%llums throughput=%.0f req/s lat p50=%lluus p99=%lluus\n", |
| 112 | + gen_var(ok), (unsigned long long)(elapsed_us/1000), rps, |
| 113 | + (unsigned long long)p50, (unsigned long long)p99); |
| 114 | + } |
| 115 | + free(gen_var(lat)); |
| 116 | + |
| 117 | + anet_socket_close(gen_var(cli)); |
| 118 | + free(gen_var(cli)); |
| 119 | + gen_var(cli) = NULL; |
| 120 | + |
| 121 | + gen_return(0); /* 子任务: 由 master 收尾, 不自行 loop_stop */ |
| 122 | + gen_end(NULL); |
| 123 | +} |
| 124 | + |
| 125 | +/* 一条并发连接: keep-alive 连发 CONC_PER 个请求, 完成后递减 pending。 */ |
| 126 | +task_t* task(conc_conn_task) { |
| 127 | + gen_dec_vars( |
| 128 | + anet_socket_t *cli; |
| 129 | + future_t *fut; |
| 130 | + int j; |
| 131 | + char rbuf[4096]; |
| 132 | + ); |
| 133 | + gen_begin(ctx); |
| 134 | + |
| 135 | + gen_var(cli) = conn_client(); |
| 136 | + if (!gen_var(cli)) { g_conc_pending--; gen_return(NULL); } |
| 137 | + { |
| 138 | + struct sockaddr_in sin; memset(&sin,0,sizeof(sin)); |
| 139 | + sin.sin_family=AF_INET; sin.sin_addr.s_addr=htonl(INADDR_LOOPBACK); sin.sin_port=htons(g_port); |
| 140 | + gen_var(fut) = anet_socket_connect(gen_var(cli),(struct sockaddr*)&sin,sizeof(sin)); |
| 141 | + } |
| 142 | + gen_yield(gen_var(fut)); |
| 143 | + if (future_is_rejected(gen_var(fut))) { goto done; } |
| 144 | + |
| 145 | + for (gen_var(j) = 0; gen_var(j) < CONC_PER; gen_var(j)++) { |
| 146 | + static const char *REQ = "GET /p HTTP/1.1\r\nHost: x\r\n\r\n"; |
| 147 | + gen_var(fut) = anet_socket_send(gen_var(cli), REQ, strlen(REQ)); |
| 148 | + gen_yield(gen_var(fut)); |
| 149 | + if (future_is_rejected(gen_var(fut))) break; |
| 150 | + gen_var(fut) = anet_socket_recv(gen_var(cli), gen_var(rbuf), sizeof(gen_var(rbuf))-1); |
| 151 | + gen_yield(gen_var(fut)); |
| 152 | + if (future_is_rejected(gen_var(fut)) || anet_code_of(future_result(gen_var(fut))) <= 0) break; |
| 153 | + g_conc_done_reqs++; |
| 154 | + } |
| 155 | + |
| 156 | +done: |
| 157 | + anet_socket_close(gen_var(cli)); |
| 158 | + free(gen_var(cli)); |
| 159 | + gen_var(cli) = NULL; |
| 160 | + g_conc_pending--; |
| 161 | + gen_return(NULL); |
| 162 | + gen_end(NULL); |
| 163 | +} |
| 164 | + |
| 165 | +task_t* task(conc_bench_task) { |
| 166 | + gen_dec_vars( |
| 167 | + int i; |
| 168 | + uint64_t t0; |
| 169 | + task_t *ct; |
| 170 | + ); |
| 171 | + gen_begin(ctx); |
| 172 | + |
| 173 | + g_conc_pending = CONC_CONNS; |
| 174 | + g_conc_done_reqs = 0; |
| 175 | + gen_var(t0) = now_us(); |
| 176 | + |
| 177 | + for (gen_var(i) = 0; gen_var(i) < CONC_CONNS; gen_var(i)++) { |
| 178 | + gen_var(ct) = conc_conn_task(); |
| 179 | + if (!gen_var(ct)) { g_conc_pending--; continue; } |
| 180 | + task_run(gen_var(ct)); |
| 181 | + } |
| 182 | + |
| 183 | + while (g_conc_pending > 0) { |
| 184 | + gen_yield(async_sleep(2)); |
| 185 | + } |
| 186 | + |
| 187 | + { |
| 188 | + uint64_t elapsed_us = now_us() - gen_var(t0); |
| 189 | + if (elapsed_us == 0) elapsed_us = 1; |
| 190 | + double rps = (double)g_conc_done_reqs * 1000000.0 / (double)elapsed_us; |
| 191 | + printf("[conc] conns=%d x %d = %d reqs elapsed=%llums throughput=%.0f req/s\n", |
| 192 | + CONC_CONNS, CONC_PER, g_conc_done_reqs, (unsigned long long)(elapsed_us/1000), rps); |
| 193 | + } |
| 194 | + |
| 195 | + gen_return(0); /* 子任务: 由 master 收尾 */ |
| 196 | + gen_end(NULL); |
| 197 | +} |
| 198 | + |
| 199 | +/* master: 依次跑 seq 与 conc 两个子场景, 全程一个 accept 循环。 */ |
| 200 | +task_t* task(bench_master_task) { |
| 201 | + gen_dec_vars( |
| 202 | + task_t *accept_task; |
| 203 | + task_t *phase; |
| 204 | + ); |
| 205 | + gen_begin(ctx); |
| 206 | + |
| 207 | + gen_var(accept_task) = anet_http_server_run(g_server); |
| 208 | + task_run(gen_var(accept_task)); |
| 209 | + |
| 210 | + gen_var(phase) = seq_bench_task(); |
| 211 | + gen_yield_from_task(gen_var(phase)); |
| 212 | + |
| 213 | + gen_var(phase) = conc_bench_task(); |
| 214 | + gen_yield_from_task(gen_var(phase)); |
| 215 | + |
| 216 | + anet_http_server_stop(g_server); |
| 217 | + gen_yield(async_sleep(10)); |
| 218 | + |
| 219 | + loop_stop(); |
| 220 | + gen_return(0); |
| 221 | + gen_end(NULL); |
| 222 | +} |
| 223 | + |
| 224 | +int main(int argc, char **argv) { |
| 225 | + if (argc > 1) SEQ_N = atoi(argv[1]); |
| 226 | + if (argc > 2) CONC_CONNS = atoi(argv[2]); |
| 227 | + if (argc > 3) CONC_PER = atoi(argv[3]); |
| 228 | + |
| 229 | + if (anet_init() != ANET_OK) { printf("init failed\n"); return 1; } |
| 230 | + g_server = anet_http_server_create(0, bench_handler, NULL); |
| 231 | + if (!g_server) { printf("server create failed\n"); return 1; } |
| 232 | + g_port = anet_http_server_port(g_server); |
| 233 | + printf("bench server on 127.0.0.1:%u\n", g_port); |
| 234 | + |
| 235 | + /* 单个 accept 循环, master 依次驱动 seq 与 conc 两阶段。 */ |
| 236 | + loop_run(bench_master_task()); |
| 237 | + |
| 238 | + anet_http_server_destroy(g_server); |
| 239 | + anet_cleanup(); |
| 240 | + return 0; |
| 241 | +} |
0 commit comments