| /***************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * $Id$ |
| * |
| * Connect N connections. Z are idle, and X are active. Transfer as fast as |
| * possible. |
| * |
| * Run for a specific amount of time (10 secs for now). Output detailed timing |
| * information. |
| * |
| * The same is hiper.c but instead using the new *socket() API instead of the |
| * "old" *perform() call. |
| * |
| * Uses libevent. |
| * |
| */ |
| |
| /* The maximum number of simultanoues connections/transfers we support */ |
| #define NCONNECTIONS 50000 |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <sys/poll.h> |
| |
| #include <curl/curl.h> |
| |
| #include <event.h> /* for libevent */ |
| |
| #ifndef FALSE |
| #define FALSE 0 |
| #endif |
| |
| #ifndef TRUE |
| #define TRUE 1 |
| #endif |
| |
| #define MICROSEC 1000000 /* number of microseconds in one second */ |
| |
| /* The maximum time (in microseconds) we run the test */ |
| #define RUN_FOR_THIS_LONG (5*MICROSEC) |
| |
| /* Number of loops (seconds) we allow the total download amount and alive |
| connections to remain the same until we bail out. Set this slightly higher |
| when using asynch supported libcurl. */ |
| #define IDLE_TIME 10 |
| |
| struct ourfdset { |
| /* __fds_bits is what the Linux glibc headers use when they declare the |
| fd_set struct so by using this we can actually avoid the typecase for the |
| FD_SET() macro usage but it would hardly be portable */ |
| char __fds_bits[NCONNECTIONS/8]; |
| }; |
| #define FD2_ZERO(x) memset(x, 0, sizeof(struct ourfdset)) |
| |
| typedef struct ourfdset fd2_set; |
| |
| struct globalinfo { |
| size_t dlcounter; |
| }; |
| |
| struct connection { |
| CURL *e; |
| int id; /* just a counter for easy browsing */ |
| char *url; |
| size_t dlcounter; |
| struct globalinfo *global; |
| char error[CURL_ERROR_SIZE]; |
| }; |
| |
| struct fdinfo { |
| /* create a link list of fdinfo structs */ |
| struct fdinfo *next; |
| struct fdinfo *prev; |
| curl_socket_t sockfd; |
| CURL *easy; |
| int action; /* as set by libcurl */ |
| long timeout; /* as set by libcurl */ |
| struct event ev; |
| }; |
| |
| static struct fdinfo *allsocks; |
| |
| static struct fdinfo *findsock(curl_socket_t s) |
| { |
| /* return the struct for the given socket */ |
| struct fdinfo *fdp = allsocks; |
| |
| while(fdp) { |
| if(fdp->sockfd == s) |
| break; |
| fdp = fdp->next; |
| } |
| return fdp; /* a struct pointer or NULL */ |
| } |
| |
| static void remsock(curl_socket_t s) |
| { |
| struct fdinfo *fdp = allsocks; |
| |
| while(fdp) { |
| if(fdp->sockfd == s) |
| break; |
| fdp = fdp->next; |
| } |
| if(!fdp) |
| /* did not find socket to remove! */ |
| return; |
| |
| if(fdp->prev) |
| fdp->prev->next = fdp->next; |
| if(fdp->next) |
| fdp->next->prev = fdp->prev; |
| else |
| /* this was the last entry */ |
| allsocks = NULL; |
| } |
| |
| static void setsock(struct fdinfo *fdp, curl_socket_t s, CURL *easy, |
| int action) |
| { |
| fdp->sockfd = s; |
| fdp->action = action; |
| fdp->easy = easy; |
| } |
| |
| static void addsock(curl_socket_t s, CURL *easy, int action) |
| { |
| struct fdinfo *fdp = calloc(sizeof(struct fdinfo), 1); |
| |
| setsock(fdp, s, easy, action); |
| |
| if(allsocks) { |
| fdp->next = allsocks; |
| allsocks->prev = fdp; |
| |
| /* now set allsocks to point to the new struct */ |
| allsocks = fdp; |
| } |
| else |
| allsocks = fdp; |
| } |
| |
| static void fdinfo2fdset(fd2_set *fdread, fd2_set *fdwrite, int *maxfd) |
| { |
| struct fdinfo *fdp = allsocks; |
| int writable=0; |
| |
| FD2_ZERO(fdread); |
| FD2_ZERO(fdwrite); |
| |
| *maxfd = 0; |
| |
| #if 0 |
| printf("Wait for: "); |
| #endif |
| |
| while(fdp) { |
| if(fdp->action & CURL_POLL_IN) { |
| FD_SET(fdp->sockfd, (fd_set *)fdread); |
| } |
| if(fdp->action & CURL_POLL_OUT) { |
| FD_SET(fdp->sockfd, (fd_set *)fdwrite); |
| writable++; |
| } |
| |
| #if 0 |
| printf("%d (%s%s) ", |
| fdp->sockfd, |
| (fdp->action & CURL_POLL_IN)?"r":"", |
| (fdp->action & CURL_POLL_OUT)?"w":""); |
| #endif |
| |
| if(fdp->sockfd > *maxfd) |
| *maxfd = fdp->sockfd; |
| |
| fdp = fdp->next; |
| } |
| #if 0 |
| if(writable) |
| printf("Check for %d writable sockets\n", writable); |
| #endif |
| } |
| |
| /* on port 8999 we run a fork enabled sws that supports 'idle' and 'stream' */ |
| #define PORT "8999" |
| |
| #define HOST "192.168.1.13" |
| |
| #define URL_IDLE "http://" HOST ":" PORT "/1000" |
| #define URL_ACTIVE "http://" HOST ":" PORT "/1001" |
| |
| |
| static int socket_callback(CURL *easy, /* easy handle */ |
| curl_socket_t s, /* socket */ |
| int what, /* see above */ |
| void *userp) /* "private" pointer */ |
| { |
| struct fdinfo *fdp; |
| printf("socket %d easy %p what %d\n", s, easy, what); |
| |
| if(what == CURL_POLL_REMOVE) |
| remsock(s); |
| else { |
| fdp = findsock(s); |
| |
| if(!fdp) { |
| addsock(s, easy, what); |
| } |
| else { |
| /* we already know about it, just change action/timeout */ |
| printf("Changing info for socket %d from %d to %d\n", |
| s, fdp->action, what); |
| setsock(fdp, s, easy, what); |
| } |
| } |
| return 0; /* return code meaning? */ |
| } |
| |
| |
| static size_t |
| writecallback(void *ptr, size_t size, size_t nmemb, void *data) |
| { |
| size_t realsize = size * nmemb; |
| struct connection *c = (struct connection *)data; |
| |
| c->dlcounter += realsize; |
| c->global->dlcounter += realsize; |
| |
| #if 0 |
| printf("%02d: %d, total %d\n", |
| c->id, c->dlcounter, c->global->dlcounter); |
| #endif |
| return realsize; |
| } |
| |
| /* return the diff between two timevals, in us */ |
| static long tvdiff(struct timeval *newer, struct timeval *older) |
| { |
| return (newer->tv_sec-older->tv_sec)*1000000+ |
| (newer->tv_usec-older->tv_usec); |
| } |
| |
| |
| /* store the start time of the program in this variable */ |
| static struct timeval timer; |
| |
| static void timer_start(void) |
| { |
| /* capture the time of the start moment */ |
| gettimeofday(&timer, NULL); |
| } |
| |
| static struct timeval cont; /* at this moment we continued */ |
| |
| int still_running; /* keep number of running handles */ |
| |
| struct conncount { |
| long time_us; |
| long laps; |
| long maxtime; |
| }; |
| |
| static struct timeval timerpause; |
| static void timer_pause(void) |
| { |
| /* capture the time of the pause moment */ |
| gettimeofday(&timerpause, NULL); |
| |
| /* If we have a previous continue (all times except the first), we can now |
| store the time for a whole "lap" */ |
| if(cont.tv_sec) { |
| long lap; |
| |
| lap = tvdiff(&timerpause, &cont); |
| } |
| } |
| |
| static long paused; /* amount of us we have been pausing */ |
| |
| static void timer_continue(void) |
| { |
| /* Capture the time of the restored operation moment, now calculate how long |
| time we were paused and added that to the 'paused' variable. |
| */ |
| gettimeofday(&cont, NULL); |
| |
| paused += tvdiff(&cont, &timerpause); |
| } |
| |
| static long total; /* amount of us from start to stop */ |
| static void timer_total(void) |
| { |
| struct timeval stop; |
| /* Capture the time of the operation stopped moment, now calculate how long |
| time we were running and how much of that pausing. |
| */ |
| gettimeofday(&stop, NULL); |
| |
| total = tvdiff(&stop, &timer); |
| } |
| |
| struct globalinfo info; |
| struct connection *conns; |
| |
| long selects; |
| long timeouts; |
| |
| long multi_socket; |
| long performalive; |
| long performselect; |
| long topselect; |
| |
| int num_total; |
| int num_idle; |
| int num_active; |
| |
| static void report(void) |
| { |
| int i; |
| long active = total - paused; |
| long numdl = 0; |
| |
| for(i=0; i < num_total; i++) { |
| if(conns[i].dlcounter) |
| numdl++; |
| } |
| |
| printf("Summary from %d simultanoues transfers (%d active)\n", |
| num_total, num_active); |
| printf("%d out of %d connections provided data\n", numdl, num_total); |
| |
| printf("Total time: %ldus paused: %ldus curl_multi_socket(): %ldus\n", |
| total, paused, active); |
| |
| printf("%d calls to select() " |
| "Average time: %dus\n", |
| selects, paused/selects); |
| printf(" Average number of readable connections per select() return: %d\n", |
| performselect/selects); |
| |
| printf(" Max number of readable connections for a single select() " |
| "return: %d\n", |
| topselect); |
| |
| printf("%ld calls to multi_socket(), " |
| "Average time: %ldus\n", |
| multi_socket, active/multi_socket); |
| |
| printf("%ld select() timeouts\n", timeouts); |
| |
| printf("Downloaded %ld bytes in %ld bytes/sec, %ld usec/byte\n", |
| info.dlcounter, |
| info.dlcounter/(total/1000000), |
| total/info.dlcounter); |
| |
| } |
| |
| int main(int argc, char **argv) |
| { |
| CURLM *multi_handle; |
| CURLMsg *msg; |
| CURLcode code = CURLE_OK; |
| CURLMcode mcode = CURLM_OK; |
| int rc; |
| int i; |
| fd2_set fdsizecheck; |
| int selectmaxamount; |
| struct fdinfo *fdp; |
| char act; |
| |
| memset(&info, 0, sizeof(struct globalinfo)); |
| |
| selectmaxamount = sizeof(fdsizecheck) * 8; |
| printf("select() supports max %d connections\n", selectmaxamount); |
| |
| if(argc < 3) { |
| printf("Usage: hiper [num idle] [num active]\n"); |
| return 1; |
| } |
| |
| num_idle = atoi(argv[1]); |
| num_active = atoi(argv[2]); |
| |
| num_total = num_idle + num_active; |
| |
| if(num_total > selectmaxamount) { |
| printf("Requested more connections than supported!\n"); |
| return 4; |
| } |
| |
| conns = calloc(num_total, sizeof(struct connection)); |
| if(!conns) { |
| printf("Out of memory\n"); |
| return 3; |
| } |
| |
| if(num_total >= NCONNECTIONS) { |
| printf("Too many connections requested, increase NCONNECTIONS!\n"); |
| return 2; |
| } |
| |
| printf("About to do %d connections\n", num_total); |
| |
| /* init the multi stack */ |
| multi_handle = curl_multi_init(); |
| |
| for(i=0; i< num_total; i++) { |
| CURL *e; |
| char *nl; |
| |
| memset(&conns[i], 0, sizeof(struct connection)); |
| |
| if(i < num_idle) |
| conns[i].url = URL_IDLE; |
| else |
| conns[i].url = URL_ACTIVE; |
| |
| e = curl_easy_init(); |
| |
| if(!e) { |
| printf("curl_easy_init() for handle %d failed, exiting!\n", i); |
| return 2; |
| } |
| |
| conns[i].e = e; |
| conns[i].id = i; |
| conns[i].global = &info; |
| |
| curl_easy_setopt(e, CURLOPT_URL, conns[i].url); |
| curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, writecallback); |
| curl_easy_setopt(e, CURLOPT_WRITEDATA, &conns[i]); |
| curl_easy_setopt(e, CURLOPT_VERBOSE, 0); |
| curl_easy_setopt(e, CURLOPT_ERRORBUFFER, conns[i].error); |
| curl_easy_setopt(e, CURLOPT_PRIVATE, &conns[i]); |
| |
| /* add the easy to the multi */ |
| if(CURLM_OK != curl_multi_add_handle(multi_handle, e)) { |
| printf("curl_multi_add_handle() returned error for %d\n", i); |
| return 3; |
| } |
| } |
| |
| curl_multi_setopt(multi_handle, CURLMOPT_SOCKETFUNCTION, socket_callback); |
| curl_multi_setopt(multi_handle, CURLMOPT_SOCKETDATA, NULL); |
| |
| /* we start the action by calling *socket() right away */ |
| while(CURLM_CALL_MULTI_PERFORM == curl_multi_socket_all(multi_handle)); |
| |
| printf("Starting timer, expects to run for %ldus\n", RUN_FOR_THIS_LONG); |
| timer_start(); |
| timer_pause(); |
| |
| while(1) { |
| struct timeval timeout; |
| int rc; /* select() return code */ |
| long timeout_ms; |
| |
| fd2_set fdread; |
| fd2_set fdwrite; |
| int maxfd; |
| |
| curl_multi_timeout(multi_handle, &timeout_ms); |
| |
| /* set timeout to wait */ |
| timeout.tv_sec = timeout_ms/1000; |
| timeout.tv_usec = (timeout_ms%1000)*1000; |
| |
| /* convert file descriptors from the transfers to fd_sets */ |
| fdinfo2fdset(&fdread, &fdwrite, &maxfd); |
| |
| selects++; |
| rc = select(maxfd+1, |
| (fd_set *)&fdread, |
| (fd_set *)&fdwrite, |
| NULL, &timeout); |
| switch(rc) { |
| case -1: |
| /* select error */ |
| break; |
| case 0: |
| timeouts++; |
| curl_multi_socket(multi_handle, CURL_SOCKET_TIMEOUT); |
| break; |
| |
| default: |
| /* timeout or readable/writable sockets */ |
| |
| for(i=0, fdp = allsocks; fdp; fdp = fdp->next) { |
| act = 0; |
| if((fdp->action & CURL_POLL_IN) && |
| FD_ISSET(fdp->sockfd, &fdread)) { |
| act |= CURL_POLL_IN; |
| i++; |
| } |
| if((fdp->action & CURL_POLL_OUT) && |
| FD_ISSET(fdp->sockfd, &fdwrite)) { |
| act |= CURL_POLL_OUT; |
| i++; |
| } |
| |
| if(act) { |
| multi_socket++; |
| timer_continue(); |
| if(act & CURL_POLL_OUT) |
| act--; |
| curl_multi_socket(multi_handle, fdp->sockfd); |
| timer_pause(); |
| } |
| } |
| |
| performselect += rc; |
| if(rc > topselect) |
| topselect = rc; |
| break; |
| } |
| |
| timer_total(); /* calculate the total time spent so far */ |
| |
| if(total > RUN_FOR_THIS_LONG) { |
| printf("Stopped after %ldus\n", total); |
| break; |
| } |
| } |
| |
| if(still_running != num_total) { |
| /* something made connections fail, extract the reason and tell */ |
| int msgs_left; |
| struct connection *cptr; |
| while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { |
| if (msg->msg == CURLMSG_DONE) { |
| curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &cptr); |
| |
| printf("%d => (%d) %s", cptr->id, msg->data.result, cptr->error); |
| } |
| } |
| |
| } |
| |
| curl_multi_cleanup(multi_handle); |
| |
| /* cleanup all the easy handles */ |
| for(i=0; i< num_total; i++) |
| curl_easy_cleanup(conns[i].e); |
| |
| report(); |
| |
| return code; |
| } |