progressmeter.c revision 157016
1113908Sdes/* 2124208Sdes * Copyright (c) 2003 Nils Nordman. All rights reserved. 3113908Sdes * 4113908Sdes * Redistribution and use in source and binary forms, with or without 5113908Sdes * modification, are permitted provided that the following conditions 6113908Sdes * are met: 7113908Sdes * 1. Redistributions of source code must retain the above copyright 8113908Sdes * notice, this list of conditions and the following disclaimer. 9113908Sdes * 2. Redistributions in binary form must reproduce the above copyright 10113908Sdes * notice, this list of conditions and the following disclaimer in the 11113908Sdes * documentation and/or other materials provided with the distribution. 12113908Sdes * 13113908Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14113908Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15113908Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16113908Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17113908Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18113908Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19113908Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20113908Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21113908Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22113908Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23113908Sdes */ 24113908Sdes 25113908Sdes#include "includes.h" 26149749SdesRCSID("$OpenBSD: progressmeter.c,v 1.24 2005/06/07 13:25:23 jaredy Exp $"); 27113908Sdes 28124208Sdes#include "progressmeter.h" 29113908Sdes#include "atomicio.h" 30124208Sdes#include "misc.h" 31113908Sdes 32124208Sdes#define DEFAULT_WINSIZE 80 33124208Sdes#define MAX_WINSIZE 512 34124208Sdes#define PADDING 1 /* padding between the progress indicators */ 35124208Sdes#define UPDATE_INTERVAL 1 /* update the progress meter every second */ 36124208Sdes#define STALL_TIME 5 /* we're stalled after this many seconds */ 37113908Sdes 38124208Sdes/* determines whether we can output to the terminal */ 39124208Sdesstatic int can_output(void); 40113908Sdes 41124208Sdes/* formats and inserts the specified size into the given buffer */ 42124208Sdesstatic void format_size(char *, int, off_t); 43124208Sdesstatic void format_rate(char *, int, off_t); 44113908Sdes 45149749Sdes/* window resizing */ 46149749Sdesstatic void sig_winch(int); 47149749Sdesstatic void setscreensize(void); 48149749Sdes 49124208Sdes/* updates the progressmeter to reflect the current state of the transfer */ 50124208Sdesvoid refresh_progress_meter(void); 51113908Sdes 52124208Sdes/* signal handler for updating the progress meter */ 53124208Sdesstatic void update_progress_meter(int); 54113908Sdes 55137015Sdesstatic time_t start; /* start progress */ 56137015Sdesstatic time_t last_update; /* last progress update */ 57137015Sdesstatic char *file; /* name of the file being transferred */ 58137015Sdesstatic off_t end_pos; /* ending position of transfer */ 59137015Sdesstatic off_t cur_pos; /* transfer position as of last refresh */ 60124208Sdesstatic volatile off_t *counter; /* progress counter */ 61137015Sdesstatic long stalled; /* how long we have been stalled */ 62137015Sdesstatic int bytes_per_second; /* current speed in bytes per second */ 63137015Sdesstatic int win_size; /* terminal window size */ 64149749Sdesstatic volatile sig_atomic_t win_resized; /* for window resizing */ 65113908Sdes 66124208Sdes/* units for format_size */ 67124208Sdesstatic const char unit[] = " KMGT"; 68113908Sdes 69124208Sdesstatic int 70124208Sdescan_output(void) 71113908Sdes{ 72124208Sdes return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); 73113908Sdes} 74113908Sdes 75113908Sdesstatic void 76124208Sdesformat_rate(char *buf, int size, off_t bytes) 77113908Sdes{ 78124208Sdes int i; 79113908Sdes 80124208Sdes bytes *= 100; 81124208Sdes for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) 82124208Sdes bytes = (bytes + 512) / 1024; 83124208Sdes if (i == 0) { 84124208Sdes i++; 85124208Sdes bytes = (bytes + 512) / 1024; 86124208Sdes } 87124208Sdes snprintf(buf, size, "%3lld.%1lld%c%s", 88157016Sdes (long long) (bytes + 5) / 100, 89157016Sdes (long long) (bytes + 5) / 10 % 10, 90124208Sdes unit[i], 91124208Sdes i ? "B" : " "); 92113908Sdes} 93113908Sdes 94124208Sdesstatic void 95124208Sdesformat_size(char *buf, int size, off_t bytes) 96113908Sdes{ 97124208Sdes int i; 98113908Sdes 99124208Sdes for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) 100124208Sdes bytes = (bytes + 512) / 1024; 101124208Sdes snprintf(buf, size, "%4lld%c%s", 102157016Sdes (long long) bytes, 103124208Sdes unit[i], 104124208Sdes i ? "B" : " "); 105113908Sdes} 106113908Sdes 107124208Sdesvoid 108124208Sdesrefresh_progress_meter(void) 109113908Sdes{ 110124208Sdes char buf[MAX_WINSIZE + 1]; 111124208Sdes time_t now; 112124208Sdes off_t transferred; 113113908Sdes double elapsed; 114124208Sdes int percent; 115126274Sdes off_t bytes_left; 116124208Sdes int cur_speed; 117124208Sdes int hours, minutes, seconds; 118124208Sdes int i, len; 119124208Sdes int file_len; 120113908Sdes 121124208Sdes transferred = *counter - cur_pos; 122124208Sdes cur_pos = *counter; 123124208Sdes now = time(NULL); 124124208Sdes bytes_left = end_pos - cur_pos; 125113908Sdes 126124208Sdes if (bytes_left > 0) 127124208Sdes elapsed = now - last_update; 128126274Sdes else { 129124208Sdes elapsed = now - start; 130126274Sdes /* Calculate true total speed when done */ 131126274Sdes transferred = end_pos; 132126274Sdes bytes_per_second = 0; 133126274Sdes } 134113908Sdes 135124208Sdes /* calculate speed */ 136124208Sdes if (elapsed != 0) 137124208Sdes cur_speed = (transferred / elapsed); 138124208Sdes else 139126274Sdes cur_speed = transferred; 140113908Sdes 141124208Sdes#define AGE_FACTOR 0.9 142124208Sdes if (bytes_per_second != 0) { 143124208Sdes bytes_per_second = (bytes_per_second * AGE_FACTOR) + 144124208Sdes (cur_speed * (1.0 - AGE_FACTOR)); 145124208Sdes } else 146124208Sdes bytes_per_second = cur_speed; 147113908Sdes 148124208Sdes /* filename */ 149124208Sdes buf[0] = '\0'; 150124208Sdes file_len = win_size - 35; 151124208Sdes if (file_len > 0) { 152124208Sdes len = snprintf(buf, file_len + 1, "\r%s", file); 153124208Sdes if (len < 0) 154124208Sdes len = 0; 155149749Sdes if (len >= file_len + 1) 156149749Sdes len = file_len; 157124208Sdes for (i = len; i < file_len; i++ ) 158124208Sdes buf[i] = ' '; 159124208Sdes buf[file_len] = '\0'; 160113908Sdes } 161113908Sdes 162124208Sdes /* percent of transfer done */ 163124208Sdes if (end_pos != 0) 164124208Sdes percent = ((float)cur_pos / end_pos) * 100; 165124208Sdes else 166124208Sdes percent = 100; 167124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 168124208Sdes " %3d%% ", percent); 169113908Sdes 170124208Sdes /* amount transferred */ 171124208Sdes format_size(buf + strlen(buf), win_size - strlen(buf), 172124208Sdes cur_pos); 173124208Sdes strlcat(buf, " ", win_size); 174113908Sdes 175124208Sdes /* bandwidth usage */ 176124208Sdes format_rate(buf + strlen(buf), win_size - strlen(buf), 177137015Sdes (off_t)bytes_per_second); 178124208Sdes strlcat(buf, "/s ", win_size); 179124208Sdes 180124208Sdes /* ETA */ 181124208Sdes if (!transferred) 182124208Sdes stalled += elapsed; 183124208Sdes else 184124208Sdes stalled = 0; 185124208Sdes 186124208Sdes if (stalled >= STALL_TIME) 187124208Sdes strlcat(buf, "- stalled -", win_size); 188124208Sdes else if (bytes_per_second == 0 && bytes_left) 189124208Sdes strlcat(buf, " --:-- ETA", win_size); 190124208Sdes else { 191124208Sdes if (bytes_left > 0) 192124208Sdes seconds = bytes_left / bytes_per_second; 193113908Sdes else 194124208Sdes seconds = elapsed; 195113908Sdes 196124208Sdes hours = seconds / 3600; 197124208Sdes seconds -= hours * 3600; 198124208Sdes minutes = seconds / 60; 199124208Sdes seconds -= minutes * 60; 200124208Sdes 201124208Sdes if (hours != 0) 202124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 203124208Sdes "%d:%02d:%02d", hours, minutes, seconds); 204113908Sdes else 205124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 206124208Sdes " %02d:%02d", minutes, seconds); 207124208Sdes 208124208Sdes if (bytes_left > 0) 209124208Sdes strlcat(buf, " ETA", win_size); 210124208Sdes else 211124208Sdes strlcat(buf, " ", win_size); 212113908Sdes } 213124208Sdes 214124287Sdes atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); 215124208Sdes last_update = now; 216113908Sdes} 217113908Sdes 218124208Sdesstatic void 219124208Sdesupdate_progress_meter(int ignore) 220113908Sdes{ 221124208Sdes int save_errno; 222124208Sdes 223124208Sdes save_errno = errno; 224124208Sdes 225149749Sdes if (win_resized) { 226149749Sdes setscreensize(); 227149749Sdes win_resized = 0; 228149749Sdes } 229124208Sdes if (can_output()) 230124208Sdes refresh_progress_meter(); 231124208Sdes 232124208Sdes signal(SIGALRM, update_progress_meter); 233124208Sdes alarm(UPDATE_INTERVAL); 234124208Sdes errno = save_errno; 235124208Sdes} 236124208Sdes 237124208Sdesvoid 238137015Sdesstart_progress_meter(char *f, off_t filesize, off_t *ctr) 239124208Sdes{ 240124208Sdes start = last_update = time(NULL); 241124208Sdes file = f; 242124208Sdes end_pos = filesize; 243124208Sdes cur_pos = 0; 244137015Sdes counter = ctr; 245124208Sdes stalled = 0; 246124208Sdes bytes_per_second = 0; 247124208Sdes 248149749Sdes setscreensize(); 249124208Sdes if (can_output()) 250124208Sdes refresh_progress_meter(); 251124208Sdes 252124208Sdes signal(SIGALRM, update_progress_meter); 253149749Sdes signal(SIGWINCH, sig_winch); 254124208Sdes alarm(UPDATE_INTERVAL); 255113908Sdes} 256124208Sdes 257124208Sdesvoid 258124208Sdesstop_progress_meter(void) 259124208Sdes{ 260124208Sdes alarm(0); 261124208Sdes 262124208Sdes if (!can_output()) 263124208Sdes return; 264124208Sdes 265124208Sdes /* Ensure we complete the progress */ 266124208Sdes if (cur_pos != end_pos) 267124208Sdes refresh_progress_meter(); 268124208Sdes 269124208Sdes atomicio(vwrite, STDOUT_FILENO, "\n", 1); 270124208Sdes} 271149749Sdes 272149749Sdesstatic void 273149749Sdessig_winch(int sig) 274149749Sdes{ 275149749Sdes win_resized = 1; 276149749Sdes} 277149749Sdes 278149749Sdesstatic void 279149749Sdessetscreensize(void) 280149749Sdes{ 281149749Sdes struct winsize winsize; 282149749Sdes 283149749Sdes if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && 284149749Sdes winsize.ws_col != 0) { 285149749Sdes if (winsize.ws_col > MAX_WINSIZE) 286149749Sdes win_size = MAX_WINSIZE; 287149749Sdes else 288149749Sdes win_size = winsize.ws_col; 289149749Sdes } else 290149749Sdes win_size = DEFAULT_WINSIZE; 291149749Sdes win_size += 1; /* trailing \0 */ 292149749Sdes} 293