Chapter 19

The CPU Monitor Plug-In


CONTENTS


Introduction

Most UNIX and NT Web servers have an interesting system-level command called vmstat. This operating system utility provides a plethora of system-level information such as thread status, virtual memory statistics, interrupts, system calls, context switches, and CPU usage-all the important performance data for a preemptive multitasking operating system. This chapter is about the CPU Monitor plug-in sample, which uses vmstat to retrieve Web Server CPU statistics.

The CPU Monitor works in conjunction with a CGI program to display a bar chart in a Navigator Web page showing relative user, system, and idle CPU time within a time slice specified by the vmstat command. The plug-in runs a CGI program by calling it with NPN_GetURL. The CGI program in turn runs vmstat and output is sent back to the Navigator.

This plug-in is file-based; that is, it gets data from a Navigator cached file. The plug-in API NPP_StreamAsFile is used, rather than NPP_WriteReady and NPP_Write as in the audio plug-in sample.

Where to Find the Files

All the necessary files for building NPCPUMON.DLL are located on the CD-ROM. These files are in the \CODE\CPUMON, \CODE\INC, and \CODE\COMMON directories. You will need the files shown in the following lists.

For the directory \CODE\CPUMON, you need these files:

For the directory \CODE\INC, you need these files:

For the directory \CODE\COMMON, you need these files:

The Design of the CPU Monitor Plug-In

The CPU Monitor plug-in is a much simpler Windows 95 plug-in implementation than the audio sample plug-in. Because no threads or callbacks are used, synchronization is not necessary. The CPU monitor does not process data on the fly with NPP_WriteReady and NPP_Write, but instead uses NPP_StreamAsFile. A filename is passed via this API that is opened and read using the standard I/O functions fopen and fgets.

This plug-in actually runs a piece of software on the HTTP server using the Common Gateway Interface (CGI), which is a small program written in C, via the NPN_GetURL plug-in API. The program kicks off vmstat with a one second interval. Standard output is routed back to the Navigator, as it is with any CGI program.

When vmstat's data comes back, the plug-in displays the CPU usage in a bar chart on the plug-in window. A button called Update is also on this window. When you click this button, it repeats the process, runs the CGI program with NPN_GetURL, and displays the results on the window.

As you can see in Figure 19.1, two classes are used for this plug-in: CCpuMon and CPluginWindow. You might remember CPluginWindow from the audio sample. This class has the same name but a slightly different implementation. The name is kept because, as in the audio sample, the resulting object is used to subclass the Navigator-created plug-in window. The CCpuMon class is the central class that handles calls from both CPluginWindow and NPP_ type APIs from the file npshell.cpp.

Figure 19.1 : The components of the CPU monitor plug-in.

About vmstat

The heart of the CPU monitor plug-in is the UNIX-based vmstat command. This command reports statistics about kernel threads, virtual memory statistics, interrupts, system calls, context switches, and CPU usage. The sample plug-in uses only CPU usage statistics. You might be interested in adding page faults or system call statistics as a programming exercise.

The CPU monitor CGI program runs vmstat for a one second interval with two reports. The first report contains the statistics since system startup, and the second report contains statistics for the requested time interval. It is run like this:

vmstat 1 2

In this statement, 1 is one second and 2 is two reports.

It takes at least one second for the vmstat program to finish, because it must calculate the statistics over a one second interval. When it is completed, the output might look something like this:

procs     memory              page               disk       faults     cpu
r b w   avm   fre  re at  pi  po  fr  de  sr d0 s1 s2 s3  in  sy  cs us sy id
0 0 0     0  1280   0  5   3   3   1   0  16  4  8 10  0 223 607 134 20 25 55
0 0 0     0  1280   0  0  32   0   0   0   0  0  1  0  0 112 319  79  3 10 87

Kind of cryptic isn't it? The CPU monitor plug-in only uses the last three numbers of the second report. These numbers are in the cpu section under us, sy, and id. In this case, us indicates user, sy is system, and id is idle. Because these three number always total 100, each is a percentage of total CPU time.

How vmstat Is Run with the CGI Program

A very basic C program runs vmstat using the system call. It looks like this:

#include <stdio.h>

