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); }