Systems Administration Toolkit: Monitor user usage

Explore new ways to record UNIX® logins and other system activities in a number of different logs, and take advantage of this information to monitor user usage. This can be helpful from a number of perspectives, either to use for chargeback reporting or just to get an idea of how busy and active individual users are on the system to help when planning and allocating resources.

Martin Brown (mc@mcslp.com), Freelance Writer, Consultant

Martin Brown has been a professional writer for more than seven years. He is the author of numerous books and articles across a range of topics. His expertise spans myriad development languages and platforms—Perl, Python, Java™, JavaScript, Basic, Pascal, Modula-2, C, C++, Rebol, Gawk, Shellscript, Windows®, Solaris, Linux, BeOS, Mac OS X and more—as well as Web programming, systems management, and integration. He is a Subject Matter Expert (SME) for Microsoft® and regular contributor to ServerWatch.com, LinuxToday.com, and IBM developerWorks. He is also a regular blogger at Computerworld, The Apple Blog, and other sites. You can contact him through his Web site.



23 October 2007

Also available in Chinese Russian

About this series

The typical UNIX® administrator has a key range of utilities, tricks, and systems he or she uses regularly to aid in the process of administration. There are key utilities, command-line chains, and scripts that are used to simplify different processes. Some of these tools come with the operating system, but a majority of the tricks come through years of experience and a desire to ease the system administrator's life. The focus of this series is on getting the most from the available tools across a range of different UNIX environments, including methods of simplifying administration in a heterogeneous environment.

Getting current user information

Getting a list of users currently using the UNIX system can be achieved in a number of different ways. The most obvious and straightforward is to use the who command. The who command returns a list of users currently logged in, the terminal they are connected to, the date they logged in and, if they are a remote user, the IP address of the hostname that they logged in from.

You can see an example in Listing 1 below.

Listing 1. Using the who command to return a list of users currently logged in
$ who 
mc         pts/2        Sep 12 14:29    (sulaco.mcslp.pri)
mcbrown    pts/3        Sep 12 14:37    (nautilus.mcslp.pri)

On some systems, there is also a quicker format available using the -q command-line option, as shown here in Listing 2.

Listing 2. The who command with the -q option
$ who -q
mc       mcbrown
# users=2

This output is similar to another command available on some systems called users, which just outputs the list of users with no count (see Listing 3).

Listing 3. The users command
$ users
mc  mcbrown

You can often find out more detailed information by adding the -a and -H command-line options. The -a option includes all of the most recent information from the /var/adm/utmpx file, which is used to record login information. The same file also records other events, such as the date and time of the boot and the last run level change. You'll be taking a closer look at the information stored in that file later in this article. The -H option adds header information to the columns of output. You can see an example of this in Listing 4.

Listing 4. The who command with the -a and -H options
$ who -aH
NAME       LINE         TIME          IDLE    PID  COMMENTS
   .       system boot  Sep 12 11:35
   .       run-level 3  Sep 12 11:35     3      0  S
zsmon           .       Sep 12 11:35  3:14    215
LOGIN      console      Sep 12 11:35  0:20    221
LOGIN      console      Sep 12 11:35  0:20    510       (:0)
mc       + pts/2        Sep 12 14:29   .      569       (sulaco.mcslp.pri)
mcbrown  + pts/3        Sep 12 14:37  0:12    675       (nautilus.mcslp.pri)

The first two lines give you the information about when the system was last booted and the date and details of the last run level change. The machine shown here is running Solaris, and it monitors the serial ports for logins using the zsmon daemon. Then you have two logins tagged to the console, one of these is the login for the active user, and the other is attached to an X Server (the :0 indicates the X Windows System screen reference).

The final two lines are remote logins using Secure Shell (SSH). For all active processes, you get a process ID, and that means you can identify the original user within the ps list by their ID.

The LINE column is the terminal the user is connected on; the console is obviously the main keyboard and monitor of the machine. The pts lines refer to pseudo-terminals created automatically to handle the remote SSH connections.

Of course, knowing who is on the local machine is interesting, but how about on other machines in your network?

Getting remote user information

There are two background services that provide remote user information, rusers and rwho, which are backed up by two daemons, rusersd (more commonly referred to as in.rusersd) and rwhod (also known as in.rwhod). These both rely on the Remote Procedure Call (RPC) protocol to share information across the network.

Traditionally, enabling rwhod and rusersd was seen as a wasteful use of network resources, because these daemons broadcast information over the network continually, using up bandwidth. On a network with a large number of hosts, the amount of information generated can be considerable. Today, the quantity of information generated is small in comparison to the speed of the network.

