I figured something could be done by using R's mechanisms for hooking code into various places, but there seems to be no way to hook in before code runs. There's the taskCallBack mechanism, but that happens at the end of the code, just before your prompt appears. I wasted a bit of time trying to figure something out using that, and gave up.
So the solution. A stopwatch function that evaluates its argument. You can do:
stopwatch(foo(a))
stopwatch({z=foo(a)})
stopwatch({x=foo(a);y=foo(b)})
and a handy little HH:MM:SS dialog will tick along until your code is finished. Then it will stop, and you can close it.
The dialog is done with the tcltk package, using a one-second timer tick. There's just the one main function, and a timeForm function that produces the HH:MM:SS display from a time in seconds:
stopwatch <- function(expr){
# tcltk R expression timer function, Barry Rowlingson Feb 2012
require(tcltk)
start=Sys.time()
tt <- tktoplevel()
tkwm.title(tt,gsub(" ","",paste(deparse(substitute(expr)),sep="",collapse=";")))
closeme <- function(){
tkdestroy(tt)
}
labelText <- tclVar("Running...")
label1 <- tklabel(tt,text=tclvalue(labelText))
tkconfigure(label1,textvariable=labelText)
tkgrid(label1)
e = environment()
z <- function () {
tclvalue(labelText)=paste("Running: ",timeForm(Sys.time()-start));
assign("sw", tcl("after", 1000, z), env=e)
}
quit.but <- tkbutton(tt,text="Close",command=closeme)
tkgrid(quit.but)
sw = tcl("after", 1000, z)
finished=function(){
tcl("after","cancel",sw)
tclvalue(labelText)=paste("Done after: ",timeForm(Sys.time()-start));
}
tryCatch(eval(expr),finally=finished())
}
timeForm <- function(t){
t=as.numeric(t)
s=t %% 60
m=((t-s)/60) %% 60
h = t %/% (60*60)
stopifnot(s+m*60+h*60*60==t)
tstring = sprintf("%02d",c(h,m,s))
return(paste(tstring,collapse=":"))
list(h=h,m=m,s=s)
}
This code is hereby released for anyone to do anything with, but keep the credit in the comment unless you've really changed a lot and its unrecognizable from my original...
I can think of a few improvements - possibly you might want to run a few things on different lines and have a cumulative stopwatch - a bit like split lap times. You might then need a different paradigm, something like:
s = stopwatch()
timeOn(s,foo(a))
timeOn(s,foo(b))
foo(c)
timeOn(s,foo(c))
This would create a TclTk dialog with a timer that only runs within the timeOn evaluations.
Another possibility is a text-based timer rather than a GUI. You might also want it to return the time taken - currently it returns the value of the expression. Go ahead, knock yourself out.
Hey Barry,
ReplyDeleteKiller blog!
I was wondering if you would you be interested in sharing your articles with other like- minded people in the API and dev industry. We are building an online community containing links to informative articles about mobile & web apps, software dev, LBS & geo topics, hack events etc.
If you are interested and want to learn more about this, please send an email to info@atomicreach.com
Thanks,
Anne