Last active
February 20, 2020 22:37
-
-
Save jay/3db44871ccbe7cc089a25381135e122f to your computer and use it in GitHub Desktop.
Use libcurl to show the current speed while transferring a file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Use libcurl to show the current speed while transferring a file. | |
Usage: CurrentSpeed | |
This example downloads a 200MB file from a speedtest server and shows the | |
current speed during the download. The current speed is updated approximately | |
once every second based on the last 5 seconds. The file is not saved to disk. | |
The bulk of the code in this example came from libcurl's lib/progress.c. | |
https://github.com/curl/curl/blob/curl-7_68_0/lib/progress.c | |
curl-library mailing list thread: | |
'use libcurl to obtain the real-time speed of git clone.' | |
https://curl.haxx.se/mail/lib-2020-02/0041.html | |
Copyright (C) 2020 Jay Satiro <[email protected]> | |
http://curl.haxx.se/docs/copyright.html | |
https://gist.github.com/jay/3db44871ccbe7cc089a25381135e122f | |
*/ | |
#ifdef _WIN32 | |
#define _CRT_NONSTDC_NO_DEPRECATE | |
#define _CRT_SECURE_NO_WARNINGS | |
#include <windows.h> | |
#endif | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <curl/curl.h> | |
#include <curl/mprintf.h> | |
/* Use curl's msnprintf since Windows (and other OSes) may not have snprintf. | |
Windows has _snprintf but it behaves differently. */ | |
#undef msnprintf | |
#define msnprintf curl_msnprintf | |
/* This is a best guess to determine if curl_off_t is larger than 32-bits. | |
CURL_SIZEOF_CURL_OFF_T is the most accurate but the symbol was deprecated. | |
Compilers may do preprocessing math with the largest integer type, however | |
curl_off_t *should* be larger than 32-bits when such a type is available. */ | |
#if (defined(CURL_SIZEOF_CURL_OFF_T) && (CURL_SIZEOF_CURL_OFF_T > 4)) || \ | |
(!defined(CURL_SIZEOF_CURL_OFF_T) && \ | |
(CURL_OFF_TU_C(0xffffffff) < \ | |
CURL_OFF_TU_C(0xffffffff) + CURL_OFF_TU_C(1))) | |
#define LARGE_CURL_OFF_T | |
#endif | |
/* Rate unit selector. | |
* | |
* The point of this function would be to return a string of the input data, | |
* but never longer than 5 columns (+ one zero byte). | |
* Add suffix k, M, G when suitable... | |
* | |
* This function was copied from libcurl's lib/progress.c (7.68.0). | |
*/ | |
static char *max5data(curl_off_t bytes, char *max5) | |
{ | |
#define ONE_KILOBYTE CURL_OFF_T_C(1024) | |
#define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) | |
#define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) | |
#define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) | |
#define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) | |
memset(max5, 0, 6); | |
if(bytes < CURL_OFF_T_C(100000)) | |
msnprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); | |
else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) | |
msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); | |
else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) | |
/* 'XX.XM' is good as long as we're less than 100 megs */ | |
msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" | |
CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE, | |
(bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); | |
#ifdef LARGE_CURL_OFF_T | |
else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) | |
/* 'XXXXM' is good until we're at 10000MB or above */ | |
msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); | |
else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) | |
/* 10000 MB - 100 GB, we show it as XX.XG */ | |
msnprintf(max5, 6, "%2" CURL_FORMAT_CURL_OFF_T ".%0" | |
CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE, | |
(bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); | |
else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) | |
/* up to 10000GB, display without decimal: XXXXG */ | |
msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE); | |
else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) | |
/* up to 10000TB, display without decimal: XXXXT */ | |
msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE); | |
else | |
/* up to 10000PB, display without decimal: XXXXP */ | |
msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "P", bytes/ONE_PETABYTE); | |
/* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number | |
can hold, but our data type is signed so 8192PB will be the maximum. */ | |
#else | |
else | |
msnprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); | |
#endif | |
max5[5] = 0; | |
return max5; | |
} | |
struct progress { | |
CURL *curl; | |
curl_off_t lastshow; | |
curl_off_t current_speed; | |
#define CURR_TIME (5 + 1) /* 6 entries for 5 seconds */ | |
curl_off_t speeder[CURR_TIME]; | |
curl_off_t speeder_time[CURR_TIME]; | |
int speeder_c; | |
}; | |
int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, | |
curl_off_t ultotal, curl_off_t ulnow) | |
{ | |
struct progress *progress = (struct progress *)clientp; | |
double timespent_dbl; /* total time in seconds and fraction */ | |
curl_off_t timespent; /* total time in seconds */ | |
curl_off_t timespent_ms; /* total time in milliseconds */ | |
(void)dltotal; | |
(void)ultotal; | |
/* Get the total time in seconds, since the start of the transfer. | |
CURLINFO_TOTAL_TIME returns a double that is seconds and fraction. | |
CURLINFO_TOTAL_TIME_T returns an integer type that is microseconds. | |
The latter isn't in old versions of curl so it's not used here. */ | |
if(curl_easy_getinfo(progress->curl, CURLINFO_TOTAL_TIME, ×pent_dbl)) | |
return 1; | |
timespent = (curl_off_t)timespent_dbl; | |
timespent_ms = (curl_off_t)(timespent_dbl * 1000); | |
/* Calculate the current transfer speed at most once a second, and show it. | |
* | |
* This was copied from libcurl's progress_calc() in lib/progress.c (7.68.0). | |
* Some minor modifications were made for type, timestamp and rate output. | |
*/ | |
if(progress->lastshow != timespent) { | |
int countindex; /* amount of seconds stored in the speeder array */ | |
int nowindex = progress->speeder_c % CURR_TIME; | |
char max5[6]; | |
progress->lastshow = timespent; | |
/* Let's do the "current speed" thing, with the dl + ul speeds | |
combined. Store the speed at entry 'nowindex'. */ | |
progress->speeder[nowindex] = dlnow + ulnow; | |
/* remember the exact time for this moment */ | |
progress->speeder_time[nowindex] = timespent_ms; | |
/* advance our speeder_c counter, which is increased every time we get | |
here and we expect it to never wrap as 2^32 is a lot of seconds! */ | |
progress->speeder_c++; | |
/* figure out how many index entries of data we have stored in our speeder | |
array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of | |
transfer. Imagine, after one second we have filled in two entries, | |
after two seconds we've filled in three entries etc. */ | |
countindex = ((progress->speeder_c >= CURR_TIME) ? | |
CURR_TIME : progress->speeder_c) - 1; | |
/* first of all, we don't do this if there's no counted seconds yet */ | |
if(countindex) { | |
int checkindex; | |
curl_off_t span_ms; | |
/* Get the index position to compare with the 'nowindex' position. | |
Get the oldest entry possible. While we have less than CURR_TIME | |
entries, the first entry will remain the oldest. */ | |
checkindex = ((progress->speeder_c >= CURR_TIME) ? | |
progress->speeder_c % CURR_TIME : 0); | |
/* Figure out the exact time for the time span */ | |
span_ms = timespent_ms - progress->speeder_time[checkindex]; | |
if(0 == span_ms) | |
span_ms = 1; /* at least one millisecond MUST have passed */ | |
/* Calculate the average speed the last 'span_ms' milliseconds */ | |
{ | |
curl_off_t amount = progress->speeder[nowindex] - | |
progress->speeder[checkindex]; | |
if(amount > CURL_OFF_T_C(4294967) /* 0xffffffff/1000 */) | |
/* the 'amount' value is bigger than would fit in 32 bits if | |
multiplied with 1000, so we use the double math for this */ | |
progress->current_speed = (curl_off_t) | |
((double)amount/((double)span_ms/1000.0)); | |
else | |
/* the 'amount' value is small enough to fit within 32 bits even | |
when multiplied with 1000 */ | |
progress->current_speed = amount*CURL_OFF_T_C(1000)/span_ms; | |
} | |
} | |
else { | |
/* the first second we use the average */ | |
double ulspeed, dlspeed; | |
if(curl_easy_getinfo(progress->curl, CURLINFO_SPEED_UPLOAD, &ulspeed) || | |
curl_easy_getinfo(progress->curl, CURLINFO_SPEED_DOWNLOAD, &dlspeed)) | |
return 1; | |
progress->current_speed = (curl_off_t)(ulspeed + dlspeed); | |
} | |
fprintf(stderr, "\rCurrent speed: %s", | |
max5data(progress->current_speed, max5)); | |
} | |
return 0; | |
} | |
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) | |
{ | |
(void)ptr; | |
(void)userdata; | |
return (size * nmemb); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
CURLcode result = CURLE_OK; | |
struct progress progress; | |
CURL *curl; | |
(void)argv; | |
(void)argc; | |
result = curl_global_init(CURL_GLOBAL_ALL); | |
if(result) { | |
fprintf(stderr, "Error: libcurl: (%d) %s\n", result, | |
curl_easy_strerror(result)); | |
return EXIT_FAILURE; | |
} | |
if(atexit(curl_global_cleanup)) { | |
fprintf(stderr, "Error: atexit failed to register curl_global_cleanup.\n"); | |
return EXIT_FAILURE; | |
} | |
curl = curl_easy_init(); | |
if(!curl) { | |
fprintf(stderr, "Error: libcurl: curl_easy_init failed.\n"); | |
return EXIT_FAILURE; | |
} | |
curl_easy_setopt(curl, CURLOPT_CAINFO, "curl-ca-bundle.crt"); | |
curl_easy_setopt(curl, CURLOPT_URL, | |
"https://ny2.testmy.net/b/download?special=1&tt=1&st=st&nfw=1&s=200MB"); | |
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); | |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); | |
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); | |
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &progress); | |
memset(&progress, 0, sizeof(progress)); | |
progress.curl = curl; | |
result = curl_easy_perform(curl); | |
if(progress.lastshow) | |
printf("\n"); | |
if(result) { | |
fprintf(stderr, "Error: libcurl: (%d) %s\n", result, | |
curl_easy_strerror(result)); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment