/***************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * $Id$ * * Connect to N sites simultanouesly and download data. * */ #include #include #include #include #include #include #include /* The number of simultanoues connections/transfers we do */ #define NCONNECTIONS 2000 /* The least number of connections we are interested in, so when we go below this amount we can just as well stop */ #define NMARGIN 50 /* 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[80]; size_t dlcounter; struct globalinfo *global; }; 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; }; struct conncount timecount[NCONNECTIONS+1]; 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); timecount[still_running].time_us += lap; timecount[still_running].laps++; /* number of times added */ if(lap > timecount[still_running].maxtime) { timecount[still_running].maxtime = lap; } } } 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_stop(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[NCONNECTIONS]; long selects; long selectsalive; long timeouts; long perform; long performalive; long performselect; long topselect; static void report(void) { int i; long active = total - paused; long numdl = 0; for(i=0; i < NCONNECTIONS; i++) { if(conns[i].dlcounter) numdl++; } printf("Summary from %d simultanoues transfers:\n", NCONNECTIONS); printf("Total time %ldus - Paused %ldus = Active %ldus =\n Active/total" " %ldus\n", total, paused, active, active/NCONNECTIONS); printf(" Active/(connections that delivered data) = %ldus\n", active/numdl); printf("%d out of %d connections provided data\n", numdl, NCONNECTIONS); printf("%d calls to curl_multi_perform(), average %d alive. " "Average time: %dus\n", perform, performalive/perform, active/perform); printf("%d calls to select(), average %d alive\n", selects, selectsalive/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 select() timeouts\n", timeouts); for(i=1; i< NCONNECTIONS; i++) { if(timecount[i].laps) { printf("Time %d connections, average %ld max %ld (%ld laps) average/conn: %ld\n", i, timecount[i].time_us/timecount[i].laps, timecount[i].maxtime, timecount[i].laps, (timecount[i].time_us/timecount[i].laps)/i ); } } } int main(int argc, char **argv) { CURLM *multi_handle; CURLMsg *msg; CURLcode code = CURLE_OK; CURLMcode mcode = CURLM_OK; int rc; int i; FILE *urls; int startindex=0; char buffer[256]; int prevalive=-1; int prevsamecounter=0; int prevtotal = -1; memset(&info, 0, sizeof(struct globalinfo)); if(argc < 2) { printf("Usage: hiper [file] [start index]\n"); return 1; } urls = fopen(argv[1], "r"); if(!urls) /* failed to open list of urls */ return 1; if(argc > 2) startindex = atoi(argv[2]); if(startindex) { /* Pass this many lines before we start using URLs from the file. On repeated invokes, try using different indexes to avoid torturing the same servers. */ while(startindex--) { if(!fgets(buffer, sizeof(buffer), urls)) break; } } /* init the multi stack */ multi_handle = curl_multi_init(); for(i=0; i< NCONNECTIONS; i++) { CURL *e; char *nl; memset(&conns[i], 0, sizeof(struct connection)); /* read a line from the file of URLs */ if(!fgets(conns[i].url, sizeof(conns[i].url), urls)) /* failed to read a line */ break; /* strip off trailing newlines */ nl = strchr(conns[i].url, '\n'); if(nl) *nl=0; /* cut */ printf("%d: Add URL %s\n", i, conns[i].url); e = curl_easy_init(); 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]); #if 0 curl_easy_setopt(e, CURLOPT_VERBOSE, 1); curl_easy_setopt(e, CURLOPT_ERRORBUFFER, errorbuffer); #endif /* add the easy to the multi */ curl_multi_add_handle(multi_handle, e); } /* we start some action by calling perform right away */ while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &still_running)); printf("Starting timer!\n"); timer_start(); while(still_running) { struct timeval timeout; int rc; /* select() return code */ fd_set fdread; fd_set fdwrite; fd_set fdexcep; int maxfd; FD_ZERO(&fdread); FD_ZERO(&fdwrite); FD_ZERO(&fdexcep); /* set a suitable timeout to play around with */ timeout.tv_sec = 0; timeout.tv_usec = 50000; /* get file descriptors from the transfers */ curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); timer_pause(); selects++; selectsalive += still_running; rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); /* Output this here to make it outside the timer */ printf("Running: %d (%d bytes)\n", still_running, info.dlcounter); timer_continue(); switch(rc) { case -1: /* select error */ break; case 0: timeouts++; default: /* timeout or readable/writable sockets */ do { perform++; performalive += still_running; } while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi_handle, &still_running)); performselect += rc; if(rc > topselect) topselect = rc; break; } if(still_running < NMARGIN) { printf("Only %d connections left alive, existing\n", still_running); break; } if((prevalive == still_running) && (prevtotal == info.dlcounter) && info.dlcounter) { /* The same amount of still alive transfers as last lap, increase counter. Only do this if _anything_ has been downloaded since it tends to come here during the initial name lookup phase when using asynch DNS libcurl otherwise. */ prevsamecounter++; if(prevsamecounter >= IDLE_TIME) { /* for the sake of being efficient, we stop the operation when IDLE_TIME has passed without any bytes transfered */ printf("Idle time (%d secs) reached (with %d still claimed alive)," " exiting\n", IDLE_TIME, still_running); break; } } else { prevsamecounter=0; } prevalive = still_running; prevtotal = info.dlcounter; } timer_stop(); curl_multi_cleanup(multi_handle); /* cleanup all the easy handles */ for(i=0; i< NCONNECTIONS; i++) curl_easy_cleanup(conns[i].e); report(); return code; }