src/weighttp.c - weighttp

Functions defined

Source code

  1. /*
  2. * weighttp - a lightweight and simple webserver benchmarking tool
  3. *
  4. * Author:
  5. *     Copyright (c) 2009-2011 Thomas Porzelt
  6. *
  7. * License:
  8. *     MIT, see COPYING file
  9. */

  10. #include "weighttp.h"

  11. extern int optind, optopt; /* getopt */

  12. static void show_help(void) {
  13.     printf("weighttp <options> <url>\n");
  14.     printf("  -n num   number of requests    (mandatory)\n");
  15.     printf("  -t num   threadcount           (default: 1)\n");
  16.     printf("  -c num   concurrent clients    (default: 1)\n");
  17.     printf("  -k       keep alive            (default: no)\n");
  18.     printf("  -6       use ipv6              (default: no)\n");
  19.     printf("  -H str   add header to request\n");
  20.     printf("  -h       show help and exit\n");
  21.     printf("  -v       show version and exit\n\n");
  22.     printf("example: weighttpd -n 10000 -c 10 -t 2 -k -H \"User-Agent: foo\" localhost/index.html\n\n");
  23. }

  24. static struct addrinfo *resolve_host(char *hostname, uint16_t port, uint8_t use_ipv6) {
  25.     int err;
  26.     char port_str[6];
  27.     struct addrinfo hints, *res, *res_first, *res_last;

  28.     memset(&hints, 0, sizeof(hints));
  29.     hints.ai_family = PF_UNSPEC;
  30.     hints.ai_socktype = SOCK_STREAM;

  31.     sprintf(port_str, "%d", port);

  32.     err = getaddrinfo(hostname, port_str, &hints, &res_first);

  33.     if (err) {
  34.         W_ERROR("could not resolve hostname: %s", hostname);
  35.         return NULL;
  36.     }

  37.     /* search for an ipv4 address, no ipv6 yet */
  38.     res_last = NULL;
  39.     for (res = res_first; res != NULL; res = res->ai_next) {
  40.         if (res->ai_family == AF_INET && !use_ipv6)
  41.             break;
  42.         else if (res->ai_family == AF_INET6 && use_ipv6)
  43.             break;

  44.         res_last = res;
  45.     }

  46.     if (!res) {
  47.         freeaddrinfo(res_first);
  48.         W_ERROR("could not resolve hostname: %s", hostname);
  49.         return NULL;
  50.     }

  51.     if (res != res_first) {
  52.         /* unlink from list and free rest */
  53.         res_last->ai_next = res->ai_next;
  54.         freeaddrinfo(res_first);
  55.         res->ai_next = NULL;
  56.     }

  57.     return res;
  58. }

  59. static char *forge_request(char *url, char keep_alive, char **host, uint16_t *port, char **headers, uint8_t headers_num) {
  60.     char *c, *end;
  61.     char *req;
  62.     uint32_t len;
  63.     uint8_t i;
  64.     uint8_t have_user_agent, have_host;

  65.     *host = NULL;
  66.     *port = 0;

  67.     if (strncmp(url, "http://", 7) == 0)
  68.         url += 7;
  69.     else if (strncmp(url, "https://", 8) == 0) {
  70.         W_ERROR("%s", "no ssl support yet");
  71.         url += 8;
  72.         return NULL;
  73.     }

  74.     len = strlen(url);

  75.     if ((c = strchr(url, ':'))) {
  76.         /* found ':' => host:port */
  77.         *host = W_MALLOC(char, c - url + 1);
  78.         memcpy(*host, url, c - url);
  79.         (*host)[c - url] = '\0';

  80.         if ((end = strchr(c+1, '/'))) {
  81.             *end = '\0';
  82.             *port = atoi(c+1);
  83.             *end = '/';
  84.             url = end;
  85.         } else {
  86.             *port = atoi(c+1);
  87.             url += len;
  88.         }
  89.     } else {
  90.         *port = 80;

  91.         if ((c = strchr(url, '/'))) {
  92.             *host = W_MALLOC(char, c - url + 1);
  93.             memcpy(*host, url, c - url);
  94.             (*host)[c - url] = '\0';
  95.             url = c;
  96.         } else {
  97.             *host = W_MALLOC(char, len + 1);
  98.             memcpy(*host, url, len);
  99.             (*host)[len] = '\0';
  100.             url += len;
  101.         }
  102.     }

  103.     if (*port == 0) {
  104.         W_ERROR("%s", "could not parse url");
  105.         free(*host);
  106.         return NULL;
  107.     }

  108.     if (*url == '\0')
  109.         url = "/";

  110.     // total request size
  111.     len = strlen("GET HTTP/1.1\r\nConnection: keep-alive\r\n\r\n") + 1;
  112.     len += strlen(url);

  113.     have_user_agent = 0;
  114.     have_host = 0;
  115.     for (i = 0; i < headers_num; i++) {
  116.         len += strlen(headers[i]) + strlen("\r\n");
  117.         if (strncmp(headers[i], "User-Agent: ", sizeof("User-Agent: ")-1) == 0)
  118.             have_user_agent = 1;
  119.         if (strncasecmp(headers[i], "Host:", sizeof("Host:")-1) == 0)
  120.             have_host = 1;
  121.     }

  122.     if (!have_user_agent)
  123.         len += strlen("User-Agent: weighttp/" VERSION "\r\n");

  124.     if (!have_host) {
  125.         len += strlen("Host: :65536\r\n")-1;
  126.         len += strlen(*host);
  127.     }

  128.     req = W_MALLOC(char, len);

  129.     strcpy(req, "GET ");
  130.     strcat(req, url);
  131.     strcat(req, " HTTP/1.1\r\n");

  132.     if (!have_host) {
  133.         strcat(req, "Host: ");
  134.         strcat(req, *host);
  135.         if (*port != 80)
  136.             sprintf(req + strlen(req), ":%"PRIu16, *port);
  137.         strcat(req, "\r\n");
  138.     }

  139.     if (!have_user_agent)
  140.         sprintf(req + strlen(req), "User-Agent: weighttp/" VERSION "\r\n");

  141.     for (i = 0; i < headers_num; i++) {
  142.         strcat(req, headers[i]);
  143.         strcat(req, "\r\n");
  144.     }

  145.     if (keep_alive)
  146.         strcat(req, "Connection: keep-alive\r\n\r\n");
  147.     else
  148.         strcat(req, "Connection: close\r\n\r\n");

  149.     return req;
  150. }

  151. uint64_t str_to_uint64(char *str) {
  152.     uint64_t i;

  153.     for (i = 0; *str; str++) {
  154.         if (*str < '0' || *str > '9')
  155.             return UINT64_MAX;

  156.         i *= 10;
  157.         i += *str - '0';
  158.     }

  159.     return i;
  160. }

  161. int main(int argc, char *argv[]) {
  162.     Worker **workers;
  163.     pthread_t *threads;
  164.     int i;
  165.     char c;
  166.     int err;
  167.     struct ev_loop *loop;
  168.     ev_tstamp ts_start, ts_end;
  169.     Config config;
  170.     Worker *worker;
  171.     char *host;
  172.     uint16_t port;
  173.     uint8_t use_ipv6;
  174.     uint16_t rest_concur, rest_req;
  175.     Stats stats;
  176.     ev_tstamp duration;
  177.     int sec, millisec, microsec;
  178.     uint64_t rps;
  179.     uint64_t kbps;
  180.     char **headers;
  181.     uint8_t headers_num;

  182.     printf("weighttp - a lightweight and simple webserver benchmarking tool\n\n");

  183.     headers = NULL;
  184.     headers_num = 0;

  185.     /* default settings */
  186.     use_ipv6 = 0;
  187.     config.thread_count = 1;
  188.     config.concur_count = 1;
  189.     config.req_count = 0;
  190.     config.keep_alive = 0;

  191.     while ((c = getopt(argc, argv, ":hv6kn:t:c:H:")) != -1) {
  192.         switch (c) {
  193.             case 'h':
  194.                 show_help();
  195.                 return 0;
  196.             case 'v':
  197.                 printf("version:    " VERSION "\n");
  198.                 printf("build-date: " __DATE__ " " __TIME__ "\n\n");
  199.                 return 0;
  200.             case '6':
  201.                 use_ipv6 = 1;
  202.                 break;
  203.             case 'k':
  204.                 config.keep_alive = 1;
  205.                 break;
  206.             case 'n':
  207.                 config.req_count = str_to_uint64(optarg);
  208.                 break;
  209.             case 't':
  210.                 config.thread_count = atoi(optarg);
  211.                 break;
  212.             case 'c':
  213.                 config.concur_count = atoi(optarg);
  214.                 break;
  215.             case 'H':
  216.                 headers = W_REALLOC(headers, char*, headers_num+1);
  217.                 headers[headers_num] = optarg;
  218.                 headers_num++;
  219.                 break;
  220.             case '?':
  221.                 W_ERROR("unkown option: -%c", optopt);
  222.                 show_help();
  223.                 return 1;
  224.         }
  225.     }

  226.     if ((argc - optind) < 1) {
  227.         W_ERROR("%s", "missing url argument\n");
  228.         show_help();
  229.         return 1;
  230.     } else if ((argc - optind) > 1) {
  231.         W_ERROR("%s", "too many arguments\n");
  232.         show_help();
  233.         return 1;
  234.     }

  235.     /* check for sane arguments */
  236.     if (!config.thread_count) {
  237.         W_ERROR("%s", "thread count has to be > 0\n");
  238.         show_help();
  239.         return 1;
  240.     }
  241.     if (!config.concur_count) {
  242.         W_ERROR("%s", "number of concurrent clients has to be > 0\n");
  243.         show_help();
  244.         return 1;
  245.     }
  246.     if (!config.req_count) {
  247.         W_ERROR("%s", "number of requests has to be > 0\n");
  248.         show_help();
  249.         return 1;
  250.     }
  251.     if (config.req_count == UINT64_MAX || config.thread_count > config.req_count || config.thread_count > config.concur_count || config.concur_count > config.req_count) {
  252.         W_ERROR("%s", "insane arguments\n");
  253.         show_help();
  254.         return 1;
  255.     }


  256.     loop = ev_default_loop(0);
  257.     if (!loop) {
  258.         W_ERROR("%s", "could not initialize libev\n");
  259.         return 2;
  260.     }

  261.     if (NULL == (config.request = forge_request(argv[optind], config.keep_alive, &host, &port, headers, headers_num))) {
  262.         return 1;
  263.     }

  264.     config.request_size = strlen(config.request);
  265.     //printf("Request (%d):\n==========\n%s==========\n", config.request_size, config.request);
  266.     //printf("host: '%s', port: %d\n", host, port);

  267.     /* resolve hostname */
  268.     if(!(config.saddr = resolve_host(host, port, use_ipv6))) {
  269.         return 1;
  270.     }

  271.     /* spawn threads */
  272.     threads = W_MALLOC(pthread_t, config.thread_count);
  273.     workers = W_MALLOC(Worker*, config.thread_count);

  274.     rest_concur = config.concur_count % config.thread_count;
  275.     rest_req = config.req_count % config.thread_count;

  276.     printf("starting benchmark...\n");

  277.     memset(&stats, 0, sizeof(stats));
  278.     ts_start = ev_time();

  279.     for (i = 0; i < config.thread_count; i++) {
  280.         uint64_t reqs = config.req_count / config.thread_count;
  281.         uint16_t concur = config.concur_count / config.thread_count;
  282.         uint64_t diff;

  283.         if (rest_concur) {
  284.             diff = (i == config.thread_count) ? rest_concur : (rest_concur / config.thread_count);
  285.             diff = diff ? diff : 1;
  286.             concur += diff;
  287.             rest_concur -= diff;
  288.         }

  289.         if (rest_req) {
  290.             diff = (i == config.thread_count) ? rest_req : (rest_req / config.thread_count);
  291.             diff = diff ? diff : 1;
  292.             reqs += diff;
  293.             rest_req -= diff;
  294.         }
  295.         printf("spawning thread #%d: %"PRIu16" concurrent requests, %"PRIu64" total requests\n", i+1, concur, reqs);
  296.         workers[i] = worker_new(i+1, &config, concur, reqs);

  297.         if (!(workers[i])) {
  298.             W_ERROR("%s", "failed to allocate worker or client");
  299.             return 1;
  300.         }

  301.         err = pthread_create(&threads[i], NULL, worker_thread, (void*)workers[i]);

  302.         if (err != 0) {
  303.             W_ERROR("failed spawning thread (%d)", err);
  304.             return 2;
  305.         }
  306.     }

  307.     for (i = 0; i < config.thread_count; i++) {
  308.         err = pthread_join(threads[i], NULL);
  309.         worker = workers[i];

  310.         if (err != 0) {
  311.             W_ERROR("failed joining thread (%d)", err);
  312.             return 3;
  313.         }

  314.         stats.req_started += worker->stats.req_started;
  315.         stats.req_done += worker->stats.req_done;
  316.         stats.req_success += worker->stats.req_success;
  317.         stats.req_failed += worker->stats.req_failed;
  318.         stats.bytes_total += worker->stats.bytes_total;
  319.         stats.bytes_body += worker->stats.bytes_body;
  320.         stats.req_2xx += worker->stats.req_2xx;
  321.         stats.req_3xx += worker->stats.req_3xx;
  322.         stats.req_4xx += worker->stats.req_4xx;
  323.         stats.req_5xx += worker->stats.req_5xx;

  324.         worker_free(worker);
  325.     }

  326.     ts_end = ev_time();
  327.     duration = ts_end - ts_start;
  328.     sec = duration;
  329.     duration -= sec;
  330.     duration = duration * 1000;
  331.     millisec = duration;
  332.     duration -= millisec;
  333.     microsec = duration * 1000;
  334.     rps = stats.req_done / (ts_end - ts_start);
  335.     kbps = stats.bytes_total / (ts_end - ts_start) / 1024;
  336.     printf("\nfinished in %d sec, %d millisec and %d microsec, %"PRIu64" req/s, %"PRIu64" kbyte/s\n", sec, millisec, microsec, rps, kbps);
  337.     printf("requests: %"PRIu64" total, %"PRIu64" started, %"PRIu64" done, %"PRIu64" succeeded, %"PRIu64" failed, %"PRIu64" errored\n",
  338.         config.req_count, stats.req_started, stats.req_done, stats.req_success, stats.req_failed, stats.req_error
  339.     );
  340.     printf("status codes: %"PRIu64" 2xx, %"PRIu64" 3xx, %"PRIu64" 4xx, %"PRIu64" 5xx\n",
  341.         stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx
  342.     );
  343.     printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes http, %"PRIu64" bytes data\n",
  344.         stats.bytes_total,  stats.bytes_total - stats.bytes_body, stats.bytes_body
  345.     );

  346.     ev_default_destroy();

  347.     free(threads);
  348.     free(workers);
  349.     free(config.request);
  350.     free(host);
  351.     free(headers);
  352.     freeaddrinfo(config.saddr);

  353.     return 0;
  354. }