Neither rwhod or rusersd are available on all systems but, for those that provide the tool, it can be an easy way of getting the information you want. If the tools are not already running, you need to start up the in.rwhod and in.rusersd daemons.

Once the daemons are running, you have a number of tools available for outputting the information about all of the machines. The rwho tool is the remote and network-enabled version of the who tool. See Listing 5 for an example.

Listing 5. The rwho tool
$ rwho -a
mc       solaris-desktop:console Sep 12 11:29  3:41
mc       solaris-desktop:pts/1   Sep 12 11:32   :06
mc       ultra3:pts/2            Sep 12 14:29
mcbrown  ultra3:pts/3            Sep 12 14:37   :37
M

The rwhod tool broadcasts information, and it captures the broadcast information from other hosts and collates it into the /var/spool/rwhod directory.

The rusers tool sends a broadcast over the network for the rusersd daemon on the other hosts to return the information about the users currently logged in, as seen here in Listing 6.

Listing 6. The rusers tool
$ rusers
Sending broadcast for rusersd protocol version 3...
192.168.0.31 mc mcbrown
solaris-desktop. mc mc

Another tool available once the rwhod daemon is running is ruptime, which returns the loading and uptime information for all servers running rwhod in the local network. Listing 7 shows an example. This makes a really great tool for determining the status of your machines without having to log in to them individually.

Listing 7. The ruptime tool
$ ruptime
solaris-desktop  up     3:53,     1 user,   load 0.00, 0.00, 0.00
ultra3        up     3:46,     2 users,  load 0.00, 0.00, 0.00

For who and rwho, the information about who that is currently connected to your machine is recorded in the /var/adm/utmp file, and historical information about users that have logged in (and logged out) is in the wtmp machine. Both of these files contain a wealth of information, but the files are not available in an immediate format. Let's take a closer look at these files and how you can display that information.

Log files used for user activity

There are a number of files used to record user logins and activity. The three main files for recording this information are:

  • utmp—This file records the information about users currently logged in. It should contain one record for each logged in user.
  • wtmp—This file records all logins (and logouts) by users. On a busy system, this file is quite large, as it contains one login and one logout record. This file also contains some system-related log information, such as restart, shutdown, and date changes.
  • lastlog—This file records the last login time for each user. This file contains only one record for each user.

Once you know how and when the files are written, the relationship between these files is quite straightforward. The basic sequence is as follows:

  • When a user logs in, the lastlog file is opened, and their record with the login date and time is updated. Then the utmp is opened and their current login information is recorded. The login record (generally a copy of the information added to lastlog) is written to the utmp log to record the login information.
  • During logout, the login record written to utmp is deleted (since the user is no longer logged in), but a new record is written to wtmp to record the fact that the user has logged out.
  • The format of these files is entirely binary; there is no way to report the information without using a separate tool that reads and parses the information. In general, the basic format of the wtmp and lastlog files is the same, and they use a simple format to record the login time, the login line, and the hostname line information, as shown here in Listing 8, which shows the Solaris format.
Listing 8. Solaris format from wtmp
struct lastlog {
        time_t  ll_time;
        char    ll_line[UT_LINESIZE];
        char    ll_host[UT_HOSTSIZE];
};

The format is not the same on different operating systems, and you have to be careful to extract the right information. On AIX®, the utmp definition is shown in Listing 9.

