Wednesday, January 18, 2012

the basics of textual progress bars

While running through some maintenance of some log directories, I learned a little bit about $stdout and textual progress indicators.

The app I work with logs like crazy. There are time-date-stamped log files all over, products of the much appreciated log roller we use. Obviously, log rolling helps eliminate the need to peruse through an 8+ gygabyte text file. That's good. The accumulation of so many log files, however, get's to be almost as annoying. Just like my commuter car, at some point you can't stand it anymore and it's time to do some house cleaning.

I'm a software developer, and a lazy software developer at that. If there is any reason to code something, I'll gravitate towards creating a script, even to generate lines I can run in my console to tar up log files. There are so many log files to be tarred that I found myself wanting to view the progress since it's no fun staring at a frozen screen.

I use tail -f often to watch log entries being made from the web application I'm working with.

tail -f log/development.log


I can also see what files in a directory were last touched.

ls -lat | head


Wouldn't it be nice to have a script that monitored the directory and let me see those tar archives grow? That much was pretty easy.

while(true)
puts `ls -lat log/archive | head`
sleep(1)
end


But it is ugly. You get entries that scroll you off the page. Then I start thinking about those textual progress bars. They seem to print some character that moves the cursor back to the beginning of the line so that the next print of characters over-writes the last. That would solve my problem with the scrolling.

Ok; again, I'm lazy. I did a little research, but admittedly, just enough to get the results I wanted. So here are some facts/theories:
  1. carriage returns (\r) are different from newlines (\n). When used together, the cursor shows up on the next line. If you only use a carriage return, the cursor returns to the beginning of the line it is currently on.
  2. 'puts' automatically adds a carriage return (cr) and newline (\r\n) to the outputted string. Ok; that's a step in the right direction.
  3. 'printf' may be used to print to the console without a cr or newline
  4. 'printf' automatically buffers, spitting content out to screen only when the script is finished. If we could only force a flush. Including a call to 'flush' doesn't seem to do the trick.
  5. $stdout and $stderr are built in handles to STDOUT and STDERR (go figure!). I'm not sure, but I think that 'printf' is a method associated with Kernel. I do know that calls to $stdout.printf("hello world") followed by $stdout.flush() does work.
We should have everything that is required now. Let's try this again.

while(true)
res = `ls -lat log/archive | head`
$stdout.printf("%s\r", res) #note: don't use a newline; only a carriage return
$stdout.flush() #or you won't see anything,... ever
sleep(1)
end


Ah, much better! Now, since I can't leave well enough alone, I will clean up my output, limiting the output only to the first actual file in the list.

#note: hit Ctrl-c to end the loop
while(true)
#note: there is no check for when no match was found
res = `ls -lat log/archive | head`.match(/^[^\r\n]+[\r\n]+([^\r\n]+)/)[1]

$stdout.printf("%s\r", res) #note: don't use a newline; only a carriage return
$stdout.flush() #note: flush or you won't see anything,... ever
sleep(1)
end


So to sum up, while there are several gems or plugins that offer the nice convenience of a textual progress bar, it all comes down to printing a line that ends in a carriage return (\r) only and flushing the STDOUT buffer.

Here are several references I discovered during the fun little journey I made into the world of progress bars:

http://www.ruby-forum.com/topic/175626
http://blog.dhavalparikh.co.in/2009/04/progress-bar-in-rails/
http://0xcc.net/ruby-progressbar/index.html.en
http://stackoverflow.com/questions/238073/how-to-add-a-progress-bar-to-a-bash-script
https://github.com/jfelchner/ruby-progressbar/blob/master/lib/progressbar.rb

# shortcut for doing sprintf
http://snippets.dzone.com/posts/show/5027

No comments:

Post a Comment