1 /* -*- mode: c++ -*- */ 2 3 #ifndef R_PROGRESS_H 4 #define R_PROGRESS_H 5 6 #include <unistd.h> 7 #include <sys/time.h> 8 9 #ifdef Win32 10 # include <io.h> 11 #endif 12 13 #include <string> 14 #include <sstream> 15 #include <iomanip> 16 #include <cmath> 17 #include <cstring> 18 #include <cstdlib> 19 #include <cerrno> 20 21 #include <R.h> 22 #include <Rinternals.h> 23 #include <R_ext/Print.h> 24 25 // For gettimeofday implementation on windows 26 #ifdef Win32 27 28 // MSVC defines this in winsock2.h!? 29 typedef struct timeval { 30 long tv_sec; 31 long tv_usec; 32 } timeval; 33 int gettimeofday(struct timeval * tp, struct timezone * tzp); 34 35 #endif 36 37 namespace RProgress { 38 39 class RProgress { 40 41 public: 42 RProgress(std::string format,double total,int width,std::string cursor_char,std::string complete_char,std::string incomplete_char,bool clear,double show_after)43 RProgress(std::string format, 44 double total, 45 int width, 46 std::string cursor_char, 47 std::string complete_char, 48 std::string incomplete_char, 49 bool clear, 50 double show_after) : 51 52 first(true), format(format), total(total), current(0), count(0), 53 width(width), cursor_char(cursor_char), complete_char(complete_char), 54 incomplete_char(incomplete_char), clear(clear), show_after(show_after), 55 last_draw(""), start(0), toupdate(false), complete(false), reverse(false) { 56 57 supported = is_supported(); 58 use_stderr = default_stderr(); 59 } 60 61 RProgress(std::string format = "[:bar] :percent", 62 double total = 100, 63 int width = Rf_GetOptionWidth() - 2, 64 char complete_char = '=', 65 char incomplete_char = '-', 66 bool clear = true, 67 double show_after = 0.2) : 68 first(true)69 first(true), format(format), total(total), current(0), count(0), 70 width(width), cursor_char(1, complete_char), complete_char(1, complete_char), 71 incomplete_char(1, incomplete_char), clear(clear), show_after(show_after), 72 last_draw(""), start(0), toupdate(false), complete(false), reverse(false) { 73 74 supported = is_supported(); 75 use_stderr = default_stderr(); 76 } 77 78 ~RProgress()79 ~RProgress() { } 80 set_format(std::string format)81 void set_format(std::string format) { this->format = format; } set_total(double total)82 void set_total(double total) { this->total = total; } set_width(int width)83 void set_width(int width) { this->width = width; } set_cursor_char(const char * cursor_char)84 void set_cursor_char(const char* cursor_char) { 85 this->cursor_char = cursor_char; 86 } set_complete_char(const char * complete_char)87 void set_complete_char(const char* complete_char) { 88 this->complete_char = complete_char; 89 } set_incomplete_char(const char * incomplete_char)90 void set_incomplete_char(const char* incomplete_char) { 91 this->incomplete_char = incomplete_char; 92 } set_clear(bool clear)93 void set_clear(bool clear) { this->clear = clear; } set_show_after(double show_after)94 void set_show_after(double show_after) { this->show_after = show_after; } 95 set_reverse(bool reverse)96 void set_reverse(bool reverse) { this->reverse = reverse; } 97 98 void tick(double len = 1) { 99 // Start the timer 100 if (first) { start = time_now(); } 101 102 current += len; 103 count++; 104 105 // We only update after show_after secs 106 toupdate = toupdate || time_now() - start > show_after; 107 108 if (current >= total) complete = true; 109 110 // Need to render at the beginning and at the end, always 111 if (first || toupdate || complete) this->render(); 112 113 if (complete) this->terminate(); 114 115 first = false; 116 } 117 update(double ratio)118 void update(double ratio) { 119 double goal = ratio * total; 120 this->tick(goal - current); 121 } 122 123 private: 124 125 bool first; // Is the next one the first tick? 126 bool supported; // \r supported at all? 127 std::string format; // Format template 128 double total; // Total number of ticks 129 double current; // Current number of ticks 130 int count; // Total number of calls 131 int width; // Width of progress bar 132 bool use_stderr; // Whether to print to stderr 133 std::string cursor_char; // Character for cursor tick 134 std::string complete_char; // Character for completed ticks 135 std::string incomplete_char; // Character for incomplete ticks 136 bool clear; // Should we clear the line at the end? 137 double show_after; // Delay to show/increase the progress bar 138 std::string last_draw; // Last progress bar drawn 139 140 double start; // Start time 141 bool toupdate; // Are we updating? (After show_after.) 142 bool complete; // Are we complete? 143 bool reverse; // go from right to left rather than left to right 144 render()145 void render() { 146 if (!supported) return; 147 148 std::string str = format; 149 150 std::stringstream buffer; 151 152 double ratio_now = ratio(); 153 154 // percent 155 buffer << std::setw(3) << ratio_now * 100 << "%"; 156 replace_all(str, ":percent", buffer.str()); 157 buffer.str(""); buffer.clear(); 158 159 // elapsed 160 double elapsed_secs = time_now() - start; 161 std::string elapsed = vague_dt(elapsed_secs); 162 replace_all(str, ":elapsed", elapsed); 163 164 // eta 165 double percent = round(ratio_now * 100); 166 double eta_secs = percent == 100 ? 0 : 167 elapsed_secs * (total / current - 1.0); 168 std::string eta = std::isinf(eta_secs) ? "?s" : vague_dt(eta_secs); 169 replace_all(str, ":eta", eta); 170 171 // rate 172 if (elapsed_secs == 0) { 173 buffer << "?"; 174 } else { 175 double rate_num = elapsed_secs == 0 ? 0 : current / elapsed_secs; 176 buffer << pretty_bytes(rate_num) << "/s"; 177 } 178 replace_all(str, ":rate", buffer.str()); 179 buffer.str(""); buffer.clear(); 180 181 // current 182 buffer << round(current); 183 replace_all(str, ":current", buffer.str()); 184 buffer.str(""); buffer.clear(); 185 186 // total 187 buffer << round(total); 188 replace_all(str, ":total", buffer.str()); 189 buffer.str(""); buffer.clear(); 190 191 // bytes 192 replace_all(str, ":bytes", pretty_bytes(current)); 193 194 // spin 195 replace_all(str, ":spin", spin_symbol()); 196 197 // bar 198 std::string str_no_bar = str; 199 replace_all(str_no_bar, ":bar", ""); 200 long int bar_width = width - str_no_bar.length(); 201 if (bar_width < 0) bar_width = 0; 202 203 double complete_len = round(bar_width * ratio_now); 204 std::string bar; 205 206 if (reverse) { 207 for (long int i = (long int) complete_len; i < bar_width; i++) { 208 bar += incomplete_char; 209 } 210 if (complete_len > 0) { 211 bar += cursor_char; 212 } 213 for (int i = 0; i < (complete_len - 1); i++) { bar += complete_char; } 214 } else { 215 for (int i = 0; i < (complete_len - 1); i++) { bar += complete_char; } 216 if (complete_len > 0) { 217 bar += cursor_char; 218 } 219 for (long int i = (long int) complete_len; i < bar_width; i++) { 220 bar += incomplete_char; 221 } 222 } 223 replace_all(str, ":bar", bar); 224 225 if (last_draw != str) { 226 if (last_draw.length() > str.length()) { clear_line(use_stderr, width); } 227 cursor_to_start(use_stderr); 228 if (use_stderr) { 229 REprintf(str.c_str()); 230 } else { 231 Rprintf(str.c_str()); 232 } 233 last_draw = str; 234 } 235 } 236 terminate()237 void terminate() { 238 if (! supported) return; 239 if (clear) { 240 clear_line(use_stderr, width); 241 cursor_to_start(use_stderr); 242 } else { 243 if (use_stderr) { 244 REprintf("\n"); 245 } else { 246 Rprintf("\n"); 247 } 248 } 249 } 250 ratio()251 double ratio() { 252 double ratio = current / total; 253 if (ratio < 0) ratio = 0; 254 if (ratio > 1) ratio = 1; 255 return ratio; 256 } 257 spin_symbol()258 std::string spin_symbol() { 259 const char symbols[4] = {'-', '\\', '|', '/'}; 260 return std::string(1, symbols[(count - 1) % 4]); 261 } 262 clear_line(bool use_stderr,int width)263 void clear_line(bool use_stderr, int width) { 264 265 char *spaces = (char*) calloc(width + 2, sizeof(char)); 266 if (!spaces) Rf_error("Progress bar: out of memory"); 267 for (int i = 1; i <= width; i++) spaces[i] = ' '; 268 spaces[0] = '\r'; 269 spaces[width + 1] = '\0'; 270 271 if (use_stderr) { 272 REprintf(spaces); 273 } else { 274 Rprintf(spaces); 275 } 276 free(spaces); 277 } 278 cursor_to_start(bool use_stderr)279 void cursor_to_start(bool use_stderr) { 280 281 if (use_stderr) { 282 REprintf("\r"); 283 } else { 284 Rprintf("\r"); 285 } 286 } 287 is_r_studio()288 bool is_r_studio() { 289 290 char *v = std::getenv("RSTUDIO"); 291 292 return v != 0 && v[0] == '1' && v[1] == '\0'; 293 } 294 is_r_app()295 bool is_r_app() { 296 297 char *v = std::getenv("R_GUI_APP_VERSION"); 298 299 return v != 0; 300 } 301 302 // In R Studio we should print to stdout, because priting a \r 303 // to stderr is buggy (reported) 304 default_stderr()305 bool default_stderr() { 306 307 return !is_r_studio(); 308 } 309 310 // If stdout is a terminal, or R Studio or macOS R.app 311 // On windows, stdout is a terminal, apparently 312 is_supported()313 bool is_supported() { 314 315 return is_option_enabled() && 316 (isatty(1) || is_r_studio() || is_r_app()); 317 } 318 319 // gettimeofday for windows, from 320 // https://stackoverflow.com/questions/10905892 321 322 #ifdef Win32 323 324 #define WIN32_LEAN_AND_MEAN 325 #include <Windows.h> 326 #include <stdint.h> // portable: uint64_t MSVC: __int64 327 gettimeofday(struct timeval * tp,struct timezone * tzp)328 int gettimeofday(struct timeval * tp, struct timezone * tzp) { 329 // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's 330 static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); 331 332 SYSTEMTIME system_time; 333 FILETIME file_time; 334 uint64_t time; 335 336 GetSystemTime( &system_time ); 337 SystemTimeToFileTime( &system_time, &file_time ); 338 time = ((uint64_t)file_time.dwLowDateTime ) ; 339 time += ((uint64_t)file_time.dwHighDateTime) << 32; 340 341 tp->tv_sec = (long) ((time - EPOCH) / 10000000L); 342 tp->tv_usec = (long) (system_time.wMilliseconds * 1000); 343 return 0; 344 } 345 346 #endif 347 time_now()348 static double time_now() { 349 struct timeval now; 350 gettimeofday(&now, /* tzp = */ 0); 351 return now.tv_sec + now.tv_usec / 1000000.0; 352 } 353 replace_all(std::string & str,const std::string & from,const std::string & to)354 static void replace_all(std::string& str, const std::string& from, 355 const std::string& to) { 356 if (from.empty()) return; 357 358 size_t start_pos = 0; 359 360 while ((start_pos = str.find(from, start_pos)) != std::string::npos) { 361 str.replace(start_pos, from.length(), to); 362 start_pos += to.length(); 363 } 364 } 365 366 public: 367 vague_dt(double seconds)368 static std::string vague_dt(double seconds) { 369 double minutes = seconds / 60; 370 double hours = minutes / 60; 371 double days = hours / 24; 372 double years = days / 365.25; 373 374 std::stringstream buffer; 375 376 buffer << std::setw(2); 377 378 if (seconds < 50) { 379 buffer << round(seconds) << "s"; 380 } else if (minutes < 50) { 381 buffer << round(minutes) << "m"; 382 } else if (hours < 18) { 383 buffer << round(hours) << "h"; 384 } else if (days < 30) { 385 buffer << round(days) << "d"; 386 } else if (days < 335) { 387 buffer << round(days/30) << "M"; 388 } else { 389 buffer << round(years) << "y"; 390 } 391 392 return buffer.str(); 393 } 394 pretty_bytes(double rate)395 static std::string pretty_bytes(double rate) { 396 397 errno = 0; 398 long bytes = lround(rate); 399 if (errno == ERANGE) { 400 bytes = LONG_MAX; 401 } 402 403 if (bytes == 0) { return "0B"; } 404 405 std::string units[] = { "B", "kB", "MB", "GB", "TB", "PB", "EB", 406 "ZB", "YB" }; 407 long int num_units = (long int)(sizeof(units) / sizeof(units[0])); 408 double idx = std::floor(std::log(bytes) / std::log(1000.0)); 409 if (idx >= num_units) { idx = num_units - 1; } 410 411 double res = round(bytes / std::pow(1000.0, idx) * 100.0) / 100.0; 412 std::stringstream buffer; 413 414 buffer.precision(2); 415 buffer << std::fixed << res << units[(long) idx]; 416 return buffer.str(); 417 } 418 is_option_enabled()419 static bool is_option_enabled() { 420 SEXP opt = PROTECT(Rf_GetOption1(Rf_install("progress_enabled"))); 421 if (Rf_isNull(opt)) { 422 UNPROTECT(1); 423 return true; 424 } 425 Rboolean t = R_compute_identical(opt, Rf_ScalarLogical(1), 16); 426 UNPROTECT(1); 427 return t; 428 } 429 430 }; // class RProgress 431 432 } // namespace RProgress 433 434 #endif 435