mirror of
https://github.com/ntop/ntopng.git
synced 2026-05-06 03:45:26 +00:00
1091 lines
27 KiB
C
1091 lines
27 KiB
C
/*
|
|
Code taken from
|
|
https://github.com/compex-systems/speedtest-cli
|
|
*/
|
|
|
|
/*
|
|
* To compile as standalone application:
|
|
* gcc -DTEST_SPEEDTEST -DHAVE_EXPAT -I/usr/include/json-c speedtest.c -o speedtest -ljson-c -lexpat -lcurl -lpthread -lm
|
|
* Run:
|
|
* ./speedtest
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#endif
|
|
#include <math.h>
|
|
#include <curl/curl.h>
|
|
#include "json.h"
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
#define INFO_SPEEDTEST
|
|
#endif
|
|
|
|
#ifdef HAVE_EXPAT
|
|
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include "expat.h"
|
|
|
|
#define URL_LENGTH_MAX 255
|
|
#define THREAD_NUM_MAX 3
|
|
#define UPLOAD_EXT_LENGTH_MAX 5
|
|
#define SPEEDTEST_TIME_MAX 10
|
|
#define CUNTRY_NAME_MAX 64
|
|
|
|
#define UPLOAD_EXTENSION_TAG "upload_extension"
|
|
#define LATENCY_TXT_URL "/speedtest/latency.txt"
|
|
|
|
#define INIT_DOWNLOAD_FILE_RESOLUTION 750
|
|
#define FILE_350_SIZE 245388
|
|
|
|
#define MAX_ISP_NAME 255
|
|
#define MAX_IPADDRESS_STRLEN 48
|
|
#define MAX_CLOSEST_SERVER_NUM 64
|
|
#define MIN_SERVERS_TO_CHECK 5
|
|
|
|
#define RECORED_EVERY_SEC 0.2
|
|
#define PRINT_RECORED_NUM 2
|
|
|
|
#define PI 3.1415926
|
|
#define EARTH_RADIUS 6378.137
|
|
|
|
#define UPLOAD_CHRUNK_SIZE_MAX 512000 // Max upload chunk size 512K
|
|
|
|
#define OK 0
|
|
#define NOK 1
|
|
#define FULL_REPORT
|
|
|
|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
|
|
|
|
struct thread_para
|
|
{
|
|
pthread_t tid;
|
|
char url[URL_LENGTH_MAX + 32];
|
|
long result;
|
|
long upload_size;
|
|
long chunk_size;
|
|
double now;
|
|
char finish;
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_t lock;
|
|
#endif
|
|
};
|
|
|
|
|
|
struct web_buffer
|
|
{
|
|
char *data;
|
|
int size;
|
|
};
|
|
|
|
struct client_info
|
|
{
|
|
char ip[MAX_IPADDRESS_STRLEN];
|
|
double lat;
|
|
double lon;
|
|
char isp[MAX_ISP_NAME];
|
|
};
|
|
|
|
struct server_info
|
|
{
|
|
char url[URL_LENGTH_MAX];
|
|
double lat;
|
|
double lon;
|
|
char country[CUNTRY_NAME_MAX];
|
|
int id;
|
|
double distance;
|
|
};
|
|
|
|
static int depth;
|
|
static struct client_info client;
|
|
static struct server_info servers[MAX_CLOSEST_SERVER_NUM];
|
|
static int num_servers = 0;
|
|
|
|
/* ***************************************** */
|
|
|
|
static int calc_past_time(struct timeval* start, struct timeval* end)
|
|
{
|
|
return (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec)/1000;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static size_t write_data(void* ptr, size_t size, size_t nmemb, void *stream)
|
|
{
|
|
struct thread_para *p_thread = (struct thread_para*)stream;
|
|
if (p_thread) {
|
|
//p_thread->data++;
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_lock(&p_thread->lock);
|
|
#endif
|
|
p_thread->now += size * nmemb;
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_unlock(&p_thread->lock);
|
|
#endif
|
|
}
|
|
|
|
/* printf("Upload: %u\n", size * nmemb); */
|
|
return size * nmemb;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
size_t
|
|
write_web_buf(void *ptr, size_t size, size_t nmemb, void *data)
|
|
{
|
|
size_t realsize = size * nmemb;
|
|
struct web_buffer *mem = (struct web_buffer *)data;
|
|
|
|
mem->data = (char *)realloc(mem->data, mem->size + realsize + 1);
|
|
if (mem->data) {
|
|
memcpy(&(mem->data[mem->size]), ptr, realsize);
|
|
mem->size += realsize;
|
|
mem->data[mem->size] = 0;
|
|
}
|
|
return realsize;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
double radian(double d) {
|
|
return d * PI / 180.0;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
double get_distance(double lat1, double lng1, double lat2, double lng2)
|
|
{
|
|
double radLat1 = radian(lat1);
|
|
double radLat2 = radian(lat2);
|
|
double a = radLat1 - radLat2;
|
|
double b = radian(lng1) - radian(lng2);
|
|
|
|
double dst = 2 * asin((sqrt(pow(sin(a / 2), 2) + cos(radLat1) * cos(radLat2) * pow(sin(b / 2), 2) )));
|
|
|
|
dst = dst * EARTH_RADIUS;
|
|
dst= round(dst * 10000) / 10000;
|
|
return dst;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static void XMLCALL start_element(void *userData, const char *el, const char **atts)
|
|
{
|
|
int i;
|
|
|
|
if (depth == 1 && strcmp(el, "client") == 0) {
|
|
|
|
struct client_info *p_client = (struct client_info *)userData;
|
|
|
|
for (i = 0; atts[i]; i += 2) {
|
|
//printf(" %s \n", atts[i]);
|
|
if (strcmp(atts[i], "ip") == 0)
|
|
strcpy(p_client->ip, atts[i + 1]);
|
|
if (strcmp(atts[i], "isp") == 0)
|
|
strcpy(p_client->isp, atts[i + 1]);
|
|
if (strcmp(atts[i], "lat") == 0)
|
|
p_client->lat = atof(atts[i + 1]);
|
|
if (strcmp(atts[i], "lon") == 0)
|
|
p_client->lon = atof(atts[i + 1]);
|
|
|
|
//printf("client %s %s %lf %lf\n", p_client->ip, p_client->isp, p_client->lat, p_client->lon);
|
|
}
|
|
}
|
|
|
|
if (depth == 2 && strcmp(el, "server") == 0) {
|
|
struct server_info *p_server = (struct server_info *)userData;
|
|
|
|
for (i = 0; atts[i]; i += 2) {
|
|
//printf(" %s \n", atts[i]);
|
|
if (strcmp(atts[i], "url") == 0)
|
|
strcpy(p_server->url, atts[i + 1]);
|
|
if (strcmp(atts[i], "country") == 0)
|
|
strcpy(p_server->country, atts[i + 1]);
|
|
if (strcmp(atts[i], "lat") == 0)
|
|
p_server->lat = atof(atts[i + 1]);
|
|
if (strcmp(atts[i], "lon") == 0)
|
|
p_server->lon = atof(atts[i + 1]);
|
|
if (strcmp(atts[i], "id") == 0)
|
|
p_server->id = atof(atts[i + 1]);
|
|
}
|
|
}
|
|
depth++;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static void XMLCALL end_element(void *userData, const char *name)
|
|
{
|
|
depth--;
|
|
|
|
if (strcmp(name, "server") == 0) {
|
|
int i;
|
|
struct server_info *p_server = (struct server_info *)userData;
|
|
double max_distance = 0;
|
|
int max_distance_i = -1;
|
|
|
|
p_server->distance = get_distance(client.lat, client.lon, p_server->lat, p_server->lon);
|
|
|
|
for (i = 0; i < MAX_CLOSEST_SERVER_NUM; i++) {
|
|
if (servers[i].url[0] == 0 ) {
|
|
break;
|
|
} else {
|
|
if (servers[i].distance >= max_distance) {
|
|
/* Keep track of the server with max distance */
|
|
max_distance = servers[i].distance;
|
|
max_distance_i = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i == MAX_CLOSEST_SERVER_NUM) {
|
|
/* No room */
|
|
if (max_distance > p_server->distance) {
|
|
/* Replacing the server with max distance */
|
|
i = max_distance_i;
|
|
}
|
|
}
|
|
|
|
if (i != MAX_CLOSEST_SERVER_NUM) {
|
|
memcpy(&servers[i], p_server, sizeof(struct server_info));
|
|
num_servers++; /* Note: this also includes servers discarded due to distance */
|
|
}
|
|
|
|
memset(p_server, 0, sizeof(struct server_info));
|
|
}
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static int do_latency(char *p_url)
|
|
{
|
|
char latency_url[URL_LENGTH_MAX + sizeof(LATENCY_TXT_URL)] = {0};
|
|
CURL *curl;
|
|
CURLcode res;
|
|
long response_code;
|
|
double v = (double) (rand() % 1000) / 100;
|
|
char useragent[16];
|
|
|
|
curl = curl_easy_init();
|
|
|
|
snprintf(latency_url, sizeof(latency_url), "%s%s", p_url, LATENCY_TXT_URL);
|
|
snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Calling %s\n", latency_url);
|
|
#endif
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, latency_url);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
#endif
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
curl_easy_cleanup(curl);
|
|
|
|
if (res != CURLE_OK || response_code != 200) {
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("curl_easy_perform() failed for %s: '%s' (%ld)\n", p_url, curl_easy_strerror(res), response_code);
|
|
#endif
|
|
return NOK;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static double test_latency(char *p_url)
|
|
{
|
|
struct timeval s_time, e_time;
|
|
double latency;
|
|
|
|
gettimeofday(&s_time, NULL);
|
|
if (do_latency(p_url) != OK)
|
|
return DBL_MAX;
|
|
gettimeofday(&e_time, NULL);
|
|
|
|
latency = calc_past_time(&s_time, &e_time);
|
|
return latency;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static void* do_download(void* data)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct thread_para* p_para = (struct thread_para*)data;
|
|
curl_off_t size = 0;
|
|
double time = 0, time1 = 0, time2;
|
|
char useragent[16];
|
|
double v = (double) (rand() % 1000) / 100;
|
|
|
|
curl = curl_easy_init();
|
|
snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("image url = %s\n", p_para->url);
|
|
#endif
|
|
curl_easy_setopt(curl, CURLOPT_URL, p_para->url);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, p_para);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
#endif
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
#endif
|
|
} else {
|
|
#if LIBCURL_VERSION_NUM >= 0x073700
|
|
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &size);
|
|
#else
|
|
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &size);
|
|
#endif
|
|
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &time);
|
|
curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &time1);
|
|
curl_easy_getinfo(curl, CURLINFO_STARTTRANSFER_TIME, &time2);
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Completed: [Size: %lu][TotalTime: %lf][ConnectTime: %lf][TransferTime: %lf]\n", size, time, time1, time2);
|
|
#endif
|
|
p_para->result = size;
|
|
p_para->finish = 1;
|
|
}
|
|
|
|
curl_easy_cleanup(curl);
|
|
return NULL;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static void loop_threads(struct thread_para *p_thread,
|
|
int num_thread, double *speed, int *p_num_speed)
|
|
{
|
|
char alive = 0;
|
|
int num_speed = 0;
|
|
|
|
|
|
do {
|
|
int i;
|
|
double sum = 0;
|
|
alive = 0;
|
|
|
|
for (i = 0;i < num_thread; i++) {
|
|
if (p_thread[i].finish == 0)
|
|
alive = 1;
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_lock(&p_thread->lock);
|
|
#endif
|
|
sum += p_thread[i].now;
|
|
p_thread[i].now = 0;
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_unlock(&p_thread->lock);
|
|
#endif
|
|
//printf("p_thread[i].now = %lf\n", p_thread[i].now);
|
|
}
|
|
|
|
usleep(RECORED_EVERY_SEC*1000*1000); //0.2s
|
|
if (sum == 0 && num_speed == 0)
|
|
continue;
|
|
|
|
if (num_speed < *p_num_speed) {
|
|
|
|
speed[num_speed] = sum/RECORED_EVERY_SEC; //Byte
|
|
num_speed++;
|
|
}
|
|
|
|
#if 0
|
|
if (++num % PRINT_RECORED_NUM == 0) {
|
|
printf(".");
|
|
fflush(stdout);
|
|
}
|
|
#endif
|
|
} while(alive);
|
|
// printf("\n");
|
|
*p_num_speed = num_speed;
|
|
return;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static double calculate_average_speed(double *p_speed, int num_speed)
|
|
{
|
|
|
|
int i = 0;
|
|
int start ,end;
|
|
double sum = 0;
|
|
for (i = 0; i < num_speed; i++) {
|
|
|
|
int j, min;
|
|
|
|
for ( min = i, j = i + 1; j < num_speed; j++) {
|
|
|
|
if (p_speed[min] > p_speed[j])
|
|
min = j;
|
|
}
|
|
if (min != i) {
|
|
|
|
double tmp;
|
|
tmp = p_speed[i];
|
|
p_speed[i] = p_speed[min];
|
|
p_speed[min] = tmp;
|
|
}
|
|
|
|
}
|
|
#ifdef DEBUG_SPEEDTEST
|
|
for (i = 0; i < num_speed; i++) {
|
|
printf("%0.2lf ", p_speed[i]*8/(1024*1024));
|
|
if (i%10 == 0)
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
/*The fastest 10% and slowest 20% of the slices are discarded*/
|
|
start = num_speed*0.2;
|
|
end = num_speed - (int)(num_speed*0.1);
|
|
//end = num_speed;
|
|
for (i = start; i < end; i++) {
|
|
|
|
sum += p_speed[i];
|
|
}
|
|
//printf("speed = %0.2lf\n", (sum*8/(end - start))/(1024*1024));
|
|
|
|
if(end == start) end++;
|
|
|
|
return sum/(end - start);
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static int init_instant_speed(double **p_speed, int *p_speed_num)
|
|
{
|
|
*p_speed_num = SPEEDTEST_TIME_MAX/RECORED_EVERY_SEC + 1;
|
|
*p_speed = (double*)malloc((*p_speed_num)*sizeof(double));
|
|
|
|
if (*p_speed == NULL) {
|
|
// fprintf(stderr, "malloc failed\n");
|
|
return -1;
|
|
}
|
|
memset(*p_speed, 0, (*p_speed_num)*sizeof(double));
|
|
return 0;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static double test_download(char *p_url, int num_thread, int dsize, char init)
|
|
{
|
|
struct timeval s_time;
|
|
int time, i;
|
|
struct thread_para paras[THREAD_NUM_MAX];
|
|
double sum = 0;
|
|
double speed = 0;
|
|
double *instant_speed = NULL;
|
|
int speed_num = 0;
|
|
|
|
gettimeofday(&s_time, NULL);
|
|
init_instant_speed(&instant_speed, &speed_num);
|
|
|
|
for (i = 0; i < num_thread; i++) {
|
|
//int error;
|
|
|
|
memset(¶s[i], 0, sizeof(struct thread_para));
|
|
snprintf(paras[i].url, sizeof(paras[i].url), "%s/speedtest/random%dx%d.jpg", p_url, dsize, dsize);
|
|
paras[i].result = 0;
|
|
|
|
//error =
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_init(¶s[i].lock, NULL);
|
|
#endif
|
|
|
|
pthread_create(¶s[i].tid, NULL, do_download, (void*)¶s[i]);
|
|
|
|
// if ( error != 0) printf("Can't Run thread num %d, error %d\n", i, error);
|
|
}
|
|
|
|
if (init != 0) {
|
|
loop_threads(paras, num_thread, instant_speed, &speed_num);
|
|
}
|
|
|
|
for (i = 0;i < num_thread; i++) {
|
|
pthread_join(paras[i].tid, NULL);
|
|
sum += paras[i].result;
|
|
}
|
|
|
|
if (init != 0) {
|
|
speed = calculate_average_speed(instant_speed, speed_num);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("speed = %0.2fMbps (%0.1fBps)\n", (speed*8)/(1024*1024), speed);
|
|
#endif
|
|
} else {
|
|
struct timeval e_time;
|
|
|
|
gettimeofday(&e_time, NULL);
|
|
time = calc_past_time(&s_time, &e_time);
|
|
speed = (sum*1000)/time;
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("speed = %0.2fMbps (sum = %f) (msec = %d)\n", (speed*8)/(1024*1024), sum, time);
|
|
#endif
|
|
}
|
|
free(instant_speed);
|
|
if (!(speed >= 0)) speed = 0;
|
|
return speed;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
#ifdef FULL_REPORT
|
|
static size_t read_data(void* ptr, size_t size, size_t nmemb, void *userp)
|
|
{
|
|
struct thread_para* para = (struct thread_para*)userp;
|
|
int length;
|
|
char data[16284] = {0};
|
|
|
|
if (size * nmemb < 1 && para->chunk_size)
|
|
return 0;
|
|
int i;
|
|
for (i = 0; i < 16284; i++) {
|
|
|
|
data[i] = i%26 + 'a';
|
|
}
|
|
|
|
if ((size_t) para->chunk_size > size * nmemb) {
|
|
|
|
length = size * nmemb < 16284 ? size*nmemb : 16284;
|
|
}
|
|
else
|
|
length = para->chunk_size < 16284 ? para->chunk_size : 16284;
|
|
memcpy(ptr, data, length);
|
|
|
|
para->chunk_size -= length;
|
|
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_lock(¶->lock);
|
|
#endif
|
|
para->now += length;
|
|
#if THREAD_NUM_MAX > 1
|
|
pthread_mutex_unlock(¶->lock);
|
|
#endif
|
|
//printf("length = %d\n", length);
|
|
return length;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static void* do_upload(void *p) {
|
|
struct thread_para* para = (struct thread_para*)p;
|
|
CURL *curl;
|
|
CURLcode res;
|
|
long size = para->upload_size;
|
|
int loop = 1;
|
|
char useragent[16];
|
|
double v = (double) (rand() % 1000) / 100;
|
|
|
|
if (size > UPLOAD_CHRUNK_SIZE_MAX)
|
|
loop = (size / UPLOAD_CHRUNK_SIZE_MAX) + 1;
|
|
|
|
curl = curl_easy_init();
|
|
snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, para->url);
|
|
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_data);
|
|
curl_easy_setopt(curl, CURLOPT_READDATA, para);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
#endif
|
|
|
|
while (loop) {
|
|
double size_upload;
|
|
|
|
para->chunk_size = size - para->result> UPLOAD_CHRUNK_SIZE_MAX ?
|
|
UPLOAD_CHRUNK_SIZE_MAX : size - para->result;
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE , (curl_off_t)para->chunk_size);
|
|
|
|
res = curl_easy_perform(curl);
|
|
if (res != CURLE_OK) {
|
|
// fprintf(stderr, "Error: curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
curl_easy_cleanup(curl);
|
|
para->finish = 1;
|
|
return(NULL);
|
|
}
|
|
|
|
#if LIBCURL_VERSION_NUM >= 0x073700
|
|
curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD_T, &size_upload);
|
|
#else
|
|
curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &size_upload);
|
|
#endif
|
|
|
|
para->result += size_upload;
|
|
loop--;
|
|
}
|
|
|
|
curl_easy_cleanup(curl);
|
|
para->finish = 1;
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("size upload = %lu\n", para->result);
|
|
#endif
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static double test_upload(char *p_url, int num_thread, long ul_size,
|
|
char *p_ext, char init)
|
|
{
|
|
struct timeval s_time;
|
|
int i;
|
|
struct thread_para paras[THREAD_NUM_MAX];
|
|
double sum = 0, speed = 0;
|
|
double *instant_speed = NULL;
|
|
int speed_num = 0;
|
|
|
|
gettimeofday(&s_time, NULL);
|
|
|
|
init_instant_speed(&instant_speed, &speed_num);
|
|
|
|
for ( i = 0; i < num_thread; i++) {
|
|
memset(¶s[i], 0, sizeof(struct thread_para));
|
|
snprintf(paras[i].url, sizeof(paras[i].url), "%s/speedtest/upload.%s", p_url, p_ext);
|
|
paras[i].result = 0;
|
|
paras[i].finish = 0;
|
|
paras[i].upload_size = ul_size/num_thread;
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("[thread %u] Upload size: %lu\n", i, paras[i].upload_size);
|
|
#endif
|
|
|
|
//printf("szeleft = %ld\n", paras[i].upload_size);
|
|
//int error =
|
|
pthread_create(¶s[i].tid, NULL, do_upload, (void*)¶s[i]);
|
|
// if ( error != 0) printf("Can't Run thread num %d, error %d\n", i, error);
|
|
}
|
|
|
|
if (init != 0) {
|
|
loop_threads(paras, num_thread, instant_speed, &speed_num);
|
|
}
|
|
|
|
for (i = 0;i < num_thread; i++) {
|
|
pthread_join(paras[i].tid, NULL);
|
|
sum += paras[i].result;
|
|
}
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Total size upload = %lf\n", sum);
|
|
#endif
|
|
|
|
if (init != 0) {
|
|
speed = calculate_average_speed(instant_speed, speed_num);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("speed = %0.2fMbps\n", (speed*8)/(1024*1024));
|
|
#endif
|
|
} else {
|
|
struct timeval e_time;
|
|
int time;
|
|
|
|
gettimeofday(&e_time, NULL);
|
|
time = calc_past_time(&s_time, &e_time);
|
|
speed = (sum*1000)/time; //bytes per second
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("speed = %0.2fMbps (msec = %d)\n", (speed*8)/(1024*1024), time);
|
|
#endif
|
|
}
|
|
free(instant_speed);
|
|
if (!(speed >= 0)) speed = 0;
|
|
return speed;
|
|
}
|
|
#endif
|
|
|
|
/* ***************************************** */
|
|
|
|
static int get_download_filename(double speed, int num_thread)
|
|
{
|
|
int i;
|
|
int filelist[] = {350, 500, 750, 1000, 1500, 2000, 3000, 3500, 4000};
|
|
int num_file = ARRAY_SIZE(filelist);
|
|
|
|
if(speed == 0) speed = 1;
|
|
|
|
for (i = 1; i < num_file; i++) {
|
|
long long int time;
|
|
float times = (float)filelist[i]/350;
|
|
//printf("time %f speed %lf\n", times, speed);
|
|
times = (times*times);
|
|
time = (long long int) ((long long int) num_thread*times*FILE_350_SIZE)/speed;
|
|
//printf("%d %lld %f\n", filelist[i], time, times);
|
|
if (time > SPEEDTEST_TIME_MAX)
|
|
break;
|
|
}
|
|
|
|
if (i < num_file)
|
|
return filelist[i - 1];
|
|
|
|
return filelist[num_file - 1];
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static int get_upload_extension(char *server, char *p_ext)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct web_buffer web;
|
|
char* p = NULL;
|
|
int rv = NOK;
|
|
|
|
memset(&web, 0, sizeof(web));
|
|
|
|
curl = curl_easy_init();
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, server);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_web_buf);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web);
|
|
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
res = curl_easy_perform(curl);
|
|
curl_easy_cleanup(curl);
|
|
if (res != CURLE_OK) {
|
|
// printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
goto cleanup;
|
|
}
|
|
p = strstr(web.data, UPLOAD_EXTENSION_TAG);
|
|
if (p == NULL ||
|
|
sscanf(p + strlen(UPLOAD_EXTENSION_TAG), "%*[^a-zA-Z]%[a-zA-Z]", p_ext) <= 0) {
|
|
// fprintf(stderr, "Upload extension not found\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
rv = OK;
|
|
|
|
cleanup:
|
|
if(web.data) free(web.data);
|
|
|
|
// printf("Upload extension: %s\n", p_ext);
|
|
return rv;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static int get_client_info(struct client_info *p_client)
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
XML_Parser xml = XML_ParserCreate(NULL);
|
|
struct web_buffer web;
|
|
int rv = NOK;
|
|
|
|
memset(&web, 0, sizeof(web));
|
|
|
|
curl = curl_easy_init();
|
|
curl_easy_setopt(curl, CURLOPT_URL, "https://www.speedtest.net/speedtest-config.php");
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_web_buf);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "haibbo speedtest-cli");
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
#endif
|
|
|
|
res = curl_easy_perform(curl);
|
|
curl_easy_cleanup(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
#endif
|
|
goto cleanup;
|
|
}
|
|
XML_SetUserData(xml, p_client);
|
|
XML_SetElementHandler(xml, start_element, end_element);
|
|
if (XML_Parse(xml, web.data, web.size , 1) == XML_STATUS_ERROR) {
|
|
#ifdef DEBUG_SPEEDTEST
|
|
fprintf(stderr, "Parse client failed\n");
|
|
#endif
|
|
// exit(-1);
|
|
goto cleanup;
|
|
}
|
|
|
|
rv = OK;
|
|
|
|
cleanup:
|
|
if(web.data) free(web.data);
|
|
XML_ParserFree(xml);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static int get_closest_server()
|
|
{
|
|
CURL *curl;
|
|
CURLcode res;
|
|
XML_Parser xml = XML_ParserCreate(NULL);
|
|
struct web_buffer web;
|
|
struct server_info server;
|
|
int rv = NOK;
|
|
char useragent[16];
|
|
const char *url = "https://www.speedtest.net/speedtest-servers.php";
|
|
double v = (double) (rand() % 1000) / 100;
|
|
|
|
memset(&web, 0, sizeof(web));
|
|
snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v);
|
|
|
|
curl = curl_easy_init();
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Retrieving servers list %s\n", url);
|
|
#endif
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_web_buf);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web);
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
|
#endif
|
|
|
|
res = curl_easy_perform(curl);
|
|
curl_easy_cleanup(curl);
|
|
|
|
if (res != CURLE_OK) {
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
|
#endif
|
|
goto cleanup;
|
|
}
|
|
XML_SetUserData(xml, &server);
|
|
depth = 0;
|
|
XML_SetElementHandler(xml, start_element, end_element);
|
|
if (XML_Parse(xml, web.data, web.size , 1) == XML_STATUS_ERROR) {
|
|
#ifdef DEBUG_SPEEDTEST
|
|
fprintf(stderr, "Parse servers list failed\n");
|
|
#endif
|
|
// exit(-1);
|
|
goto cleanup;
|
|
}
|
|
|
|
rv = OK;
|
|
|
|
cleanup:
|
|
if(web.data) free(web.data);
|
|
XML_ParserFree(xml);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
static int get_best_server(int *p_index)
|
|
{
|
|
int i;
|
|
double minimum = DBL_MAX;
|
|
char server[URL_LENGTH_MAX] = {0};
|
|
|
|
for (i = 0; i < MAX_CLOSEST_SERVER_NUM; i++) {
|
|
double latency;
|
|
|
|
if (minimum != DBL_MAX && i >= MIN_SERVERS_TO_CHECK)
|
|
break; /* MIN_SERVERS_TO_CHECK evaluated and at least one found */
|
|
|
|
if((strlen(servers[i].url) == 0))
|
|
continue;
|
|
|
|
sscanf(servers[i].url, "http://%[^/]speedtest/upload.%*s", server);
|
|
if(server[0] == '\0')
|
|
sscanf(servers[i].url, "https://%[^/]speedtest/upload.%*s", server);
|
|
|
|
if(server[0] == '\0')
|
|
continue;
|
|
|
|
latency = test_latency(server);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Measured latency for %s is %0.3fms\n", server, latency);
|
|
#endif
|
|
|
|
if (minimum > latency ) {
|
|
minimum = latency;
|
|
*p_index = i;
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Best server set to %u (%s)\n", i, server);
|
|
#endif
|
|
}
|
|
} /* for */
|
|
|
|
if (minimum == DBL_MAX)
|
|
return NOK;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
json_object* speedtest() {
|
|
int num_thread;
|
|
char server_url[URL_LENGTH_MAX], ext[UPLOAD_EXT_LENGTH_MAX];
|
|
#ifdef FULL_REPORT
|
|
double latency, upload_speed;
|
|
#endif
|
|
double speed, download_speed;
|
|
int dsize, sindex;
|
|
json_object *rc = json_object_new_object();
|
|
|
|
if(rc == NULL) {
|
|
#ifdef INFO_SPEEDTEST
|
|
printf("speedtest: failure allocating memory\n");
|
|
#endif
|
|
return(rc);
|
|
}
|
|
|
|
//Initialization
|
|
|
|
sindex = -1;
|
|
num_thread = 1;
|
|
dsize = INIT_DOWNLOAD_FILE_RESOLUTION;
|
|
memset(server_url, 0, sizeof(server_url));
|
|
memset(ext, 0, sizeof(ext));
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Retrieving speedtest.net configuration...\n");
|
|
#endif
|
|
get_client_info(&client);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Retrieving speedtest.net server list...\n");
|
|
#endif
|
|
get_closest_server();
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Testing from %s (%s)...\n", client.isp, client.ip);
|
|
#endif
|
|
|
|
json_object_object_add(rc, "client.isp", json_object_new_string(client.isp));
|
|
json_object_object_add(rc, "client.ip", json_object_new_string(client.ip));
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Selecting best server based on ping...\n");
|
|
#endif
|
|
|
|
if (get_best_server(&sindex) != OK) {
|
|
#ifdef INFO_SPEEDTEST
|
|
printf("speedtest: failure selecting the best server out of %u\n", num_servers);
|
|
#endif
|
|
return(rc);
|
|
}
|
|
|
|
sscanf(servers[sindex].url, "http://%[^/]/speedtest/upload.%4s", server_url, ext);
|
|
|
|
if(server_url[0] == '\0')
|
|
sscanf(servers[sindex].url, "https://%[^/]/speedtest/upload.%4s", server_url, ext);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Best server: %s (%0.2fKM) [index: %u][%s]\n",
|
|
server_url, servers[sindex].distance, sindex, servers[sindex].url);
|
|
#endif
|
|
json_object_object_add(rc, "server.url", json_object_new_string(server_url));
|
|
json_object_object_add(rc, "server.distance", json_object_new_double(servers[sindex].distance));
|
|
|
|
/* Must initialize libcurl before any threads are started */
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
#ifdef FULL_REPORT
|
|
latency = test_latency(server_url);
|
|
if (latency == DBL_MAX) {
|
|
#ifdef INFO_SPEEDTEST
|
|
printf("speedtest: failure testing latency\n");
|
|
#endif
|
|
return(rc);
|
|
}
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Server latency is %0.0fms\n", latency);
|
|
#endif
|
|
json_object_object_add(rc, "server.latency", json_object_new_double(latency));
|
|
#endif
|
|
|
|
speed = test_download(server_url, num_thread, dsize, 0);
|
|
|
|
dsize = get_download_filename(speed, num_thread);
|
|
#ifdef DEBUG_SPEEDTEST
|
|
fprintf(stderr, "Testing download speed\n");
|
|
#endif
|
|
download_speed = test_download(server_url, num_thread, dsize, 1);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Download speed: %0.2fMbps\n", ((download_speed*8)/(1024*1024)));
|
|
#endif
|
|
json_object_object_add(rc, "download.speed", json_object_new_double((download_speed*8)/(1024*1024)));
|
|
|
|
if (ext[0] == 0 && get_upload_extension(server_url, ext) != OK) {
|
|
#ifdef INFO_SPEEDTEST
|
|
printf("speedtest: failure in upload extension\n");
|
|
#endif
|
|
return(rc);
|
|
}
|
|
|
|
#ifdef FULL_REPORT
|
|
if(speed == 0) speed = download_speed;
|
|
speed = test_upload(server_url, num_thread, speed, ext, 0);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
fprintf(stderr, "Testing upload speed\n");
|
|
#endif
|
|
if(speed == 0) speed = download_speed;
|
|
upload_speed = test_upload(server_url, num_thread, speed*SPEEDTEST_TIME_MAX, ext, 1);
|
|
|
|
#ifdef DEBUG_SPEEDTEST
|
|
printf("Upload speed: %0.2fMbps\n", ((upload_speed*8)/(1024*1024)));
|
|
#endif
|
|
json_object_object_add(rc, "upload.speed", json_object_new_double(((upload_speed*8)/(1024*1024))));
|
|
#endif
|
|
|
|
return(rc);
|
|
}
|
|
|
|
#else
|
|
json_object* speedtest() { return(NULL); }
|
|
#endif /* HAVE_EXPAT */
|
|
|
|
#ifdef TEST_SPEEDTEST
|
|
int main(int argc, char *argv[]) {
|
|
json_object *out;
|
|
|
|
out = speedtest();
|
|
|
|
if (out == NULL) {
|
|
printf("failure\n");
|
|
return 1;
|
|
}
|
|
|
|
printf("%s\n", json_object_to_json_string(out));
|
|
|
|
json_object_put(out);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|