/*****************************************************************************/ /* Graph.c This module provides some core plotting and GIF image functions, and although it's a slight anomaly, activity statistic recording, for which the graphing is used to produce a GIF image of server activity used in a simple "snapshot" report. The activity report is potentially JavaScript enhanced. Works well with Netscape Navigator 3.0ff, slightly less well with Microsoft Internet Explorer 3.02ff. MSIE 3.02 doesn't seem to honour the "onFocus" and "onBlur" window events. ACTIVITY STATISTICS NOTES ------------------------- Server activity is gathered by accumlating all requests and bytes transmitted on a per-minute basis, along with the request peak for that minute. These statistics are kept in a two arrays of longwords, and one shorts for the peaks. The arrays are sized on a per-day basis, 1440 long/short words per-day, maximum 28 days (although that's an arbitrary number I can't imagine anyone usefully using this facility over a longer period - that becomes the provence of log analysis tools). The arrays are stored in a permananet global section allowing these statistics, and the fact of startups and shutdowns, to be stored across startups and shutdown. The index into the array is based on the day of activity (zero to whatever day it is), plus the hour and minute. Days are the VMS absolute day (day number from start of epoch). The index into the array is calculated using a modulas of the day of activity by the number of days in the array (hence ranges from zero to the number of days in the array) multiplied by the number of minutes in the day, plus the hour multiplied by sixty, plus the minute. ACTIVITY REPORT/PLOT NOTES -------------------------- Activity statistics are reported/plotted from an absolute day and hour BACK for a specified number of hours. That is, the date and hour specified is the date and hour the report/plot ends, the start is calculated as a relative offset backwards from that. Slightly curious, but the idea was for a quick snapshot of activity up until the current time, so default behaviour (without any parameters) is for the current hour, beginning at minute 00 and ending at minute 59. If a duration is specified it is the number of hours prior to current time, so a duration of 4 at time 11:15 ranges from 08:00 to 11:59! If a time is specified then it is the period leading up to that, etc. GIF/PLOTTING NOTES ------------------ The plot functions have been designed to be a general as possible, so although only used for activity reports at this stage another HTTPd use may be found for them sometime in the future! NOTE: The GIF functions in this module are not designed to be reentrant and cannot be used for multi-threaded processing. All processing for a single request must begin and end before returning from AST delivery. The GIF code in these functions is implemented in accordance with Compuserve's Graphic Interchange Format Programming Reference specification, version 89a, 31st July 1990. The LZW compression employed by the GIF algorithm is implemented using code derived from the PBM suite. This code is copyright by the original author and used within the specified licensing conditions, as noted immediately above the applicable functions. The graphing functions functions use a lazy and faster approach to plotting. Although only providing 16 colours (4 bits) it represents each pixel in 8 bits, thus trading off memory against ease of programming and to a certain extent plotting speed. VERSION HISTORY --------------- 29-SEP-2009 MGD activity ByteCount increased from long to quad word (this ripples through the code something chronic!) GraphActivityUpdate() is used to periodically update network network traffic activity statistics and now indexes statistics on current time rather than request 31-OCT-2007 MGD provide a slash-delimitted 'max-requests' that scales the Y axis (requests) allowing finer detail to be displayed refine zooming bugfix; X axis scaling for non-integral factors 21-SEP-2007 MGD bugfix; GraphActivityReport() uninitialised 'cptr' before use in processing '"form"-based query string' 11-JUL-2006 MGD see 'CRAZY' note in GraphActivityReport() 06-JUL-2006 MGD add request peak data (connections has been masquerading) 15-JUN-2005 MGD make 'ByteTotal' a quad for better calculating 'ByteMean' 04-SEP-2004 MGD adjustments for data stored in quadwords, removed (ancient) JavaScript checks for (ancient) browsers 30-OCT-2003 MGD bugfix; GraphActivityPlotBegin(), GraphActivityDataScan() signed/unsigned issue masking out request value 02-APR-2003 MGD bugfix; GraphActivityClearDay() again 15-OCT-2002 MGD bugfix; GraphActivityClearDay() 18-MAY-2002 MGD activity statistics stored in global section, record/display startup events and peak requests 06-JAN-2002 MGD refine instance support 05-AUG-2001 MGD support module WATCHing, bugfix; sscanf() from %d to %u bugfix; provide "elbow-room" in activity data storage 09-MAY-2000 MGD remove keep-alive paraphanelia 04-MAR-2000 MGD use FaolToNet(), et.al. 07-JAN-1998 MGD same bugfix (obviously wasn't); in GraphActivityClearDay() (and a plethora of other annoyances/problems ... bit of a holiday does you the world of good :^) 01-DEC-1997 MGD bugfix; in GraphActivityClearDay() 18-OCT-1997 MGD remove dependence on Unix time functions after irritating experience related to VMS 7.1, add JavaScript-driven descriptions to client-side map links 01-AUG-1997 MGD new for v4.3 (hope my plotting functions are not too brain-dead, I'm only a graphics novice, sigh!) */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include #include /* VMS header files */ #include #include #include #include /* application header files */ #include "wasd.h" #define WASD_MODULE "GRAPH" unsigned char GraphicRgbRed [] = { 000, 255, 000, 000, 255, 255, 000, 255, 192, 128, 000, 000, 128, 128, 128, 250 }; unsigned char GraphicRgbGreen [] = { 000, 000, 255, 000, 255, 000, 255, 255, 192, 000, 128, 000, 128, 000, 128, 240 }; unsigned char GraphicRgbBlue [] = { 000, 000, 000, 255, 000, 255, 255, 255, 192, 000, 000, 128, 000, 128, 128, 230 }; #define ACTIVITY_GRAPH_WIDTH 480 #define ACTIVITY_GRAPH_HEIGHT 200 #define ACTIVITY_GRAPH_ZOOM 5 /*********************/ /* functional macros */ /*********************/ /* Used with an absolute VMS day number. Provide the day number relative to the start of activity data collection. Will be in the range 0 ... maximum number of days in the activity data. If the day specified occurs before the current day/hour it will be folded back into the data so it is up to the using code to ensure reading data from there is legitimate. */ #define ACTIVITY_DATA_IDX(absday,hour) \ ((absday%ActivityNumberOfDays)*MINUTES_IN_DAY)+(hour*MINUTES_IN_HOUR) /******************/ /* global storage */ /******************/ BOOL GraphDebug = false; int ActivityConnectCurrent, ActivityConnectProcessing, ActivityNumberOfDays, ActivityTotalMinutes; ACTIVITY_GBLSEC *ActivityGblSecPtr; int GraphAbsDay, GraphicBitsPerPixel = 4, GraphicMaximumHeight = 1505, GraphicMaximumWidth = 1505; unsigned short GraphPrevDate; char ErrorGraphNotInit [] = "Activity statistics not initialized!", ErrorGraphPeriod [] = "Period problem.", ErrorGraphQuery [] = "Query not understood", ErrorGraphFuture [] = "Future history!", ErrorGraphHistory [] = "Too far back in history!"; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern BOOL CliGblSecDelete, CliGblSecNoPerm; extern int ActivityGblSecVersion, GblPageCount, GblPagePermCount, GblSectionCount, GblSectionPermCount, InstanceEnvNumber, InstanceNodeCurrent; extern unsigned long GblSecPrvMask[], HttpdBinTime[]; extern unsigned short HttpdNumTime[]; extern char ServerHostPort[], WasdCss[]; extern CONFIG_STRUCT Config; extern HTTPD_PROCESS HttpdProcess; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Initialize the per-minute activity statistics data arrays. If only one instance can execute (from configuration) then allocate a block of process-local dynamic memory and point to that. If multiple instances create and map a global section and point to that. */ GraphActivityGblSecInit () { static char ReportActivityPages [] = "%HTTPD-I-ACTIVITY, !AZ global section of !UL page(let)s\n", ReportActivityWarning [] = "%HTTPD-W-ACTIVITY, error mapping !UL page(let)s (disabled)\n-!&M\n"; /* global, allocate space, system, in page file, writable */ static int CreFlags = SEC$M_GBL | SEC$M_EXPREG | SEC$M_SYSGBL | SEC$M_PAGFIL | SEC$M_PERM | SEC$M_WRT; static int DelFlags = SEC$M_SYSGBL; /* system & owner full access, group and world no access */ static unsigned long ProtectionMask = 0xff00; /* it is recommended to map into any virtual address in the region (P0) */ static unsigned long InAddr [2] = { 0x200, 0x200 }; int status, attempt, BaseGblSecPages, BytesRequired, GblSecPages, PageCount; short ShortLength; unsigned long RetAddr [2]; char GblSecName [32]; $DESCRIPTOR (GblSecNameDsc, GblSecName); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityGblSecInit()"); FaoToBuffer (GblSecName, sizeof(GblSecName), &ShortLength, GBLSEC_NAME_FAO, HTTPD_NAME, ACTIVITY_GBLSEC_VERSION_NUMBER, InstanceEnvNumber, "ACTIVITY"); GblSecNameDsc.dsc$w_length = ShortLength; if (CliGblSecDelete) { /* delete the specified global section */ sys$setprv (1, &GblSecPrvMask, 0, 0); status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0); sys$setprv (0, &GblSecPrvMask, 0, 0); return (status); } if (Config.cfMisc.ActivityNumberOfDays <= 0) { ActivityTotalMinutes = 0; return; } ActivityNumberOfDays = ACTIVITY_DAYS; ActivityTotalMinutes = ActivityNumberOfDays * MINUTES_IN_DAY; GblSecPages = sizeof(ACTIVITY_GBLSEC) / 512; if (GblSecPages & 0x1ff) GblSecPages++; /* do not create a permanent global section */ if (CliGblSecNoPerm) CreFlags &= ~SEC$M_PERM; for (attempt = 1; attempt <= 2; attempt++) { /* create and/or map the global section */ sys$setprv (1, &GblSecPrvMask, 0, 0); status = sys$crmpsc (&InAddr, &RetAddr, 0, CreFlags, &GblSecNameDsc, 0, 0, 0, GblSecPages, 0, ProtectionMask, GblSecPages); sys$setprv (0, &GblSecPrvMask, 0, 0); PageCount = (RetAddr[1]+1) - RetAddr[0] >> 9; ActivityGblSecPtr = (ACTIVITY_GBLSEC*)RetAddr[0]; if (VMSnok (status) || status == SS$_CREATED) break; /* section already exists, break if 'same size' and version! */ if (PageCount >= GblSecPages && ActivityGblSecPtr->GblSecVersion == ActivityGblSecVersion) break; /* delete the current global section, have one more attempt */ sys$setprv (1, &GblSecPrvMask, 0, 0); status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0); sys$setprv (0, &GblSecPrvMask, 0, 0); status = SS$_IDMISMATCH; } if (VMSnok (status)) { /* just disable it */ ActivityTotalMinutes = 0; FaoToStdout (ReportActivityWarning, GblSecPages, status); return (status); } if (status == SS$_CREATED) { /* first time it's been mapped */ FaoToStdout (ReportActivityPages, "created", PageCount); InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); memset (ActivityGblSecPtr, 0, PageCount * 512); ActivityGblSecPtr->GblSecVersion = ActivityGblSecVersion; sys$gettim (&ActivityGblSecPtr->StartBinTime); lib$day (&ActivityGblSecPtr->StartAbsDay, &ActivityGblSecPtr->StartBinTime, &ActivityGblSecPtr->StartMinute); /* adjust from ten milli-second to one minute units */ ActivityGblSecPtr->StartMinute = ActivityGblSecPtr->StartAbsDay * MINUTES_IN_DAY + ActivityGblSecPtr->StartMinute / 6000; InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); } else FaoToStdout (ReportActivityPages, "existing", PageCount); if (CliGblSecNoPerm) { GblSectionCount++; GblPageCount += PageCount; } else { GblSectionPermCount++; GblPagePermCount += PageCount; } return (status); } /*****************************************************************************/ /* Resets to zero the activity request and byte acumulators for all days between the last day that was cleared and this day. Relies on the calling routine(s) to have locked the activity global section. */ GraphActivityClearDay () { int idx, status; unsigned long AbsDay, Day; unsigned long BinTime [2]; unsigned short NumTime [7]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityClearDay()"); sys$gettim (&BinTime); lib$day (&AbsDay, &BinTime, 0); /* just return if there are no intervening days to be cleared */ if (AbsDay == ActivityGblSecPtr->ClearAbsDay) return; Day = ActivityGblSecPtr->ClearAbsDay; if (!Day) Day = AbsDay; Day++; while (Day <= AbsDay) { idx = ACTIVITY_DATA_IDX (Day, 0); memset (&ActivityGblSecPtr->ByteCount[idx], 0, MINUTES_IN_DAY * (sizeof(unsigned long)+sizeof(unsigned long))); memset (&ActivityGblSecPtr->ConnectPeak[idx], 0, MINUTES_IN_DAY * sizeof(unsigned short)); memset (&ActivityGblSecPtr->RequestCount[idx], 0, MINUTES_IN_DAY * sizeof(unsigned long)); memset (&ActivityGblSecPtr->RequestPeak[idx], 0, MINUTES_IN_DAY * sizeof(unsigned short)); Day++; } ActivityGblSecPtr->ClearAbsDay = AbsDay; /* check if the data buffer has "wrapped around" */ if (AbsDay >= ActivityGblSecPtr->StartAbsDay + ActivityNumberOfDays) { /* yep, data is now available from a new start date at midnight */ sys$gettim (&ActivityGblSecPtr->StartBinTime); if (VMSnok (status = GraphActivityOffsetTime (-(ActivityTotalMinutes-MINUTES_IN_DAY), &ActivityGblSecPtr->StartBinTime, &BinTime))) ErrorExitVmsStatus (status, "GraphActivityOffsetTime()", FI_LI); sys$numtim (&NumTime, &BinTime); NumTime[3] = NumTime[4] = NumTime[5] = NumTime[6] = 0; if (VMSnok (status = lib$cvt_vectim (&NumTime, &ActivityGblSecPtr->StartBinTime))) ErrorExitVmsStatus (status, "lib$cvt_vectim()", FI_LI); lib$day (&ActivityGblSecPtr->StartAbsDay, &ActivityGblSecPtr->StartBinTime, 0); ActivityGblSecPtr->StartMinute = ActivityGblSecPtr->StartAbsDay * MINUTES_IN_DAY; } } /*****************************************************************************/ /* Adjusts per-minute activity statistics. Relies on the calling routine to lock the activity global section. 1) Called as a final update by RequestEndEnd() when concluding each request. 2) Called periodically by HttpdSupervisor() to update the network transfered bytes in the activity accumulator for the minute. */ GraphActivityUpdate ( REQUEST_STRUCT *rqptr, BOOL FinalUpdate ) { int idx; long Day; unsigned long BytesRawRx [2], BytesRawTx [2]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityUpdate()"); if (!ActivityTotalMinutes) return; /* save the overhead of the lib$day() call if the date has not changed! */ if (HttpdNumTime[2] != GraphPrevDate) { GraphActivityClearDay (); lib$day (&GraphAbsDay, &HttpdBinTime, 0); GraphPrevDate = HttpdNumTime[2]; } Day = GraphAbsDay % ActivityNumberOfDays; idx = Day * MINUTES_IN_DAY + HttpdNumTime[3] * MINUTES_IN_HOUR + HttpdNumTime[4]; if (FinalUpdate) { ActivityGblSecPtr->RequestCount[idx]++; if (ActivityConnectCurrent > ActivityGblSecPtr->ConnectPeak[idx]) ActivityGblSecPtr->ConnectPeak[idx] = ActivityConnectCurrent; if (ActivityConnectProcessing > ActivityGblSecPtr->RequestPeak[idx]) ActivityGblSecPtr->RequestPeak[idx] = ActivityConnectProcessing; } PUT_QUAD_QUAD (rqptr->BytesRawRx, BytesRawRx); SUB_QUAD_QUAD (rqptr->BytesAccountedForRx, BytesRawRx); ADD_QUAD_QUAD (BytesRawRx, ActivityGblSecPtr->ByteCount[idx]); PUT_QUAD_QUAD (rqptr->BytesRawTx, BytesRawTx); SUB_QUAD_QUAD (rqptr->BytesAccountedForTx, BytesRawTx); ADD_QUAD_QUAD (BytesRawTx, ActivityGblSecPtr->ByteCount[idx]); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL-!UL=!SL !UL !UL !UL !@SQ[!UL] !UL[!UL] !UL[!UL]", ActivityGblSecPtr->StartAbsDay, GraphAbsDay, ActivityGblSecPtr->StartAbsDay - GraphAbsDay, rqptr->rqTime.VmsVector[3], rqptr->rqTime.VmsVector[4], Day, ActivityGblSecPtr->ByteCount[idx], idx, ActivityGblSecPtr->RequestCount[idx] & ACTIVITY_MASK, idx, ActivityGblSecPtr->ConnectPeak[idx], idx); } /*****************************************************************************/ /* Place a high-order bit indicating some server event (startup, shutdown, restart or error-induced exit) into request storage for the specific second */ GraphActivityEvent (unsigned long EventBit) { int idx; long Day; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityEvent() !&X", EventBit); if (!ActivityTotalMinutes) return; /* avoid using a mutex just in case it's the cause of the error! */ if (EventBit != ACTIVITY_EXIT_ERROR) InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); /* save the overhead of the lib$day() call if the date has not changed! */ if (HttpdNumTime[2] != GraphPrevDate) { GraphActivityClearDay (); lib$day (&GraphAbsDay, &HttpdBinTime, 0); GraphPrevDate = HttpdNumTime[2]; } Day = GraphAbsDay % ActivityNumberOfDays; idx = Day * MINUTES_IN_DAY + HttpdNumTime[3] * MINUTES_IN_HOUR + HttpdNumTime[4]; ActivityGblSecPtr->RequestCount[idx] |= EventBit; if (EventBit != ACTIVITY_EXIT_ERROR) InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); } /*****************************************************************************/ /* Scan the specified range in the activity statistics finding the peak-per- minute and total requests and bytes. Returns the number of minutes in the specified range, -1 for an error. 'StartAbsDay' is the VMS absolute day, and 'Hour' the hour of the day (0..23) for the start of the scan. 'NumberOfHours' specifies the duration of the scan. 'MinuteGranularity', if greater than one, specifies that the simple mean of the data for that period of minutes is to be used as peak values (totals are raw!). This function relies on calling functions to ensure the time and range makes sense! */ int GraphActivityDataScan ( int StartAbsDay, int StartHour, int NumberOfHours, int MinuteGranularity, unsigned int *PeakConnectPtr, unsigned int *PeakPerMinRequestPtr, unsigned int *PeakRequestPtr, /* points to a quadword */ unsigned long *PeakBytePtr, unsigned long *TotalRequestsPtr, /* points to a quadword */ unsigned long *TotalBytesPtr ) { BOOL gthan; int cnt, idx, mcnt; unsigned int AbsDay, ConnectPeak, NumberOfMinutes, PeakConnect, PeakPerMinRequest, PeakRequest, RequestCount, RequestPeak, RequestValue; unsigned long ByteCount [2], ByteValue [2], PeakBytes [2], SignScratch [2], TotalBytes [2], TotalRequests [2]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityDataScan() !UL !UL !UL !UL !&X !&X", StartAbsDay, StartHour, NumberOfHours, MinuteGranularity, TotalRequestsPtr, TotalBytesPtr); if (!ActivityTotalMinutes) return (-1); if (NumberOfHours <= 0 || NumberOfHours > ACTIVITY_DAYS * 24) return (-1); InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); /* ensure any days' data between the last request and now are cleared */ GraphActivityClearDay (); NumberOfMinutes = NumberOfHours * MINUTES_IN_HOUR; idx = ACTIVITY_DATA_IDX (StartAbsDay, StartHour); PeakConnect = PeakPerMinRequest = PeakRequest = 0; PUT_ZERO_QUAD (PeakBytes); PUT_ZERO_QUAD (TotalBytes); PUT_ZERO_QUAD (TotalRequests); for (mcnt = 0; mcnt < NumberOfMinutes; mcnt += MinuteGranularity) { if (idx >= ActivityTotalMinutes) idx = 0; if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL", mcnt, idx); if (MinuteGranularity == 1) { PUT_QUAD_QUAD (ActivityGblSecPtr->ByteCount[idx], ByteCount); ADD_QUAD_QUAD (ByteCount, TotalBytes); RequestCount = ActivityGblSecPtr->RequestCount[idx] & ACTIVITY_MASK; ADD_LONG_QUAD (RequestCount, TotalRequests); ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx]; RequestPeak = ActivityGblSecPtr->RequestPeak[idx]; idx++; } else { /* find the peak or average of the minute counts */ RequestCount = ConnectPeak = RequestPeak = 0; PUT_ZERO_QUAD (ByteCount); for (cnt = 0; cnt < MinuteGranularity; cnt++) { PUT_QUAD_QUAD (ActivityGblSecPtr->ByteCount[idx], ByteValue); ADD_QUAD_QUAD (ByteValue, TotalBytes); QUAD_GT_QUAD (ByteValue, ByteCount, gthan); if (gthan) PUT_QUAD_QUAD (ByteValue, ByteCount); RequestValue = ActivityGblSecPtr->RequestCount[idx]; RequestValue = (unsigned long)RequestValue & ACTIVITY_MASK; ADD_LONG_QUAD (RequestValue, TotalRequests); if (RequestValue > RequestCount) RequestCount = RequestValue; if (ActivityGblSecPtr->ConnectPeak[idx] > ConnectPeak) ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx]; if (ActivityGblSecPtr->RequestPeak[idx] > RequestPeak) RequestPeak = ActivityGblSecPtr->RequestPeak[idx]; idx++; } if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL", ByteCount, RequestCount); } QUAD_GT_QUAD (ByteCount, PeakBytes, gthan); if (gthan) PUT_QUAD_QUAD (ByteCount, PeakBytes); if (RequestCount > PeakPerMinRequest) PeakPerMinRequest = RequestCount; if (ConnectPeak > PeakConnect) PeakConnect = ConnectPeak; if (RequestPeak > PeakRequest) PeakRequest = RequestPeak; } InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!@SQ !@SQ !@SQ !UL !UL !UL", &TotalBytes, &TotalRequests, &PeakBytes, PeakPerMinRequest, PeakRequest, PeakConnect); PUT_QUAD_QUAD (PeakBytes, PeakBytePtr); *PeakConnectPtr = PeakConnect; *PeakPerMinRequestPtr = PeakPerMinRequest; *PeakRequestPtr = PeakRequest; if (TotalBytesPtr) PUT_QUAD_QUAD (TotalBytes, TotalBytesPtr); if (TotalRequestsPtr) PUT_QUAD_QUAD (TotalRequests, TotalRequestsPtr); return (NumberOfMinutes); } /*****************************************************************************/ /* Round the request and byte maxima up to the next whole digit in the range (e.g. 8745 to 9000, 320 to 400) */ GraphActivityMaxima ( unsigned int *PeakRequestPtr, unsigned int *MaxRequestPtr, /* points to a quadword */ unsigned long *MaxBytePtr ) { unsigned int MaxRequests, PeakRequests; unsigned long MaxBytesScratch [2]; float MaxBytesFloat; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityMaxima()"); PeakRequests = MaxRequests = 0; if (PeakRequestPtr) PeakRequests = *PeakRequestPtr; if (MaxRequestPtr) MaxRequests = *MaxRequestPtr; if (!MaxBytePtr) { MaxBytePtr = &MaxBytesScratch; PUT_ZERO_QUAD (MaxBytesScratch); } if (PeakRequests < 10) PeakRequests = 10; else if (PeakRequests < 100) PeakRequests = ((PeakRequests / 10) * 10) + 10; else if (PeakRequests < 1000) PeakRequests = ((PeakRequests / 100) * 100) + 100; else if (PeakRequests < 10000) PeakRequests = ((PeakRequests / 1000) * 1000) + 1000; if (MaxRequests < 10) MaxRequests = 10; else if (MaxRequests < 100) MaxRequests = ((MaxRequests / 10) * 10) + 10; else if (MaxRequests < 1000) MaxRequests = ((MaxRequests / 100) * 100) + 100; else if (MaxRequests < 10000) MaxRequests = ((MaxRequests / 1000) * 1000) + 1000; else if (MaxRequests < 100000) MaxRequests = ((MaxRequests / 10000) * 10000) + 10000; else if (MaxRequests < 1000000) MaxRequests = ((MaxRequests / 100000) * 100000) + 100000; MaxBytesFloat = (float)MaxBytePtr[0] + (float)MaxBytePtr[1] * 4294967296.0; MaxBytePtr[1] = 0; if (MaxBytesFloat < 10.0) MaxBytePtr[0] = 10; else if (MaxBytesFloat < 100.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 10.0) * 10.0) + 10.0); else if (MaxBytesFloat < 1000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 100.0) * 100.0) + 100.0); else if (MaxBytesFloat < 10000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 1000.0) * 1000.0) + 1000.0); else if (MaxBytesFloat < 100000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 10000.0) * 10000.0) + 10000.0); else if (MaxBytesFloat < 1000000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 100000.0) * 100000.0) + 100000.0); else if (MaxBytesFloat < 10000000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 1000000.0) * 1000000.0) + 1000000.0); else if (MaxBytesFloat < 100000000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 10000000.0) * 10000000.0) + 10000000.0); else if (MaxBytesFloat < 1000000000.0) MaxBytePtr[0] = (unsigned long)((abs(MaxBytesFloat / 100000000.0) * 100000000.0) + 100000000.0); if (MaxRequestPtr) *MaxRequestPtr = MaxRequests; if (PeakRequestPtr) *PeakRequestPtr = PeakRequests; } /*****************************************************************************/ /* Set the offset binary time to the base binary time plus or minus the number of minutes specified. */ int GraphActivityOffsetTime ( int NumberOfMinutes, unsigned long *BaseBinTimePtr, unsigned long *OffsetBinTimePtr ) { static unsigned long Addend = 0, OneSecond = -10000000; int status; unsigned long Minutes, Seconds; unsigned long DeltaBinTime [2]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityOffsetTime() !SL", NumberOfMinutes); if (NumberOfMinutes >= 0) Minutes = NumberOfMinutes; else Minutes = NumberOfMinutes * -1; if (Minutes) Seconds = Minutes * MINUTES_IN_HOUR; else Seconds = 1; status = lib$emul (&Seconds, &OneSecond, &Addend, &DeltaBinTime); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); if (NumberOfMinutes >= 0) { status = lib$add_times (BaseBinTimePtr, &DeltaBinTime, OffsetBinTimePtr); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); return (status); } else { status = lib$sub_times (BaseBinTimePtr, &DeltaBinTime, OffsetBinTimePtr); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); return (status); } } /*****************************************************************************/ /* Generate the HTML activity report page. The supplied query string specifies the END TIME for the the display, NOT THE START. The period actually specifies for what duration prior to the specified end time the display should be! The JavaScript code activates when a duration-only request is made. The JavaScript provides an automatic update of the activity report at a frequency appropriate to the period of the report. If the browser is not in focus then the update is not made (at least that's how the script is designed and Navigator 3.0 behaves, MSIE 3.02 has still a ways to go :^), it is defered until it is brought back into focus. This saves "background" updates that would essentially be wasted processing and bandwidth. JavaScript 1.1 is required, and the script is only enabled for versions of Navigator and MSIE that are known to support the required functionality (primarily the "location.replace" method). The "uniquifier" performs two tasks. First, although the images and reports are pre-expired MSIE (3.02) continues to use images from the cache over and again! Second, it prevents a browser doing a JavaSciprt automatic update using a cached report or image if the server becomes unavailable. */ GraphActivityReport ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { static char *MonthName [] = { "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; char UpdateJavaScript [] = "\n"; static char OnLoadJavaScriptFao [] = " onload=\"startUpdatePage(\'!AZ?!UL:!ULx!UL\',!UL)\"\ onerror=\"suppressError()\"\ onfocus=\"windowFocus()\"\ onblur=\"windowInFocus = false\""; static char ResponseFao [] = "!AZ\ \n\ \n\ !AZ\ !AZ\ !AZ\ !AZ\ WASD !AZ ... Server Activity\n\ \n\ \n\

