Get the Boot Time of a Linux Machine

The boot time and uptime of a Linux computer are available from the /proc filesystem.
/proc/stat returns, among other information, the boot time in seconds since the Unix epoch.
/proc/uptime returns the number of seconds the system has been up.

Perhaps the easiest way to get this information is to execute the shell command cat and extract the time data from the output. Here is the output from a terminal session.

foo@bar:~$ cat /proc/uptime 
5377546.89 4696377.57

The first time is the uptime of the system; the second is the time the machine has spent idle.

Here is some REALbasic code to get the time.

dim s as new Shell
s.Execute "cat /proc/uptime"
dim uptimeSeconds as Double = Val(NthField(s.Result, " " , 1))
dim uptime as new Date
uptime.TotalSeconds = uptime.TotalSeconds - uptimeSeconds

Now let's refactor this by isolating the code that parses the shell output. This makes it easier to write unit tests for this bit of code, and to easily change the parsing code if, for instance, I learn how to use regular expressions.

Function ParseUptime(s as String) as String
    return NthField(s, " ", 1)
End Function

Now the code above becomes this.

dim s as new Shell
s.Execute "cat /proc/uptime"
dim uptimeSeconds as Double = Val(ParseUptime(s.Result))
dim uptime as new Date
uptime.TotalSeconds = uptime.TotalSeconds - uptimeSeconds

Something is still missing -- error checking. I expect @/proc/uptime@ to be available, but I cannot guarantee it. I don''t know how to handle an error, because I don't know how this code is to be used. So I raise a ShellError and let the caller deal with it.

dim uptime as Date
dim s as new Shell
s.Execute "cat /proc/uptime"
if s.ErrorCode = 0 then
    dim uptimeSeconds as Double = Val(ParseUptime(s.Result))
    uptime = new Date
    uptime.TotalSeconds = uptime.TotalSeconds - uptimeSeconds
else
    raise new ShellError(s)
end if

The error-checking logic is code that needs to be written for each invocation of Shell.Execute.
That is to say, it will be duplicated code, and duplication should usually be refactored out.

The part of the code that varies is the code that parses the shell output. It already lives in a separate method; thus we can pass it as a delegate.

So let us first define a module ShellExtension. To it, add the following delegate declaration.

Delegate Function ShellOutputParser(s as String) As String

Next is a method that extends the Shell class.

Function Execute(extends s as Shell, command as String, outputParser as ShellOutputParser=nil) As String
    if outputParser = nil then
        outputParser = TrivialParser
    end if

    s.Execute command
    if s.ErrorCode = 0 then
        return outputParser.Invoke(s.Result)
    else
        raise new ShellError(s)
    end if
End Function

The outputParser parameter has default value Nil. In this case, the method uses a trivial parsing function that returns the string passed to it, unchanged.

Private Function id(s as String) As String
    return s
End Function

The function TrivialParser wraps the construction of the delegate, and returns it.

Private Function TrivialParser() As ShellOutputParser
    return AddressOf id
End Function

I considered having this trivial parser trim trailing whitespace, but realized that was unnecessary, because I can simply pass the existing function RTrim.

Here is a final version of the code that gets the machine uptime.

dim uptime as new Date
dim s as new Shell
uptime.TotalSeconds = uptime.TotalSeconds - Val(s.Execute("cat /proc/uptime", AddressOf ParseUptime))

Finally, I was a little surprised that my code compiled, because the extension method overloads the existing method Shell.Execute, and uses a default parameter. My guess is that the presence of a return type means that the compiler finds no match when it first searches for a Shell method with the signature, then searches for extension methods and finds a match.

Were I feeling more cautious, I would change the name of the extension method. But it works now, and I like being able to use the same name as the existing method.