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.

342 lines
8.0 KiB

  1. /*
  2. * weighttp - a lightweight and simple webserver benchmarking tool
  3. *
  4. * Author:
  5. * Copyright (c) 2009 Thomas Porzelt
  6. *
  7. * License:
  8. * MIT, see COPYING file
  9. */
  10. #define VERSION "0.1"
  11. #include "weighttp.h"
  12. extern int optind, optopt; /* getopt */
  13. static void show_help(void) {
  14. printf("weighttp <options> <url>\n");
  15. printf(" -n num number of requests (mandatory)\n");
  16. printf(" -k keep alive (default: no)\n");
  17. printf(" -t num threadcount (default: 1)\n");
  18. printf(" -c num concurrent clients (default: 1)\n");
  19. printf(" -h show help and exit\n");
  20. printf(" -v show version and exit\n\n");
  21. }
  22. static struct addrinfo *resolve_host(char *hostname, uint16_t port) {
  23. int err;
  24. char port_str[6];
  25. struct addrinfo hints, *res, *res_first, *res_last;
  26. memset(&hints, 0, sizeof(hints));
  27. hints.ai_family = PF_UNSPEC;
  28. hints.ai_socktype = SOCK_STREAM;
  29. sprintf(port_str, "%d", port);
  30. err = getaddrinfo(hostname, port_str, &hints, &res_first);
  31. if (err) {
  32. W_ERROR("could not resolve hostname: %s", hostname);
  33. return NULL;
  34. }
  35. /* search for an ipv4 address, no ipv6 yet */
  36. res_last = NULL;
  37. for (res = res_first; res != NULL; res = res->ai_next) {
  38. if (res->ai_family == AF_INET)
  39. break;
  40. res_last = res;
  41. }
  42. if (!res) {
  43. freeaddrinfo(res_first);
  44. W_ERROR("could not resolve hostname: %s", hostname);
  45. return NULL;
  46. }
  47. if (res != res_first) {
  48. /* unlink from list and free rest */
  49. res_last->ai_next = res->ai_next;
  50. freeaddrinfo(res_first);
  51. res->ai_next = NULL;
  52. }
  53. return res;
  54. }
  55. static char *forge_request(char *url, char keep_alive, char **host, uint16_t *port) {
  56. char *c, *end;
  57. char *req;
  58. uint32_t len;
  59. *host = NULL;
  60. *port = 0;
  61. if (strncmp(url, "http://", 7) == 0)
  62. url += 7;
  63. else if (strncmp(url, "https://", 8) == 0) {
  64. W_ERROR("%s", "no ssl support yet");
  65. url += 8;
  66. return NULL;
  67. }
  68. len = strlen(url);
  69. if ((c = strchr(url, ':'))) {
  70. /* found ':' => host:port */
  71. *host = W_MALLOC(char, c - url + 1);
  72. memcpy(*host, url, c - url);
  73. (*host)[c - url] = '\0';
  74. if ((end = strchr(c+1, '/'))) {
  75. *end = '\0';
  76. *port = atoi(c+1);
  77. *end = '/';
  78. url = end;
  79. } else {
  80. *port = atoi(c+1);
  81. url += len;
  82. }
  83. } else {
  84. *port = 80;
  85. if ((c = strchr(url, '/'))) {
  86. *host = W_MALLOC(char, c - url + 1);
  87. memcpy(*host, url, c - url);
  88. (*host)[c - url] = '\0';
  89. url = c;
  90. } else {
  91. *host = W_MALLOC(char, len + 1);
  92. memcpy(*host, url, len);
  93. (*host)[len] = '\0';
  94. url += len;
  95. }
  96. }
  97. if (*port == 0) {
  98. W_ERROR("%s", "could not parse url");
  99. free(*host);
  100. return NULL;
  101. }
  102. if (*url == '\0')
  103. url = "/";
  104. req = W_MALLOC(char, sizeof("GET HTTP/1.1\r\nHost: :65536\r\nConnection: keep-alive\r\n\r\n") + strlen(*host) + strlen(url));
  105. strcpy(req, "GET ");
  106. strcat(req, url);
  107. strcat(req, " HTTP/1.1\r\nHost: ");
  108. strcat(req, *host);
  109. if (*port != 80)
  110. sprintf(req + strlen(req), ":%"PRIu16, *port);
  111. if (keep_alive)
  112. strcat(req, "\r\nConnection: keep-alive\r\n\r\n");
  113. else
  114. strcat(req, "\r\nConnection: close\r\n\r\n");
  115. return req;
  116. }
  117. int main(int argc, char *argv[]) {
  118. Worker **workers;
  119. pthread_t *threads;
  120. int i;
  121. char c;
  122. int err;
  123. struct ev_loop *loop;
  124. Config config;
  125. Worker *worker;
  126. char *host;
  127. uint16_t port;
  128. uint16_t rest_concur, rest_req;
  129. Stats stats;
  130. ev_tstamp duration;
  131. int sec, millisec, microsec;
  132. uint64_t rps;
  133. uint64_t kbps;
  134. printf("weighttp - a lightweight and simple webserver benchmarking tool\n\n");
  135. /* default settings */
  136. config.thread_count = 1;
  137. config.concur_count = 1;
  138. config.req_count = 0;
  139. config.keep_alive = 0;
  140. while ((c = getopt(argc, argv, ":hvkn:t:c:")) != -1) {
  141. switch (c) {
  142. case 'h':
  143. show_help();
  144. return 0;
  145. case 'v':
  146. printf("version: " VERSION "\n");
  147. printf("build-date: " __DATE__ " " __TIME__ "\n\n");
  148. return 0;
  149. case 'k':
  150. config.keep_alive = 1;
  151. break;
  152. case 'n':
  153. config.req_count = atoi(optarg);
  154. break;
  155. case 't':
  156. config.thread_count = atoi(optarg);
  157. break;
  158. case 'c':
  159. config.concur_count = atoi(optarg);
  160. break;
  161. case '?':
  162. W_ERROR("unkown option: -%c", optopt);
  163. show_help();
  164. return 1;
  165. }
  166. }
  167. if ((argc - optind) < 1) {
  168. W_ERROR("%s", "missing url argument\n");
  169. show_help();
  170. return 1;
  171. } else if ((argc - optind) > 1) {
  172. W_ERROR("%s", "too many arguments\n");
  173. show_help();
  174. return 1;
  175. }
  176. /* check for sane arguments */
  177. if (!config.thread_count) {
  178. W_ERROR("%s", "thread count has to be > 0\n");
  179. show_help();
  180. return 1;
  181. }
  182. if (!config.concur_count) {
  183. W_ERROR("%s", "number of concurrent clients has to be > 0\n");
  184. show_help();
  185. return 1;
  186. }
  187. if (!config.req_count) {
  188. W_ERROR("%s", "number of requests has to be > 0\n");
  189. show_help();
  190. return 1;
  191. }
  192. if (config.thread_count > config.req_count || config.thread_count > config.concur_count || config.concur_count > config.req_count) {
  193. W_ERROR("%s", "insane arguments\n");
  194. show_help();
  195. return 1;
  196. }
  197. loop = ev_default_loop(0);
  198. if (!loop) {
  199. W_ERROR("%s", "could not initialize libev\n");
  200. return 2;
  201. }
  202. if (NULL == (config.request = forge_request(argv[optind], config.keep_alive, &host, &port)) {
  203. return 1;
  204. }
  205. config.request_size = strlen(config.request);
  206. //printf("Request (%d):\n==========\n%s==========\n", config.request_size, config.request);
  207. //printf("host: '%s', port: %d\n", host, port);
  208. /* resolve hostname */
  209. if(!(config.saddr = resolve_host(host, port))) {
  210. return 1;
  211. }
  212. /* spawn threads */
  213. threads = W_MALLOC(pthread_t, config.thread_count);
  214. workers = W_MALLOC(Worker*, config.thread_count);
  215. rest_concur = config.concur_count % config.thread_count;
  216. rest_req = config.req_count % config.thread_count;
  217. printf("starting benchmark...\n");
  218. memset(&stats, 0, sizeof(stats));
  219. stats.ts_start = ev_time();
  220. for (i = 0; i < config.thread_count; i++) {
  221. uint16_t reqs = config.req_count / config.thread_count;
  222. uint16_t concur = config.concur_count / config.thread_count;
  223. uint16_t diff;
  224. if (rest_concur) {
  225. diff = (i == config.thread_count) ? rest_concur : (rest_concur / config.thread_count);
  226. diff = diff ? diff : 1;
  227. concur += diff;
  228. rest_concur -= diff;
  229. }
  230. if (rest_req) {
  231. diff = (i == config.thread_count) ? rest_req : (rest_req / config.thread_count);
  232. diff = diff ? diff : 1;
  233. reqs += diff;
  234. rest_req -= diff;
  235. }
  236. workers[i] = worker = worker_new(i+1, &config, concur, reqs);
  237. if (!worker) {
  238. W_ERROR("%s", "failed to allocate worker or client");
  239. return 1;
  240. }
  241. err = pthread_create(&threads[i], NULL, worker_thread, (void*)worker);
  242. if (err != 0) {
  243. W_ERROR("failed spawning thread (%d)", err);
  244. return 2;
  245. }
  246. }
  247. for (i = 0; i < config.thread_count; i++) {
  248. err = pthread_join(threads[i], NULL);
  249. worker = workers[i];
  250. if (err != 0) {
  251. W_ERROR("failed joining thread (%d)", err);
  252. return 3;
  253. }
  254. stats.req_started += worker->stats.req_started;
  255. stats.req_done += worker->stats.req_done;
  256. stats.req_success += worker->stats.req_success;
  257. stats.req_failed += worker->stats.req_failed;
  258. stats.bytes_total += worker->stats.bytes_total;
  259. stats.bytes_body += worker->stats.bytes_body;
  260. worker_free(worker);
  261. }
  262. stats.ts_end = ev_time();
  263. duration = stats.ts_end - stats.ts_start;
  264. sec = duration;
  265. duration -= sec;
  266. duration = duration * 1000;
  267. millisec = duration;
  268. duration -= millisec;
  269. microsec = duration * 1000;
  270. rps = stats.req_done / (stats.ts_end - stats.ts_start);
  271. kbps = stats.bytes_total / (stats.ts_end - stats.ts_start) / 1024;
  272. printf("\nfinished in %d sec, %d millisec and %d microsec, %"PRIu64" req/s, %"PRIu64" kbyte/s\n", sec, millisec, microsec, rps, kbps);
  273. printf("requests: %"PRIu64" total, %"PRIu64" started, %"PRIu64" done, %"PRIu64" succeeded, %"PRIu64" failed, %"PRIu64" errored\n",
  274. config.req_count, stats.req_started, stats.req_done, stats.req_success, stats.req_failed, stats.req_error
  275. );
  276. printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes http, %"PRIu64" bytes data\n",
  277. stats.bytes_total, stats.bytes_total - stats.bytes_body, stats.bytes_body
  278. );
  279. ev_default_destroy();
  280. free(threads);
  281. free(workers);
  282. free(config.request);
  283. freeaddrinfo(config.saddr);
  284. return 0;
  285. }