#define    INTERVAL    1

main ()
{
    char buff[100];

    printf ("Content-type: text/plain%c%c", 10,10);
    fflush (stdout);

    printf ("VMSTAT %d second intervals.\n", INTERVAL);
    fflush (stdout);

    sprintf (buff, "vmstat %d 2",INTERVAL);

    system (buff);
}

It is necessary to flush standard output with fflush because the order of output might be wrong if you don't. For example, the Content-type: section of the header might come after the vmstat output. Notice that the content type (or MIME type) is text/plain. Why? It could be anything. The plug-in makes a call to NPN_GetURL with a NULL window parameter, which guarantees that the plug-in will get the resulting URL data regardless of the MIME type.

Take note of the header printf:

printf ("Content-type: text/plain%c%c", 10,10);

Those two decimal 10s produce two line feeds, which are needed for a proper HTTP header. After this header, any output that the program generates goes to the Web Browser. In this case, it is the vmstat output.

The Plug-In Entry Points

You'll notice a familiar file in the CPU monitor plug-in called npshell.cpp. This file contains the entry points for the plug-in. It is very much similar to the audio plug-in's npshell.cpp.

Of the standard plug-in API entry points, NPP_Initialize, NPP_Shutdown, NPP_WriteReady, and NPP_Print are not used by the plug-in.

NPP_New

During the NPP_New API, the plug-in allocates a CCpuMon object with the new operator, saves it to Netscape's NPP structure, and saves the mode. Nothing is done with the HTML attributes for embedded plug-ins.