Listing 9. utmp definition
struct utmp
{
   char   ut_user[8];
   char   ut_id[14]                    
   char   ut_line[12];
   short  ut_type;
   pid_t  ut_pid;                       
   struct exit_status
   {
     short    e_termination;           
     short    e_exit;
}

Listing 10 shows the definition of the value of the ut_type field from the record.

Listing 10. Definition of the value of the ut_type field
#define EMPTY              0
#define RUN_LVL            1
#define BOOT_TIME          2
#define OLD_TIME           3
#define NEW_TIME           4
#define INIT_PROCESS       5
#define LOGIN_PROCESS      6

         
#define USER_PROCESS       7
#define DEAD_PROCESS       8
#define ACCOUNTING         9
#define UTMAXTYPE ACCOUNTING

The username information is not included in the record because it is encoded according to the users ID. If the ID of the user that logs in is 1000, then the lastlog record can be located by accessing the record written at 1000 x sizeof (struct lastlog).

The utmp file structure is similar but, because it is a sequential log of login and logout and other event entries, the record includes the login information in Listing 11.

Listing 11. utmp file structure definition
struct utmp {
        char    ut_line[UT_LINESIZE];
        char    ut_name[UT_NAMESIZE];
        char    ut_host[UT_HOSTSIZE];
        time_t  ut_time;
};

The Linux® ut_type has similar value definitions, as shown here in Listing 12.

Listing 12. Linux ut_type definition
#define UT_UNKNOWN      0
#define RUN_LVL         1
#define BOOT_TIME       2
#define NEW_TIME        3
#define OLD_TIME        4
#define INIT_PROCESS    5
#define LOGIN_PROCESS   6
#define USER_PROCESS    7
#define DEAD_PROCESS    8
#define ACCOUNTING      9

And a much more complex structure definition, as shown here in Listing 13.

Listing 13. More complex utmp structure definition
struct utmp {
    short ut_type;              /* type of login */
    pid_t ut_pid;               /* PID of login process */
    char ut_line[UT_LINESIZE];  /* device name of tty - "/dev/" */
    char ut_id[4];              /* init id or abbrev. ttyname */
    char ut_user[UT_NAMESIZE];  /* user name */
    char ut_host[UT_HOSTSIZE];  /* hostname for remote login */
    struct exit_status ut_exit; /* The exit status of a process
                                   marked as DEAD_PROCESS */

/* The ut_session and ut_tv fields must be the same size when
    compiled 32- and 64-bit.  This allows data files and shared
    memory to be shared between 32- and 64-bit applications */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
    int32_t ut_session;         /* Session ID, used for windowing */
    struct {
        int32_t tv_sec;         /* Seconds */
        int32_t tv_usec;        /* Microseconds */
    } ut_tv;                    /* Time entry was made */
#else
    long int ut_session;        /* Session ID, used for windowing */
    struct timeval ut_tv;       /* Time entry was made */
#endif

