The boot time and uptime of a Linux computer are available from the
/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.