174 Uptime Since...

Description

Nice and easy one this week. Your task is to write a Ruby script that reports the date and time of your last reboot, making use of the uptime command.

While uptime is available on the various Unixes and Mac OS X, Windows users might need to do a little extra work. Here are two options for Windows users.

Summary

First thing: the quiz name is off. Really, it should properly have been called "Up Since...", as we're looking for the time of reboot. The uptime command was in my mind, though, which is why the title is strange.

There are a number of ways to attack this problem, as shown in the variety of the solutions. First, let's look at "the right way" to solve this problem: use a library or module that does the work for you. Such was Daniel Berger's solution, shown here:

require 'sys/uptime'
p Sys::Uptime.uptime

Unfortunately, this didn't work on my machine, and hopefully that's only because of an outdated version of rb-sys-uptime, or some similar reason. I'll take Daniel's word that this works on his, though I wonder whether he's returning the uptime or the time of last reboot (as requested). Still, with such a module that abstracts the platform differences, this is an easy win... if you get it to work. (Offhand, this seems to be a Darwin, i.e. Mac OS X, module; if true, then it's not "the right way" for other platforms.)

Let's go on to the submission from Erik Hollensbe, which isn't even Ruby code.

last | grep reboot | head -1

Sometimes the Unix way is the best way. Of course, you need to know where this information resides, and there may still be platform differences (along with significant command-line differences on Windows), but this is a simple and quick one-liner that requires only a few common tools.

Let's look now at some Ruby code. We'll look first at Jesus Gabriel's second submission:

captures = (`uptime`.match /up (?:(?:(\d+) days,)?\s+(\d+):(\d+)|(\d+) min)/).captures
elapsed_seconds = captures.zip([86440, 3600, 60, 60]).inject(0) do |total, (x,y)|
    total + (x.nil? ? 0 : x.to_i * y)
end
puts "Last reboot was on #{Time.now - elapsed_seconds}"

The uptime information is gathered from the call to uptime. Note the backticks, which indicate that this is a shell command to be executed, and its output returned. The output is matched against a regular expression, containing a number of groups, several of which are optional. Four of those groups, however, are returned, to match against days, hours or minutes passed. (The minutes may be grouped in one of two ways.)

The captured results are paired up with the array [86440, 3600, 60, 60], each entry corresponding to the number of seconds in a day, hour or minute. Finally, using inject, the total number of seconds since the last reboot is determined. Subtracting this from Time.now results in the time of the last reboot.

A few comments... As was mentioned on the mailing list, the regular expression to match the output of uptime is fragile. A few variations were shown to exist. A more complex regular expression might be able to capture more variants, though the better answer is not to call uptime as a shell command, but rather use the appropriate system services to access the information directly. However, all this parsing is a direct result of my asking for it, so for this quiz, I'm not too concerned about this problem.

What I found a bit interesting (or confusing) was the different classes available for date/time information: Date, Time and DateTime. My own solution used DateTime, which I wrongly assumed I would need (thinking Time was only time information). I should have explored more, since the Time solution seems simpler.

Additionally, one subtracts days from DateTime objects, but subtracts seconds from Time objects. Time supports a now method, while DateTime does not. I expect there is some amount of logic to these classes, but it seems to have escaped me this time around.

One last comment on Jesus' solution. The uptime shell command provides the current time simultaneously with the elapsed time since last reboot. Yet his solution uses Time.now rather than the provide time. For this quiz, it's not big thing: the answer would, at most, be off by one minute. Still, it may be an important consideration for other scripts to preserve the matching time, to ensure accuracy.

Make sure to take a look at the other solutions. In particular, the Windows solution from Gordon Thiesfeld which makes use of an operating system module to get the last reboot time directly. Also, from Jesse Merriman is a solution that access the process file system.


Wednesday, February 04, 2009