/src/ntopng/third-party/speedtest.c
Line | Count | Source |
1 | | /* |
2 | | Code taken from |
3 | | https://github.com/compex-systems/speedtest-cli |
4 | | */ |
5 | | |
6 | | /* |
7 | | * To compile as standalone application: |
8 | | * gcc -DTEST_SPEEDTEST -DHAVE_EXPAT -I/usr/include/json-c speedtest.c -o speedtest -ljson-c -lexpat -lcurl -lpthread -lm |
9 | | * Run: |
10 | | * ./speedtest |
11 | | */ |
12 | | |
13 | | #include <stdio.h> |
14 | | #include <string.h> |
15 | | #ifndef WIN32 |
16 | | #include <unistd.h> |
17 | | #include <pthread.h> |
18 | | #endif |
19 | | #include <math.h> |
20 | | #include <curl/curl.h> |
21 | | #include "json.h" |
22 | | |
23 | | #ifdef DEBUG_SPEEDTEST |
24 | | #define INFO_SPEEDTEST |
25 | | #endif |
26 | | |
27 | | #ifdef HAVE_EXPAT |
28 | | |
29 | | #include <float.h> |
30 | | #include <math.h> |
31 | | #include "expat.h" |
32 | | |
33 | | #define URL_LENGTH_MAX 255 |
34 | | #define THREAD_NUM_MAX 3 |
35 | | #define UPLOAD_EXT_LENGTH_MAX 5 |
36 | | #define SPEEDTEST_TIME_MAX 10 |
37 | | #define CUNTRY_NAME_MAX 64 |
38 | | |
39 | | #define UPLOAD_EXTENSION_TAG "upload_extension" |
40 | | #define LATENCY_TXT_URL "/speedtest/latency.txt" |
41 | | |
42 | | #define INIT_DOWNLOAD_FILE_RESOLUTION 750 |
43 | | #define FILE_350_SIZE 245388 |
44 | | |
45 | | #define MAX_ISP_NAME 255 |
46 | | #define MAX_IPADDRESS_STRLEN 48 |
47 | | #define MAX_CLOSEST_SERVER_NUM 64 |
48 | | #define MIN_SERVERS_TO_CHECK 5 |
49 | | |
50 | | #define RECORED_EVERY_SEC 0.2 |
51 | | #define PRINT_RECORED_NUM 2 |
52 | | |
53 | | #define PI 3.1415926 |
54 | | #define EARTH_RADIUS 6378.137 |
55 | | |
56 | | #define UPLOAD_CHRUNK_SIZE_MAX 512000 // Max upload chunk size 512K |
57 | | |
58 | | #define OK 0 |
59 | | #define NOK 1 |
60 | | #define FULL_REPORT |
61 | | |
62 | | #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) |
63 | | |
64 | | struct thread_para |
65 | | { |
66 | | pthread_t tid; |
67 | | char url[URL_LENGTH_MAX + 32]; |
68 | | long result; |
69 | | long upload_size; |
70 | | long chunk_size; |
71 | | double now; |
72 | | char finish; |
73 | | #if THREAD_NUM_MAX > 1 |
74 | | pthread_mutex_t lock; |
75 | | #endif |
76 | | }; |
77 | | |
78 | | |
79 | | struct web_buffer |
80 | | { |
81 | | char *data; |
82 | | int size; |
83 | | }; |
84 | | |
85 | | struct client_info |
86 | | { |
87 | | char ip[MAX_IPADDRESS_STRLEN]; |
88 | | double lat; |
89 | | double lon; |
90 | | char isp[MAX_ISP_NAME]; |
91 | | }; |
92 | | |
93 | | struct server_info |
94 | | { |
95 | | char url[URL_LENGTH_MAX]; |
96 | | double lat; |
97 | | double lon; |
98 | | char country[CUNTRY_NAME_MAX]; |
99 | | int id; |
100 | | double distance; |
101 | | }; |
102 | | |
103 | | static int depth; |
104 | | static struct client_info client; |
105 | | static struct server_info servers[MAX_CLOSEST_SERVER_NUM]; |
106 | | static int num_servers = 0; |
107 | | |
108 | | /* ***************************************** */ |
109 | | |
110 | | static int calc_past_time(struct timeval* start, struct timeval* end) |
111 | | { |
112 | | return (end->tv_sec - start->tv_sec) * 1000 + (end->tv_usec - start->tv_usec)/1000; |
113 | | } |
114 | | |
115 | | /* ***************************************** */ |
116 | | |
117 | | static size_t write_data(void* ptr, size_t size, size_t nmemb, void *stream) |
118 | | { |
119 | | struct thread_para *p_thread = (struct thread_para*)stream; |
120 | | if (p_thread) { |
121 | | //p_thread->data++; |
122 | | #if THREAD_NUM_MAX > 1 |
123 | | pthread_mutex_lock(&p_thread->lock); |
124 | | #endif |
125 | | p_thread->now += size * nmemb; |
126 | | #if THREAD_NUM_MAX > 1 |
127 | | pthread_mutex_unlock(&p_thread->lock); |
128 | | #endif |
129 | | } |
130 | | |
131 | | /* printf("Upload: %u\n", size * nmemb); */ |
132 | | return size * nmemb; |
133 | | } |
134 | | |
135 | | /* ***************************************** */ |
136 | | |
137 | | size_t |
138 | | write_web_buf(void *ptr, size_t size, size_t nmemb, void *data) |
139 | | { |
140 | | size_t realsize = size * nmemb; |
141 | | struct web_buffer *mem = (struct web_buffer *)data; |
142 | | |
143 | | mem->data = (char *)realloc(mem->data, mem->size + realsize + 1); |
144 | | if (mem->data) { |
145 | | memcpy(&(mem->data[mem->size]), ptr, realsize); |
146 | | mem->size += realsize; |
147 | | mem->data[mem->size] = 0; |
148 | | } |
149 | | return realsize; |
150 | | } |
151 | | |
152 | | /* ***************************************** */ |
153 | | |
154 | | double radian(double d) { |
155 | | return d * PI / 180.0; |
156 | | } |
157 | | |
158 | | /* ***************************************** */ |
159 | | |
160 | | double get_distance(double lat1, double lng1, double lat2, double lng2) |
161 | | { |
162 | | double radLat1 = radian(lat1); |
163 | | double radLat2 = radian(lat2); |
164 | | double a = radLat1 - radLat2; |
165 | | double b = radian(lng1) - radian(lng2); |
166 | | |
167 | | double dst = 2 * asin((sqrt(pow(sin(a / 2), 2) + cos(radLat1) * cos(radLat2) * pow(sin(b / 2), 2) ))); |
168 | | |
169 | | dst = dst * EARTH_RADIUS; |
170 | | dst= round(dst * 10000) / 10000; |
171 | | return dst; |
172 | | } |
173 | | |
174 | | /* ***************************************** */ |
175 | | |
176 | | static void XMLCALL start_element(void *userData, const char *el, const char **atts) |
177 | | { |
178 | | int i; |
179 | | |
180 | | if (depth == 1 && strcmp(el, "client") == 0) { |
181 | | |
182 | | struct client_info *p_client = (struct client_info *)userData; |
183 | | |
184 | | for (i = 0; atts[i]; i += 2) { |
185 | | //printf(" %s \n", atts[i]); |
186 | | if (strcmp(atts[i], "ip") == 0) |
187 | | strcpy(p_client->ip, atts[i + 1]); |
188 | | if (strcmp(atts[i], "isp") == 0) |
189 | | strcpy(p_client->isp, atts[i + 1]); |
190 | | if (strcmp(atts[i], "lat") == 0) |
191 | | p_client->lat = atof(atts[i + 1]); |
192 | | if (strcmp(atts[i], "lon") == 0) |
193 | | p_client->lon = atof(atts[i + 1]); |
194 | | |
195 | | //printf("client %s %s %lf %lf\n", p_client->ip, p_client->isp, p_client->lat, p_client->lon); |
196 | | } |
197 | | } |
198 | | |
199 | | if (depth == 2 && strcmp(el, "server") == 0) { |
200 | | struct server_info *p_server = (struct server_info *)userData; |
201 | | |
202 | | for (i = 0; atts[i]; i += 2) { |
203 | | //printf(" %s \n", atts[i]); |
204 | | if (strcmp(atts[i], "url") == 0) |
205 | | strcpy(p_server->url, atts[i + 1]); |
206 | | if (strcmp(atts[i], "country") == 0) |
207 | | strcpy(p_server->country, atts[i + 1]); |
208 | | if (strcmp(atts[i], "lat") == 0) |
209 | | p_server->lat = atof(atts[i + 1]); |
210 | | if (strcmp(atts[i], "lon") == 0) |
211 | | p_server->lon = atof(atts[i + 1]); |
212 | | if (strcmp(atts[i], "id") == 0) |
213 | | p_server->id = atof(atts[i + 1]); |
214 | | } |
215 | | } |
216 | | depth++; |
217 | | } |
218 | | |
219 | | /* ***************************************** */ |
220 | | |
221 | | static void XMLCALL end_element(void *userData, const char *name) |
222 | | { |
223 | | depth--; |
224 | | |
225 | | if (strcmp(name, "server") == 0) { |
226 | | int i; |
227 | | struct server_info *p_server = (struct server_info *)userData; |
228 | | double max_distance = 0; |
229 | | int max_distance_i = -1; |
230 | | |
231 | | p_server->distance = get_distance(client.lat, client.lon, p_server->lat, p_server->lon); |
232 | | |
233 | | for (i = 0; i < MAX_CLOSEST_SERVER_NUM; i++) { |
234 | | if (servers[i].url[0] == 0 ) { |
235 | | break; |
236 | | } else { |
237 | | if (servers[i].distance >= max_distance) { |
238 | | /* Keep track of the server with max distance */ |
239 | | max_distance = servers[i].distance; |
240 | | max_distance_i = i; |
241 | | } |
242 | | } |
243 | | } |
244 | | |
245 | | if (i == MAX_CLOSEST_SERVER_NUM) { |
246 | | /* No room */ |
247 | | if (max_distance > p_server->distance) { |
248 | | /* Replacing the server with max distance */ |
249 | | i = max_distance_i; |
250 | | } |
251 | | } |
252 | | |
253 | | if (i != MAX_CLOSEST_SERVER_NUM) { |
254 | | memcpy(&servers[i], p_server, sizeof(struct server_info)); |
255 | | num_servers++; /* Note: this also includes servers discarded due to distance */ |
256 | | } |
257 | | |
258 | | memset(p_server, 0, sizeof(struct server_info)); |
259 | | } |
260 | | } |
261 | | |
262 | | /* ***************************************** */ |
263 | | |
264 | | static int do_latency(char *p_url) |
265 | | { |
266 | | char latency_url[URL_LENGTH_MAX + sizeof(LATENCY_TXT_URL)] = {0}; |
267 | | CURL *curl; |
268 | | CURLcode res; |
269 | | long response_code; |
270 | | double v = (double) (rand() % 1000) / 100; |
271 | | char useragent[16]; |
272 | | |
273 | | curl = curl_easy_init(); |
274 | | |
275 | | snprintf(latency_url, sizeof(latency_url), "%s%s", p_url, LATENCY_TXT_URL); |
276 | | snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v); |
277 | | |
278 | | #ifdef DEBUG_SPEEDTEST |
279 | | printf("Calling %s\n", latency_url); |
280 | | #endif |
281 | | |
282 | | curl_easy_setopt(curl, CURLOPT_URL, latency_url); |
283 | | curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent); |
284 | | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); |
285 | | curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); |
286 | | curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); |
287 | | |
288 | | #ifdef DEBUG_SPEEDTEST |
289 | | curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
290 | | #endif |
291 | | |
292 | | res = curl_easy_perform(curl); |
293 | | |
294 | | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); |
295 | | curl_easy_cleanup(curl); |
296 | | |
297 | | if (res != CURLE_OK || response_code != 200) { |
298 | | #ifdef DEBUG_SPEEDTEST |
299 | | printf("curl_easy_perform() failed for %s: '%s' (%ld)\n", p_url, curl_easy_strerror(res), response_code); |
300 | | #endif |
301 | | return NOK; |
302 | | } |
303 | | return OK; |
304 | | } |
305 | | |
306 | | /* ***************************************** */ |
307 | | |
308 | | static double test_latency(char *p_url) |
309 | | { |
310 | | struct timeval s_time, e_time; |
311 | | double latency; |
312 | | |
313 | | gettimeofday(&s_time, NULL); |
314 | | if (do_latency(p_url) != OK) |
315 | | return DBL_MAX; |
316 | | gettimeofday(&e_time, NULL); |
317 | | |
318 | | latency = calc_past_time(&s_time, &e_time); |
319 | | return latency; |
320 | | } |
321 | | |
322 | | /* ***************************************** */ |
323 | | |
324 | | static void* do_download(void* data) |
325 | | { |
326 | | CURL *curl; |
327 | | CURLcode res; |
328 | | struct thread_para* p_para = (struct thread_para*)data; |
329 | | curl_off_t size = 0; |
330 | | double time = 0, time1 = 0, time2; |
331 | | char useragent[16]; |
332 | | double v = (double) (rand() % 1000) / 100; |
333 | | |
334 | | curl = curl_easy_init(); |
335 | | snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v); |
336 | | |
337 | | #ifdef DEBUG_SPEEDTEST |
338 | | printf("image url = %s\n", p_para->url); |
339 | | #endif |
340 | | curl_easy_setopt(curl, CURLOPT_URL, p_para->url); |
341 | | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); |
342 | | curl_easy_setopt(curl, CURLOPT_WRITEDATA, p_para); |
343 | | curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent); |
344 | | |
345 | | #ifdef DEBUG_SPEEDTEST |
346 | | curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
347 | | #endif |
348 | | |
349 | | res = curl_easy_perform(curl); |
350 | | |
351 | | if (res != CURLE_OK) { |
352 | | #ifdef DEBUG_SPEEDTEST |
353 | | printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); |
354 | | #endif |
355 | | } else { |
356 | | #if LIBCURL_VERSION_NUM >= 0x073700 |
357 | | curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &size); |
358 | | #else |
359 | | curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &size); |
360 | | #endif |
361 | | curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &time); |
362 | | curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &time1); |
363 | | curl_easy_getinfo(curl, CURLINFO_STARTTRANSFER_TIME, &time2); |
364 | | #ifdef DEBUG_SPEEDTEST |
365 | | printf("Completed: [Size: %lu][TotalTime: %lf][ConnectTime: %lf][TransferTime: %lf]\n", size, time, time1, time2); |
366 | | #endif |
367 | | p_para->result = size; |
368 | | p_para->finish = 1; |
369 | | } |
370 | | |
371 | | curl_easy_cleanup(curl); |
372 | | return NULL; |
373 | | } |
374 | | |
375 | | /* ***************************************** */ |
376 | | |
377 | | static void loop_threads(struct thread_para *p_thread, |
378 | | int num_thread, double *speed, int *p_num_speed) |
379 | | { |
380 | | char alive = 0; |
381 | | int num_speed = 0; |
382 | | |
383 | | |
384 | | do { |
385 | | int i; |
386 | | double sum = 0; |
387 | | alive = 0; |
388 | | |
389 | | for (i = 0;i < num_thread; i++) { |
390 | | if (p_thread[i].finish == 0) |
391 | | alive = 1; |
392 | | #if THREAD_NUM_MAX > 1 |
393 | | pthread_mutex_lock(&p_thread->lock); |
394 | | #endif |
395 | | sum += p_thread[i].now; |
396 | | p_thread[i].now = 0; |
397 | | #if THREAD_NUM_MAX > 1 |
398 | | pthread_mutex_unlock(&p_thread->lock); |
399 | | #endif |
400 | | //printf("p_thread[i].now = %lf\n", p_thread[i].now); |
401 | | } |
402 | | |
403 | | usleep(RECORED_EVERY_SEC*1000*1000); //0.2s |
404 | | if (sum == 0 && num_speed == 0) |
405 | | continue; |
406 | | |
407 | | if (num_speed < *p_num_speed) { |
408 | | |
409 | | speed[num_speed] = sum/RECORED_EVERY_SEC; //Byte |
410 | | num_speed++; |
411 | | } |
412 | | |
413 | | #if 0 |
414 | | if (++num % PRINT_RECORED_NUM == 0) { |
415 | | printf("."); |
416 | | fflush(stdout); |
417 | | } |
418 | | #endif |
419 | | } while(alive); |
420 | | // printf("\n"); |
421 | | *p_num_speed = num_speed; |
422 | | return; |
423 | | } |
424 | | |
425 | | /* ***************************************** */ |
426 | | |
427 | | static double calculate_average_speed(double *p_speed, int num_speed) |
428 | | { |
429 | | |
430 | | int i = 0; |
431 | | int start ,end; |
432 | | double sum = 0; |
433 | | for (i = 0; i < num_speed; i++) { |
434 | | |
435 | | int j, min; |
436 | | |
437 | | for ( min = i, j = i + 1; j < num_speed; j++) { |
438 | | |
439 | | if (p_speed[min] > p_speed[j]) |
440 | | min = j; |
441 | | } |
442 | | if (min != i) { |
443 | | |
444 | | double tmp; |
445 | | tmp = p_speed[i]; |
446 | | p_speed[i] = p_speed[min]; |
447 | | p_speed[min] = tmp; |
448 | | } |
449 | | |
450 | | } |
451 | | #ifdef DEBUG_SPEEDTEST |
452 | | for (i = 0; i < num_speed; i++) { |
453 | | printf("%0.2lf ", p_speed[i]*8/(1024*1024)); |
454 | | if (i%10 == 0) |
455 | | printf("\n"); |
456 | | } |
457 | | #endif |
458 | | /*The fastest 10% and slowest 20% of the slices are discarded*/ |
459 | | start = num_speed*0.2; |
460 | | end = num_speed - (int)(num_speed*0.1); |
461 | | //end = num_speed; |
462 | | for (i = start; i < end; i++) { |
463 | | |
464 | | sum += p_speed[i]; |
465 | | } |
466 | | //printf("speed = %0.2lf\n", (sum*8/(end - start))/(1024*1024)); |
467 | | |
468 | | if(end == start) end++; |
469 | | |
470 | | return sum/(end - start); |
471 | | } |
472 | | |
473 | | /* ***************************************** */ |
474 | | |
475 | | static int init_instant_speed(double **p_speed, int *p_speed_num) |
476 | | { |
477 | | *p_speed_num = SPEEDTEST_TIME_MAX/RECORED_EVERY_SEC + 1; |
478 | | *p_speed = (double*)malloc((*p_speed_num)*sizeof(double)); |
479 | | |
480 | | if (*p_speed == NULL) { |
481 | | // fprintf(stderr, "malloc failed\n"); |
482 | | return -1; |
483 | | } |
484 | | memset(*p_speed, 0, (*p_speed_num)*sizeof(double)); |
485 | | return 0; |
486 | | } |
487 | | |
488 | | /* ***************************************** */ |
489 | | |
490 | | static double test_download(char *p_url, int num_thread, int dsize, char init) |
491 | | { |
492 | | struct timeval s_time; |
493 | | int time, i; |
494 | | struct thread_para paras[THREAD_NUM_MAX]; |
495 | | double sum = 0; |
496 | | double speed = 0; |
497 | | double *instant_speed = NULL; |
498 | | int speed_num = 0; |
499 | | |
500 | | gettimeofday(&s_time, NULL); |
501 | | init_instant_speed(&instant_speed, &speed_num); |
502 | | |
503 | | for (i = 0; i < num_thread; i++) { |
504 | | //int error; |
505 | | |
506 | | memset(¶s[i], 0, sizeof(struct thread_para)); |
507 | | snprintf(paras[i].url, sizeof(paras[i].url), "%s/speedtest/random%dx%d.jpg", p_url, dsize, dsize); |
508 | | paras[i].result = 0; |
509 | | |
510 | | //error = |
511 | | #if THREAD_NUM_MAX > 1 |
512 | | pthread_mutex_init(¶s[i].lock, NULL); |
513 | | #endif |
514 | | |
515 | | pthread_create(¶s[i].tid, NULL, do_download, (void*)¶s[i]); |
516 | | |
517 | | // if ( error != 0) printf("Can't Run thread num %d, error %d\n", i, error); |
518 | | } |
519 | | |
520 | | if (init != 0) { |
521 | | loop_threads(paras, num_thread, instant_speed, &speed_num); |
522 | | } |
523 | | |
524 | | for (i = 0;i < num_thread; i++) { |
525 | | pthread_join(paras[i].tid, NULL); |
526 | | sum += paras[i].result; |
527 | | } |
528 | | |
529 | | if (init != 0) { |
530 | | speed = calculate_average_speed(instant_speed, speed_num); |
531 | | |
532 | | #ifdef DEBUG_SPEEDTEST |
533 | | printf("speed = %0.2fMbps (%0.1fBps)\n", (speed*8)/(1024*1024), speed); |
534 | | #endif |
535 | | } else { |
536 | | struct timeval e_time; |
537 | | |
538 | | gettimeofday(&e_time, NULL); |
539 | | time = calc_past_time(&s_time, &e_time); |
540 | | speed = (sum*1000)/time; |
541 | | |
542 | | #ifdef DEBUG_SPEEDTEST |
543 | | printf("speed = %0.2fMbps (sum = %f) (msec = %d)\n", (speed*8)/(1024*1024), sum, time); |
544 | | #endif |
545 | | } |
546 | | free(instant_speed); |
547 | | if (!(speed >= 0)) speed = 0; |
548 | | return speed; |
549 | | } |
550 | | |
551 | | /* ***************************************** */ |
552 | | |
553 | | #ifdef FULL_REPORT |
554 | | static size_t read_data(void* ptr, size_t size, size_t nmemb, void *userp) |
555 | | { |
556 | | struct thread_para* para = (struct thread_para*)userp; |
557 | | int length; |
558 | | char data[16284] = {0}; |
559 | | |
560 | | if (size * nmemb < 1 && para->chunk_size) |
561 | | return 0; |
562 | | int i; |
563 | | for (i = 0; i < 16284; i++) { |
564 | | |
565 | | data[i] = i%26 + 'a'; |
566 | | } |
567 | | |
568 | | if ((size_t) para->chunk_size > size * nmemb) { |
569 | | |
570 | | length = size * nmemb < 16284 ? size*nmemb : 16284; |
571 | | } |
572 | | else |
573 | | length = para->chunk_size < 16284 ? para->chunk_size : 16284; |
574 | | memcpy(ptr, data, length); |
575 | | |
576 | | para->chunk_size -= length; |
577 | | |
578 | | #if THREAD_NUM_MAX > 1 |
579 | | pthread_mutex_lock(¶->lock); |
580 | | #endif |
581 | | para->now += length; |
582 | | #if THREAD_NUM_MAX > 1 |
583 | | pthread_mutex_unlock(¶->lock); |
584 | | #endif |
585 | | //printf("length = %d\n", length); |
586 | | return length; |
587 | | } |
588 | | |
589 | | /* ***************************************** */ |
590 | | |
591 | | static void* do_upload(void *p) { |
592 | | struct thread_para* para = (struct thread_para*)p; |
593 | | CURL *curl; |
594 | | CURLcode res; |
595 | | long size = para->upload_size; |
596 | | int loop = 1; |
597 | | char useragent[16]; |
598 | | double v = (double) (rand() % 1000) / 100; |
599 | | |
600 | | if (size > UPLOAD_CHRUNK_SIZE_MAX) |
601 | | loop = (size / UPLOAD_CHRUNK_SIZE_MAX) + 1; |
602 | | |
603 | | curl = curl_easy_init(); |
604 | | snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v); |
605 | | |
606 | | curl_easy_setopt(curl, CURLOPT_URL, para->url); |
607 | | curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_data); |
608 | | curl_easy_setopt(curl, CURLOPT_READDATA, para); |
609 | | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); |
610 | | curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); |
611 | | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); |
612 | | curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent); |
613 | | |
614 | | #ifdef DEBUG_SPEEDTEST |
615 | | curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
616 | | #endif |
617 | | |
618 | | while (loop) { |
619 | | double size_upload; |
620 | | |
621 | | para->chunk_size = size - para->result> UPLOAD_CHRUNK_SIZE_MAX ? |
622 | | UPLOAD_CHRUNK_SIZE_MAX : size - para->result; |
623 | | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE , (curl_off_t)para->chunk_size); |
624 | | |
625 | | res = curl_easy_perform(curl); |
626 | | if (res != CURLE_OK) { |
627 | | // fprintf(stderr, "Error: curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); |
628 | | curl_easy_cleanup(curl); |
629 | | para->finish = 1; |
630 | | return(NULL); |
631 | | } |
632 | | |
633 | | #if LIBCURL_VERSION_NUM >= 0x073700 |
634 | | curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD_T, &size_upload); |
635 | | #else |
636 | | curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD, &size_upload); |
637 | | #endif |
638 | | |
639 | | para->result += size_upload; |
640 | | loop--; |
641 | | } |
642 | | |
643 | | curl_easy_cleanup(curl); |
644 | | para->finish = 1; |
645 | | #ifdef DEBUG_SPEEDTEST |
646 | | printf("size upload = %lu\n", para->result); |
647 | | #endif |
648 | | |
649 | | return(NULL); |
650 | | } |
651 | | |
652 | | /* ***************************************** */ |
653 | | |
654 | | static double test_upload(char *p_url, int num_thread, long ul_size, |
655 | | char *p_ext, char init) |
656 | | { |
657 | | struct timeval s_time; |
658 | | int i; |
659 | | struct thread_para paras[THREAD_NUM_MAX]; |
660 | | double sum = 0, speed = 0; |
661 | | double *instant_speed = NULL; |
662 | | int speed_num = 0; |
663 | | |
664 | | gettimeofday(&s_time, NULL); |
665 | | |
666 | | init_instant_speed(&instant_speed, &speed_num); |
667 | | |
668 | | for ( i = 0; i < num_thread; i++) { |
669 | | memset(¶s[i], 0, sizeof(struct thread_para)); |
670 | | snprintf(paras[i].url, sizeof(paras[i].url), "%s/speedtest/upload.%s", p_url, p_ext); |
671 | | paras[i].result = 0; |
672 | | paras[i].finish = 0; |
673 | | paras[i].upload_size = ul_size/num_thread; |
674 | | #ifdef DEBUG_SPEEDTEST |
675 | | printf("[thread %u] Upload size: %lu\n", i, paras[i].upload_size); |
676 | | #endif |
677 | | |
678 | | //printf("szeleft = %ld\n", paras[i].upload_size); |
679 | | //int error = |
680 | | pthread_create(¶s[i].tid, NULL, do_upload, (void*)¶s[i]); |
681 | | // if ( error != 0) printf("Can't Run thread num %d, error %d\n", i, error); |
682 | | } |
683 | | |
684 | | if (init != 0) { |
685 | | loop_threads(paras, num_thread, instant_speed, &speed_num); |
686 | | } |
687 | | |
688 | | for (i = 0;i < num_thread; i++) { |
689 | | pthread_join(paras[i].tid, NULL); |
690 | | sum += paras[i].result; |
691 | | } |
692 | | |
693 | | #ifdef DEBUG_SPEEDTEST |
694 | | printf("Total size upload = %lf\n", sum); |
695 | | #endif |
696 | | |
697 | | if (init != 0) { |
698 | | speed = calculate_average_speed(instant_speed, speed_num); |
699 | | |
700 | | #ifdef DEBUG_SPEEDTEST |
701 | | printf("speed = %0.2fMbps\n", (speed*8)/(1024*1024)); |
702 | | #endif |
703 | | } else { |
704 | | struct timeval e_time; |
705 | | int time; |
706 | | |
707 | | gettimeofday(&e_time, NULL); |
708 | | time = calc_past_time(&s_time, &e_time); |
709 | | speed = (sum*1000)/time; //bytes per second |
710 | | |
711 | | #ifdef DEBUG_SPEEDTEST |
712 | | printf("speed = %0.2fMbps (msec = %d)\n", (speed*8)/(1024*1024), time); |
713 | | #endif |
714 | | } |
715 | | free(instant_speed); |
716 | | if (!(speed >= 0)) speed = 0; |
717 | | return speed; |
718 | | } |
719 | | #endif |
720 | | |
721 | | /* ***************************************** */ |
722 | | |
723 | | static int get_download_filename(double speed, int num_thread) |
724 | | { |
725 | | int i; |
726 | | int filelist[] = {350, 500, 750, 1000, 1500, 2000, 3000, 3500, 4000}; |
727 | | int num_file = ARRAY_SIZE(filelist); |
728 | | |
729 | | if(speed == 0) speed = 1; |
730 | | |
731 | | for (i = 1; i < num_file; i++) { |
732 | | long long int time; |
733 | | float times = (float)filelist[i]/350; |
734 | | //printf("time %f speed %lf\n", times, speed); |
735 | | times = (times*times); |
736 | | time = (long long int) ((long long int) num_thread*times*FILE_350_SIZE)/speed; |
737 | | //printf("%d %lld %f\n", filelist[i], time, times); |
738 | | if (time > SPEEDTEST_TIME_MAX) |
739 | | break; |
740 | | } |
741 | | |
742 | | if (i < num_file) |
743 | | return filelist[i - 1]; |
744 | | |
745 | | return filelist[num_file - 1]; |
746 | | } |
747 | | |
748 | | /* ***************************************** */ |
749 | | |
750 | | static int get_upload_extension(char *server, char *p_ext) |
751 | | { |
752 | | CURL *curl; |
753 | | CURLcode res; |
754 | | struct web_buffer web; |
755 | | char* p = NULL; |
756 | | int rv = NOK; |
757 | | |
758 | | memset(&web, 0, sizeof(web)); |
759 | | |
760 | | curl = curl_easy_init(); |
761 | | |
762 | | curl_easy_setopt(curl, CURLOPT_URL, server); |
763 | | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_web_buf); |
764 | | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web); |
765 | | //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
766 | | res = curl_easy_perform(curl); |
767 | | curl_easy_cleanup(curl); |
768 | | if (res != CURLE_OK) { |
769 | | // printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); |
770 | | goto cleanup; |
771 | | } |
772 | | p = strstr(web.data, UPLOAD_EXTENSION_TAG); |
773 | | if (p == NULL || |
774 | | sscanf(p + strlen(UPLOAD_EXTENSION_TAG), "%*[^a-zA-Z]%[a-zA-Z]", p_ext) <= 0) { |
775 | | // fprintf(stderr, "Upload extension not found\n"); |
776 | | goto cleanup; |
777 | | } |
778 | | |
779 | | rv = OK; |
780 | | |
781 | | cleanup: |
782 | | if(web.data) free(web.data); |
783 | | |
784 | | // printf("Upload extension: %s\n", p_ext); |
785 | | return rv; |
786 | | } |
787 | | |
788 | | /* ***************************************** */ |
789 | | |
790 | | static int get_client_info(struct client_info *p_client) |
791 | | { |
792 | | CURL *curl; |
793 | | CURLcode res; |
794 | | XML_Parser xml = XML_ParserCreate(NULL); |
795 | | struct web_buffer web; |
796 | | int rv = NOK; |
797 | | |
798 | | memset(&web, 0, sizeof(web)); |
799 | | |
800 | | curl = curl_easy_init(); |
801 | | curl_easy_setopt(curl, CURLOPT_URL, "https://www.speedtest.net/speedtest-config.php"); |
802 | | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_web_buf); |
803 | | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web); |
804 | | curl_easy_setopt(curl, CURLOPT_USERAGENT, "haibbo speedtest-cli"); |
805 | | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); |
806 | | |
807 | | #ifdef DEBUG_SPEEDTEST |
808 | | curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
809 | | #endif |
810 | | |
811 | | res = curl_easy_perform(curl); |
812 | | curl_easy_cleanup(curl); |
813 | | |
814 | | if (res != CURLE_OK) { |
815 | | #ifdef DEBUG_SPEEDTEST |
816 | | printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); |
817 | | #endif |
818 | | goto cleanup; |
819 | | } |
820 | | XML_SetUserData(xml, p_client); |
821 | | XML_SetElementHandler(xml, start_element, end_element); |
822 | | if (XML_Parse(xml, web.data, web.size , 1) == XML_STATUS_ERROR) { |
823 | | #ifdef DEBUG_SPEEDTEST |
824 | | fprintf(stderr, "Parse client failed\n"); |
825 | | #endif |
826 | | // exit(-1); |
827 | | goto cleanup; |
828 | | } |
829 | | |
830 | | rv = OK; |
831 | | |
832 | | cleanup: |
833 | | if(web.data) free(web.data); |
834 | | XML_ParserFree(xml); |
835 | | |
836 | | return rv; |
837 | | } |
838 | | |
839 | | /* ***************************************** */ |
840 | | |
841 | | static int get_closest_server() |
842 | | { |
843 | | CURL *curl; |
844 | | CURLcode res; |
845 | | XML_Parser xml = XML_ParserCreate(NULL); |
846 | | struct web_buffer web; |
847 | | struct server_info server; |
848 | | int rv = NOK; |
849 | | char useragent[16]; |
850 | | const char *url = "https://www.speedtest.net/speedtest-servers.php"; |
851 | | double v = (double) (rand() % 1000) / 100; |
852 | | |
853 | | memset(&web, 0, sizeof(web)); |
854 | | snprintf(useragent, sizeof(useragent), "curl/%.02f.0", v); |
855 | | |
856 | | curl = curl_easy_init(); |
857 | | |
858 | | #ifdef DEBUG_SPEEDTEST |
859 | | printf("Retrieving servers list %s\n", url); |
860 | | #endif |
861 | | |
862 | | curl_easy_setopt(curl, CURLOPT_URL, url); |
863 | | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); |
864 | | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_web_buf); |
865 | | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &web); |
866 | | curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent); |
867 | | |
868 | | #ifdef DEBUG_SPEEDTEST |
869 | | curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); |
870 | | #endif |
871 | | |
872 | | res = curl_easy_perform(curl); |
873 | | curl_easy_cleanup(curl); |
874 | | |
875 | | if (res != CURLE_OK) { |
876 | | #ifdef DEBUG_SPEEDTEST |
877 | | printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); |
878 | | #endif |
879 | | goto cleanup; |
880 | | } |
881 | | XML_SetUserData(xml, &server); |
882 | | depth = 0; |
883 | | XML_SetElementHandler(xml, start_element, end_element); |
884 | | if (XML_Parse(xml, web.data, web.size , 1) == XML_STATUS_ERROR) { |
885 | | #ifdef DEBUG_SPEEDTEST |
886 | | fprintf(stderr, "Parse servers list failed\n"); |
887 | | #endif |
888 | | // exit(-1); |
889 | | goto cleanup; |
890 | | } |
891 | | |
892 | | rv = OK; |
893 | | |
894 | | cleanup: |
895 | | if(web.data) free(web.data); |
896 | | XML_ParserFree(xml); |
897 | | |
898 | | return rv; |
899 | | } |
900 | | |
901 | | /* ***************************************** */ |
902 | | |
903 | | static int get_best_server(int *p_index) |
904 | | { |
905 | | int i; |
906 | | double minimum = DBL_MAX; |
907 | | char server[URL_LENGTH_MAX] = {0}; |
908 | | |
909 | | for (i = 0; i < MAX_CLOSEST_SERVER_NUM; i++) { |
910 | | double latency; |
911 | | char *p; |
912 | | size_t len; |
913 | | |
914 | | if (minimum != DBL_MAX && i >= MIN_SERVERS_TO_CHECK) |
915 | | break; /* MIN_SERVERS_TO_CHECK evaluated and at least one found */ |
916 | | |
917 | | if((strlen(servers[i].url) == 0)) |
918 | | continue; |
919 | | |
920 | | /* Read base URL (e.g. http://speedtest1.uniconnect.it:8080) by removing /speedtest/.. */ |
921 | | p = strstr(servers[i].url, "/speedtest/"); |
922 | | server[0] = '\0'; |
923 | | if(p == NULL) continue; |
924 | | len = (size_t)(p - servers[i].url); |
925 | | if(len == 0 || len >= URL_LENGTH_MAX) continue; |
926 | | strncpy(server, servers[i].url, len); |
927 | | server[len] = '\0'; |
928 | | |
929 | | if(server[0] == '\0') |
930 | | continue; |
931 | | |
932 | | latency = test_latency(server); |
933 | | |
934 | | #ifdef DEBUG_SPEEDTEST |
935 | | printf("Measured latency for %s is %0.3fms\n", server, latency); |
936 | | #endif |
937 | | |
938 | | if (minimum > latency ) { |
939 | | minimum = latency; |
940 | | *p_index = i; |
941 | | #ifdef DEBUG_SPEEDTEST |
942 | | printf("Best server set to %u (%s)\n", i, server); |
943 | | #endif |
944 | | } |
945 | | } /* for */ |
946 | | |
947 | | if (minimum == DBL_MAX) |
948 | | return NOK; |
949 | | |
950 | | return OK; |
951 | | } |
952 | | |
953 | | /* ***************************************** */ |
954 | | |
955 | | json_object* speedtest() { |
956 | | int num_thread; |
957 | | char server_url[URL_LENGTH_MAX], ext[UPLOAD_EXT_LENGTH_MAX]; |
958 | | #ifdef FULL_REPORT |
959 | | double latency, upload_speed; |
960 | | #endif |
961 | | double speed, download_speed; |
962 | | int dsize, sindex; |
963 | | json_object *rc = json_object_new_object(); |
964 | | char *p; |
965 | | |
966 | | if(rc == NULL) { |
967 | | #ifdef INFO_SPEEDTEST |
968 | | printf("speedtest: failure allocating memory\n"); |
969 | | #endif |
970 | | return(rc); |
971 | | } |
972 | | |
973 | | //Initialization |
974 | | |
975 | | sindex = -1; |
976 | | num_thread = 1; |
977 | | dsize = INIT_DOWNLOAD_FILE_RESOLUTION; |
978 | | memset(server_url, 0, sizeof(server_url)); |
979 | | memset(ext, 0, sizeof(ext)); |
980 | | |
981 | | #ifdef DEBUG_SPEEDTEST |
982 | | printf("Retrieving speedtest.net configuration...\n"); |
983 | | #endif |
984 | | get_client_info(&client); |
985 | | |
986 | | #ifdef DEBUG_SPEEDTEST |
987 | | printf("Retrieving speedtest.net server list...\n"); |
988 | | #endif |
989 | | get_closest_server(); |
990 | | |
991 | | #ifdef DEBUG_SPEEDTEST |
992 | | printf("Testing from %s (%s)...\n", client.isp, client.ip); |
993 | | #endif |
994 | | |
995 | | json_object_object_add(rc, "client.isp", json_object_new_string(client.isp)); |
996 | | json_object_object_add(rc, "client.ip", json_object_new_string(client.ip)); |
997 | | |
998 | | #ifdef DEBUG_SPEEDTEST |
999 | | printf("Selecting best server based on ping...\n"); |
1000 | | #endif |
1001 | | |
1002 | | if (get_best_server(&sindex) != OK) { |
1003 | | #ifdef INFO_SPEEDTEST |
1004 | | printf("speedtest: failure selecting the best server out of %u\n", num_servers); |
1005 | | #endif |
1006 | | return(rc); |
1007 | | } |
1008 | | |
1009 | | /* Read base URL and upload extension */ |
1010 | | p = strstr(servers[sindex].url, "/speedtest/upload."); |
1011 | | if (p != NULL) { |
1012 | | size_t len = (size_t)(p - servers[sindex].url); |
1013 | | if(len > 0 && len < URL_LENGTH_MAX) { |
1014 | | strncpy(server_url, servers[sindex].url, len); |
1015 | | server_url[len] = '\0'; |
1016 | | sscanf(p, "/speedtest/upload.%4s", ext); |
1017 | | } |
1018 | | } |
1019 | | |
1020 | | #ifdef DEBUG_SPEEDTEST |
1021 | | printf("Best server: %s (%0.2fKM) [index: %u][%s]\n", |
1022 | | server_url, servers[sindex].distance, sindex, servers[sindex].url); |
1023 | | #endif |
1024 | | json_object_object_add(rc, "server.url", json_object_new_string(server_url)); |
1025 | | json_object_object_add(rc, "server.distance", json_object_new_double(servers[sindex].distance)); |
1026 | | |
1027 | | /* Must initialize libcurl before any threads are started */ |
1028 | | curl_global_init(CURL_GLOBAL_ALL); |
1029 | | |
1030 | | #ifdef FULL_REPORT |
1031 | | latency = test_latency(server_url); |
1032 | | if (latency == DBL_MAX) { |
1033 | | #ifdef INFO_SPEEDTEST |
1034 | | printf("speedtest: failure testing latency\n"); |
1035 | | #endif |
1036 | | return(rc); |
1037 | | } |
1038 | | |
1039 | | #ifdef DEBUG_SPEEDTEST |
1040 | | printf("Server latency is %0.0fms\n", latency); |
1041 | | #endif |
1042 | | json_object_object_add(rc, "server.latency", json_object_new_double(latency)); |
1043 | | #endif |
1044 | | |
1045 | | speed = test_download(server_url, num_thread, dsize, 0); |
1046 | | |
1047 | | dsize = get_download_filename(speed, num_thread); |
1048 | | #ifdef DEBUG_SPEEDTEST |
1049 | | fprintf(stderr, "Testing download speed\n"); |
1050 | | #endif |
1051 | | download_speed = test_download(server_url, num_thread, dsize, 1); |
1052 | | |
1053 | | #ifdef DEBUG_SPEEDTEST |
1054 | | printf("Download speed: %0.2fMbps\n", ((download_speed*8)/(1024*1024))); |
1055 | | #endif |
1056 | | json_object_object_add(rc, "download.speed", json_object_new_double((download_speed*8)/(1024*1024))); |
1057 | | |
1058 | | if (ext[0] == 0 && get_upload_extension(server_url, ext) != OK) { |
1059 | | #ifdef INFO_SPEEDTEST |
1060 | | printf("speedtest: failure in upload extension\n"); |
1061 | | #endif |
1062 | | return(rc); |
1063 | | } |
1064 | | |
1065 | | #ifdef FULL_REPORT |
1066 | | if(speed == 0) speed = download_speed; |
1067 | | speed = test_upload(server_url, num_thread, speed, ext, 0); |
1068 | | |
1069 | | #ifdef DEBUG_SPEEDTEST |
1070 | | fprintf(stderr, "Testing upload speed\n"); |
1071 | | #endif |
1072 | | if(speed == 0) speed = download_speed; |
1073 | | upload_speed = test_upload(server_url, num_thread, speed*SPEEDTEST_TIME_MAX, ext, 1); |
1074 | | |
1075 | | #ifdef DEBUG_SPEEDTEST |
1076 | | printf("Upload speed: %0.2fMbps\n", ((upload_speed*8)/(1024*1024))); |
1077 | | #endif |
1078 | | json_object_object_add(rc, "upload.speed", json_object_new_double(((upload_speed*8)/(1024*1024)))); |
1079 | | #endif |
1080 | | |
1081 | | return(rc); |
1082 | | } |
1083 | | |
1084 | | #else |
1085 | 0 | json_object* speedtest() { return(NULL); } |
1086 | | #endif /* HAVE_EXPAT */ |
1087 | | |
1088 | | #ifdef TEST_SPEEDTEST |
1089 | | int main(int argc, char *argv[]) { |
1090 | | json_object *out; |
1091 | | |
1092 | | out = speedtest(); |
1093 | | |
1094 | | if (out == NULL) { |
1095 | | printf("failure\n"); |
1096 | | return 1; |
1097 | | } |
1098 | | |
1099 | | printf("%s\n", json_object_to_json_string(out)); |
1100 | | |
1101 | | json_object_put(out); |
1102 | | |
1103 | | return 0; |
1104 | | } |
1105 | | #endif |