    int32_t ut_addr_v6[4];       /* IP address of remote host */
    char __unused[20];           /* Reserved for future use */
};

Most systems come with simple tools to extract the information for you. To dump out the contents of the wtmp file, use the last command. This simply dumps out all the information (see Listing 14).

Listing 14. Dumping out the contents of the wtmp file using the last command
$ last
statmon   ftp      nautilus.mcslp.p Wed Sep 12 15:50 - 15:50  (00:00)
statmon   ftp      narcissus.mcslp. Wed Sep 12 15:50 - 15:50  (00:00)
statmon   ftp      nostromo.mcslp.p Wed Sep 12 15:50 - 15:50  (00:00)
statmon   ftp      sulaco.mcslp.pri Wed Sep 12 15:49 - 15:49  (00:00)
statmon   ftp      nautilus.mcslp.p Wed Sep 12 15:45 - 15:45  (00:00)
statmon   ftp      nostromo.mcslp.p Wed Sep 12 15:45 - 15:45  (00:00)
statmon   ftp      narcissus.mcslp. Wed Sep 12 15:45 - 15:45  (00:00)

You can often filter the information by user, host, or tty if you need to. For example, to get all of the logins by root, use what is shown in Listing 15.

Listing 15. Getting all logins by root
$ last root
root      console      :0   Mon Sep 25 11:32 - 11:32  (00:00)
root      console      :0   Mon Sep 25 11:27 - 11:27  (00:00)
root      console           Sat Sep  9 13:17 - 13:28  (00:11)
root      console           Sat Sep  9 10:47 - 13:14  (02:26)
root      console           Sun Sep  3 06:52 - down  (6+03:54)
root      console      :0   Sat Sep  2 14:24 - down   (16:27)
root      console           Sat Sep  2 08:02 - down   (06:13)
root      console           Fri Aug 25 17:16 - down   (00:39)
root      console           Sun Aug 20 16:04 - 16:04  (00:00)
root      console           Thu Jul 20 07:23 - 07:31  (00:07)
root      console           Thu Jul 20 07:22 - 07:23  (00:00)
root      console           Thu Jul 20 02:57 - 02:57  (00:00)
root      console           Wed Jul 19 12:22 - down   (05:38)
root      console           Wed Jul 19 12:10 - 12:19  (00:08)
root      console      :0   Wed Jul 19 12:05 - 12:09  (00:04)
root      console           Wed Jul 19 11:47 - 11:55  (00:07)

wtmp begins Wed Jul 19 09:54

Because the wtmp also contains information about reboots and shutdowns, you can list that information, too (see Listing 16).

Listing 16. Getting information about reboots and shutdowns
$ last reboot 
reboot    system boot                   Wed Sep 12 11:28
reboot    system down                   Wed Sep  5 12:16
reboot    system boot                   Mon Sep  3 13:03
reboot    system down                   Thu Mar  1 11:33
reboot    system boot                   Thu Mar  1 09:57
reboot    system down                   Thu Mar  1 08:12
reboot    system boot                   Thu Mar  1 08:05
reboot    system down                   Thu Mar  1 08:12
reboot    system boot                   Thu Mar  1 08:05
reboot    system down                   Thu Mar  1 08:03
reboot    system boot                   Thu Mar  1 08:02
reboot    system down                   Sun Dec 17 10:04
reboot    system boot                   Sun Dec 17 10:02
reboot    system down                   Mon Sep 25 11:44
reboot    system boot                   Mon Sep 25 10:2

The previous tools are fine, but what if you want to parse the information yourself? You could write a simple C program to extract the information but, in fact, you can also do it with Perl by using the unpack() function.

Parsing the binary log files

Using the unpack function within Perl requires creating a suitable packstring, which defines the datatypes that you are going to extract from the binary record data that you actually read from the raw binary utmp file.

Listing 17 shows a very simple Perl script that does a direct dump of the data in wtmp.

Listing 17. Perl script that dumps the data in wtmp
my $packstring = "a8a8a8ssssl";
my $reclength = length(pack($packstring));
my @ut_types = qw(EMPTY RUN_LVL BOOT_TIME OLD_TIME
                  NEW_TIME INIT_PROCESS LOGIN_PROCESS
                  USER_PROCESS DEAD_PROCESS ACCOUNTING);

open(D,"</var/log/wtmp") or die "Couldn't open wtmp, $!";

while(sysread(D,my $rec,$reclength))
{
    my ($user,$userid,$line,$pid,$type,$eterm,$eexit,$time)
        = unpack($packstring,$rec);
    print("$user, $userid, $line, $pid, $ut_types[$type], ",
          "$eterm, $eexit, ", scalar localtime($time),"\n");
}

close(D) or die "Couldn't close wtmp, $!";

Note that you have to read the data using sysread because you are reading raw binary packets (not lines). The packstring you use gets the username, userid, ttyline, PID, utmp type, terminal exit code, and time.

The first three fields are the important ones, as they contain the line, name, and host information. The size of these fields is conveniently defined in utmp.h (see Listing 18).

Listing 18. Size of the first three fields defined in utmp.h
#define UT_NAMESIZE     8
#define UT_LINESIZE     8
#define UT_HOSTSIZE     16

Note that you must get the size right, as it affects the information you read. If you have a Solaris or BSD system, it uses the simpler structure that was shown in Listing 8, so you need a different packstring and extracted fields, as shown here in Listing 19.

Listing 19. Data dump with Solaris or BSD system
my $packstring = "a8a8a16l";
my $reclength = length(pack($packstring));
my @ut_types = qw(EMPTY RUN_LVL BOOT_TIME OLD_TIME
                  NEW_TIME INIT_PROCESS LOGIN_PROCESS
                  USER_PROCESS DEAD_PROCESS ACCOUNTING);

open(D,"</var/log/wtmp") or die "Couldn't open wtmp, $!";

while(sysread(D,my $rec,$reclength))
{
    my ($line,$name,$host,$time)
        = unpack($packstring,$rec);
    print("$line, $name, $host,", scalar localtime($time),"\n");
}

close(D) or die "Couldn't close wtmp, $!";

Running this script gives you the login information for wtmp. The lines where you get only the line information are logouts rather than logins, as shown here in this fragment in Listing 20.

Listing 20. Getting login information for wtmp
ftp599, statmon, nautilus.mcslp.p,Wed Sep 12 16:00:13 2007
ftp599, , ,Wed Sep 12 16:00:14 2007
ftp4003, statmon, sulaco.mcslp.pri,Wed Sep 12 16:04:35 2007
ftp4003, , ,Wed Sep 12 16:04:35 2007
ftp4035, statmon, narcissus.mcslp.,Wed Sep 12 16:05:00 2007
ftp4035, , ,Wed Sep 12 16:05:00 2007
ftp4037, statmon, nostromo.mcslp.p,Wed Sep 12 16:05:01 2007
ftp4037, , ,Wed Sep 12 16:05:02 2007
ftp4057, statmon, nautilus.mcslp.p,Wed Sep 12 16:05:14 2007
ftp4057, , ,Wed Sep 12 16:05:14 2007

Summary

In this article, you've taken a quick look at what happens when you log in, how the logins are recorded into the UNIX system, and how you can use that information to determine who is logged on currently and who has been logged on in the past. You could use a modified version of the Perl script, for example, to provide total user-time information and charge it back to a user or department.

Resources

Learn

Get products and technologies

  • IBM trial software: Build your next development project with software for download directly from developerWorks.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into AIX and Unix on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=AIX and UNIX
ArticleID=263815
ArticleTitle=Systems Administration Toolkit: Monitor user usage
publish-date=10232007