curl/hiper/hiper.c

378 lines
9.2 KiB
C
Raw Normal View History

/*****************************************************************************
* _ _ ____ _
* 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;
}