One the Unix system I am used to have a command the reports on users called last
.
The format is a single line per log-on that contains the users name, the port used and the time
and duration of the session. A short section of the report looks like this;
jjenkins tty6 :0 Wed Jun 1 00:00 - 01:09 (01:09) hbrown tty3 :0 Wed Jun 1 01:18 - 01:22 (00:04) kdavis tty6 :0 Wed Jun 1 01:26 - 02:08 (00:42)
From this short segment we can see that the user jjenkins logged in at midnight on June first. They were on until 1:09am for a total time of an hour and nine minutes.
This is a nice report about individual sessions but a summary by user and port useful. In the early 90s I was working for a small Internet Service Provider. We needed a summary report and I was learning Perl. This problem is a good fit for Perl.
First comes the standard housekeeping. Using the strict pragma use strict;
helps to keep track of variables and avoid stupid mistakes.
Using strict means we have to declare variables my($log_file_name, $FH, %data);
. Then we open
the input file open($FH, '<', $log_file_name) || die("Cannot open $log_file_name $@");
. I like
to throw an error if the file does not open. There is not much to do if it fails to open. This script could
also be written to take input from a pipe or a file listed on the command line.
With the data source ready to go there are two tasks. The first is to read it in line by line. Then calculate the elapsed time in minutes. The final part of the fist step is to push the data into a useful data structure. The structure used is designed to make the second task easy. Using that data structure to print a report. The script reports three things for each of there categories. The first data point in each category is a total for minutes used the second is the total number of sessions and the last is the average time per session. This is all reported as a grand total, for each user and for each port.
The %data
hash contains all of the data. The data for time and number of sessions is saved
as an anonymous array. The average time can be computed from these two numbers. This uses Perl's ability
to store references to data elements any place that accepts a scalar. If you need to read up on this technique
go to the perlreftut tutorial. For the total category this
anonymous array is just built using a scalar in the data hash
$data{'total'}->[0] += $time_minutes; $data{'total'}->[1]++;
. For the user and port categories
things get a little more complicated. An anonymous array for each user or port is built as part of an
anonymous hash using the names as keys$data{'user'}->{$record{'user'}}[0] += $time_minutes; $data{'user'}->{$record{'user'}}[1]++;
. This looks a little cumbersome, but it makes printing the report very easy.
Print the total category is very easy. We just pass some values to printf
printf(" Total - %5i / %3i = %6.2f\n", $data{'total'}->[0], $data{'total'}->[1], $data{'total'}->[0] / $data{'total'}->[1]);
.
For the users and ports we need to use a loop of all of the names stored as keys in the anonymous hashs. It is really easier
then it sounds. The only tricky part is dereferencing the values for the names. There is a special form
foreach my $user (sort keys %{$data{'user'}})
for that. With this notation the keys
function works as it would with any other hash. The only difference in the actual print is that the name is
passed as a value to printf.
The first time I wrote this script I did not know about Perl's ability to use complicated data structures. As with any code written a long way in the past, nobody really wants to see that code. This version is much better. It is simple to understand and easy to maintain and could be a good base for additional study. There are a lot of tasks that could be done with variations of this script.