//
// NPP_New - Create a new plug-in instance.
//
NPError NP_LOADDS NPP_New (NPMIMEType pluginType,
    NPP pInstance,
    uint16 mode,
    int16 argc,
    char* argn[],
    char* argv[],
    NPSavedData* saved)
{
    if (pInstance == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;

    // Create a new CCpuMon object

    CCpuMon* pCpuMon = new CCpuMon (pInstance);

    // Attach it to the instance structure

    pInstance->pdata = pCpuMon;

    // No window yet

    pCpuMon->pWindow = NULL;

    // Save our plug-in's mode

    pCpuMon->mode = mode;

    return NPERR_NO_ERROR;
}

NPP_Destroy

The NPP_Destroy method is typical of the audio plug-in in which the window is unsubclassed, and then its cleanup method is called and deleted. After that, the CCpuMon object is closed and deleted:

//
// NPP_Destroy - Destroy our plug-in instance.
//
NPError NP_LOADDS NPP_Destroy (NPP pInstance, NPSavedData** save)
{
    CCpuMon* pCpuMon = (CCpuMon *)pInstance->pdata;

    if (pCpuMon)
    {
        if (pCpuMon->pWindow)
        {
            // Unsubclass the window, clean it up and delete it.

            pCpuMon->pWindow->UnsubclassWindow ();
            pCpuMon->pWindow->CleanupWindow();

            delete pCpuMon->pWindow;
        }

        pCpuMon->Close ();

        delete pCpuMon;
        pInstance->pdata = NULL;
    }
    return NPERR_NO_ERROR;
}

NPP_SetWindow

The NPP_SetWindow method is identical to the audio plug-in sample. Instance data is retrieved, and during the first call, a CPluginWindow object is created and subclassed to the Navigator- created plug-in window. After this, the window is initialized with a call to CPluginWindow::InitWindow.

//
// NPP_SetWindow - A window was created, resized, or destroyed.
//
NPError NP_LOADDS NPP_SetWindow (NPP pInstance, NPWindow* window)
{
    if (!window)
        return NPERR_GENERIC_ERROR;

    if (!pInstance)
        return NPERR_INVALID_INSTANCE_ERROR;

    // Get instance data

    CCpuMon* pCpuMon = (CCpuMon *)pInstance->pdata;

    if (!pCpuMon)
        return NPERR_GENERIC_ERROR;

    // Spurious entry - just return

    if (!window->window && !pCpuMon->pWindow)
        return NPERR_NO_ERROR;

    // Window should have been destroyed, but because of a bug in
    // Navigator, we consider this a spurious entry.

    if (!window->window && pCpuMon->pWindow)
        return NPERR_NO_ERROR;

    if (!pCpuMon->pWindow && window->window)
    {
        // Create our plug-in's window class and
        // subclass it to Navigators.

        pCpuMon->pWindow = new CPluginWindow (pCpuMon);

        BOOL rc = pCpuMon->pWindow->SubclassWindow ((HWND)window->window);

        // Init window and give the object a pointer to
        // the cpu monitor object.

        pCpuMon->pWindow->InitWindow ();
    }

    // Redraw the window.

    pCpuMon->pWindow->InvalidateRect (NULL);
    pCpuMon->pWindow->UpdateWindow();

    return NPERR_NO_ERROR;
}

NPP_NewStream

In NPP_NewStream, the stype is set to NP_ASFILE. This allows the use of NPP_StreamAsFile. The CCpuMon object is opened with CCpuMon::Open.

//
// NPP_NewStream - A new stream was created.
//
NPError NP_LOADDS NPP_NewStream(NPP pInstance,
    NPMIMEType type,
    NPStream* pStream,
    NPBool seekable,
    uint16* stype)
{
    if(!pInstance)
        return NPERR_INVALID_INSTANCE_ERROR;

    CCpuMon* pCpuMon = (CCpuMon *)pInstance->pdata;

    *stype = NP_ASFILE;

    if (pCpuMon)
        pCpuMon->Open (pStream);

    return NPERR_NO_ERROR;
}

NPP_WriteReady and NPP_Write

Although NPP_WriteReady and NPP_Write are not really used, this plug-in returns a large value to NPP_WriteReady so that the Navigator will write data. NPP_Write just returns the length written and does nothing with the data. You can still use these methods even with a file-based plug-in.

//
// NPP_WriteReady - Returns amount of data we can handle for the next NPP_Write
//
int32 NP_LOADDS    NPP_WriteReady (NPP pInstance, NPStream *stream)
{
    return 0x0FFFFFFF;
}

//
// NPP_Write
//
int32 NP_LOADDS NPP_Write (NPP pInstance,
    NPStream *stream,
    int32 offset,
    int32 len,
    void *buffer)
{
    return len;
}

NPP_StreamAsFile

The plug-in has been waiting for the name of the file containing data in Navigator's file cache. Here CCpuMon::GotFileName is called to process this file.

//
// NPP_StreamAsFile
//
void NP_LOADDS NPP_StreamAsFile (NPP pInstance,
    NPStream* stream,
    const char* fname)
{
    CCpuMon* pCpuMon = (CCpuMon *)pInstance->pdata;

     if (pCpuMon)
        pCpuMon->GotFileName (fname);
}

NPP_DestroyStream

CCpuMon::EndOfStream is called during NPP_DestroyStream. This plug-in doesn't really care when the stream ends. The call was made for any future implementation.

//
// NPP_DestroyStream
//
NPError NP_LOADDS NPP_DestroyStream (NPP pInstance,
    NPStream *stream,
    NPError reason)
{
     CCpuMon* pCpuMon = (CCpuMon *)pInstance->pdata;

     if (pCpuMon)
        pCpuMon->EndOfStream ();

    return NPERR_NO_ERROR;
}

The Main Object: CCpuMon

The CCpuMon class is the central controller for the plug-in. Its methods are called from both npshell.cpp and CPluginWindow. Some of these methods are just stubs for future code. The methods are as follows:

CCpuMon::CCpuMon
CCpuMon::~CCpuMon
CCpuMon::Open
CCpuMon::EndOfStream
CCpuMon::GotFileName
CCpuMon::RequestUpdate
CCpuMon::Close

Construction and Destruction

The constructor for CCpuMon gets a pointer to the Navigator instance structure, NPP. This reference is needed for a future call to NPN_GetURL. The destructor does nothing.

//
// Constructor - Initialize variables. Get the instance pointer for any
//               NPN type calls.
//
CCpuMon::CCpuMon (NPP pInstance)
{
    this->pInstance = pInstance;

    bRunningCGIProgram = FALSE;
}

//
// Destructor
//
CCpuMon::~CCpuMon ()
{
}

Open

The Open method is called from npshell.cpp during the NPP_NewStream API. This method saves a pointer to the stream instance structure, NPStream. The stream reference is never used.

//
// Open - Get ready for data, called from Netscape's NPP_NewStream
//
BOOL CCpuMon::Open (NPStream* pStream)
{
    this->pStream = pStream;

    return TRUE;
}

EndOfStream

The CCpuMon::EndOfStream method is called during the NPP_DestroyStream API. It is not currently used.

//
// EndOfStream - Called from NPP_DestroyStream. The stream is done. Our plug-in
//               has all the data.
//
BOOL CCpuMon::EndOfStream (void)
{
    // CPU Monitor is file based, just return.

    return TRUE;
}

Getting the Filename

A call to the method CCpuMon::GotFileName is made during the NPP_StreamAsFile API. The filename and path are provided during the call. This file can live in either Netscape's file cache or another directory, depending on where the file was opened. If the file was opened locally, there is no need to cache and the real file path is given.

During the life of the CPU Monitor plug-in, GotFileName is called at least two times. In both cases, the file is first opened using fopen. The first file is the one that started the plug-in. This file must have the appropriate MIME type extension for this plug-in. Within this file must be the URL of the CGI program. This URL is retrieved using fgets, after which the newline character is stripped. The URL is stored in ProgramURL, and NPN_GetURL is called.

Note
Because this is just a sample plug-in, it uses the MIME type of application/x-tex. Normally, you would add a new MIME type for this plug-in, but if you don't have access to an HTTP server, you can use any MIME type for testing purposes.

The plug-in knows that it is the second call or greater when the bRunningCGIProgram flag is set to TRUE. If it is set to TRUE, CPluginWindow::UpdateChart is called to draw the CPU bar chart.

//
// GotFileName - Called from NPP_StreamAsFile with a local filename
//
BOOL CCpuMon::GotFileName (const char* fname)
{
    // Open the file in ASCII read only

    FILE* fp = fopen (fname, "r");

    if (fp)
    {
        if (bRunningCGIProgram)
        {
            // CGI program is running, update the chart

            pWindow->UpdateChart (fp);
            fclose (fp);

            return TRUE;
        }

        if (fgets (ProgramURL, sizeof(ProgramURL), fp))
        {
            // The first file starts us. It must contain the URL
            // for the CGI program.

            fclose (fp);

            char* p = strchr(ProgramURL, '\n');  // Remove newline

            if (p)
                *p = NULL;

            // Flag the CGI program as running and get the URL

            bRunningCGIProgram = TRUE;

            NPError rc = NPN_GetURL (pInstance, ProgramURL, NULL);
        }
    }

    return TRUE;
}

Manually Updating the CPU Bar Chart

The user of the CPU monitor plug-in can continuously update the bar chart by clicking on the Update button. Each button click resolves to a CPluginWindow::OnUpdate, which then calls CCpuMon::RequestUpdate. This method calls NPN_GetURL with the CGI program's URL. Remember that NPN_GetURL is an asynchronous call, so multiple update requests can be queued. Try clicking the Update button really fast. It can be quite entertaining!

//
// RequestUpdate - Called from CPluginWindow::OnUpdate when the update button
//                 is clicked.
//
BOOL CCpuMon::RequestUpdate (void)
{
    if (!bRunningCGIProgram)
        return FALSE;

    NPError rc = NPN_GetURL (pInstance, ProgramURL, NULL);

    if (rc)
        return FALSE;

    return TRUE;
}

Close

CCpuMon::Close is not used, but it is provided for future implementation.

//
// Close - Called from NPP_Destroy. This plug-in instance is history. Free
//         all resources.
//
BOOL CCpuMon::Close (void)
{
    return TRUE;
}

The Window Object: CPluginWindow

The CPluginWindow object is used to subclass to the Navigator-created plug-in window. This object creates and deletes the Update button for user interaction. It parses the vmstat data and displays it on a bar chart in the window. The methods in this class are as follows:

CPluginWindow::CPluginWindow
CPluginWindow::~CPluginWindow
CPluginWindow::InitWindow
CPluginWindow::CleanupWindow
CPluginWindow::UpdateChart
CPluginWindow::DrawRects
CPluginWindow::OnPaint

Construction and Destruction

The constructor saves a pointer to CCpuMon for calls resulting from clicking the Update button. The vmstat structure is zeroed during construction. Nothing is done during destruction.

//
// Constructor
//
CPluginWindow::CPluginWindow (CCpuMon *pCpuMon)
{
    this->pCpuMon = pCpuMon;

    memset (&vmstat, 0, sizeof(VMSTAT));
}

//
// Destructor
//
CPluginWindow::~CPluginWindow()
{
}

Window Initialization and Cleanup

InitWindow and CleanupWindow are called from npshell.cpp. The InitWindow method creates a Cbutton for the Update button, and CleanupWindow deletes it.

//
// InitWindow - Create the button
//
void CPluginWindow::InitWindow()
{
    CRect rect(30,70,110,110);
    cbUpdate = new CButton();
    cbUpdate->Create("Update",
        BS_PUSHBUTTON | WS_VISIBLE,
        rect,
        this,
        ID_UPDATE_CHART);
}

//
// CleanupWindow - Delete the button
//
void CPluginWindow::CleanupWindow ()
{
    delete cbUpdate;
}

Parsing vmstat Data

UpdateChart is called from CCpuMon::GotFileName with a pointer to the file containing the raw vmstat output. A beep is produced so that the user knows when the update is complete. The vmstat data is generated from the CGI program discussed previously. The data looks something like this:

VMSTAT 1 second interval
procs     memory              page               disk       faults     cpu
r b w   avm   fre  re at  pi  po  fr  de  sr d0 s1 s2 s3  in  sy  cs us sy id
2 0 0     0   292   0  5   2   0   0   0  16  4  8 10  0 222 607 133 20 25 55
2 0 0     0   292   1  6  40  20  84   0  40  0  7  0  0 357 662 174 46 25 28

The first line in the data block is checked for the VMSTAT signature, after which the fifth line is retrieved by five calls to fgets:

//
// UpdateChart - Parse out the vmstat data
//
BOOL CPluginWindow::UpdateChart (FILE* fp)
{
    MessageBeep (0xFFFFFFFF);

    // Read five lines from the file

    for (int i=0; i<5; i++)
    {
        // First line should contain the VMSTAT signiture

        if (fgets (FileLine, sizeof(FileLine), fp))
        {
            if (i == 0)
                if (strncmp(FileLine, "VMSTAT", sizeof("VMSTAT")-1))
                    return FALSE;
        }
    }

After the data of interest is safely stored in FileLine, a Herculean call to sscanf parses the vmstat line:

    // Parse out the vmstat data

    sscanf (FileLine, "%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d
%d %d %d %d",
        &vmstat.r, &vmstat.b, &vmstat.w,    // Threads
        &vmstat.avm, &vmstat.fre,    // Memory
        &vmstat.re, &vmstat.at, &vmstat.pi, &vmstat.po,  // Pages
        &vmstat.fr, &vmstat.de,// Pages
  &vmstat.sr, &vmstat.d0, &vmstat.s1, &vmstat.s2, &vmstat.s3, // Pages
        &vmstat.interrupts, &vmstat.syscalls, &vmstat.contextswitch,  // Faults
        &vmstat.user, &vmstat.system, &vmstat.idle);    // CPU

Finally, the numbers are multiplied to make future math easier, and the DrawRects method is called to draw the bar chart.

    // Make the numbers bigger

    vmstat.user *= 4;
    vmstat.system *= 4;
    vmstat.idle *= 4;

    CDC* dc = GetDC ();

    DrawRects (dc);

    ReleaseDC (dc);

    return TRUE;
}

Drawing the Bar Chart

The method CPluginWindow::DrawRects first draws each colored rectangle in proportion to the CPU percentage. Brushes of red, green, and blue are created and selected into the device context. A call to CDC::Rectangle draws the colored rectangle:

//
// DrawRects - Draw the CPU Monitor bar chart and color keys
//
void CPluginWindow::DrawRects(CDC* dc)
{
    // Draw the bar chart

    CBrush br(RGB(0xFF,0,0));
    dc->SelectObject (br);
    dc->Rectangle (10, 10, vmstat.user+10, 60);

    CBrush br2(RGB(0,0xFF,0));
    dc->SelectObject (br2);
    dc->Rectangle (vmstat.user+10, 10, vmstat.user+vmstat.system+10, 60);

    CBrush br3(RGB(0,0,0xFF));
    dc->SelectObject (br3);
    dc->Rectangle (vmstat.user+vmstat.system+10, 10,
        vmstat.user+vmstat.system+vmstat.idle+10, 60);

After the proportional rectangles are drawn, color key rectangles are drawn:

    // Draw the color keys

    CRect rect(120,70,200,110);
    dc->SelectObject (br);
    dc->Rectangle (&rect);
    dc->SetBkColor (RGB(0xFF,0,0));
    dc->DrawText ("User", strlen("User"), &rect,
        DT_CENTER | DT_VCENTER | DT_SINGLELINE);

    rect.SetRect(210,70,290,110);
    dc->SelectObject (br2);
    dc->Rectangle (&rect);
    dc->SetBkColor (RGB(0,0xFF,0));
    dc->DrawText ("System", strlen("System"), &rect,
        DT_CENTER | DT_VCENTER | DT_SINGLELINE);

    rect.SetRect(300,70,380,110);
    dc->SelectObject (br3);
    dc->Rectangle (&rect);
    dc->SetBkColor (RGB(0,0,0xFF));
    dc->DrawText ("Idle", strlen("Idle"), &rect,
        DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}

Handling the Update Button

When the Update button is clicked, a call is made to OnUpdate. This method calls into CCpuMon::RequestUpdate with the previously saved pointer:

//
// OnUpdate - The "Update" button was clicked
//
void CPluginWindow::OnUpdate()
{
    if (pCpuMon)
        if (!pCpuMon->RequestUpdate ())
            AfxMessageBox ("Can't update now !");
}

Repainting the Window

The WM_PAINT message is handled by OnPaint in which the bar chart is redrawn:

//
// OnPaint - Redraw bar chart
//
void CPluginWindow::OnPaint()
{
    PAINTSTRUCT ps;

    CDC* dc = BeginPaint (&ps);

    DrawRects (dc);

    EndPaint (&ps);
}

Setting Up and Running the CPU Monitor Plug-In

The CPU Monitor plug-in requires quite a bit of setup to run. For one thing, you need have access to a Web Server's CGI file area. Then, upload the included unixprog.c and compile with cc or whatever is available. Move the executable to a CGI directory and determine what the correct URL path is to execute the program. This plug-in was developed using the following CGI program URL:

http://www.swcp.com/zan-bin/vmstat.cgi

After you have determined the correct URL to run the CGI program, create a text file with that URL in it. Currently, this plug-in uses the MIME type of application/x-tex, so the file extension should be .TEX. To use this MIME type, create the text file with this extension. You should end up with a URL looking something like this:

http://www.swcp.com/~zan/cpumon.tex

Now open cpumon.tex in the Navigator. The plug-in should load and immediately try to run the CGI program. You can embed the plug-in in a Web page. Here is a very simple example of the plug-in embedded in HTML code:

<html>
<body>

<h1 align=center>CPU Monitor Plug-in</h1>

<center><p><embed SRC=cpumon.tex WIDTH=420 HEIGHT=120></p></center>

</body>
</html>

The Navigator with an embedded version of the CPU Monitor plug-in should look something like the one shown in Figure 19.2. You can see the bar chart with different colors for User, System, and Idle CPU percentage in addition to the Update button.

Figure 19.2 : The CPU Monitor plug-in running in embedded mode.

Conclusion

The sample CPU Monitor plug-in shows a good example of using Navigator's NPP_GetURL API. This API communicates with a small CGI program written in C that executes the UNIX vmstat program. The output of vmstat is given back to the plug-in with a file in Netscape's cache. This output is parsed and displayed with a bar chart on the plug-in window.

What's Next?

The next chapter shows how to use the Microsoft Windows MCIWnd window class to create highly functional, yet simple to code, plug-ins. With very little code, this sample plug-in supports AVI video, WAV audio, and MIDI playback.