|
|
The following example provides several cooperating facilities to implement a simple connection statistics system. The module includes a trace to collect the statistics on each connection, a scheduled procedure to aggregate the statistics, the /NS/Stat request function to print the statistics, and a Tcl command to access the statistics in a script.
This example can be found in the examples/c/stats directory.
/*
* stat.c - Example C module.
*
* This example incorporates many of the features of the AOLserver
* C API in a single simple, but useful, statistics gathering module.
* The module maitains statistics on the number of connections and
* bytes sent by a server. The data is stored in a simple
* `StatContext' structure which is allocated when the module is
* loaded. The StatContext stores the current, last, and total numbers
* and is updated after each connection by the `StatTrace' connection
* trace procedure. At a regular interval (the interval in seconds
* is configurable), the `StatUpdate' funcation is called to log the
* current and total number to the AOLserver log file and then
* makes the current data the last data. Access to these data is
* provided in two forms:
*
* 1. A simple C request function, `StatRequest', will return the
* current data as a simple notice page. The StatRequest function
* is registered at the GET /NS/Stat URL of the server.
*
* 2. The `ns_stat' Tcl command is added to each Tcl interpreter
* of the server interpreter pool. This allows a Tcl script
* to access the current, last, or total data at any time.
*
* Because the AOLserver is completely multithreaded, the statistics
* data can be updated and queried simultaneously. An Ns_Mutex is
* included in the ServerContext structure to keep multiple threads
* from accessing the data at the same time.
*
* Finally, on shutdown, the `StatShutdown' function is called to
* print out a final tally and clean up the ServerContext structure.
*
* Note that all the features of this module are specific to the
* server it is loaded into. The server handle, `hServer',
* is used to makes sure the trace, request, Tcl command, and
* shutdown functions are all run in the context of the single
* server. Other virtaul servers may ignore or load the
* stat module as well and the AOLserver guarantees the data
* will be keep separate for each server.
*/
#include "ns.h"
#include "nstcl.h"
/*
* The default statistics update interval is 1 minute or 60 seconds.
*/
#define DEFAULT_INTERVAL 60
/*
* Definitions of the StatContext structure which is allocated
* on a per-server basis.
*/
typedef struct {
int bytes;
int conns;
} StatData;
typedef struct {
Ns_Mutex lock;
int interval;
char *hServer;
StatData current;
StatData last;
StatData total;
} StatContext;
/*
* Forward declarations of functions which will be referenced
* in the stat module initialization function.
*/
static Tcl_CmdProc StatCmd;
static Ns_TclInterpInitProc StatTclInit;
static Ns_TraceProc StatTrace;
static Ns_Callback StatUpdate;
static Ns_Callback StatShutdown;
static Ns_OpProc StatRequest;
/*
* The Ns_ModuleVersion exported integer is used to verify
* this module version when loaded. For AOLserver 2.0,
* 1 (one) is the only valid value for this variable.
*/
int Ns_ModuleVersion = 1;
/*
* The Ns_ModuleInit function is the function the AOLserver
* will call each time the module is loaded into a
* server. The function is passed two parameters:
*
* hServer: The server `handle' as a string. This is the
* short name given to the virutal server such
* as `server1'.
*
* hModule: The module `handle' as a string. This is the
* short name given to the module such as `stat'
*
* For example, if this module is known as `stat' and loaded
* into the `server1' server with entries similar to the following
* in the nsd.ini file:
*
* [ns\servers]
* server1=My First Server
*
* [ns\server1\modules]
* stat=stat.dll
*
* This function would be called with "server1" and "stat" as
* its arguments.
*
*/
int
Ns_ModuleInit(char *hServer, char *hModule)
{
char *configPath;
StatContext *ctx;
/*
* Create and initalize the statistics context.
*/
ctx = ns_malloc(sizeof(StatContext));
Ns_InitializeMutex(&ctx->lock);
ctx->hServer = hServer;
ctx->current.bytes = 0;
ctx->current.conns = 0;
ctx->last.bytes = 0;
ctx->last.conns = 0;
ctx->total.bytes = 0;
ctx->total.conns = 0;
/*
* Determine the statistics interval from the config file.
* The Ns_ConfigGetPath function will expand to the
* `server module specific' configuration section, e.g.,
* [ns\server1\module\stat].
*/
configPath = Ns_ConfigGetPath(hServer, hModule, NULL);
if (!Ns_ConfigGetInt(configPath, "Interval", &ctx->interval)) {
ctx->interval = DEFAULT_INTERVAL;
}
/*
* Register the trace to accumulate the statistics.
*/
Ns_RegisterServerTrace(hServer, StatTrace, ctx);
/*
* Register the statistics update function to run at
* regular intervals.
*/
Ns_ScheduleProc(StatUpdate, ctx, 0, ctx->interval);
/*
* Add the GET /NS/Stat request function.
*/
Ns_RegisterRequest(hServer, "GET", "/NS/Stat", StatRequest,
NULL, ctx, 0);
/*
* Add the Tcl "ns_stat" command to the interpreter pool.
* Note how the context is passed to StatTclInit which
* then passes it to the Tcl_CreateCommand function.
*/
Ns_TclInitInterps(hServer, StatTclInit, ctx);
/*
* Register the statistics shutdown procedure which
* cleans up the context on server shutdown.
*/
Ns_RegisterServerShutdown(hServer, StatShutdown, ctx);
return NS_OK;
}
/*
* StatTrace is called after each connection and accumulates the
* statistics.
*/
static void
StatTrace(void *ctx, Ns_Conn *conn)
{
StatContext *sc;
int bytes;
sc = (StatContext *) ctx;
Ns_LockMutex(&sc->lock);
bytes = Ns_ConnResponseLength(conn);
sc->current.bytes += bytes;
sc->total.bytes += bytes;
++sc->current.conns;
++sc->total.conns;
Ns_UnlockMutex(&sc->lock);
}
/*
* StatUpdate aggregates the statistics and logs the data to
* the AOLserver server log.
*/
static void
StatUpdate(void *ctx)
{
StatContext *sc;
sc = (StatContext *) ctx;
Ns_LockMutex(&sc->lock);
Ns_Log(Notice,
"StatUpdate(%s): Last: conns %d, bytes %d Total: conns %d, bytes %d",
sc->hServer, sc->current.conns, sc->current.bytes,
sc->total.conns, sc->total.bytes);
sc->last.conns = sc->current.conns;
sc->last.bytes = sc->current.bytes;
sc->current.conns = 0;
sc->current.bytes = 0;
Ns_UnlockMutex(&sc->lock);
}
/*
* StatTclInit is called once for each Tcl interpreter
* in the virutal server's Tcl interpreter pool.
*/
static int
StatTclInit(Tcl_Interp *interp, void *ctx)
{
Tcl_CreateCommand(interp, "ns_stat", StatCmd, ctx, NULL);
return NS_OK;
}
/*
* StatCmd implements the Tcl "ns_stat" command.
*/
static int
StatCmd(ClientData ctx, Tcl_Interp *interp, int argc, char **argv)
{
StatContext *sc;
StatData *sd;
sc = (StatContext *) ctx;
if (argc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " command\"", NULL);
return TCL_ERROR;
}
if (strcmp(argv[1], "current") == 0) {
sd = &sc->current;
} else if (strcmp(argv[1], "last") == 0) {
sd = &sc->last;
} else if (strcmp(argv[1], "total") == 0) {
sd = &sc->total;
} else {
Tcl_AppendResult(interp, "unknown command \"",
argv[1], "\": should be current, last, or total", NULL);
return TCL_ERROR;
}
Ns_LockMutex(&sc->lock);
sprintf(interp->result, "%d %d", sd->conns, sd->bytes);
Ns_UnlockMutex(&sc->lock);
return TCL_OK;
}
/*
* StatRequest is a simple AOLserver request function which returns
* the current data in a simple HTML page. The page is generated
* with the Ns_ConnReturnNotice() function which is used throughout the
* AOLserver to generate simple HTML page responses with the
* AOLserver banner logo. Ns_ConnReturnNotice() takes a string as
* the HTML page content. We use an Ns_DString to quickly build
* up the string - Ns_DString's grow as needed so we don't have
* to worry about buffer overflow. The HTML page is a simple
* HTML3 <TABLE> which formats the current, last, and total
* statistics for the server.
*/
static int
StatRequest(void *ctx, Ns_Conn *conn)
{
StatContext *sc;
Ns_DString ds;
int retcode;
sc = (StatContext *) ctx;
Ns_DStringInit(&ds);
/*
* Build up the HTML3 <TABLE> with the latest data.
*/
Ns_DStringAppend(&ds, "<table border cellpadding=\"10\">");
Ns_DStringVarAppend(&ds, "<tr><th>", sc->hServer, "</th>", NULL);
Ns_DStringAppend(&ds,
"<th>Current</th><th>Last</th><th>Total</th></tr>");
Ns_LockMutex(&sc->lock);
Ns_DStringPrintf(&ds,
"<tr><th># connections</th><td>%d</td><td>%d</td><td>%d</td></tr>",
sc->current.conns, sc->last.conns, sc->total.conns);
Ns_DStringPrintf(&ds,
"<tr><th># bytes</th><td>%d</td><td>%d</td><td>%d</td></tr>",
sc->current.bytes, sc->last.bytes, sc->total.bytes);
Ns_UnlockMutex(&sc->lock);
Ns_DStringAppend(&ds, "</table>");
/*
* Return the HTML page using Ns_ConnReturnNotice().
*/
retcode = Ns_ConnReturnNotice(conn, 200, "Server Statistics",
ds.string);
/*
* Don't forget to free the dstring!
*/
Ns_DStringFree(&ds);
return retcode;
}
/*
* StatShutdown simple prints a final statistics entry to the
* server log and then cleans up the StatContext structure.
*/
static void
StatShutdown(void *ctx)
{
StatContext *sc;
sc = (StatContext *) ctx;
Ns_Log(Notice, "StatShutdown(%s): Total: conns %d, bytes %d.",
sc->hServer, sc->total.conns, sc->total.bytes);
Ns_DestroyMutex(&sc->lock);
ns_free(ctx);
}