/***************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * $Id$ * * Connect N connections. Z are idle, and X are active. Transfer as fast as * possible. * * Output detailed timing information. * * Uses libevent. * */ /* The maximum number of simultanoues connections/transfers we support */ #define NCONNECTIONS 50000 #include #include #include #include #include #include #include #include #include /* 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 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]; }; /* this is the struct associated with each file descriptor libcurl tells us it is dealing with */ 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; /* */ int evset; /* true if the 'ev' struct has been used in a event_set() call */ CURLM *multi; /* pointer to the multi handle */ int *running_handles; /* pointer to the running_handles counter */ }; static struct fdinfo *allsocks; static int running_handles; /* we have the timerevent global so that when the final socket-based event is done, we can remove the timerevent as well */ static struct event timerevent; static void update_timeout(CURLM *multi_handle); /* called from libevent on action on a particular socket ("event") */ static void eventcallback(int fd, short type, void *userp) { struct fdinfo *fdp = (struct fdinfo *)userp; CURLMcode rc; fprintf(stderr, "EVENT callback type %d\n", type); /* tell libcurl to deal with the transfer associated with this socket */ do { rc = curl_multi_socket(fdp->multi, fd, fdp->running_handles); } while (rc == CURLM_CALL_MULTI_PERFORM); if(rc) { fprintf(stderr, "curl_multi_socket() returned %d\n", (int)rc); } fprintf(stderr, "running_handles: %d\n", *fdp->running_handles); if(!*fdp->running_handles) { /* last transfer is complete, kill pending timeout */ fprintf(stderr, "last transfer done, kill timeout\n"); if(evtimer_pending(&timerevent, NULL)) evtimer_del(&timerevent); } else update_timeout(fdp->multi); } /* called from libevent when our timer event expires */ static void timercallback(int fd, short type, void *userp) { (void)fd; /* not used for this */ (void)type; /* ignored in here */ CURLM *multi_handle = (CURLM *)userp; int running_handles; CURLMcode rc; fprintf(stderr, "EVENT timeout\n"); /* tell libcurl to deal with the transfer associated with this socket */ do { rc = curl_multi_socket(multi_handle, CURL_SOCKET_TIMEOUT, &running_handles); } while (rc == CURLM_CALL_MULTI_PERFORM); if(running_handles) /* Get the current timeout value from libcurl and set a new timeout */ update_timeout(multi_handle); } static void remsock(struct fdinfo *f) { if(!f) /* did not find socket to remove! */ return; if(f->evset) event_del(&f->ev); if(f->prev) f->prev->next = f->next; if(f->next) f->next->prev = f->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; if(fdp->evset) /* first remove the existing event if the old setup was used */ event_del(&fdp->ev); /* now use and add the current socket setup to libevent. The EV_PERSIST is the key here as otherwise libevent will automatically remove the event when it occurs the first time */ event_set(&fdp->ev, fdp->sockfd, (action&CURL_POLL_IN?EV_READ:0)| (action&CURL_POLL_OUT?EV_WRITE:0)| EV_PERSIST, eventcallback, fdp); fdp->evset=1; fprintf(stderr, "event_add() for fd %d\n", s); /* We don't use any socket-specific timeout but intead we use a single global one. This is (mostly) because libcurl doesn't expose any particular socket- based timeout value. */ event_add(&fdp->ev, NULL); } static void addsock(curl_socket_t s, CURL *easy, int action, CURLM *multi) { struct fdinfo *fdp = calloc(sizeof(struct fdinfo), 1); fdp->multi = multi; fdp->running_handles = &running_handles; 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; /* Set this association in libcurl */ curl_multi_assign(multi, s, fdp); } /* on port 8999 we run a fork enabled sws that supports 'idle' and 'stream' */ #define PORT "8999" #define HOST "127.0.0.1" #define URL_IDLE "http://" HOST ":" PORT "/1000" #if 1 #define URL_ACTIVE "http://" HOST ":" PORT "/1001" #else #define URL_ACTIVE "http://localhost/" #endif static int socket_callback(CURL *easy, /* easy handle */ curl_socket_t s, /* socket */ int what, /* see above */ void *cbp, /* callback pointer */ void *socketp) /* socket pointer */ { struct fdinfo *fdp = (struct fdinfo *)socketp; char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE"}; fprintf(stderr, "socket %d easy %p what %s\n", s, easy, whatstr[what]); if(what == CURL_POLL_REMOVE) remsock(fdp); else { if(!fdp) { /* not previously known, add it and set association */ printf("Add info for socket %d %s%s\n", s, what&CURL_POLL_IN?"READ":"", what&CURL_POLL_OUT?"WRITE":"" ); addsock(s, easy, what, cbp); } 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; (void)ptr; c->dlcounter += realsize; c->global->dlcounter += realsize; printf("%02d: %d, total %d\n", c->id, c->dlcounter, c->global->dlcounter); return realsize; } struct globalinfo info; struct connection *conns; int num_total; int num_idle; int num_active; static void update_timeout(CURLM *multi_handle) { long timeout_ms; struct timeval timeout; /* Since we need a global timeout to occur after a given time of inactivity, we use a single timeout-event. Get the timeout value from libcurl, and update it after every call to libcurl. */ curl_multi_timeout(multi_handle, &timeout_ms); /* convert ms to timeval */ timeout.tv_sec = timeout_ms/1000; timeout.tv_usec = (timeout_ms%1000)*1000; evtimer_add(&timerevent, &timeout); } int main(int argc, char **argv) { CURLM *multi_handle; CURLMsg *msg; CURLcode code = CURLE_OK; int i; memset(&info, 0, sizeof(struct globalinfo)); if(argc < 3) { printf("Usage: hiper-event [num idle] [num active]\n"); return 1; } num_idle = atoi(argv[1]); num_active = atoi(argv[2]); num_total = num_idle + num_active; 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; } event_init(); /* Initalize the event library */ printf("About to do %d connections\n", num_total); /* init the multi stack */ multi_handle = curl_multi_init(); /* initialize the timeout event */ evtimer_set(&timerevent, timercallback, multi_handle); for(i=0; i< num_total; i++) { CURL *e; 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, multi_handle); /* we start the action by calling *socket_all() */ while(CURLM_CALL_MULTI_PERFORM == curl_multi_socket_all(multi_handle, &running_handles)); /* update timeout */ update_timeout(multi_handle); /* event_dispatch() runs the event main loop. It ends when no events are left to wait for. */ event_dispatch(); { /* 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\n", 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); return code; }