/*
 * Perform HTTP requests as fast as we can, using 10000 handles
 * concurrently in a single curl_multi.
 */
#include <sys/time.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <curl/curl.h>


#define HTTP_HOST       "127.0.0.1"
#define HTTP_PORT       8080
#define CONCURRENCY     10000

CURLM *multi_handle = NULL;
volatile int completed = 0;
struct curl_slist *curl_headers = NULL;

long get_time_ms() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec*1000 + tv.tv_usec/1000;
}

void launch_request() {
    char url[256];
    CURL *handle;
    sprintf(url, "http://%s:%d/", HTTP_HOST, HTTP_PORT);
    handle = curl_easy_init();
    curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_NOBODY, 1);
    curl_easy_setopt(handle, CURLOPT_HTTPHEADER, curl_headers);
    curl_multi_add_handle(multi_handle, handle);
}

void finish_request(CURL *handle, CURLcode result) {
    long http_code;
    assert(result == CURLE_OK);
    curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &http_code);
    assert(http_code == 200);
    curl_multi_remove_handle(multi_handle, handle);
    curl_easy_cleanup(handle);
    ++completed;
}

void *status_thread(void *arg) {
    /* Print out a status report every 2 seconds. */
    int last_completed = 0;
    long last_time = 0;
    for (;;) {
        int completed_now = completed;
        long time_now = get_time_ms();
        long requests_per_sec = (1000*(completed_now - last_completed))/(time_now - last_time);
        printf("Completed requests: %d \t Requests/sec: %ld\n", completed_now, requests_per_sec);
        last_completed = completed_now;
        last_time = time_now;
        sleep(2);
    }
}

int main() {
    int i;
    pthread_t t;
    /* Sleep to let the mock_http_server startup. */
    sleep(1);
    pthread_create(&t, NULL, status_thread, NULL);
    multi_handle = curl_multi_init();
    curl_headers = curl_slist_append(curl_headers, "Expect:");
    for (i = 0; i < CONCURRENCY; ++i) {
        launch_request();
    }
    for (;;) {
        CURLMcode cc;
        CURLMsg *msg;
        int msgs_left;
        int rc;
        do {
            int unused;
            long s, e;
            s = get_time_ms();
            rc = curl_multi_perform(multi_handle, &unused);
            e = get_time_ms();
            printf("curl_multi_perform took %ld ms\n", e - s);
        } while (rc == CURLM_CALL_MULTI_PERFORM);

        while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
            assert(msg->msg == CURLMSG_DONE);
            /* Launch a new request every time we finish a request,
             * to keep the number of running requests equal to CONCURRENCY. */
            launch_request();
            finish_request(msg->easy_handle, msg->data.result);
        }
        cc = curl_multi_wait(multi_handle, NULL, 0, 1000000, NULL);
        assert(cc == CURLM_OK);
    }
    /* Unreachable */
}
