Skip to content

Instantly share code, notes, and snippets.

@jay
Last active February 20, 2020 22:37
Show Gist options
  • Save jay/3db44871ccbe7cc089a25381135e122f to your computer and use it in GitHub Desktop.
Save jay/3db44871ccbe7cc089a25381135e122f to your computer and use it in GitHub Desktop.
Use libcurl to show the current speed while transferring a file.
/* 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, &timespent_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