progressmeter.c revision 255767
1279377Simp/* $OpenBSD: progressmeter.c,v 1.39 2013/06/02 13:33:05 dtucker Exp $ */ 2279377Simp/* 3279377Simp * Copyright (c) 2003 Nils Nordman. All rights reserved. 4279377Simp * 5279377Simp * Redistribution and use in source and binary forms, with or without 6279377Simp * modification, are permitted provided that the following conditions 7279377Simp * are met: 8279377Simp * 1. Redistributions of source code must retain the above copyright 9279377Simp * notice, this list of conditions and the following disclaimer. 10279377Simp * 2. Redistributions in binary form must reproduce the above copyright 11279377Simp * notice, this list of conditions and the following disclaimer in the 12279377Simp * documentation and/or other materials provided with the distribution. 13279377Simp * 14279377Simp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15279377Simp * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16279377Simp * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17279377Simp * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18279377Simp * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19279377Simp * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20279377Simp * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21279377Simp * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22279377Simp * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23279377Simp * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24279377Simp */ 25279377Simp 26279377Simp#include "includes.h" 27279377Simp 28279377Simp#include <sys/types.h> 29279377Simp#include <sys/ioctl.h> 30279377Simp#include <sys/uio.h> 31279377Simp 32279377Simp#include <errno.h> 33279377Simp#include <signal.h> 34279377Simp#include <stdio.h> 35279377Simp#include <string.h> 36279377Simp#include <time.h> 37279377Simp#include <unistd.h> 38279377Simp 39279377Simp#include "progressmeter.h" 40279377Simp#include "atomicio.h" 41279377Simp#include "misc.h" 42279377Simp 43279377Simp#define DEFAULT_WINSIZE 80 44279377Simp#define MAX_WINSIZE 512 45279377Simp#define PADDING 1 /* padding between the progress indicators */ 46279377Simp#define UPDATE_INTERVAL 1 /* update the progress meter every second */ 47279377Simp#define STALL_TIME 5 /* we're stalled after this many seconds */ 48279377Simp 49279377Simp/* determines whether we can output to the terminal */ 50279377Simpstatic int can_output(void); 51279377Simp 52279377Simp/* formats and inserts the specified size into the given buffer */ 53279377Simpstatic void format_size(char *, int, off_t); 54279377Simpstatic void format_rate(char *, int, off_t); 55279377Simp 56279377Simp/* window resizing */ 57279377Simpstatic void sig_winch(int); 58279377Simpstatic void setscreensize(void); 59279377Simp 60279377Simp/* updates the progressmeter to reflect the current state of the transfer */ 61279377Simpvoid refresh_progress_meter(void); 62279377Simp 63279377Simp/* signal handler for updating the progress meter */ 64279377Simpstatic void update_progress_meter(int); 65279377Simp 66279377Simpstatic time_t start; /* start progress */ 67279377Simpstatic time_t last_update; /* last progress update */ 68279377Simpstatic char *file; /* name of the file being transferred */ 69279377Simpstatic off_t end_pos; /* ending position of transfer */ 70279377Simpstatic off_t cur_pos; /* transfer position as of last refresh */ 71279377Simpstatic volatile off_t *counter; /* progress counter */ 72279377Simpstatic long stalled; /* how long we have been stalled */ 73279377Simpstatic int bytes_per_second; /* current speed in bytes per second */ 74279377Simpstatic int win_size; /* terminal window size */ 75279377Simpstatic volatile sig_atomic_t win_resized; /* for window resizing */ 76279377Simp 77279377Simp/* units for format_size */ 78279377Simpstatic const char unit[] = " KMGT"; 79279377Simp 80279377Simpstatic int 81279377Simpcan_output(void) 82279377Simp{ 83279377Simp return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); 84279377Simp} 85279377Simp 86279377Simpstatic void 87279377Simpformat_rate(char *buf, int size, off_t bytes) 88279377Simp{ 89279377Simp int i; 90279377Simp 91279377Simp bytes *= 100; 92279377Simp for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) 93279377Simp bytes = (bytes + 512) / 1024; 94279377Simp if (i == 0) { 95279377Simp i++; 96279377Simp bytes = (bytes + 512) / 1024; 97279377Simp } 98279377Simp snprintf(buf, size, "%3lld.%1lld%c%s", 99279377Simp (long long) (bytes + 5) / 100, 100279377Simp (long long) (bytes + 5) / 10 % 10, 101279377Simp unit[i], 102279377Simp i ? "B" : " "); 103279377Simp} 104279377Simp 105279377Simpstatic void 106279377Simpformat_size(char *buf, int size, off_t bytes) 107279377Simp{ 108279377Simp int i; 109279377Simp 110279377Simp for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) 111279377Simp bytes = (bytes + 512) / 1024; 112279377Simp snprintf(buf, size, "%4lld%c%s", 113279377Simp (long long) bytes, 114279377Simp unit[i], 115279377Simp i ? "B" : " "); 116279377Simp} 117279377Simp 118279377Simpvoid 119279377Simprefresh_progress_meter(void) 120279377Simp{ 121279377Simp char buf[MAX_WINSIZE + 1]; 122279377Simp time_t now; 123279377Simp off_t transferred; 124279377Simp double elapsed; 125279377Simp int percent; 126279377Simp off_t bytes_left; 127279377Simp int cur_speed; 128279377Simp int hours, minutes, seconds; 129279377Simp int i, len; 130279377Simp int file_len; 131279377Simp 132279377Simp transferred = *counter - cur_pos; 133279377Simp cur_pos = *counter; 134279377Simp now = monotime(); 135279377Simp bytes_left = end_pos - cur_pos; 136279377Simp 137279377Simp if (bytes_left > 0) 138279377Simp elapsed = now - last_update; 139279377Simp else { 140279377Simp elapsed = now - start; 141279377Simp /* Calculate true total speed when done */ 142279377Simp transferred = end_pos; 143279377Simp bytes_per_second = 0; 144279377Simp } 145279377Simp 146279377Simp /* calculate speed */ 147279377Simp if (elapsed != 0) 148279377Simp cur_speed = (transferred / elapsed); 149279377Simp else 150279377Simp cur_speed = transferred; 151279377Simp 152279377Simp#define AGE_FACTOR 0.9 153279377Simp if (bytes_per_second != 0) { 154279377Simp bytes_per_second = (bytes_per_second * AGE_FACTOR) + 155279377Simp (cur_speed * (1.0 - AGE_FACTOR)); 156279377Simp } else 157279377Simp bytes_per_second = cur_speed; 158279377Simp 159279377Simp /* filename */ 160279377Simp buf[0] = '\0'; 161279377Simp file_len = win_size - 35; 162279377Simp if (file_len > 0) { 163279377Simp len = snprintf(buf, file_len + 1, "\r%s", file); 164279377Simp if (len < 0) 165279377Simp len = 0; 166279377Simp if (len >= file_len + 1) 167279377Simp len = file_len; 168279377Simp for (i = len; i < file_len; i++) 169279377Simp buf[i] = ' '; 170279377Simp buf[file_len] = '\0'; 171279377Simp } 172279377Simp 173279377Simp /* percent of transfer done */ 174279377Simp if (end_pos != 0) 175279377Simp percent = ((float)cur_pos / end_pos) * 100; 176279377Simp else 177279377Simp percent = 100; 178279377Simp snprintf(buf + strlen(buf), win_size - strlen(buf), 179279377Simp " %3d%% ", percent); 180279377Simp 181279377Simp /* amount transferred */ 182279377Simp format_size(buf + strlen(buf), win_size - strlen(buf), 183279377Simp cur_pos); 184279377Simp strlcat(buf, " ", win_size); 185279377Simp 186279377Simp /* bandwidth usage */ 187279377Simp format_rate(buf + strlen(buf), win_size - strlen(buf), 188279377Simp (off_t)bytes_per_second); 189279377Simp strlcat(buf, "/s ", win_size); 190279377Simp 191279377Simp /* ETA */ 192279377Simp if (!transferred) 193279377Simp stalled += elapsed; 194279377Simp else 195279377Simp stalled = 0; 196279377Simp 197279377Simp if (stalled >= STALL_TIME) 198279377Simp strlcat(buf, "- stalled -", win_size); 199279377Simp else if (bytes_per_second == 0 && bytes_left) 200279377Simp strlcat(buf, " --:-- ETA", win_size); 201279377Simp else { 202279377Simp if (bytes_left > 0) 203279377Simp seconds = bytes_left / bytes_per_second; 204279377Simp else 205279377Simp seconds = elapsed; 206279377Simp 207279377Simp hours = seconds / 3600; 208279377Simp seconds -= hours * 3600; 209279377Simp minutes = seconds / 60; 210279377Simp seconds -= minutes * 60; 211279377Simp 212279377Simp if (hours != 0) 213279377Simp snprintf(buf + strlen(buf), win_size - strlen(buf), 214279377Simp "%d:%02d:%02d", hours, minutes, seconds); 215279377Simp else 216279377Simp snprintf(buf + strlen(buf), win_size - strlen(buf), 217279377Simp " %02d:%02d", minutes, seconds); 218279377Simp 219279377Simp if (bytes_left > 0) 220279377Simp strlcat(buf, " ETA", win_size); 221279377Simp else 222279377Simp strlcat(buf, " ", win_size); 223279377Simp } 224279377Simp 225279377Simp atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); 226279377Simp last_update = now; 227279377Simp} 228279377Simp 229279377Simp/*ARGSUSED*/ 230279377Simpstatic void 231279377Simpupdate_progress_meter(int ignore) 232279377Simp{ 233279377Simp int save_errno; 234279377Simp 235279377Simp save_errno = errno; 236279377Simp 237279377Simp if (win_resized) { 238279377Simp setscreensize(); 239279377Simp win_resized = 0; 240279377Simp } 241279377Simp if (can_output()) 242279377Simp refresh_progress_meter(); 243279377Simp 244279377Simp signal(SIGALRM, update_progress_meter); 245279377Simp alarm(UPDATE_INTERVAL); 246279377Simp errno = save_errno; 247279377Simp} 248279377Simp 249279377Simpvoid 250279377Simpstart_progress_meter(char *f, off_t filesize, off_t *ctr) 251279377Simp{ 252279377Simp start = last_update = monotime(); 253279377Simp file = f; 254279377Simp end_pos = filesize; 255279377Simp cur_pos = 0; 256279377Simp counter = ctr; 257279377Simp stalled = 0; 258279377Simp bytes_per_second = 0; 259279377Simp 260279377Simp setscreensize(); 261279377Simp if (can_output()) 262279377Simp refresh_progress_meter(); 263279377Simp 264279377Simp signal(SIGALRM, update_progress_meter); 265279377Simp signal(SIGWINCH, sig_winch); 266279377Simp alarm(UPDATE_INTERVAL); 267279377Simp} 268279377Simp 269279377Simpvoid 270279377Simpstop_progress_meter(void) 271279377Simp{ 272279377Simp alarm(0); 273279377Simp 274279377Simp if (!can_output()) 275279377Simp return; 276279377Simp 277279377Simp /* Ensure we complete the progress */ 278279377Simp if (cur_pos != end_pos) 279279377Simp refresh_progress_meter(); 280279377Simp 281279377Simp atomicio(vwrite, STDOUT_FILENO, "\n", 1); 282279377Simp} 283279377Simp 284279377Simp/*ARGSUSED*/ 285279377Simpstatic void 286279377Simpsig_winch(int sig) 287279377Simp{ 288279377Simp win_resized = 1; 289279377Simp} 290279377Simp 291279377Simpstatic void 292279377Simpsetscreensize(void) 293279377Simp{ 294279377Simp struct winsize winsize; 295279377Simp 296279377Simp if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && 297279377Simp winsize.ws_col != 0) { 298279377Simp if (winsize.ws_col > MAX_WINSIZE) 299279377Simp win_size = MAX_WINSIZE; 300279377Simp else 301279377Simp win_size = winsize.ws_col; 302279377Simp } else 303279377Simp win_size = DEFAULT_WINSIZE; 304279377Simp win_size += 1; /* trailing \0 */ 305279377Simp} 306279377Simp