· Home · Programing · Simple Report Hack ·

Simple Report Hack

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.

Here is the entire script

Perl Code gen_wtmp_report.pl
#!/usr/bin/perl use strict; my($log_file_name, $FH, %data); $log_file_name = 'wtmp_sample.txt'; open($FH, '<', $log_file_name) || die("Cannot open $log_file_name $@"); while(my $line = <$FH>) { my(%record, $time_minutes); @record{'user', 'tty', 'not_needed', 'hours', 'sep', 'minutes'} = unpack('A9A13A44A2A1A2', $line); $time_minutes = $record{'hours'} * 60 + $record{'minutes'}; $data{'total'}->[0] += $time_minutes; $data{'total'}->[1]++; $data{'user'}->{$record{'user'}}[0] += $time_minutes; $data{'user'}->{$record{'user'}}[1]++; $data{'tty'}->{$record{'tty'}}[0] += $time_minutes; $data{'tty'}->{$record{'tty'}}[1]++; } print "\n----- Report -----\n"; printf(" Total - %5i / %3i = %6.2f\n", $data{'total'}->[0], $data{'total'}->[1], $data{'total'}->[0] / $data{'total'}->[1]); print "\n----- Users -----\n"; foreach my $user (sort keys %{$data{'user'}}) { printf("%8s - %5i / %3i = %6.2f\n", $user, $data{'user'}->{$user}[0], $data{'user'}->{$user}[1], $data{'user'}->{$user}[0]/$data{'user'}->{$user}[1]); } print "\n----- Ports -----\n"; foreach my $tty (sort keys %{$data{'tty'}}) { printf("%8s - %5i / %3i = %6.2f\n", $tty, $data{'tty'}->{$tty}[0], $data{'tty'}->{$tty}[1], $data{'tty'}->{$tty}[0]/$data{'tty'}->{$tty}[1]); }

Here is a link to some sample data to use with the script

wtmp_sample.txt

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.

· Home · Programing · Tinkering · Recreation · Thoughts · Site Map ·