378 lines
9.2 KiB
C
378 lines
9.2 KiB
C
|
/*****************************************************************************
|
||
|
* _ _ ____ _
|
||
|
* Project ___| | | | _ \| |
|
||
|
* / __| | | | |_) | |
|
||
|
* | (__| |_| | _ <| |___
|
||
|
* \___|\___/|_| \_\_____|
|
||
|
*
|
||
|
* $Id$
|
||
|
*
|
||
|
* Connect to N sites simultanouesly and download data.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <time.h>
|
||
|
#include <unistd.h>
|
||
|
#include <sys/poll.h>
|
||
|
|
||
|
#include <curl/curl.h>
|
||
|
|
||
|
/* 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;
|
||
|
}
|