WASD !AZ

\n\

Server Activity!&@

\n\ !20&W\n\ \

\n\ \ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \ \ \n\ \
!UL  !UL!AZ
!AZ \ \"[activity\  !AZ
!UL   !UL-!AZ !2ZL:!2ZL!&@!&@!UL-!AZ !2ZL:!2ZL   !UL
\n\ \

\n\ \n\ \n\ \n\ \n\ \ \ \n\
Period: !AZ  (!UL hour!%s)
Connections: !&L peak!&@
Requests: !&,@SQ total; \  !&L max;  !&L peak!&@
Bytes: !&,@SQ total; \  !&,@SQ max
 (Data available from !17&W)
\n\ \n"; static char AreaFao [] = "\n"; static char EndPageFao [] = "\n\ \n\ \n\ \n"; static char MultipleDaysFao [] = "!17&W to !17&W"; static char UniquifierFao [] = "+!2ZL!2ZL!2ZL!2ZL!2ZL"; static char WithinOneDayFao [] = "!17&W to !2ZL:!2ZL"; BOOL CurrentStats, JavaScriptEnabled, HourSupplied; int cnt, scnt, status, AdminInstanceCount, AtX, Bytes, ColumnWidth, Count, Day, GraphHeight, GraphWidth, Hour, IncrementMinutes, MapSections, MinuteGranularity, Minutes, Month, NodeInstanceCount, NumberOfDays, NumberOfHours, NumberOfMinutes, PeriodHours, StartHour, UpdateSeconds, Year; unsigned short Length; unsigned short CurrentNumTime [7], EndNumTime [7], NumTime [7], StartNumTime [7]; unsigned long AbsDay, CurrentAbsDay, CurrentMinute, CurrentSecond, EndMinute, EndSecond, MaxPeak, MaxRequests, MaxYRequests, Minute, PeakConnect, PeakPerMinRequests, PeakRequests, StartAbsDay, Second, TenmSecSinceMidnight; unsigned long *vecptr; unsigned long FaoVector [64]; unsigned long BinTime [2], CurrentBinTime [2], EndBinTime [2], MaxBytes [2], PeakBytes [2], StartBinTime [2], EndThisPeriodBinTime [2], TotalBytes [2], TotalRequests [2]; float MaxBytesFloat; char *cptr, *sptr, *zptr, *BytesPtr, *EndPtr, *HoursPtr, *NodeInstancePtr, *StartPtr; char DeltaTime [32], OnLoadJavaScript [256], DimChange [64], Period [128], Uniquifier [16]; $DESCRIPTOR (DeltaTimeDsc, DeltaTime); $DESCRIPTOR (PeriodDsc, Period); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityReport() !&A !&Z", NextTaskFunction, rqptr->rqHeader.QueryStringPtr); if (!ActivityTotalMinutes) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorGraphNotInit, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } JavaScriptEnabled = false; GraphWidth = ACTIVITY_GRAPH_WIDTH; GraphHeight = ACTIVITY_GRAPH_HEIGHT; /**************************/ /* parse the query string */ /**************************/ HourSupplied = false; Year = Month = Day = Hour = MaxYRequests = NumberOfHours = 0; if (rqptr->rqHeader.QueryStringLength) { if (isdigit(rqptr->rqHeader.QueryStringPtr[0])) { for (cptr = rqptr->rqHeader.QueryStringPtr; *cptr; cptr++); if (cptr - rqptr->rqHeader.QueryStringPtr > 20) { scnt = sscanf (rqptr->rqHeader.QueryStringPtr, "%4u%2u%2u%2u+%u:%ux%u/%u", &Year, &Month, &Day, &Hour, &NumberOfHours, &GraphWidth, &GraphHeight, &MaxYRequests); if (scnt != 8) scnt = 0; HourSupplied = true; } else if (cptr - rqptr->rqHeader.QueryStringPtr > 10) { /* assume just a duration (and possibly graphic dimensions) */ scnt = sscanf (rqptr->rqHeader.QueryStringPtr, "%u:%ux%u/%u", &NumberOfHours, &GraphWidth, &GraphHeight, &MaxYRequests); if (scnt != 1 && scnt != 4) scnt = 0; } else { /* assume just a duration (and possibly manual max requests) */ scnt = sscanf (rqptr->rqHeader.QueryStringPtr, "%u/%u", &NumberOfHours, &MaxYRequests); /* enable JavaScript for duration-only based requests */ JavaScriptEnabled = true; } if (!scnt) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphQuery, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } } else { /* "form"-based query string */ cptr = rqptr->rqHeader.QueryStringPtr; while (*cptr) { if (strsame (cptr, "yr=", 3)) Year = atoi(cptr+3); else if (strsame (cptr, "mn=", 3)) Month = atoi(cptr+3); else if (strsame (cptr, "dy=", 3)) Day = atoi(cptr+3); else if (strsame (cptr, "hr=", 3)) { Hour = atoi(cptr+3); HourSupplied = true; } else if (strsame (cptr, "du=", 3)) NumberOfHours = atoi(cptr+3); else if (strsame (cptr, "xy=", 3)) { cptr += 3; GraphWidth = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; while (*cptr && !isdigit(*cptr)) cptr++; GraphHeight = atoi(cptr); } else { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphQuery, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } while (*cptr && *cptr != '&') cptr++; if (*cptr) cptr++; } /* disable javascript for specific-period requests */ JavaScriptEnabled = false; } } if (MaxYRequests < 0) MaxYRequests = 0; if (rqptr->rqHeader.RefererFieldPtr) MaxYRequests = 0; for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++) if (GraphWidth == cnt * (ACTIVITY_GRAPH_WIDTH / 2)) break; if (cnt >= ACTIVITY_GRAPH_ZOOM+2) GraphWidth = 0; for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++) if (GraphHeight == cnt * (ACTIVITY_GRAPH_HEIGHT / 2)) break; if (cnt >= ACTIVITY_GRAPH_ZOOM+2) GraphHeight = 0; if (!GraphWidth || !GraphHeight) { GraphWidth = ACTIVITY_GRAPH_WIDTH; GraphHeight = ACTIVITY_GRAPH_HEIGHT; } GraphWidth += 2; GraphHeight += 2; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL/!UL/!UL:!UL !UL !ULx!UL/!UL", Year, Month, Day, Hour, NumberOfHours, GraphWidth, GraphHeight, MaxYRequests); /***********/ /* process */ /***********/ sys$gettim (&CurrentBinTime); sys$numtim (&CurrentNumTime, &CurrentBinTime); lib$day (&CurrentAbsDay, &CurrentBinTime, 0); /* defaults for any time components not supplied */ if (!Year) Year = CurrentNumTime[0]; if (!Month) Month = CurrentNumTime[1]; if (!Day) Day = CurrentNumTime[2]; if (!Hour && !HourSupplied) Hour = CurrentNumTime[3]; if (!NumberOfHours) NumberOfHours = 1; /* make it a multiple of 4 (which divides the graph up nicely :^) */ if (NumberOfHours > 2) while (NumberOfHours % 4) NumberOfHours++; NumberOfMinutes = NumberOfHours * MINUTES_IN_HOUR; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL", NumberOfMinutes); EndNumTime[0] = Year; EndNumTime[1] = Month; EndNumTime[2] = Day; EndNumTime[3] = Hour; /* always ends after the 59th minute! */ EndNumTime[4] = 59; EndNumTime[5] = EndNumTime[6] = 0; if (VMSnok (status = lib$cvt_vectim (&EndNumTime, &EndBinTime))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* get the start time as the number of minutes before the end time */ if (VMSnok (status = GraphActivityOffsetTime (-(NumberOfMinutes-1), &EndBinTime, &StartBinTime))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } if (VMSnok (status = lib$day (&StartAbsDay, &StartBinTime, 0))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } sys$numtim (&StartNumTime, &StartBinTime); StartHour = StartNumTime[3]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL !UL !UL", CurrentAbsDay, StartAbsDay, CurrentNumTime[3], StartHour); if (CurrentAbsDay - StartAbsDay > ActivityNumberOfDays) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphHistory, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } if (StartAbsDay > CurrentAbsDay || (StartAbsDay == CurrentAbsDay && StartHour > CurrentNumTime[3])) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphFuture, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } status = lib$sub_times (&EndBinTime, &CurrentBinTime, &DeltaTime); if (VMSnok (status)) { if (status == LIB$_NEGTIM) CurrentStats = false; else ErrorNoticed (NULL, status, NULL, FI_LI); } else CurrentStats = TRUE; /***********************/ /* make some estimates */ /***********************/ /* width of the bar graph */ ColumnWidth = (GraphWidth-2) / (NumberOfMinutes * 2); if (!ColumnWidth) ColumnWidth = 1; /* calculate simple mean for this number of minutes when duration large */ MinuteGranularity = (int)(1.0 / ((float)(GraphWidth-2) / ((float)NumberOfMinutes * 2.0))); if (!MinuteGranularity) MinuteGranularity = 1; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL", ColumnWidth, MinuteGranularity); GraphActivityDataScan (StartAbsDay, StartHour, NumberOfHours, MinuteGranularity, &PeakConnect, &PeakPerMinRequests, &PeakRequests, &PeakBytes, &TotalRequests, &TotalBytes); PUT_QUAD_QUAD (PeakBytes, MaxBytes); MaxRequests = PeakPerMinRequests; MaxPeak = PeakRequests; GraphActivityMaxima (&MaxPeak, &MaxRequests, &MaxBytes); if (!MaxYRequests) MaxYRequests = MaxRequests; if (MaxYRequests) MaxYRequests--; GraphActivityMaxima (0, &MaxYRequests, 0); if (StartNumTime[0] != EndNumTime[0] || StartNumTime[1] != EndNumTime[1] || StartNumTime[2] != EndNumTime[2]) status = FaoToBuffer (Period, sizeof(Period), NULL, MultipleDaysFao, &StartBinTime, &EndBinTime); else status = FaoToBuffer (Period, sizeof(Period), NULL, WithinOneDayFao, &StartBinTime, EndNumTime[3], EndNumTime[4]); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); /** THIS MAY SOUND CRAZY but I had to move this code segment down here from immediately following GraphActivityMaxima() because on some Alphas it appeared as if the values in MaxBytes were not immediately stable! Some sort of pipelining issue? Compiler bug? I have no idea. Shifting if to here fixed what I suspect were MaxBytes that reflected values prior to the 'rounding-up' done by GraphActivityMaxima(). **/ MaxBytesFloat = (float)MaxBytes[0] + (float)MaxBytes[1] * 4294967296.0; if (MaxYRequests != MaxRequests) MaxBytesFloat *= (float)MaxYRequests / (float)MaxRequests; if (MaxBytesFloat < 1000.0) { Bytes = (int)MaxBytesFloat; BytesPtr = ""; } else if (MaxBytesFloat < 1000000.0) { Bytes = (int)(MaxBytesFloat / 1000.0); BytesPtr = " K"; } else if (MaxBytesFloat < 1000000000.0) { Bytes = (int)(MaxBytesFloat / 1000000.0); BytesPtr = " M"; } else { Bytes = (int)(MaxBytesFloat / 1000000000.0); BytesPtr = " G"; } /**********************/ /* generate HTML page */ /**********************/ if (NumberOfHours <= 4) { UpdateSeconds = 60 - CurrentNumTime[5]; if (UpdateSeconds < 45) UpdateSeconds += 60; } else if (NumberOfHours <= 8) UpdateSeconds = 120 - CurrentNumTime[5]; else if (NumberOfHours <= 24) UpdateSeconds = 300 - CurrentNumTime[5]; else UpdateSeconds = 3600 - CurrentNumTime[5]; /* six seconds before the new minute */ UpdateSeconds -= 5; vecptr = FaoVector; *vecptr++ = CurrentNumTime[1]; *vecptr++ = CurrentNumTime[2]; *vecptr++ = CurrentNumTime[3]; *vecptr++ = CurrentNumTime[4]; *vecptr++ = CurrentNumTime[5]; status = FaolToBuffer (Uniquifier, sizeof(Uniquifier), NULL, UniquifierFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); if (JavaScriptEnabled) { vecptr = FaoVector; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = NumberOfHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = UpdateSeconds; status = FaolToBuffer (OnLoadJavaScript, sizeof(OnLoadJavaScript), NULL, OnLoadJavaScriptFao, &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) { ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.ErrorTextPtr = "FaolToBuffer()"; ErrorVmsStatus (rqptr, status, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } } NodeInstanceCount = InstanceLockList (INSTANCE_NODE, "\n", &NodeInstancePtr); AdminInstanceCount = AdminMenuInstanceCount (rqptr, NodeInstancePtr); vecptr = FaoVector; *vecptr++ = WASD_DOCTYPE; *vecptr++ = HtmlMetaInfo (rqptr, NULL); *vecptr++ = WasdCss; if (rqptr->rqPathSet.StyleSheetPtr) { *vecptr++ = "\n"; *vecptr++ = rqptr->rqPathSet.StyleSheetPtr; } else *vecptr++ = ""; if (JavaScriptEnabled) *vecptr++ = UpdateJavaScript; else *vecptr++ = ""; *vecptr++ = ServerHostPort; if (JavaScriptEnabled) *vecptr++ = OnLoadJavaScript; else *vecptr++ = ""; /* bit of a kludge ;^) skip over the " 1) { if (rqptr->ServicePtr->AdminService) *vecptr++ = "  -  !AZ"; else *vecptr++ = "   (!AZ)"; *vecptr++ = HttpdProcess.PrcNam; } else *vecptr++ = ""; *vecptr++ = &rqptr->rqTime.Vms64bit; *vecptr++ = MaxYRequests; *vecptr++ = Bytes; *vecptr++ = BytesPtr; *vecptr++ = "Requests
\ (mean)
\ per-minute"; *vecptr++ = GraphWidth; *vecptr++ = GraphHeight; *vecptr++ = ADMIN_REPORT_ACTIVITY_GRAPHIC; *vecptr++ = Year; *vecptr++ = Month; *vecptr++ = Day; *vecptr++ = Hour; *vecptr++ = NumberOfHours; *vecptr++ = MaxRequests; *vecptr++ = MaxBytes[1]; *vecptr++ = MaxBytes[0]; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = Uniquifier; *vecptr++ = "Bytes
\ (mean)
\ per-minute"; *vecptr++ = 0; *vecptr++ = StartNumTime[2]; *vecptr++ = MonthName[StartNumTime[1]]; *vecptr++ = StartNumTime[3]; *vecptr++ = StartNumTime[4]; zptr = (sptr = DimChange) + sizeof(DimChange)-1; for (cptr = rqptr->rqHeader.QueryStringLength ? rqptr->rqHeader.QueryStringPtr : ""; *cptr && *cptr != ':' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++) { if (GraphWidth-2 != cnt * (ACTIVITY_GRAPH_WIDTH / 2)) continue; if (cnt > 2) { *vecptr++ = "[ - ]"; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = DimChange; *vecptr++ = (cnt - 1) * (ACTIVITY_GRAPH_WIDTH / 2); *vecptr++ = (cnt - 1) * (ACTIVITY_GRAPH_HEIGHT / 2); *vecptr++ = MaxYRequests; } else *vecptr++ = "[ - ]"; if (cnt < ACTIVITY_GRAPH_ZOOM+1) { *vecptr++ = "[ + ]"; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = DimChange; *vecptr++ = (cnt + 1) * (ACTIVITY_GRAPH_WIDTH / 2); *vecptr++ = (cnt + 1) * (ACTIVITY_GRAPH_HEIGHT / 2); *vecptr++ = MaxYRequests; } else *vecptr++ = "[ + ]"; break; } if (cnt >= ACTIVITY_GRAPH_ZOOM+2) { *vecptr++ = ""; *vecptr++ = ""; } *vecptr++ = EndNumTime[2]; *vecptr++ = MonthName[EndNumTime[1]]; *vecptr++ = EndNumTime[3]; *vecptr++ = EndNumTime[4]; *vecptr++ = 0; *vecptr++ = Period; *vecptr++ = NumberOfHours; *vecptr++ = PeakConnect; if (CurrentStats) { *vecptr++ = ";  !&L current"; *vecptr++ = ActivityConnectCurrent; } else *vecptr++ = ""; *vecptr++ = &TotalRequests; *vecptr++ = PeakPerMinRequests; *vecptr++ = PeakRequests; if (CurrentStats) { *vecptr++ = ";  !&L current"; *vecptr++ = ActivityConnectProcessing; } else *vecptr++ = ""; *vecptr++ = &TotalBytes; *vecptr++ = &PeakBytes; *vecptr++ = &ActivityGblSecPtr->StartBinTime; status = FaolToNet (rqptr, ResponseFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); /*************************/ /* image map coordinates */ /*************************/ NumberOfDays = NumberOfHours / 24; if (NumberOfDays > 1) { MapSections = NumberOfDays; IncrementMinutes = MINUTES_IN_DAY; } else { MapSections = NumberOfHours; IncrementMinutes = MINUTES_IN_HOUR; } Minute = 0; if (NumberOfHours > 1) { /***************************/ /* reduce period map areas */ /***************************/ switch (NumberOfHours) { case 1 : { PeriodHours = 1; break; } case 2 : { PeriodHours = 1; break; } case 4 : { PeriodHours = 2; break; } case 12 : { PeriodHours = 4; break; } case 24 : { PeriodHours = 12; break; } case 72 : { PeriodHours = 24; break; } case 168 : { PeriodHours = 72; break; } case 672 : { PeriodHours = 168; break; } default : PeriodHours = NumberOfHours / 2; } if (!PeriodHours) PeriodHours = 1; if (VMSnok (status = GraphActivityOffsetTime (PeriodHours*60, &CurrentBinTime, &EndThisPeriodBinTime))) { ErrorVmsStatus (rqptr, status, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } Count = 0; while (Count < MapSections) { AtX = (int)((float)Count * (float)GraphWidth / (float)MapSections); Count++; Minute += IncrementMinutes; /* 'BinTime' will contain the period end time */ if (VMSnok (status = GraphActivityOffsetTime (Minute-1, &StartBinTime, &BinTime))) { ErrorVmsStatus (rqptr, status, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* If this period end time is later than server start time, and the end time is earlier than the end of this period, then ... */ if (CompareVmsBinTimes (&BinTime, &ActivityGblSecPtr->StartBinTime) > 0 && CompareVmsBinTimes (&CurrentBinTime, &EndThisPeriodBinTime) < 0) { sys$numtim (&NumTime, &BinTime); vecptr = FaoVector; *vecptr++ = AtX; *vecptr++ = GraphHeight / 2; *vecptr++ = AtX + (GraphWidth / MapSections); *vecptr++ = GraphHeight; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = NumTime[0]; *vecptr++ = NumTime[1]; *vecptr++ = NumTime[2]; *vecptr++ = NumTime[3]; *vecptr++ = PeriodHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = GraphActivityOnMouseOver (&BinTime, PeriodHours); status = FaolToNet (rqptr, AreaFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } } } /************************/ /* less recent map area */ /************************/ if (CompareVmsBinTimes (&StartBinTime, &ActivityGblSecPtr->StartBinTime) > 0) { if (VMSnok (status = GraphActivityOffsetTime (-(NumberOfHours*MINUTES_IN_HOUR), &EndBinTime, &BinTime))) { ErrorVmsStatus (rqptr, status, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } sys$numtim (&NumTime, &BinTime); vecptr = FaoVector; *vecptr++ = 0; *vecptr++ = 0; *vecptr++ = GraphWidth / 4; *vecptr++ = GraphHeight / 2; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = NumTime[0]; *vecptr++ = NumTime[1]; *vecptr++ = NumTime[2]; *vecptr++ = NumTime[3]; *vecptr++ = NumberOfHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = GraphActivityOnMouseOver (&BinTime, NumberOfHours); status = FaolToNet (rqptr, AreaFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } if (NumberOfDays < ActivityNumberOfDays) { /****************************/ /* increase period map area */ /****************************/ switch (NumberOfHours) { case 1 : { PeriodHours = 2; break; } case 2 : { PeriodHours = 4; break; } case 4 : { PeriodHours = 12; break; } case 12 : { PeriodHours = 24; break; } case 24 : { PeriodHours = 72; break; } case 72 : { PeriodHours = 168; break; } case 168 : { PeriodHours = 672; break; } default : PeriodHours = NumberOfHours * 2; } if (PeriodHours > ActivityNumberOfDays * 24) PeriodHours = ActivityNumberOfDays * 24; vecptr = FaoVector; *vecptr++ = GraphWidth / 4; *vecptr++ = 0; *vecptr++ = GraphWidth / 2 + GraphWidth / 4; *vecptr++ = GraphHeight / 2; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = EndNumTime[0]; *vecptr++ = EndNumTime[1]; *vecptr++ = EndNumTime[2]; *vecptr++ = EndNumTime[3]; *vecptr++ = PeriodHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = GraphActivityOnMouseOver (&EndBinTime, PeriodHours); status = FaolToNet (rqptr, AreaFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } /************************/ /* more recent map area */ /************************/ if (CompareVmsBinTimes (&CurrentBinTime, &EndBinTime) > 0) { if (VMSnok (status = GraphActivityOffsetTime (NumberOfHours*MINUTES_IN_HOUR, &EndBinTime, &BinTime))) { ErrorVmsStatus (rqptr, status, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } sys$numtim (&NumTime, &BinTime); vecptr = FaoVector; *vecptr++ = GraphWidth / 2 + GraphWidth / 4; *vecptr++ = 0; *vecptr++ = GraphWidth; *vecptr++ = GraphHeight / 2; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = NumTime[0]; *vecptr++ = NumTime[1]; *vecptr++ = NumTime[2]; *vecptr++ = NumTime[3]; *vecptr++ = NumberOfHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = GraphActivityOnMouseOver (&BinTime, NumberOfHours); status = FaolToNet (rqptr, AreaFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } /************/ /* end page */ /************/ status = FaolToNet (rqptr, EndPageFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); SysDclAst (NextTaskFunction, rqptr); } /*****************************************************************************/ /* */ char* GraphActivityOnMouseOver ( unsigned long *EndBinTimePtr, int NumberOfHours ) { static char OnMouseOverFao [] = " onmouseover=\"window.status=\'!17%D to !17%D (!UL hour!%s)\';\ return true\"\ onmouseout=\"window.status=\'\'\;return true\"\0", OnMouseOverError [] = " onmouseover=\"window.status=\'*ERROR*\';return true\"\ onmouseout=\"window.status=\'\'\ return true\""; static char OnMouseOver [256]; int status; unsigned long *vecptr; unsigned long StartBinTime [2]; unsigned long FaoVector [8]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityOnMouseOver() !SL", NumberOfHours); if (VMSnok (status = GraphActivityOffsetTime (-(NumberOfHours*MINUTES_IN_HOUR-1), EndBinTimePtr, &StartBinTime))) return (OnMouseOverError); vecptr = FaoVector; *vecptr++ = &StartBinTime; *vecptr++ = EndBinTimePtr; *vecptr++ = NumberOfHours; status = FaolToBuffer (OnMouseOver, sizeof(OnMouseOver), NULL, OnMouseOverFao, &FaoVector); if (VMSnok (status) || status == SS$_BUFFEROVF) { if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); return (OnMouseOverError); } if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!AZ", OnMouseOver); return (OnMouseOver); } /*****************************************************************************/ /* Generate the activity graph GIF. An tag in the HTML report page results in this function begin called. */ GraphActivityPlotBegin ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction ) { static $DESCRIPTOR (DeltaTimeFaoDsc, "!UL !2ZL:!2ZL:00.00"); BOOL gthan, EventStartup, EventExit, EventExitError, EventDelPrc, HourSupplied; int cnt, idx, mcnt, scnt, status, AbsDay, AtX, AxisBytes, AxisRequests, ByteHeight, ByteMean, ColourAxis, ColourByte, ColourByteMean, ColourConnectPeak, ColourNoData, ColourRequest, ColourRequestMean, ColourRequestPeak, ColourDelPrc, ColourExit, ColourExitError, ColourStartup, ColumnWidth, CurrentMinute, Day, DisplayMean, GraphHeight, GraphWidth, Hour, MinuteGranularity, Month, NumberOfHours, NumberOfDays, NumberOfMinutes, ConnectPeakHeight, PrevAtX, PrevByteMean, PrevRequestMean, RequestCount, RequestHeight, RequestMean, RequestPeakHeight, RequestTotal, RequestValue, SampleCount, StartHour, Year; unsigned long AbsActivityDay, ConnectPeak, CurrentAbsDay, DeltaDays, DeltaHours, DeltaMinutes, EndMinute, EndSecond, MaxConnect, MaxRequests, MaxYRequests, PeakConnect, PeakRequests, RequestPeak, StartAbsDay, StartMinute, StartSecond, Minute; unsigned long FaoVector [32]; unsigned long BinTime [2], ByteCount [2], ByteTotal [2], ByteValue [2], CurrentBinTime [2], EndBinTime [2], MaxBytes [2], PeakBytes [2], SignScratch [2], StartBinTime [2]; unsigned short CurrentNumTime [7], EndNumTime [7], NumTime [7], StartNumTime [7]; float AtXfloat, AtXfactor, ByteFactor, ByteMeanFloat, MaxBytesFloat, RequestFactor; char *cptr; char DeltaTime [32], Uniquifier [16]; GRAPH_STRUCT *grptr; GRAPH_TASK *tkptr; $DESCRIPTOR (DeltaTimeDsc, DeltaTime); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityPlotBegin() !&Z", rqptr->rqHeader.QueryStringPtr); if (!ActivityTotalMinutes) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorGraphNotInit, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /**************************/ /* parse the query string */ /**************************/ GraphWidth = ACTIVITY_GRAPH_WIDTH; GraphHeight = ACTIVITY_GRAPH_HEIGHT; HourSupplied = false; Year = Month = Day = Hour = NumberOfHours = MaxRequests = MaxYRequests = 0; PUT_ZERO_QUAD (MaxBytes); if (rqptr->rqHeader.QueryStringLength) { scnt = sscanf (rqptr->rqHeader.QueryStringPtr, "%4u%2u%2u%2u+%u+%u+%u+%u:%ux%u/%u+%s", &Year, &Month, &Day, &Hour, &NumberOfHours, &MaxRequests, &MaxBytes[1], &MaxBytes[0], &GraphWidth, &GraphHeight, &MaxYRequests, Uniquifier); if (scnt != 12) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphQuery, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } HourSupplied = true; } for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++) if (GraphWidth == cnt * (ACTIVITY_GRAPH_WIDTH / 2)) break; if (cnt >= ACTIVITY_GRAPH_ZOOM+2) GraphWidth = 0; for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++) if (GraphHeight == cnt * (ACTIVITY_GRAPH_HEIGHT / 2)) break; if (cnt >= ACTIVITY_GRAPH_ZOOM+2) GraphHeight = 0; if (!GraphWidth || !GraphHeight) { GraphWidth = ACTIVITY_GRAPH_WIDTH; GraphHeight = ACTIVITY_GRAPH_HEIGHT; } GraphWidth += 2; GraphHeight += 2; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL/!UL/!UL:!UL !UL !UL !@SQ !ULx!UL/!UL", Year, Month, Day, Hour, NumberOfHours, MaxRequests, &MaxBytes, GraphWidth, GraphHeight, MaxYRequests); /**************************/ /* create graph structure */ /**************************/ /* set up the task structure (only ever one per request!) */ rqptr->GraphTaskPtr = tkptr = (GRAPH_TASK*)VmGetHeap (rqptr, sizeof(GRAPH_TASK)); tkptr->NextTaskFunction = NextTaskFunction; /* allocate heap memory for the graph structure */ grptr = rqptr->GraphTaskPtr->GraphPtr = (GRAPH_STRUCT*)VmGetHeap (rqptr, sizeof(GRAPH_STRUCT)); /***********/ /* process */ /***********/ ColourAxis= COLOUR_BLACK; ColourByte = COLOUR_CYAN; ColourNoData = COLOUR_GREY; ColourRequest = COLOUR_BLUE; ColourByteMean = COLOUR_MAGENTA; ColourConnectPeak = COLOUR_DBLUE; ColourRequestMean = COLOUR_RED; ColourRequestPeak = COLOUR_DWHITE; ColourDelPrc = COLOUR_BLACK; ColourExit = COLOUR_GREY; ColourExitError = COLOUR_RED; ColourStartup = COLOUR_GREEN; sys$gettim (&CurrentBinTime); sys$numtim (&CurrentNumTime, &CurrentBinTime); lib$day (&CurrentAbsDay, &CurrentBinTime, 0); /* defaults for any time components not supplied */ if (!Year) Year = CurrentNumTime[0]; if (!Month) Month = CurrentNumTime[1]; if (!Day) Day = CurrentNumTime[2]; if (!Hour && !HourSupplied) Hour = CurrentNumTime[3]; if (!NumberOfHours) NumberOfHours = 1; /* make it a multiple of 4 (which divides the graph up nicely :^) */ if (NumberOfHours > 2) while (NumberOfHours % 4) NumberOfHours++; NumberOfMinutes = NumberOfHours * MINUTES_IN_HOUR; EndNumTime[0] = Year; EndNumTime[1] = Month; EndNumTime[2] = Day; EndNumTime[3] = Hour; /* always ends after the 59th minute! */ EndNumTime[4] = 59; EndNumTime[5] = EndNumTime[6] = 0; if (VMSnok (status = lib$cvt_vectim (&EndNumTime, &EndBinTime))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* get the start time as the number of minutes before the end time */ if (VMSnok (status = GraphActivityOffsetTime (-(NumberOfMinutes-1), &EndBinTime, &StartBinTime))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } if (VMSnok (status = lib$day (&StartAbsDay, &StartBinTime, 0))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /* calculate the start time */ AbsDay = (StartAbsDay - ActivityGblSecPtr->StartAbsDay) % ActivityNumberOfDays; sys$numtim (&StartNumTime, &StartBinTime); StartHour = StartNumTime[3]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL !UL !UL", CurrentAbsDay, StartAbsDay, CurrentNumTime[3], StartHour); if (CurrentAbsDay - StartAbsDay > ActivityNumberOfDays) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphHistory, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } if (StartAbsDay > CurrentAbsDay || (StartAbsDay == CurrentAbsDay && StartHour > CurrentNumTime[3])) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphFuture, FI_LI); SysDclAst (NextTaskFunction, rqptr); return; } /***********************/ /* make some estimates */ /***********************/ /* width of the bar graph */ ColumnWidth = (GraphWidth-2) / (NumberOfMinutes * 2); if (!ColumnWidth) ColumnWidth = 1; /* calculate simple mean for this number of minutes when duration large */ MinuteGranularity = (int)(1.0 / ((float)(GraphWidth-2) / ((float)NumberOfMinutes * 2.0))); if (!MinuteGranularity) MinuteGranularity = 1; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL", ColumnWidth, MinuteGranularity); if (!MaxRequests || (!MaxBytes[0] && !MaxBytes[1])) { /* image is not being requested by the report, don't barf, fudge! */ GraphActivityDataScan (StartAbsDay, StartHour, NumberOfHours, MinuteGranularity, &PeakConnect, NULL, &PeakRequests, &PeakBytes, NULL, NULL); PUT_QUAD_QUAD (PeakBytes, MaxBytes); } if (MaxYRequests <= 0) MaxYRequests = MaxRequests; if (MaxRequests) RequestFactor = (float)(GraphHeight-2) / (float)MaxYRequests; else RequestFactor = 1.0; /* ratio for calculating byte bar graph height */ if (MaxBytes[0] || MaxBytes[1]) ByteFactor = (float)(GraphHeight-2) / ((float)MaxBytes[0] + (float)MaxBytes[1] * 4294967296.0); else ByteFactor = 1.0; AtXfactor = (float)(GraphWidth-2) / (float)(NumberOfMinutes * 2) * (float)MinuteGranularity; /* now just do as our master hath commanded */ MaxRequests = MaxYRequests; if (WATCH_MODULE(WATCH_MOD__OTHER)) { char String [32]; sprintf (String, "%f %f", RequestFactor, ByteFactor); WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!AZ", String); } /**************/ /* plot graph */ /**************/ if (GraphNew (rqptr, grptr, GraphWidth, GraphHeight, 7)) { GraphActivityPlotEnd (rqptr); return; } StartMinute = StartAbsDay * MINUTES_IN_DAY + StartHour * MINUTES_IN_HOUR; EndMinute = StartMinute + NumberOfHours * MINUTES_IN_HOUR - 1; CurrentMinute = CurrentAbsDay * MINUTES_IN_DAY + CurrentNumTime[3] * MINUTES_IN_HOUR + CurrentNumTime[4]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !UL !UL", StartMinute, EndMinute, CurrentMinute); PUT_ZERO_QUAD (ByteTotal); DisplayMean = RequestTotal = SampleCount = 0; AtXfloat = 1.0; Minute = StartMinute; idx = ACTIVITY_DATA_IDX (StartAbsDay, StartHour); InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); while (Minute <= EndMinute) { if (idx >= ActivityTotalMinutes) idx = 0; AtX = (int)AtXfloat; if (Minute < ActivityGblSecPtr->StartMinute) { GraphDrawBlock (grptr, AtX, 1, AtX+ColumnWidth+ColumnWidth-1, GraphHeight-1, ColourNoData); PrevAtX = AtX; AtXfloat += AtXfactor + AtXfactor; idx += MinuteGranularity; Minute += MinuteGranularity; continue; } if (Minute > CurrentMinute) break; Minute += MinuteGranularity; EventDelPrc = EventExit = EventExitError = EventStartup = false; if (MinuteGranularity == 1) { PUT_QUAD_QUAD (ActivityGblSecPtr->ByteCount[idx], ByteCount); ADD_QUAD_QUAD (ByteCount, ByteTotal) ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx]; RequestValue = ActivityGblSecPtr->RequestCount[idx]; RequestCount = (unsigned long)RequestValue & ACTIVITY_MASK; RequestTotal += RequestCount; RequestPeak = ActivityGblSecPtr->RequestPeak[idx]; if (RequestValue & ACTIVITY_DELPRC) EventDelPrc = true; if (RequestValue & ACTIVITY_EXIT) EventExit = true; if (RequestValue & ACTIVITY_EXIT_ERROR) EventExitError = true; if (RequestValue & ACTIVITY_STARTUP) EventStartup = true; idx++; } else { /* find the peak of the minute counts */ RequestCount = ConnectPeak = RequestPeak = 0; PUT_ZERO_QUAD (ByteCount); for (cnt = 0; cnt < MinuteGranularity; cnt++) { PUT_QUAD_QUAD (ActivityGblSecPtr->ByteCount[idx], ByteValue); QUAD_GT_QUAD (ByteValue, ByteCount, gthan); if (gthan) PUT_QUAD_QUAD (ByteValue, ByteCount); RequestValue = ActivityGblSecPtr->RequestCount[idx]; if (RequestValue & ACTIVITY_DELPRC) EventDelPrc = true; if (RequestValue & ACTIVITY_EXIT) EventExit = true; if (RequestValue & ACTIVITY_EXIT_ERROR) EventExitError = true; if (RequestValue & ACTIVITY_STARTUP) EventStartup = true; RequestValue = (unsigned long)RequestValue & ACTIVITY_MASK; if (RequestValue > RequestCount) RequestCount = RequestValue; if (ActivityGblSecPtr->ConnectPeak[idx] > ConnectPeak) ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx]; if (ActivityGblSecPtr->RequestPeak[idx] > RequestPeak) RequestPeak = ActivityGblSecPtr->RequestPeak[idx]; idx++; } RequestTotal += RequestCount; ADD_QUAD_QUAD (ByteCount, ByteTotal) } /* calculate means */ SampleCount++; RequestMean = (int)((float)(RequestTotal / SampleCount) * RequestFactor); /* breaking it down step-wise makes it easier (for me) to understand */ ByteMeanFloat = (float)ByteTotal[1] * 4294967296.0; ByteMeanFloat += (float)ByteTotal[0]; ByteMeanFloat /= (float)SampleCount; ByteMeanFloat *= ByteFactor; ByteMean = (int)ByteMeanFloat; /* limit the range (just in case) */ if (ConnectPeak > MaxRequests) ConnectPeak = MaxRequests; if (RequestCount > MaxRequests) RequestCount = MaxRequests; if (RequestPeak > MaxRequests) RequestPeak = MaxRequests; /* want to limit here so the put goes the other way :-) */ QUAD_GT_QUAD (ByteCount, MaxBytes, gthan); if (gthan) PUT_QUAD_QUAD (MaxBytes, ByteCount); ConnectPeakHeight = (int)((float)ConnectPeak * RequestFactor); RequestHeight = (int)((float)RequestCount * RequestFactor); RequestPeakHeight = (int)((float)RequestPeak * RequestFactor); ByteHeight = (int)(((float)ByteCount[0] + (float)ByteCount[1] * 4294967296.0) * ByteFactor); if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!UL !@SQ !UL !UL !UL !UL !UL", RequestTotal, ByteTotal, ConnectPeakHeight, RequestHeight, ByteHeight, RequestMean, ByteMean); /* display order; error exit, then shutdown, then restart, then startup */ if (EventExitError) GraphDrawBlock (grptr, AtX, 1, AtX, GraphHeight-1, ColourExitError); else if (EventDelPrc) GraphDrawBlock (grptr, AtX, 1, AtX, GraphHeight-1, ColourDelPrc); else if (EventExit) GraphDrawBlock (grptr, AtX, 1, AtX, GraphHeight-1, ColourExit); if (EventStartup) GraphDrawBlock (grptr, AtX+ColumnWidth, 1, AtX+ColumnWidth, GraphHeight-1, ColourStartup); GraphDrawBlock (grptr, AtX, 1, AtX+ColumnWidth-1, RequestHeight, ColourRequest); if (RequestPeakHeight < RequestHeight) GraphDrawLine (grptr, AtX, RequestPeakHeight, AtX+ColumnWidth, RequestPeakHeight, ColourRequestPeak, 1); if (DisplayMean) GraphDrawLine (grptr, PrevAtX+ColumnWidth, PrevRequestMean, AtX+ColumnWidth, RequestMean, ColourRequestMean, 1); GraphDrawBlock (grptr, AtX+ColumnWidth, 1, AtX+ColumnWidth+ColumnWidth-1, ByteHeight, ColourByte); GraphDrawLine (grptr, AtX+ColumnWidth, ConnectPeakHeight, AtX+ColumnWidth+ColumnWidth-1, ConnectPeakHeight, ColourConnectPeak, 1); if (DisplayMean++) GraphDrawLine (grptr, PrevAtX+ColumnWidth+ColumnWidth, PrevByteMean, AtX+ColumnWidth+ColumnWidth, ByteMean, ColourByteMean, 1); PrevRequestMean = RequestMean; PrevByteMean = ByteMean; PrevAtX = AtX; AtXfloat += AtXfactor + AtXfactor; } InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); if (Minute >= CurrentMinute) GraphDrawBlock (grptr, AtX, 1, GraphWidth-1, GraphHeight-1, ColourNoData); /****************/ /* finish graph */ /****************/ MaxBytesFloat = (float)MaxBytes[0] + (float)MaxBytes[1] * 4294967296.0; if (MaxBytesFloat < 10.0) AxisBytes = (int)MaxBytesFloat; else if (MaxBytesFloat < 100.0) AxisBytes = (int)(MaxBytesFloat / 10.0); else if (MaxBytesFloat < 1000.0) AxisBytes = (int)(MaxBytesFloat / 100.0); else if (MaxBytesFloat < 10000.0) AxisBytes = (int)(MaxBytesFloat / 1000.0); else if (MaxBytesFloat < 100000.0) AxisBytes = (int)(MaxBytesFloat / 10000.0); else if (MaxBytesFloat < 1000000.0) AxisBytes = (int)(MaxBytesFloat / 100000.0); else if (MaxBytesFloat < 10000000.0) AxisBytes = (int)(MaxBytesFloat / 1000000.0); else if (MaxBytesFloat < 100000000.0) AxisBytes = (int)(MaxBytesFloat / 10000000.0); else AxisBytes = (int)(MaxBytesFloat / 100000000.0); if (MaxRequests < 10) AxisRequests = MaxRequests; else if (MaxRequests < 100) AxisRequests = MaxRequests / 10; else if (MaxRequests < 1000) AxisRequests = MaxRequests / 100; else if (MaxRequests < 10000) AxisRequests = MaxRequests / 1000; else if (MaxRequests < 100000) AxisRequests = MaxRequests / 10000; else if (MaxRequests < 1000000) AxisRequests = MaxRequests / 100000; else AxisRequests = MaxRequests / 1000000; if (AxisRequests <= 2) GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests*10, 4, ColourAxis); GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests*2, 7, ColourAxis); GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests, 10, ColourAxis); if (AxisBytes <= 2) GraphGraduateYAxis (grptr, GRAPH_YAXIS_RIGHT, AxisBytes*10, 4, ColourAxis); GraphGraduateYAxis (grptr, GRAPH_YAXIS_RIGHT, AxisBytes*2, 7, ColourAxis); GraphGraduateYAxis (grptr, GRAPH_YAXIS_RIGHT, AxisBytes, 10, ColourAxis); if (NumberOfHours == 1) { GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, 60, 4, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, 12, 7, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, 2, 10, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, 60, 4, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, 12, 7, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, 2, 10, ColourAxis); } else if (NumberOfHours < 24) { if (NumberOfHours <= 8) { GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, NumberOfHours*12, 4, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, NumberOfHours*12, 4, ColourAxis); } GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, NumberOfHours*2, 7, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, NumberOfHours, 10, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, NumberOfHours*2, 7, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, NumberOfHours, 10, ColourAxis); } else { NumberOfDays = NumberOfHours / 24; if (NumberOfDays <= 3) { GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, NumberOfDays*24, 4, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, NumberOfDays*24, 4, ColourAxis); } GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, NumberOfDays*4, 6, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, NumberOfDays, 8, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, NumberOfDays*4, 6, ColourAxis); GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, NumberOfDays, 8, ColourAxis); } GraphDrawBorder (grptr, ColourAxis, 1); GraphActivityGifStream (rqptr); } /*****************************************************************************/ /* */ GraphActivityPlotEnd (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityPlotEnd()"); SysDclAst (rqptr->GraphTaskPtr->NextTaskFunction, rqptr); } /*****************************************************************************/ /* Convert the image pointed to by grptr->GraphPlotPtr of grptr->PixelCount size into a GIF image and send it to the client. */ GraphActivityGifStream (REQUEST_STRUCT *rqptr) { GRAPH_STRUCT *grptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "GraphActivityGifStream()"); /* get the pointer to this request's graph structure */ grptr = rqptr->GraphTaskPtr->GraphPtr; /* we can usually count on heaps of compression using LZW :^) */ grptr->GifBufferLength = (grptr->PixelCount / GIF_EXPECTED_COMPRESSION) + GIF_OVERHEAD; /* allocate heap memory for the GIF image */ grptr->GifBufferPtr = VmGetHeap (rqptr, grptr->GifBufferLength); /* set marker so we don't over run the end of the buffer */ grptr->GifBufferEndPtr = grptr->GifBufferPtr + grptr->GifBufferLength; if (GraphGifImage (grptr)) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, grptr->ErrorTextPtr, FI_LI); return; } /*******************/ /* write to client */ /*******************/ rqptr->rqResponse.HttpStatus = 200; rqptr->rqResponse.PreExpired = true; ResponseHeader (rqptr, 200, "image/gif", grptr->GifContentLength, rqptr->rqTime.Vms64bit, NULL); NetWrite (rqptr, GraphActivityPlotEnd, grptr->GifBufferPtr, grptr->GifContentLength); } /*****************************************************************************/ /* Allocate memory for a graphic of the specified size, then set all the pixels to the specified colour. */ char* GraphNew ( REQUEST_STRUCT *rqptr, GRAPH_STRUCT *grptr, int Width, int Height, int Colour ) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphNew() %dx%d %d", Width, Height, Colour); if (Width <= 0 || Width > GraphicMaximumWidth) return (grptr->ErrorTextPtr = "Graph: width problem."); if (Height <= 0 || Height > GraphicMaximumHeight) return (grptr->ErrorTextPtr = "Graph: height problem."); /* some initializations */ grptr->PreExpired = true; grptr->BitsPerPixel = GraphicBitsPerPixel; grptr->GifColourBg = grptr->ColourBg = Colour; grptr->ColourFg = 0; grptr->ColourTr = -1; grptr->Width = Width; grptr->Height = Height; grptr->PixelCount = grptr->Width * grptr->Height; if (GraphDebug) fprintf (stdout, "PixelCount: %d\n", grptr->PixelCount); /* allocate heap memory for the graph structure */ grptr->PlotBufferPtr = VmGetHeap (rqptr, grptr->PixelCount); /* set all the pixels to background colour */ memset (grptr->PlotBufferPtr, Colour, grptr->PixelCount); return (0); } /*****************************************************************************/ /* Set all the pixels in the graphic to the specified colour. */ char* GraphSetBackground ( GRAPH_STRUCT *grptr, int Colour ) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphSetBackground() %d\n", Colour); memset (grptr->PlotBufferPtr, Colour, grptr->PixelCount); return (0); } /*****************************************************************************/ /* The basic point drawing/plotting function. Draws a point at (x,y) in the specified colour. Width specifies the size of the point, 1 being one pixel, 2 being 2x2 pixels, 3 being 3x3 pixels, etc. Points with widths greater than 1 begin at the specified (x,y) and grow in the x and y directions. */ char* GraphDrawPoint ( GRAPH_STRUCT *grptr, int atx, int aty, int Colour, int Width ) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphDrawPoint() %d,%d\n", atx, aty); if (Width <= 0) Width = 1; if (Width >= grptr->Width) Width = grptr->Width - 1; if (atx < 0) atx = 0; if (atx >= grptr->Width) atx = grptr->Width - 1; if (aty < 0) aty = 0; if (aty >= grptr->Height) aty = grptr->Height - 1; if (Width == 1) { *(grptr->PlotBufferPtr + (aty * grptr->Width) + atx) = Colour; return (0); } else { int x, y, tox, toy; if ((tox = atx + Width) >= grptr->Width) tox = grptr->Width - 1; if ((toy = aty + Width) >= grptr->Height) tox = grptr->Height - 1; for (x = atx; x < tox; x++) for (y = aty; y < toy; y++) *(grptr->PlotBufferPtr + (y * grptr->Width) + x) = Colour; return (0); } } /*****************************************************************************/ /* Draws a straight line from (x1,y1) to (x2,y2) of the specified colour and width. For efficiency plots its own points without calling GraphDrawPoint(). */ char* GraphDrawLine ( GRAPH_STRUCT *grptr, int x1, int y1, int x2, int y2, int Colour, int Width ) { BOOL SlopePos; int thy, x, xinc, y, yinc; float Slope, yf; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphDrawLine() %d,%d %d,%d %d %d\n", x1, y1, x2, y2, Colour, Width); if (Width <= 0) Width = 1; if (Width >= grptr->Width) Width = grptr->Width - 1; if (x1 < 0) x1 = 0; if (x1 >= grptr->Width) x1 = grptr->Width - 1; if (x2 < 0) x2 = 0; if (x2 >= grptr->Width) x2 = grptr->Width - 1; if (y1 < 0) y1 = 0; if (y1 >= grptr->Height) y1 = grptr->Height - 1; if (y2 < 0) y2 = 0; if (y2 >= grptr->Height) y2 = grptr->Height - 1; if (x1 > x2) { x = x1; x1 = x2; x2 = x; y = y1; y1 = y2; y2 = y; } if (x2 - x1) { xinc = 1; Slope = (float)(y2 -y1) / (float)(x2 - x1); } else { xinc = 0; if (y2 > y1) Slope = 1.0; else Slope = -1.0; } if (Slope >= 0.0) { SlopePos = true; yinc = 1; } else { SlopePos = false; yinc = -1; } yf = (float)(y = thy = y1); x = x1; while (x <= x2 && ((SlopePos && y <= y2) || (!SlopePos && y >= y2))) { if (GraphDebug) fprintf (stdout, "%d,%d (%f %f)\n", x, y, yf, Slope); while ((SlopePos && thy <= y) || (!SlopePos && thy >= y)) { /* plot the point */ if (Width == 1) *(grptr->PlotBufferPtr + (thy * grptr->Width) + x) = Colour; else { int xx, yy, tox, toy; if ((tox = x + Width) >= grptr->Width) tox = grptr->Width - 1; if ((toy = thy + Width) >= grptr->Height) toy = grptr->Height - 1; for (xx = x; xx < tox; xx++) for (yy = thy; yy < toy; yy++) *(grptr->PlotBufferPtr + (yy * grptr->Width) + xx) = Colour; } thy += yinc; } thy -= yinc; x += xinc; y = (int)(yf = yf + Slope); } return (0); } /*****************************************************************************/ /* Draw a rectangle with opposite corners defined by (x1,y1) and (x2,y2) of the specified colour and width. */ char* GraphDrawRect ( GRAPH_STRUCT *grptr, int x1, int y1, int x2, int y2, int Colour, int Width ) { int x, y; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphDrawRect() %d,%d %d,%d %d\n", x1, y1, x2, y2, Colour); if (Width <= 0) Width = 1; if (Width >= grptr->Width) Width = grptr->Width - 1; if (x1 < 0) x1 = 0; if (x1 >= grptr->Width) x1 = grptr->Width - 1; if (x2 < 0) x2 = 0; if (x2 >= grptr->Width) x2 = grptr->Width - 1; if (y1 < 0) y1 = 0; if (y1 >= grptr->Height) y1 = grptr->Height - 1; if (y2 < 0) y2 = 0; if (y2 >= grptr->Height) y2 = grptr->Height - 1; GraphDrawLine (grptr, x1, y1, x1, y2, Colour, Width); GraphDrawLine (grptr, x1, y2, x2, y2, Colour, Width); GraphDrawLine (grptr, x2, y2, x2, y1, Colour, Width); GraphDrawLine (grptr, x2, y1, x1, y1, Colour, Width); return (0); } /*****************************************************************************/ /* Draw a rectangle with opposite corners defined by (x1,y1) and (x2,y2) filled with the specified colour. */ char* GraphDrawBlock ( GRAPH_STRUCT *grptr, int x1, int y1, int x2, int y2, int Colour ) { int x, y; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphDrawBlock() %d,%d %d,%d %d\n", x1, y1, x2, y2, Colour); if (x1 < 0) x1 = 0; if (x1 >= grptr->Width) x1 = grptr->Width - 1; if (x2 < 0) x2 = 0; if (x2 >= grptr->Width) x2 = grptr->Width - 1; if (y1 < 0) y1 = 0; if (y1 >= grptr->Height) y1 = grptr->Height - 1; if (y2 < 0) y2 = 0; if (y2 >= grptr->Height) y2 = grptr->Height - 1; if (x1 > x2) { x = x1; x1 = x2; x2 = x; } if (y1 > y2) { y = y1; y1 = y2; y2 = y; } for (x = x1; x <= x2; x++) GraphDrawLine (grptr, x, y1, x, y2, Colour, 1); return (0); } /*****************************************************************************/ /* Draw graduations along either the top or bottom X axis. */ char* GraphGraduateXAxis ( GRAPH_STRUCT *grptr, int TopOrBottom, int Graduations, int Length, int Colour ) { int x, FromY, ToY; float gr, xf; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphGraduateXAxis() %d %d %d\n", TopOrBottom, Graduations, Colour); if (!Graduations) return (0); if (TopOrBottom == GRAPH_XAXIS_BOTTOM) { FromY = 0; ToY = Length - 1; } else { FromY = grptr->Height - Length; ToY = grptr->Height - 1; } xf = 0.0; gr = (float)grptr->Width / ((float)Graduations + 0.001); for (x = 0; x < grptr->Width; x = (int)(xf += gr)) GraphDrawLine (grptr, x, FromY, x, ToY, Colour, 1); return (0); } /*****************************************************************************/ /* Draw graduations along either the left or right Y axis. */ char* GraphGraduateYAxis ( GRAPH_STRUCT *grptr, int LeftOrRight, int Graduations, int Length, int Colour ) { int y, FromX, ToX; float gr, yf; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphGraduateYAxis() %d %d %d\n", LeftOrRight, Graduations, Colour); if (!Graduations) return (0); if (LeftOrRight == GRAPH_YAXIS_LEFT) { FromX = 0; ToX = Length - 1; } else { FromX = grptr->Width - Length; ToX = grptr->Width - 1; } yf = 0.0; gr = (float)grptr->Height / (float)Graduations; for (y = 0; y < grptr->Height; y = (int)(yf += gr)) GraphDrawLine (grptr, FromX, y, ToX, y, Colour, 1); return (0); } /*****************************************************************************/ /* Place a border of the specified colour and width around the entire graphic. */ char* GraphDrawBorder ( GRAPH_STRUCT *grptr, int Colour, int Width ) { int x, y, cnt; unsigned char *bptr; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphDrawBorder() %d %d\n", Colour, Width); if (Width <= 0 || Width >= grptr->Width || Width >= grptr->Height) return (grptr->ErrorTextPtr = "Graph: border width problem."); for (cnt = 0; cnt < Width; cnt++) { bptr = grptr->PlotBufferPtr + (cnt * grptr->Width); for (x = 0; x < grptr->Width; x++) *bptr++ = Colour; } for (cnt = Width; cnt; cnt--) { bptr = grptr->PlotBufferPtr + (grptr->Height - cnt) * grptr->Width; for (x = 0; x < grptr->Width; x++) *bptr++ = Colour; } for (cnt = 0; cnt < Width; cnt++) { bptr = grptr->PlotBufferPtr + cnt; for (y = 0; y < grptr->Height; y++) *(bptr + (y * grptr->Width)) = Colour; } for (cnt = Width; cnt; cnt--) { bptr = grptr->PlotBufferPtr + grptr->Width - cnt; for (y = 0; y < grptr->Height; y++) *(bptr + (y * grptr->Width)) = Colour; } return (0); } /*****************************************************************************/ /* Convert a pixmap image to a GIF image. The code in these functions is implemented in accordance with Compuserve's Graphic Interchange Format Programming Reference specification, version 89a, 31st July 1990. The LZW compression employed by the GIF algorithm is implemented using code derived from the PBM suite. Two functions, slightly modified and definitely reformatted, are employed, GraphCompress() ... formally called 'compgif()', and GraphPackBits() ... formally called 'pack_bits()'. The original commentary and copyright notice remains as per the code author's request. */ char* GraphGifImage (GRAPH_STRUCT *grptr) { int idx, Byte, LeftOffset, TopOffset, Resolution, ColorMapSize, InitCodeSize; char *cptr; unsigned char *gifptr; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphGifImage()\n"); grptr->GifContentLength = 0; /* initialize non-reentrant functions() */ GraphPackBits (NULL); GraphNextPixel (NULL); ColorMapSize = 1 << grptr->BitsPerPixel; LeftOffset = TopOffset = 0; Resolution = grptr->BitsPerPixel; /* the initial code size */ if (grptr->BitsPerPixel <= 1) InitCodeSize = 2; else InitCodeSize = grptr->BitsPerPixel; /**************************/ /* GIF Data Stream header */ /**************************/ gifptr = grptr->GifBufferPtr; memcpy (gifptr, "GIF89a", 6); gifptr += 6; /*****************************/ /* Logical Screen Descriptor */ /*****************************/ /* width and height of logical screen */ *gifptr++ = grptr->Width & 0xff; *gifptr++ = (grptr->Width >> 8) & 0xff; *gifptr++ = grptr->Height & 0xff; *gifptr++ = (grptr->Height >> 8) & 0xff; /* indicate that there is a global colour map */ Byte = 0x80; /* OR in the resolution */ Byte |= (Resolution - 1) << 5; /* OR in the Bits per Pixel */ Byte |= (grptr->BitsPerPixel - 1); /* buffer it */ *gifptr++ = Byte; /* background colour */ *gifptr++ = grptr->GifColourBg; /* pixel aspect ratio */ *gifptr++ = 0; /***********************/ /* Global Colour Table */ /***********************/ for (idx = 0; idx < ColorMapSize; idx++) { *gifptr++ = GraphicRgbRed[idx]; *gifptr++ = GraphicRgbGreen[idx]; *gifptr++ = GraphicRgbBlue[idx]; } /****************************************/ /* Graphic Control Extension descriptor */ /****************************************/ if (grptr->ColourTr >= 0) { /* extension introducer and graphic control label */ *gifptr++ = 0x21; *gifptr++ = 0xf9; /* fixed size of the following data block */ *gifptr++ = 0x04; /* Transparency Index is provided */ *gifptr++ = 0x01; /* no data in these */ *gifptr++ = 0x00; *gifptr++ = 0x00; /* Transparent Color Index value, background SHOULD BE TRANSPARENT */ *gifptr++ = grptr->ColourTr; /* block terminator */ *gifptr++ = 0x00; } /********************/ /* image descriptor */ /********************/ /* write an image separator */ *gifptr++ = 0x2c; /* location of image within logical screen */ *gifptr++ = LeftOffset & 0xff; *gifptr++ = (LeftOffset >> 8) & 0xff; *gifptr++ = TopOffset & 0xff; *gifptr++ = (TopOffset >> 8) & 0xff; /* width and height of image within logical screen */ *gifptr++ = grptr->Width & 0xff; *gifptr++ = (grptr->Width >> 8) & 0xff; *gifptr++ = grptr->Height & 0xff; *gifptr++ = (grptr->Height >> 8) & 0xff; /* no local color table, image is not interlaced, not ordered, etc. */ *gifptr++ = 0x00; /**************************/ /* table-based image data */ /**************************/ /* write out the initial code size */ *gifptr++ = InitCodeSize; /* LZW compress the data using PBM-derived algorithm and code */ if (GraphDebug) fprintf (stdout, "%d bytes\n", gifptr - grptr->GifBufferPtr); grptr->GifPtr = gifptr; if (cptr = GraphCompress (grptr, InitCodeSize + 1)) return (cptr); if (GraphDebug) fprintf (stdout, "%d bytes\n", grptr->GifPtr - grptr->GifBufferPtr); /****************************************/ /* end of image data and GIF terminator */ /****************************************/ /* write out a zero-length packet (to end the series), and terminator */ if (grptr->GifPtr < grptr->GifBufferEndPtr) *grptr->GifPtr++ = 0; if (grptr->GifPtr < grptr->GifBufferEndPtr) *grptr->GifPtr++ = ';'; if (grptr->GifPtr >= grptr->GifBufferEndPtr) return("GIF: image buffer too small."); grptr->GifContentLength = grptr->GifPtr - grptr->GifBufferPtr; return (0); } /*****************************************************************************/ /* Get a series of pixels from the bitmap. It is done in such a way that the origin (0,0) is in the bottom, left hand corner, although the memory origin is at byte zero. */ int GraphNextPixel (GRAPH_STRUCT *grptr) { static int CountX, CountY; static unsigned char *PixelPtr; /*********/ /* begin */ /*********/ /* if (GraphDebug) fprintf (stdout, "GraphNextPixel()\n"); */ if (!grptr) { /* initialize */ CountX = 0; CountY = -1; return (0); } if (!CountX--) { if (CountY < 0) CountY = grptr->Height; if (!CountY--) return (EOF); CountX = grptr->Width - 1; PixelPtr = grptr->PlotBufferPtr + (CountY * grptr->Width); } return (*PixelPtr++); } /****************************************************************************/ /* * This software is copyrighted as noted below. It may be freely copied, * modified, and redistributed, provided that the copyright notice is * preserved on all copies. * * There is no warranty or other guarantee of fitness for this software, * it is provided solely "as is". Bug reports or fixes may be sent * to the author, who may or may not act on them as he desires. * * You may not include this software in a program or other software product * without supplying the source, or without informing the end-user that the * source is available for no extra charge. * * If you modify this software, you should include a notice giving the * name of the person performing the modification, the date of modification, * and the reason for such modification. */ /* compgif.c */ /* * * GIF Image compression - LZW algorithm implemented with Tree type * structure. * Written by Bailey Brown, Jr. * last change May 24, 1990 * file: compgif.c * * You may use or modify this code as you wish, as long as you mention * my name in your documentation. * * - Bailey Brown, Jr. * */ #define MAXIMUMCODE 4095 /* 2**maximum_code_size */ #define BLOCKSIZE 256 /* max block byte count + 1 */ #define NULLPREFIX -1 typedef struct str_table_entry { int code; int prefix; int suffix; } strTableEntry; typedef struct str_table_node { strTableEntry entry; struct str_table_node *left; struct str_table_node *right; struct str_table_node *children; } strTableNode, *strTableNodePtr, **strTable; /* ******************************************************************** * compgif() receives pointers to an input function and an output * * stream, and the code size as parameters and outputs successive * * blocks of LZW compressed gif data. The calling routine should * * have aready written the GIF file header out to the output file. * * It assumes that there will be no more than 8 bits/pixel and that * * each data item comes from successive bytes returned by infun. * ******************************************************************** */ char* GraphCompress ( GRAPH_STRUCT *grptr, int code_size ) { strTable heap; /* our very own memory manager */ int heap_index; int clear_code, end_code, cur_code; int i, found, num_colors, prefix, compress_size; int cur_char, end_of_data, bits_per_pix; strTableNodePtr cur_node; strTable root; /* root of string table for LZW compression is */ /* an array of 2**bits_per_pix pointers to atomic nodes */ /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphCompress()\n"); heap_index = 0; heap = (strTable)VmGet(sizeof(strTableNodePtr)*MAXIMUMCODE); if (!heap) return("GIF: VmGet() failure, heap"); for (i=0; i < MAXIMUMCODE; i++) { heap[i] = (strTableNodePtr)VmGet(sizeof(strTableNode)); if (!heap[i]) return("GIF: VmGet() failure, heap[idx]"); } bits_per_pix = code_size - 1; compress_size = code_size; num_colors = 1<<(bits_per_pix); clear_code = num_colors; end_code = clear_code + 1; cur_code = end_code + 1; prefix = NULLPREFIX; root = (strTable)VmGet(sizeof(strTableNodePtr)*num_colors); if (!root) return("GIF: VmGet() failure, root"); for(i=0; ientry.code = i; root[i]->entry.prefix = NULLPREFIX; root[i]->entry.suffix = i; root[i]->left = NULL; root[i]->right = NULL; root[i]->children = NULL; } /* initialize output block */ GraphPackBits(grptr, compress_size, -1); GraphPackBits(grptr, compress_size, clear_code); end_of_data = 0; if ((cur_char = GraphNextPixel(grptr)) == EOF) return("GIF: premature end of data."); while (!end_of_data) { prefix = cur_char; cur_node = root[prefix]; found = 1; if((cur_char = GraphNextPixel(grptr)) == EOF) { end_of_data = 1; break; } while(cur_node->children && found) { cur_node = cur_node->children; while(cur_node->entry.suffix != cur_char) { if (cur_char < cur_node->entry.suffix) { if (cur_node->left) cur_node = cur_node->left; else { cur_node->left = heap[heap_index++]; cur_node = cur_node->left; found = 0; break; } } else { if (cur_node->right) cur_node = cur_node->right; else { cur_node->right = heap[heap_index++]; cur_node = cur_node->right; found = 0; break; } } } if (found) { prefix = cur_node->entry.code; if((cur_char = GraphNextPixel(grptr)) == EOF) { end_of_data = 1; break; } } } if (end_of_data) break; if (found) { cur_node->children = heap[heap_index++]; cur_node = cur_node->children; } cur_node->children = NULL; cur_node->left = NULL; cur_node->right = NULL; cur_node->entry.code = cur_code; cur_node->entry.prefix = prefix; cur_node->entry.suffix = cur_char; GraphPackBits(grptr, compress_size, prefix); if (cur_code > ((1<<(compress_size))-1)) compress_size++; if (cur_code < MAXIMUMCODE) cur_code++; else { heap_index = num_colors; /* reinitialize string table */ for (i=0; i < num_colors; i++ ) root[i]->children = NULL; GraphPackBits(grptr, compress_size, clear_code); compress_size = bits_per_pix + 1; cur_code = end_code + 1; } } GraphPackBits(grptr, compress_size, prefix); GraphPackBits(grptr, compress_size, end_code); GraphPackBits(grptr, compress_size, -1); for (i=0; i < MAXIMUMCODE; i++) VmFree(heap[i], FI_LI); VmFree(heap, FI_LI); VmFree(root, FI_LI); return (0); } /****************************************************************************/ /* ************************************************************************ * pack_bits() packs the bits of the codes generated by gifenc() into * * a 1..256 byte output block. The first byte of the block is the * * number 0..255 of data bytes in the block. To flush or initialize * * the block, pass a negative argument. * ************************************************************************ */ int GraphPackBits ( GRAPH_STRUCT *grptr, int compress_size, int prefix ) { static int cur_bit = 8; static unsigned char block[BLOCKSIZE] = { 0 }; int i, left_over_bits; /*********/ /* begin */ /*********/ /* if (GraphDebug) fprintf (stdout, "GraphPackBits() %d %d %d\n", grptr, compress_size, prefix); */ if (!grptr) { /* initialize */ cur_bit = 8; block[0] = 0; return(1); } /* if we are about to excede the bounds of block or if the flush code (code_bis < 0) we output the block */ if((cur_bit + compress_size > (BLOCKSIZE-1)*8) || (prefix < 0)) { /* handle case of data overlapping blocks */ if ((left_over_bits = (((cur_bit>>3) + ((cur_bit & 7) != 0))<<3) - cur_bit) != 0) { for (i=0; i < left_over_bits; i++) { if (prefix & (1<>3] |= (char)(1<<(cur_bit & 7)); /* note n>>3 == n/8 and n & 7 == n % 8 */ cur_bit++; } } compress_size -= left_over_bits; prefix = prefix>>left_over_bits; block[0] = (unsigned char)((cur_bit>>3) - 1); if (block[0]) { /** if (GraphDebug) fprintf (stdout, "%d\n", block[0]+1); **/ if (grptr->GifPtr + (block[0]+1) >= grptr->GifBufferEndPtr) grptr->GifPtr = grptr->GifBufferEndPtr; else { memcpy (grptr->GifPtr, block, block[0]+1); grptr->GifPtr = grptr->GifPtr + block[0]+1; } } memset (block, 0, BLOCKSIZE); cur_bit = 8; } if (prefix >= 0) { for (i=0; i < compress_size; i++) { if (prefix & (1<>3] |= (unsigned char)(1<<(cur_bit & 7)); /* note n>>3 == n/8 and n & 7 == n % 8 */ cur_bit++; } } return (1); } /*****************************************************************************/