Coverage Report

Created: 2026-07-25 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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(&paras[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(&paras[i].lock, NULL);
513
#endif
514
515
    pthread_create(&paras[i].tid, NULL, do_download, (void*)&paras[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(&para->lock);
580
#endif
581
  para->now += length;
582
#if THREAD_NUM_MAX > 1
583
  pthread_mutex_unlock(&para->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(&paras[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(&paras[i].tid, NULL, do_upload, (void*)&paras[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