progressmeter.c revision 126274
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" 26126274SdesRCSID("$OpenBSD: progressmeter.c,v 1.19 2004/02/05 15:33:33 markus 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 45124208Sdes/* updates the progressmeter to reflect the current state of the transfer */ 46124208Sdesvoid refresh_progress_meter(void); 47113908Sdes 48124208Sdes/* signal handler for updating the progress meter */ 49124208Sdesstatic void update_progress_meter(int); 50113908Sdes 51124208Sdesstatic time_t start; /* start progress */ 52124208Sdesstatic time_t last_update; /* last progress update */ 53124208Sdesstatic char *file; /* name of the file being transferred */ 54124208Sdesstatic off_t end_pos; /* ending position of transfer */ 55124208Sdesstatic off_t cur_pos; /* transfer position as of last refresh */ 56124208Sdesstatic volatile off_t *counter; /* progress counter */ 57124208Sdesstatic long stalled; /* how long we have been stalled */ 58124208Sdesstatic int bytes_per_second; /* current speed in bytes per second */ 59124208Sdesstatic int win_size; /* terminal window size */ 60113908Sdes 61124208Sdes/* units for format_size */ 62124208Sdesstatic const char unit[] = " KMGT"; 63113908Sdes 64124208Sdesstatic int 65124208Sdescan_output(void) 66113908Sdes{ 67124208Sdes return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); 68113908Sdes} 69113908Sdes 70113908Sdesstatic void 71124208Sdesformat_rate(char *buf, int size, off_t bytes) 72113908Sdes{ 73124208Sdes int i; 74113908Sdes 75124208Sdes bytes *= 100; 76124208Sdes for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) 77124208Sdes bytes = (bytes + 512) / 1024; 78124208Sdes if (i == 0) { 79124208Sdes i++; 80124208Sdes bytes = (bytes + 512) / 1024; 81124208Sdes } 82124208Sdes snprintf(buf, size, "%3lld.%1lld%c%s", 83126274Sdes (int64_t) (bytes + 5) / 100, 84124208Sdes (int64_t) (bytes + 5) / 10 % 10, 85124208Sdes unit[i], 86124208Sdes i ? "B" : " "); 87113908Sdes} 88113908Sdes 89124208Sdesstatic void 90124208Sdesformat_size(char *buf, int size, off_t bytes) 91113908Sdes{ 92124208Sdes int i; 93113908Sdes 94124208Sdes for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) 95124208Sdes bytes = (bytes + 512) / 1024; 96124208Sdes snprintf(buf, size, "%4lld%c%s", 97124208Sdes (int64_t) bytes, 98124208Sdes unit[i], 99124208Sdes i ? "B" : " "); 100113908Sdes} 101113908Sdes 102124208Sdesvoid 103124208Sdesrefresh_progress_meter(void) 104113908Sdes{ 105124208Sdes char buf[MAX_WINSIZE + 1]; 106124208Sdes time_t now; 107124208Sdes off_t transferred; 108113908Sdes double elapsed; 109124208Sdes int percent; 110126274Sdes off_t bytes_left; 111124208Sdes int cur_speed; 112124208Sdes int hours, minutes, seconds; 113124208Sdes int i, len; 114124208Sdes int file_len; 115113908Sdes 116124208Sdes transferred = *counter - cur_pos; 117124208Sdes cur_pos = *counter; 118124208Sdes now = time(NULL); 119124208Sdes bytes_left = end_pos - cur_pos; 120113908Sdes 121124208Sdes if (bytes_left > 0) 122124208Sdes elapsed = now - last_update; 123126274Sdes else { 124124208Sdes elapsed = now - start; 125126274Sdes /* Calculate true total speed when done */ 126126274Sdes transferred = end_pos; 127126274Sdes bytes_per_second = 0; 128126274Sdes } 129113908Sdes 130124208Sdes /* calculate speed */ 131124208Sdes if (elapsed != 0) 132124208Sdes cur_speed = (transferred / elapsed); 133124208Sdes else 134126274Sdes cur_speed = transferred; 135113908Sdes 136124208Sdes#define AGE_FACTOR 0.9 137124208Sdes if (bytes_per_second != 0) { 138124208Sdes bytes_per_second = (bytes_per_second * AGE_FACTOR) + 139124208Sdes (cur_speed * (1.0 - AGE_FACTOR)); 140124208Sdes } else 141124208Sdes bytes_per_second = cur_speed; 142113908Sdes 143124208Sdes /* filename */ 144124208Sdes buf[0] = '\0'; 145124208Sdes file_len = win_size - 35; 146124208Sdes if (file_len > 0) { 147124208Sdes len = snprintf(buf, file_len + 1, "\r%s", file); 148124208Sdes if (len < 0) 149124208Sdes len = 0; 150124208Sdes for (i = len; i < file_len; i++ ) 151124208Sdes buf[i] = ' '; 152124208Sdes buf[file_len] = '\0'; 153113908Sdes } 154113908Sdes 155124208Sdes /* percent of transfer done */ 156124208Sdes if (end_pos != 0) 157124208Sdes percent = ((float)cur_pos / end_pos) * 100; 158124208Sdes else 159124208Sdes percent = 100; 160124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 161124208Sdes " %3d%% ", percent); 162113908Sdes 163124208Sdes /* amount transferred */ 164124208Sdes format_size(buf + strlen(buf), win_size - strlen(buf), 165124208Sdes cur_pos); 166124208Sdes strlcat(buf, " ", win_size); 167113908Sdes 168124208Sdes /* bandwidth usage */ 169124208Sdes format_rate(buf + strlen(buf), win_size - strlen(buf), 170124208Sdes bytes_per_second); 171124208Sdes strlcat(buf, "/s ", win_size); 172124208Sdes 173124208Sdes /* ETA */ 174124208Sdes if (!transferred) 175124208Sdes stalled += elapsed; 176124208Sdes else 177124208Sdes stalled = 0; 178124208Sdes 179124208Sdes if (stalled >= STALL_TIME) 180124208Sdes strlcat(buf, "- stalled -", win_size); 181124208Sdes else if (bytes_per_second == 0 && bytes_left) 182124208Sdes strlcat(buf, " --:-- ETA", win_size); 183124208Sdes else { 184124208Sdes if (bytes_left > 0) 185124208Sdes seconds = bytes_left / bytes_per_second; 186113908Sdes else 187124208Sdes seconds = elapsed; 188113908Sdes 189124208Sdes hours = seconds / 3600; 190124208Sdes seconds -= hours * 3600; 191124208Sdes minutes = seconds / 60; 192124208Sdes seconds -= minutes * 60; 193124208Sdes 194124208Sdes if (hours != 0) 195124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 196124208Sdes "%d:%02d:%02d", hours, minutes, seconds); 197113908Sdes else 198124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 199124208Sdes " %02d:%02d", minutes, seconds); 200124208Sdes 201124208Sdes if (bytes_left > 0) 202124208Sdes strlcat(buf, " ETA", win_size); 203124208Sdes else 204124208Sdes strlcat(buf, " ", win_size); 205113908Sdes } 206124208Sdes 207124287Sdes atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); 208124208Sdes last_update = now; 209113908Sdes} 210113908Sdes 211124208Sdesstatic void 212124208Sdesupdate_progress_meter(int ignore) 213113908Sdes{ 214124208Sdes int save_errno; 215124208Sdes 216124208Sdes save_errno = errno; 217124208Sdes 218124208Sdes if (can_output()) 219124208Sdes refresh_progress_meter(); 220124208Sdes 221124208Sdes signal(SIGALRM, update_progress_meter); 222124208Sdes alarm(UPDATE_INTERVAL); 223124208Sdes errno = save_errno; 224124208Sdes} 225124208Sdes 226124208Sdesvoid 227124208Sdesstart_progress_meter(char *f, off_t filesize, off_t *stat) 228124208Sdes{ 229113908Sdes struct winsize winsize; 230113908Sdes 231124208Sdes start = last_update = time(NULL); 232124208Sdes file = f; 233124208Sdes end_pos = filesize; 234124208Sdes cur_pos = 0; 235124208Sdes counter = stat; 236124208Sdes stalled = 0; 237124208Sdes bytes_per_second = 0; 238124208Sdes 239124208Sdes if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && 240124208Sdes winsize.ws_col != 0) { 241124208Sdes if (winsize.ws_col > MAX_WINSIZE) 242124208Sdes win_size = MAX_WINSIZE; 243124208Sdes else 244124208Sdes win_size = winsize.ws_col; 245124208Sdes } else 246124208Sdes win_size = DEFAULT_WINSIZE; 247124208Sdes win_size += 1; /* trailing \0 */ 248124208Sdes 249124208Sdes if (can_output()) 250124208Sdes refresh_progress_meter(); 251124208Sdes 252124208Sdes signal(SIGALRM, update_progress_meter); 253124208Sdes alarm(UPDATE_INTERVAL); 254113908Sdes} 255124208Sdes 256124208Sdesvoid 257124208Sdesstop_progress_meter(void) 258124208Sdes{ 259124208Sdes alarm(0); 260124208Sdes 261124208Sdes if (!can_output()) 262124208Sdes return; 263124208Sdes 264124208Sdes /* Ensure we complete the progress */ 265124208Sdes if (cur_pos != end_pos) 266124208Sdes refresh_progress_meter(); 267124208Sdes 268124208Sdes atomicio(vwrite, STDOUT_FILENO, "\n", 1); 269124208Sdes} 270