/*****************************************************************************/ /* Graph.c This module accumlates and manages server activity data in a global section. It also allows a(n admin) client to access that data, rendering it as a time-based graphic, displaying requests and bytes transferred per minute, along with associated means and peaks. Events such as server exit (normal and error) and startup are also indicated. Prior to WASD v11.1 the activity graphic was an independently generated GIF image and JavaScript was optional. From that version the graphic is generated using JavaScript and the HTML5 canvas API. Client JavaScript is a requisite. 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. VERSION HISTORY --------------- 14-NOV-2020 MGD migrate from ulong[2] to int64 for 64 bit quantities 28-APR-2018 MGD refactor Admin..() AST delivery 21-APR-2018 MGD JavaScript startUpdatePage() only when at current time 03-FEB-2018 MGD bugfix; GraphActivityReport() number hours "%4u%2u%2u%2u+%u" 01-JAN-2017 MGD push rendering from C20 (GIF) to C21 (HTML5 Canvas) (while I did want to refine the functionality I didn't want to completely reinvent this particular wheel and so restricted changes to the graphic rendering code) 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" #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 ActivityNumberOfDays, ActivityTotalMinutes; ACTIVITY_GBLSEC *ActivityGblSecPtr; int GraphAbsDay, GraphicMaximumHeight = 1505, GraphicMaximumWidth = 1505; ushort 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, NetCurrentConnected, NetCurrentProcessing; extern const int64 Delta01Sec; extern int64 HttpdTime64; extern ulong GblSecPrvMask[]; extern ushort HttpdTime7[]; extern char ServerHostPort[]; 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 ulong ProtectionMask = 0xff00; /* it is recommended to map into any virtual address in the region (P0) */ static ulong InAddr [2] = { 0x200, 0x200 }; int status, attempt, BaseGblSecPages, BytesRequired, GblSecPages, PageCount; short ShortLength; ulong RetAddr [2]; char GblSecName [32]; $DESCRIPTOR (GblSecNameDsc, GblSecName); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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->StartTime64); lib$day (&ActivityGblSecPtr->StartAbsDay, &ActivityGblSecPtr->StartTime64, &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. */ void GraphActivityClearDay () { int idx, status; ulong AbsDay, Day; int64 Time64; ushort Time7 [7]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityClearDay()"); lib$day (&AbsDay, &HttpdTime64, 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->ByteCount64[idx], 0, MINUTES_IN_DAY * sizeof(int64)); memset (&ActivityGblSecPtr->ConnectPeak[idx], 0, MINUTES_IN_DAY * sizeof(ushort)); memset (&ActivityGblSecPtr->RequestCount[idx], 0, MINUTES_IN_DAY * sizeof(ulong)); memset (&ActivityGblSecPtr->RequestPeak[idx], 0, MINUTES_IN_DAY * sizeof(ushort)); 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->StartTime64); GraphActivityOffsetTime (-(ActivityTotalMinutes-MINUTES_IN_DAY), &ActivityGblSecPtr->StartTime64, &Time64); sys$numtim (&Time7, &Time64); Time7[3] = Time7[4] = Time7[5] = Time7[6] = 0; status = lib$cvt_vectim (&Time7, &ActivityGblSecPtr->StartTime64); if (VMSnok(status)) ErrorExitVmsStatus (status, "lib$cvt_vectim()", FI_LI); lib$day (&ActivityGblSecPtr->StartAbsDay, &ActivityGblSecPtr->StartTime64, 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 RequestEnd2() when concluding each request. 2) Called periodically by HttpdSupervisor() to update the network transfered bytes in the activity accumulator for the minute. */ void GraphActivityUpdate ( REQUEST_STRUCT *rqptr, BOOL FinalUpdate ) { int idx; long Day; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityUpdate()"); if (!ActivityTotalMinutes) return; /* save the overhead of the lib$day() call if the date has not changed! */ if (HttpdTime7[2] != GraphPrevDate) { GraphActivityClearDay (); lib$day (&GraphAbsDay, &HttpdTime64, 0); GraphPrevDate = HttpdTime7[2]; } Day = GraphAbsDay % ActivityNumberOfDays; idx = Day * MINUTES_IN_DAY + HttpdTime7[3] * MINUTES_IN_HOUR + HttpdTime7[4]; if (FinalUpdate) { ActivityGblSecPtr->RequestCount[idx]++; if (NetCurrentConnected > ActivityGblSecPtr->ConnectPeak[idx]) ActivityGblSecPtr->ConnectPeak[idx] = NetCurrentConnected; if (NetCurrentProcessing > ActivityGblSecPtr->RequestPeak[idx]) ActivityGblSecPtr->RequestPeak[idx] = NetCurrentProcessing; } ActivityGblSecPtr->ByteCount64[idx] += rqptr->NetIoPtr->BytesTallyRx64; ActivityGblSecPtr->ByteCount64[idx] += rqptr->NetIoPtr->BytesTallyTx64; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL-!UL=!SL !UL !UL !UL !@UQ[!UL] !UL[!UL] !UL[!UL]", ActivityGblSecPtr->StartAbsDay, GraphAbsDay, ActivityGblSecPtr->StartAbsDay - GraphAbsDay, rqptr->rqTime.BeginTime7[3], rqptr->rqTime.BeginTime7[4], Day, &ActivityGblSecPtr->ByteCount64[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 */ void GraphActivityEvent (ulong EventBit) { int idx; long Day; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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 (HttpdTime7[2] != GraphPrevDate) { GraphActivityClearDay (); lib$day (&GraphAbsDay, &HttpdTime64, 0); GraphPrevDate = HttpdTime7[2]; } Day = GraphAbsDay % ActivityNumberOfDays; idx = Day * MINUTES_IN_DAY + HttpdTime7[3] * MINUTES_IN_HOUR + HttpdTime7[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, uint *PeakConnectPtr, uint *PeakPerMinRequestPtr, uint *PeakRequestPtr, int64 *PeakBytePtr, int64 *TotalRequestsPtr, int64 *TotalBytesPtr ) { int cnt, idx, mcnt; uint AbsDay, ConnectPeak, NumberOfMinutes, PeakConnect, PeakPerMinRequest, PeakRequest, RequestCount, RequestPeak, RequestValue; int64 ByteCount64, ByteValue64, PeakBytes64, SignScratch64, TotalBytes64, TotalRequests64; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, 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; PeakBytes64 = TotalBytes64 = TotalRequests64 = 0; for (mcnt = 0; mcnt < NumberOfMinutes; mcnt += MinuteGranularity) { if (idx >= ActivityTotalMinutes) idx = 0; if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL", mcnt, idx); if (MinuteGranularity == 1) { ByteCount64 = ActivityGblSecPtr->ByteCount64[idx]; TotalBytes64 += ByteCount64; RequestCount = ActivityGblSecPtr->RequestCount[idx] & ACTIVITY_MASK; TotalRequests64 += RequestCount; ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx]; RequestPeak = ActivityGblSecPtr->RequestPeak[idx]; idx++; } else { /* find the peak or average of the minute counts */ RequestCount = ConnectPeak = RequestPeak = 0; ByteCount64 = 0; for (cnt = 0; cnt < MinuteGranularity; cnt++) { ByteValue64 = ActivityGblSecPtr->ByteCount64[idx]; TotalBytes64 += ByteValue64; if (ByteValue64 > ByteCount64) ByteCount64 = ByteValue64; RequestValue = ActivityGblSecPtr->RequestCount[idx]; RequestValue = (ulong)RequestValue & ACTIVITY_MASK; TotalRequests64 += RequestValue; 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 (WATCHALL, WATCH_MOD__OTHER, "!@UQ !UL", &ByteCount64, RequestCount); } if (ByteCount64 > PeakBytes64) PeakBytes64 = ByteCount64; 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 (WATCHALL, WATCH_MOD__OTHER, "!@UQ !@UQ !@UQ !UL !UL !UL", &TotalBytes64, &TotalRequests64, &PeakBytes64, PeakPerMinRequest, PeakRequest, PeakConnect); if (PeakBytePtr) *PeakBytePtr = PeakBytes64; if (PeakConnectPtr) *PeakConnectPtr = PeakConnect; if (PeakPerMinRequestPtr) *PeakPerMinRequestPtr = PeakPerMinRequest; if (PeakRequestPtr) *PeakRequestPtr = PeakRequest; if (TotalBytesPtr) *TotalBytesPtr = TotalBytes64; if (TotalRequestsPtr) *TotalRequestsPtr = TotalRequests64; 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) */ void GraphActivityMaxima ( uint *PeakRequestPtr, uint *MaxRequestPtr, int64 *MaxBytePtr ) { uint MaxRequests, PeakRequests; int64 MaxBytes64, MaxBytesScratch64; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityMaxima()"); PeakRequests = MaxRequests = 0; if (PeakRequestPtr) PeakRequests = *PeakRequestPtr; if (MaxRequestPtr) MaxRequests = *MaxRequestPtr; if (!MaxBytePtr) { MaxBytePtr = &MaxBytesScratch64; MaxBytesScratch64 = 0; } 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; MaxBytes64 = *MaxBytePtr; if (MaxBytes64 < 100) *MaxBytePtr = ((MaxBytes64 / 10) * 10) + 10; else if (MaxBytes64 < 1000) *MaxBytePtr = ((MaxBytes64 / 100) * 100) + 100; else if (MaxBytes64 < 10000) *MaxBytePtr = ((MaxBytes64 / 1000) * 1000) + 1000; else if (MaxBytes64 < 100000) *MaxBytePtr = ((MaxBytes64 / 10000) * 10000) + 10000; else if (MaxBytes64 < 1000000) *MaxBytePtr = ((MaxBytes64 / 100000) * 100000) + 100000; else if (MaxBytes64 < 10000000) *MaxBytePtr = ((MaxBytes64 / 1000000) * 1000000) + 1000000; else if (MaxBytes64 < 100000000) *MaxBytePtr = ((MaxBytes64 / 10000000) * 10000000) + 10000000; else if (MaxBytes64 < 1000000000) *MaxBytePtr = ((MaxBytes64 / 100000000) * 100000000) + 100000000; 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. */ void GraphActivityOffsetTime ( int NumberOfMinutes, int64 *BaseTime64Ptr, int64 *OffsetTime64Ptr ) { int64 DeltaTime64, Seconds64; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityOffsetTime() !SL", NumberOfMinutes); if (!NumberOfMinutes) Seconds64 = 1; else if (NumberOfMinutes > 0) Seconds64 = NumberOfMinutes * MINUTES_IN_HOUR; else Seconds64 = -NumberOfMinutes * MINUTES_IN_HOUR; DeltaTime64 = Seconds64 * TIME64_ONE_SEC; if (NumberOfMinutes >= 0) *OffsetTime64Ptr = *BaseTime64Ptr + DeltaTime64; else *OffsetTime64Ptr = *BaseTime64Ptr - DeltaTime64; } /*****************************************************************************/ /* Return the absolute hour for the supplied time. */ ulong GraphActivityAbsHour (int64 *Time64Ptr) { ulong day, millisec; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityAbsHour()"); lib$day (&day, Time64Ptr, &millisec); return ((day * 24) + ((millisec / 360000)) % 60); } /*****************************************************************************/ /* Return the absolute minute for the supplied time. */ ulong GraphActivityAbsMinute (int64 *Time64Ptr) { ulong day, millisec; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityAbsHour()"); lib$day (&day, Time64Ptr, &millisec); return ((day * 24 * 60) + (millisec / 6000)); } /*****************************************************************************/ /* Return a pointer to a string containing a description of the period. */ char* GraphActivityPeriod ( int64 *StartTime64, int64 *EndTime64 ) { static char MultipleDaysFao [] = "!17&W to !17&W"; static char WithinOneDayFao [] = "!17&W to !2ZL:!2ZL"; static char PeriodBuffer [64]; int status; ushort EndTime7 [7], StartTime7 [7]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityPeriod()"); status = sys$numtim (StartTime7, StartTime64); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); status = sys$numtim (EndTime7, EndTime64); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); if (StartTime7[0] != EndTime7[0] || StartTime7[1] != EndTime7[1] || StartTime7[2] != EndTime7[2]) status = FaoToBuffer (PeriodBuffer, sizeof(PeriodBuffer), NULL, MultipleDaysFao, StartTime64, EndTime64); else status = FaoToBuffer (PeriodBuffer, sizeof(PeriodBuffer), NULL, WithinOneDayFao, StartTime64, EndTime7[3], EndTime7[4]); if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI); return (PeriodBuffer); } /*****************************************************************************/ /* 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! */ void GraphActivityReport (REQUEST_STRUCT *rqptr) { static char *MonthName [] = { "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; static char ProblemNoted [] = "
\ Problem noted. Check server process log.
\n"; static char ResponseFao [] = "!AZ\ \n\ \n\ !AZ\ !AZ\ !&@\ \n\ \n\ WASD !AZ ... Server Activity\n\ \n\ \ \n\

WASD !AZ

\n\

Server Activity!&@

\n\ !20&W\n\ \

\n\ \ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \n\ \ \ \ \ \ \ \ \ \ \n\ \
!UL  !UL!AZ
!AZ \ \
(there's been a problem!!)
\
 !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:!&,@UQ total; \  !&L max;  !&L peak!&@
Bytes:!&,@UQ total; \  !&,@UQ max
(Data available from !17&W)
\n\ !AZ!AZ"; static char EndPageFao [] = "

Requires JavaScript!!
\ \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, HourSupplied; int cnt, scnt, status, AdminInstanceCount, AtX, Bytes, ColumnWidth, Count, Day, GraphHeight, GraphWidth, Hour, IncrementMinutes, MapSections, MinuteGranularity, Minutes, Month, NodeInstanceCount, NumberOfDays, NumberOfHours, NumberOfMinutes, PeriodHours, UpdateSeconds, Year; int64 DeltaTime64, Time64, MaxBytes64, PeakBytes64, TotalBytes64, TotalRequests64; ushort Length; ushort Time7 [7]; ulong AbsDay, MaxPeak, MaxRequests, MaxYRequests, Minute, PeakConnect, PeakPerMinRequests, PeakRequests, Second; ulong *vecptr; ulong FaoVector [64]; char *cptr, *sptr, *zptr, *BytesPtr, *EndPtr, *HoursPtr, *NodeInstancePtr, *StartPtr; char OfString [32], RefreshString [32], Uniquifier [16]; GRAPH_STRUCT *grptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityReport() !&Z", rqptr->rqHeader.QueryStringPtr); if (!ActivityTotalMinutes) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorGraphNotInit, FI_LI); AdminEnd (rqptr); return; } /* allocate heap memory for the graph structure */ grptr = (GRAPH_STRUCT*)VmGetHeap (rqptr, sizeof(GRAPH_STRUCT)); grptr->RequestPtr = rqptr; 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) { cptr = rqptr->rqHeader.QueryStringPtr; if (isdigit(*cptr)) NumberOfHours = atoi(cptr); else { while (*cptr) { if (MATCH3 (cptr, "of=")) { cptr += 3; scnt = sscanf (cptr, "%4u%2u%2u%2u/%u", &Year, &Month, &Day, &Hour, &MaxYRequests); if (scnt != 5) scnt = Year = Month = Day = Hour = MaxYRequests = 0; if (!scnt) { scnt = sscanf (cptr, "%4u%2u%2u%2u+%u", &Year, &Month, &Day, &Hour, &NumberOfHours); if (scnt != 5) scnt = Year = Month = Day = Hour = 0; } if (scnt != 5) scnt = Year = Month = Day = Hour = MaxYRequests = 0; if (!scnt) { scnt = sscanf (cptr, "%4u%2u%2u%2u", &Year, &Month, &Day, &Hour); if (scnt != 4) scnt = Year = Month = Day = Hour = 0; } if (scnt) HourSupplied = true; else NumberOfHours = atoi(cptr); } else if (MATCH3 (cptr, "dm=")) { cptr += 3; scnt = sscanf (cptr, "%ux%u/%u", &GraphWidth, &GraphHeight, &MaxYRequests); if (scnt != 3) scnt = GraphWidth = GraphHeight = MaxYRequests = 0; if (!scnt) { scnt = sscanf (cptr, "%ux%u", &GraphWidth, &GraphHeight); if (scnt != 2) scnt = GraphWidth = GraphHeight = MaxYRequests = 0; } } else if (MATCH3 (cptr, "yr=")) Year = atoi(cptr+3); else if (MATCH3 (cptr, "mn=")) Month = atoi(cptr+3); else if (MATCH3 (cptr, "dy=")) Day = atoi(cptr+3); else if (MATCH3 (cptr, "hr=")) { Hour = atoi(cptr+3); HourSupplied = true; } else if (MATCH3 (cptr, "du=")) NumberOfHours = atoi(cptr+3); else if (MATCH3 (cptr, "xy=")) { cptr += 3; GraphWidth = atoi(cptr); while (*cptr && isdigit(*cptr)) cptr++; while (*cptr && !isdigit(*cptr)) cptr++; GraphHeight = atoi(cptr); } else if (!MATCH8 (cptr, "refresh=")) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphQuery, FI_LI); AdminEnd (rqptr); return; } while (*cptr && *cptr != '&') cptr++; if (*cptr) cptr++; } } } if (MaxYRequests < 0) MaxYRequests = 0; if (rqptr->rqHeader.RefererPtr) 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 (WATCHALL, WATCH_MOD__OTHER, "!UL/!UL/!UL:!UL !UL !ULx!UL/!UL", Year, Month, Day, Hour, NumberOfHours, GraphWidth, GraphHeight, MaxYRequests); /***********/ /* process */ /***********/ grptr->FirstDataAbsHour = GraphActivityAbsHour (&ActivityGblSecPtr->StartTime64); lib$day (&grptr->CurrentAbsDay, &HttpdTime64, 0); grptr->CurrentAbsHour = GraphActivityAbsHour(&HttpdTime64); grptr->CurrentAbsMinute = GraphActivityAbsMinute(&HttpdTime64); if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL !UL", grptr->CurrentAbsDay, grptr->CurrentAbsHour, grptr->CurrentAbsMinute); /* defaults for any time components not supplied */ if (!Year) Year = HttpdTime7[0]; if (!Month) Month = HttpdTime7[1]; if (!Day) Day = HttpdTime7[2]; if (!Hour && !HourSupplied) Hour = HttpdTime7[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 (WATCHALL, WATCH_MOD__OTHER, "!UL/!UL/!UL:!UL !UL !ULx!UL/!UL !UL", Year, Month, Day, Hour, NumberOfHours, GraphWidth, GraphHeight, MaxYRequests, NumberOfMinutes); grptr->EndTime7[0] = Year; grptr->EndTime7[1] = Month; grptr->EndTime7[2] = Day; grptr->EndTime7[3] = Hour; /* always ends after the 59th minute! */ grptr->EndTime7[4] = 59; grptr->EndTime7[5] = grptr->EndTime7[6] = 0; if (VMSnok (status = lib$cvt_vectim (&grptr->EndTime7, &grptr->EndTime64))) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); AdminEnd (rqptr); return; } /* get the start time as the number of minutes before the end time */ GraphActivityOffsetTime (-(NumberOfMinutes-1), &grptr->EndTime64, &grptr->StartTime64); if (grptr->StartTime64 == -1) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); AdminEnd (rqptr); return; } status = lib$day (&grptr->StartAbsDay, &grptr->StartTime64, 0); if (VMSnok (status)) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI); AdminEnd (rqptr); return; } sys$numtim (&grptr->StartTime7, &grptr->StartTime64); grptr->StartHour = grptr->StartTime7[3]; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL !UL !UL", grptr->CurrentAbsDay, grptr->StartAbsDay, HttpdTime7[3], grptr->StartHour); if (grptr->CurrentAbsDay - grptr->StartAbsDay > ActivityNumberOfDays) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphHistory, FI_LI); AdminEnd (rqptr); return; } if (grptr->StartAbsDay > grptr->CurrentAbsDay || (grptr->StartAbsDay == grptr->CurrentAbsDay && grptr->StartHour > HttpdTime7[3])) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, ErrorGraphFuture, FI_LI); AdminEnd (rqptr); return; } DeltaTime64 = HttpdTime64 - grptr->EndTime64; if (DeltaTime64 <= 0) CurrentStats = false; else CurrentStats = TRUE; grptr->StartMinute = grptr->StartAbsDay * MINUTES_IN_DAY + grptr->StartHour * MINUTES_IN_HOUR; grptr->EndMinute = grptr->StartMinute + (NumberOfHours * MINUTES_IN_HOUR) - 1; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL", grptr->StartMinute, grptr->EndMinute); /***********************/ /* 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 (WATCHALL, WATCH_MOD__OTHER, "!UL !UL", ColumnWidth, MinuteGranularity); GraphActivityDataScan (grptr->StartAbsDay, grptr->StartHour, NumberOfHours, MinuteGranularity, &PeakConnect, &PeakPerMinRequests, &PeakRequests, &PeakBytes64, &TotalRequests64, &TotalBytes64); MaxBytes64 = PeakBytes64; MaxRequests = PeakPerMinRequests; MaxPeak = PeakRequests; GraphActivityMaxima (&MaxPeak, &MaxRequests, &MaxBytes64); if (!MaxYRequests) MaxYRequests = MaxRequests; if (MaxYRequests) MaxYRequests--; GraphActivityMaxima (0, &MaxYRequests, 0); if (MaxBytes64 < 1000) { Bytes = MaxBytes64; BytesPtr = ""; } else if (MaxBytes64 < 1000000) { Bytes = (int)((float)MaxBytes64 / 1000.0); BytesPtr = " K"; } else if (MaxBytes64 < 1000000000) { Bytes = (int)((float)MaxBytes64 / 1000000.0); BytesPtr = " M"; } else { Bytes = (int)((float)MaxBytes64 / 1000000000.0); BytesPtr = " G"; } /* set these in the graphics structure */ grptr->Width = GraphWidth; grptr->Height = GraphHeight; grptr->Year = Year; grptr->Month = Month; grptr->Day = Day; grptr->Hour = Hour; grptr->MaxRequests = MaxRequests; grptr->MaxYRequests = MaxYRequests; grptr->NumberOfHours = NumberOfHours; grptr->NumberOfMinutes = NumberOfMinutes; grptr->MaxBytes64 = MaxBytes64; grptr->PeakBytes64 = PeakBytes64; /**********************/ /* generate HTML page */ /**********************/ if (NumberOfHours <= 4) { UpdateSeconds = 60 - HttpdTime7[5]; if (UpdateSeconds < 45) UpdateSeconds += 60; } else if (NumberOfHours <= 8) UpdateSeconds = 120 - HttpdTime7[5]; else if (NumberOfHours <= 24) UpdateSeconds = 300 - HttpdTime7[5]; else UpdateSeconds = 3600 - HttpdTime7[5]; /* six seconds before the new minute */ UpdateSeconds -= 5; vecptr = FaoVector; *vecptr++ = HttpdTime7[1]; *vecptr++ = HttpdTime7[2]; *vecptr++ = HttpdTime7[3]; *vecptr++ = HttpdTime7[4]; *vecptr++ = HttpdTime7[5]; status = FaolToBuffer (Uniquifier, sizeof(Uniquifier), NULL, UniquifierFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); NodeInstanceCount = InstanceLockList (INSTANCE_NODE, "\n", &NodeInstancePtr); AdminInstanceCount = AdminMenuInstanceCount (rqptr, NodeInstancePtr); vecptr = FaoVector; *vecptr++ = WASD_DOCTYPE; *vecptr++ = HtmlMetaInfo (rqptr, NULL); *vecptr++ = AdminWasdCss(); if (rqptr->rqPathSet.StyleSheetPtr) { *vecptr++ = "\n"; *vecptr++ = rqptr->rqPathSet.StyleSheetPtr; } else *vecptr++ = ""; *vecptr++ = GenerateCspNonce (rqptr); *vecptr++ = UpdateSeconds; /* canvas plotting JavaScript */ *vecptr++ = GraphActivityClick (grptr); *vecptr++ = GraphHeight; *vecptr++ = GraphActivityPlot (grptr); /* title */ *vecptr++ = ServerHostPort; /* bit of a kludge ;^) skip over the " 1) { if (rqptr->ServicePtr->AdminService) *vecptr++ = ""; else *vecptr++ = "   (!AZ)"; *vecptr++ = HttpdProcess.PrcNam; } else *vecptr++ = ""; *vecptr++ = &rqptr->rqTime.BeginTime64; *vecptr++ = MaxYRequests; *vecptr++ = Bytes; *vecptr++ = BytesPtr; *vecptr++ = "Requests
\ (mean)
\ per-minute"; *vecptr++ = GraphWidth; *vecptr++ = GraphHeight; *vecptr++ = "Bytes
\ (mean)
\ per-minute"; *vecptr++ = 0; *vecptr++ = grptr->StartTime7[2]; *vecptr++ = MonthName[grptr->StartTime7[1]]; *vecptr++ = grptr->StartTime7[3]; *vecptr++ = grptr->StartTime7[4]; OfString[0] = RefreshString[0] = '\0'; if (rqptr->rqHeader.QueryStringLength) { zptr = (sptr = OfString) + sizeof(OfString)-1; cptr = rqptr->rqHeader.QueryStringPtr; while (*cptr && *cptr != '&' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (MATCH4 (cptr, "&dm=")) { cptr++; while (*cptr && *cptr != '&') cptr++; } if (*cptr) { zptr = (sptr = RefreshString) + sizeof(RefreshString)-1; while (*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++ = OfString; *vecptr++ = (cnt - 1) * (ACTIVITY_GRAPH_WIDTH / 2); *vecptr++ = (cnt - 1) * (ACTIVITY_GRAPH_HEIGHT / 2); *vecptr++ = MaxYRequests; *vecptr++ = RefreshString; } else *vecptr++ = ""; if (cnt < ACTIVITY_GRAPH_ZOOM+1) { *vecptr++ = "\ +"; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = OfString; *vecptr++ = (cnt + 1) * (ACTIVITY_GRAPH_WIDTH / 2); *vecptr++ = (cnt + 1) * (ACTIVITY_GRAPH_HEIGHT / 2); *vecptr++ = MaxYRequests; *vecptr++ = RefreshString; } else *vecptr++ = "+"; break; } if (cnt >= ACTIVITY_GRAPH_ZOOM+2) { *vecptr++ = ""; *vecptr++ = ""; } *vecptr++ = grptr->EndTime7[2]; *vecptr++ = MonthName[grptr->EndTime7[1]]; *vecptr++ = grptr->EndTime7[3]; *vecptr++ = grptr->EndTime7[4]; *vecptr++ = 0; *vecptr++ = GraphActivityPeriod (&grptr->StartTime64, &grptr->EndTime64); *vecptr++ = NumberOfHours; *vecptr++ = PeakConnect; if (CurrentStats) { *vecptr++ = ";  !&L current"; *vecptr++ = NetCurrentConnected; } else *vecptr++ = ""; *vecptr++ = &TotalRequests64; *vecptr++ = PeakPerMinRequests; *vecptr++ = PeakRequests; if (CurrentStats) { *vecptr++ = ";  !&L current"; *vecptr++ = NetCurrentProcessing; } else *vecptr++ = ""; *vecptr++ = &TotalBytes64; *vecptr++ = &PeakBytes64; *vecptr++ = &ActivityGblSecPtr->StartTime64; *vecptr++ = grptr->ProblemCount ? ProblemNoted : ""; *vecptr++ = AdminRefresh (rqptr); status = FaolToNet (rqptr, ResponseFao, &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); AdminEnd (rqptr); } /*****************************************************************************/ /* Generate a JavaScript array of regions on the activity graph for moving around the available data by clicking on that region. Each entry is itself an array with five elements; [0]..[3] the x,y and x,y defining the region, [4] the URL to be loaded on click, and [5] a description of what that region represents that 'tooltip' when the user hovers over the activity graph. The array is used by JavaScript functions activityPlotClick() and activityPlotMove() above. Returns a pointer to a string containing JavaScript code to create the array. This functionality replicates the use of an image map used on earlier, GIF-based activity graphics. For multiple hour reports the upper and lower sections have distinct functions. The middle 50% of the upper section allows the same end time (most commonly the current hour) to be examined over twice the current period. The left 25% allows the previous period to be viewed (if such data exists), and for non-current reports the right 25% allows the next period to be viewed. The lower half can be divided into sections representing hours or days depending on the period of the current report. This allows that period to be viewed in greater detail. For single hour reports this section is not mapped. Refresh interval is NOT propagated for this periods as this navigation is intended for looking back and forth, not for monitoring the current. +-----------------+-------------------------------+-----------------+ | | | | | prev period | next in size of period | next period | | | | | +-----------------+---+-----------------------+---+-----------------+ | | | | | prev period size | prev period size | etc. | | | | | +---------------------+-----------------------+---------------------+ */ char* GraphActivityClick (GRAPH_STRUCT *grptr) { /* this initialises an array element with 5 elements of its own */ static char ClickFao [] = "plotClickArray[!UL] = \ [!UL,!UL,!UL,!UL,\ \'!AZ?of=!4ZL!2ZL!2ZL!2ZL+!UL&dm=!ULx!UL/!UL\',\ \'!UL hour!%s !AZ\'];\n"; int count, status, ArrayCount, AtX, GraphHeight, GraphWidth, MapSections, NumberOfDays, NumberOfHours, MaxYRequests, PeriodHours; int64 EndTime64, StartTime64; ushort Time7 [7]; ulong *vecptr; ulong FaoVector [24]; char *cptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; STR_DSC *clkptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityClick()"); rqptr = grptr->RequestPtr; GraphHeight = grptr->Height; GraphWidth = grptr->Width; MaxYRequests = grptr->MaxYRequests; NumberOfHours = grptr->NumberOfHours; NumberOfDays = NumberOfHours / 24; ArrayCount = 0; clkptr = grptr->ClickPtr = StrDscBegin (rqptr, NULL, GRAPH_CLICK_SIZE); /* grow the allocated memory by this amount when required */ STR_DSC_REALLOC (clkptr, GRAPH_CLICK_SIZE); if (NumberOfHours > 1) { /*******************************/ /* half period (across-bottom) */ /*******************************/ PeriodHours = 0; switch (NumberOfHours) { 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; MapSections = NumberOfHours / PeriodHours; StartTime64 = grptr->StartTime64; count = 0; while (count < MapSections) { AtX = count++ * GraphWidth / MapSections; /* given the start time calculate the end time */ GraphActivityOffsetTime ((PeriodHours*MINUTES_IN_HOUR)-1, &StartTime64, &EndTime64); if (GraphActivityAbsHour(&StartTime64) >= grptr->FirstDataAbsHour && GraphActivityAbsHour(&EndTime64) <= grptr->CurrentAbsHour) { sys$numtim (&Time7, &EndTime64); vecptr = FaoVector; *vecptr++ = ArrayCount++; *vecptr++ = AtX; *vecptr++ = GraphHeight / 2; *vecptr++ = AtX + (GraphWidth / MapSections); *vecptr++ = GraphHeight; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = Time7[0]; *vecptr++ = Time7[1]; *vecptr++ = Time7[2]; *vecptr++ = Time7[3]; *vecptr++ = PeriodHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = PeriodHours; *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64); status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector); if (VMSnok (status)) { grptr->ProblemCount++; ErrorNoticed (rqptr, status, NULL, FI_LI); } } /* next start time */ GraphActivityOffsetTime (PeriodHours*MINUTES_IN_HOUR, &StartTime64, &StartTime64); } } /**************************/ /* less recent (top-left) */ /**************************/ GraphActivityOffsetTime (-(NumberOfHours*MINUTES_IN_HOUR), &grptr->StartTime64, &StartTime64); GraphActivityOffsetTime ((NumberOfHours*MINUTES_IN_HOUR)-1, &StartTime64, &EndTime64); if (GraphActivityAbsHour(&StartTime64) >= grptr->FirstDataAbsHour) { sys$numtim (&Time7, &EndTime64); vecptr = FaoVector; *vecptr++ = ArrayCount++; *vecptr++ = 0; *vecptr++ = 0; *vecptr++ = GraphWidth / 4; *vecptr++ = GraphHeight / 2; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = Time7[0]; *vecptr++ = Time7[1]; *vecptr++ = Time7[2]; *vecptr++ = Time7[3]; *vecptr++ = NumberOfHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = NumberOfHours; *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64); status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector); if (VMSnok (status)) { grptr->ProblemCount++; ErrorNoticed (rqptr, status, NULL, FI_LI); } } if (NumberOfDays < ActivityNumberOfDays) { /******************************/ /* double period (top-centre) */ /******************************/ 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; GraphActivityOffsetTime (-((PeriodHours/2)*MINUTES_IN_HOUR), &grptr->StartTime64, &StartTime64); for (;;) { GraphActivityOffsetTime ((PeriodHours*MINUTES_IN_HOUR)-1, &StartTime64, &EndTime64); /* break if it's not later than the last data available */ if (GraphActivityAbsHour(&EndTime64) <= grptr->CurrentAbsHour) break; /* otherwise move earlier by one hour */ GraphActivityOffsetTime (-MINUTES_IN_HOUR, &StartTime64, &StartTime64); } sys$numtim (&Time7, &EndTime64); vecptr = FaoVector; *vecptr++ = ArrayCount++; *vecptr++ = GraphWidth / 4; *vecptr++ = 0; *vecptr++ = GraphWidth / 2 + GraphWidth / 4; *vecptr++ = GraphHeight / 2; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = Time7[0]; *vecptr++ = Time7[1]; *vecptr++ = Time7[2]; *vecptr++ = Time7[3]; *vecptr++ = PeriodHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = PeriodHours; *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64); status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector); if (VMSnok (status)) { grptr->ProblemCount++; ErrorNoticed (rqptr, status, NULL, FI_LI); } } /***************************/ /* more recent (top-right) */ /***************************/ GraphActivityOffsetTime (1, &grptr->EndTime64, &StartTime64); GraphActivityOffsetTime ((NumberOfHours*MINUTES_IN_HOUR)-1, &StartTime64, &EndTime64); if (GraphActivityAbsHour(&EndTime64) <= grptr->CurrentAbsHour) { sys$numtim (&Time7, &EndTime64); vecptr = FaoVector; *vecptr++ = ArrayCount++; *vecptr++ = GraphWidth / 2 + GraphWidth / 4; *vecptr++ = 0; *vecptr++ = GraphWidth; *vecptr++ = GraphHeight / 2; *vecptr++ = ADMIN_REPORT_ACTIVITY; *vecptr++ = Time7[0]; *vecptr++ = Time7[1]; *vecptr++ = Time7[2]; *vecptr++ = Time7[3]; *vecptr++ = NumberOfHours; *vecptr++ = GraphWidth-2; *vecptr++ = GraphHeight-2; *vecptr++ = MaxYRequests; *vecptr++ = NumberOfHours; *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64); status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector); if (VMSnok (status)) { grptr->ProblemCount++; ErrorNoticed (rqptr, status, NULL, FI_LI); } } FaoToBuffer (clkptr, -1, NULL, "// !UL/!UL bytes\n", STR_DSC_LEN(clkptr), STR_DSC_SIZE(clkptr)); return (STR_DSC_PTR(clkptr)); } /*****************************************************************************/ /* Return a pointer to a string containing the JavaScript code used to plot the activity graph. */ char* GraphActivityPlot (GRAPH_STRUCT *grptr) { static int 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; BOOL EventStartup, EventExit, EventExitError, EventDelPrc; int cnt, idx, mcnt, status, what, AbsDay, AtX, AxisBytes, AxisRequests, ByteHeight, ByteMean, ColumnWidth, Day, GraphHeight, GraphWidth, Hour, MinuteGranularity, Month, NumberOfHours, NumberOfDays, NumberOfMinutes, ConnectPeakHeight, PrevAtX, PrevByteMean, PrevRequestMean, RequestCount, RequestHeight, RequestMean, RequestPeakHeight, RequestTotal, RequestValue, SampleCount, Year; int64 Time64, ByteCount64, ByteTotal64, ByteValue64, MaxBytes64, PeakBytes64, SignScratch64; ulong AbsActivityDay, ConnectPeak, DeltaDays, DeltaHours, DeltaMinutes, MaxConnect, MaxRequests, MaxYRequests, PeakConnect, PeakRequests, RequestPeak, Minute; ulong FaoVector [32]; float AtXfloat, AtXfactor, ByteFactor, ByteMeanFloat, RequestFactor; char *cptr; char Uniquifier [16]; REQUEST_STRUCT *rqptr; STR_DSC *pltptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityPlot()"); rqptr = (REQUEST_STRUCT*)grptr->RequestPtr; /* retrieve these from the graphics structure */ GraphWidth = grptr->Width; GraphHeight = grptr->Height; Year = grptr->Year; Month = grptr->Month; Day = grptr->Day; Hour = grptr->Hour; MaxRequests = grptr->MaxRequests; MaxYRequests = grptr->MaxYRequests; NumberOfHours = grptr->NumberOfHours; NumberOfMinutes = grptr->NumberOfMinutes; MaxBytes64 = grptr->MaxBytes64; PeakBytes64 = grptr->PeakBytes64; if (WATCH_MODULE(WATCH_MOD__OTHER)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL/!UL/!UL:!UL !UL !UL !@UQ !ULx!UL/!UL", Year, Month, Day, Hour, NumberOfHours, MaxRequests, &MaxBytes64, GraphWidth, GraphHeight, MaxYRequests); pltptr = grptr->PlotPtr = StrDscBegin (rqptr, NULL, GRAPH_PLOT_SIZE); /* grow the allocated memory by this amount when required */ STR_DSC_REALLOC (pltptr, GRAPH_PLOT_SIZE); PrevByteMean = PrevRequestMean = -1; /***********************/ /* 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 (WATCHALL, WATCH_MOD__OTHER, "!UL !UL", ColumnWidth, MinuteGranularity); if (MaxRequests) RequestFactor = (float)(GraphHeight-2) / (float)MaxYRequests; else RequestFactor = 1.0; /* ratio for calculating byte bar graph height */ if (MaxBytes64) ByteFactor = (float)(GraphHeight-2) / (float)MaxBytes64; else ByteFactor = 1.0; AtXfactor = (float)(GraphWidth-2) / (float)(NumberOfMinutes * 2) * (float)MinuteGranularity; if (WATCH_MODULE(WATCH_MOD__OTHER)) { char String [32]; sprintf (String, "%f %f", RequestFactor, ByteFactor); WatchThis (WATCHALL, WATCH_MOD__OTHER, "!AZ", String); } /**************/ /* plot graph */ /**************/ InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); /* repeated passes to minimise number of colour stroke/fill changes */ for (what = 1; what <= 7; what++) { switch (what) { case 1 : FaoToBuffer (pltptr, -1, NULL, " // events\n"); break; case 2 : FaoToBuffer (pltptr, -1, NULL, " // requests\n"); break; case 3 : FaoToBuffer (pltptr, -1, NULL, " // peak requests\n"); break; case 4 : FaoToBuffer (pltptr, -1, NULL, " // bytes\n"); break; case 5 : FaoToBuffer (pltptr, -1, NULL, " // peak bytes\n"); break; case 6 : FaoToBuffer (pltptr, -1, NULL, " // mean requests\n"); break; case 7 : FaoToBuffer (pltptr, -1, NULL, " // mean bytes\n"); break; default : FaoToBuffer (pltptr, -1, NULL, " // woops!!\n"); } ByteTotal64 = 0; RequestTotal = SampleCount = 0; AtXfloat = 1.0; Minute = grptr->StartMinute; idx = ACTIVITY_DATA_IDX (grptr->StartAbsDay, grptr->StartHour); while (Minute <= grptr->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 > grptr->CurrentAbsMinute) break; Minute += MinuteGranularity; EventDelPrc = EventExit = EventExitError = EventStartup = false; if (MinuteGranularity == 1) { ByteCount64 = ActivityGblSecPtr->ByteCount64[idx]; ByteTotal64 += ByteCount64; ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx]; RequestValue = ActivityGblSecPtr->RequestCount[idx]; RequestCount = (ulong)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; ByteCount64 = 0; for (cnt = 0; cnt < MinuteGranularity; cnt++) { ByteValue64 = ActivityGblSecPtr->ByteCount64[idx]; if (ByteValue64 > ByteCount64) ByteCount64 = ByteValue64; 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 = (ulong)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; ByteTotal64 += ByteCount64; } /* calculate means */ SampleCount++; RequestMean = (int)((float)(RequestTotal / SampleCount) * RequestFactor); /* breaking it down step-wise makes it easier (for me) to understand */ ByteMeanFloat = (float)ByteTotal64; 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; if (ByteCount64 > MaxBytes64) ByteCount64 = MaxBytes64; ConnectPeakHeight = (int)((float)ConnectPeak * RequestFactor); RequestHeight = (int)((float)RequestCount * RequestFactor); RequestPeakHeight = (int)((float)RequestPeak * RequestFactor); ByteHeight = (int)((float)ByteCount64 * ByteFactor); if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !@UQ !UL !UL !UL !UL !UL", RequestTotal, ByteTotal64, ConnectPeakHeight, RequestHeight, ByteHeight, RequestMean, ByteMean); switch (what) { case 1 : { /* event display order; error exit, shutdown, restart, 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); break; } case 2 : { /* instantaneous request value */ GraphDrawBlock (grptr, AtX, 1, AtX+ColumnWidth-1, RequestHeight, ColourRequest); break; } case 3 : { /* peak request value */ if (RequestPeakHeight < RequestHeight) GraphDrawLine (grptr, AtX, RequestPeakHeight, AtX+ColumnWidth, RequestPeakHeight, ColourRequestPeak, 2); break; } case 4 : { /* instantaneous byte value */ GraphDrawBlock (grptr, AtX+ColumnWidth, 1, AtX+ColumnWidth+ColumnWidth-1, ByteHeight, ColourByte); break; } case 5 : { /* peak connect value */ if (ConnectPeakHeight < RequestHeight) GraphDrawLine (grptr, AtX+ColumnWidth, ConnectPeakHeight, AtX+ColumnWidth+ColumnWidth, ConnectPeakHeight, ColourConnectPeak, 1); break; } case 6 : { /* average request value */ if (PrevRequestMean >= 0) GraphDrawLine (grptr, PrevAtX+ColumnWidth, PrevRequestMean, AtX+ColumnWidth, RequestMean, ColourRequestMean, 1); PrevRequestMean = RequestMean; PrevAtX = AtX; break; } case 7 : { /* average byte value */ if (PrevByteMean >= 0) GraphDrawLine (grptr, PrevAtX+ColumnWidth+ColumnWidth, PrevByteMean, AtX+ColumnWidth+ColumnWidth, ByteMean, ColourByteMean, 1); PrevByteMean = ByteMean; PrevAtX = AtX; break; } default : { grptr->ProblemCount++; ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); } } AtXfloat += AtXfactor + AtXfactor; } switch (what) { case 6 : { /* average request value */ if (PrevRequestMean >= 0) GraphDrawLine (grptr, PrevAtX+ColumnWidth, PrevRequestMean, AtX+ColumnWidth, RequestMean, ColourRequestMean, 1); break; } case 7 : { /* average byte value */ if (PrevByteMean >= 0) GraphDrawLine (grptr, PrevAtX+ColumnWidth+ColumnWidth, PrevByteMean, AtX+ColumnWidth+ColumnWidth, ByteMean, ColourByteMean, 1); break; } } } InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); if (Minute >= grptr->CurrentAbsMinute) GraphDrawBlock (grptr, AtX, 1, GraphWidth-1, GraphHeight-1, ColourNoData); /****************/ /* finish graph */ /****************/ if (MaxBytes64 < 100) AxisBytes = MaxBytes64 / 10; else if (MaxBytes64 < 1000) AxisBytes = MaxBytes64 / 100; else if (MaxBytes64 < 10000) AxisBytes = MaxBytes64 / 1000; else if (MaxBytes64 < 100000) AxisBytes = MaxBytes64 / 10000; else if (MaxBytes64 < 1000000) AxisBytes = MaxBytes64 / 100000; else if (MaxBytes64 < 10000000) AxisBytes = MaxBytes64 / 1000000; else if (MaxBytes64 < 100000000) AxisBytes = MaxBytes64 / 10000000; else AxisBytes = MaxBytes64 / 100000000; if (MaxRequests < 10) AxisRequests = 1; else if (MaxRequests < 100) AxisRequests = (((MaxRequests - 1) / 10) + 1); else if (MaxRequests < 1000) AxisRequests = (((MaxRequests - 1) / 100) + 1); else if (MaxRequests < 10000) AxisRequests = (((MaxRequests - 1) / 1000) + 1); else if (MaxRequests < 100000) AxisRequests = (((MaxRequests - 1) / 10000) + 1); else if (MaxRequests < 1000000) AxisRequests = (((MaxRequests - 1) / 100000) + 1); else AxisRequests = (((MaxRequests - 1) / 1000000) + 1); FaoToBuffer (pltptr, -1, NULL, " // decorate axes\n"); if (AxisRequests == 1) GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests*10, 7, ColourAxis); GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests*2, 7, ColourAxis); GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests, 10, 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); if (grptr->Stroke) { /* coup de grace */ FaoToBuffer (pltptr, -1, NULL, " ctx.stroke();\n"); grptr->Stroke = false; } FaoToBuffer (pltptr, -1, NULL, " // !UL/!UL bytes\n", STR_DSC_LEN(pltptr), STR_DSC_SIZE(pltptr)); if (Minute >= grptr->CurrentAbsMinute) FaoToBuffer (pltptr, -1, NULL, " startUpdatePage(\'!AZ?of=!UL&dm=!ULx!UL\',updateSeconds);\n", ADMIN_REPORT_ACTIVITY, NumberOfHours, GraphWidth-2, GraphHeight-2); return (STR_DSC_PTR(pltptr)); } /*****************************************************************************/ /* Set the canvas rectangle fill color. Tracks current setting and only changes as required. */ void GraphSetFillColour ( GRAPH_STRUCT *grptr, int colour ) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphSetFillColour() %d\n", colour); if (grptr->FillColour == colour) return; FaoToBuffer (grptr->PlotPtr, -1, NULL, " plotFillStyle(ctx,!AZ,\'!AZ\');\n", grptr->Stroke ? "true" : "false", GraphColorString(colour)); grptr->Stroke = false; grptr->FillColour = colour; } /*****************************************************************************/ /* Set the canvas line colour. Tracks current setting and only changes as required. */ void GraphSetStrokeColour ( GRAPH_STRUCT *grptr, int colour ) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphSetStrokeColour() %d\n", colour); if (grptr->StrokeColour == colour) return; FaoToBuffer (grptr->PlotPtr, -1, NULL, " plotStrokeStyle(ctx,!AZ,\'!AZ\');\n", grptr->Stroke ? "true" : "false", GraphColorString(colour)); grptr->Stroke = false; grptr->StrokeColour = colour; } /*****************************************************************************/ /* Set the canvas line width. Tracks current setting and only changes as required. */ void GraphSetStrokeWidth ( GRAPH_STRUCT *grptr, int width ) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphSetStrokeWidth() %d\n", width); if (grptr->StrokeWidth == width) return; FaoToBuffer (grptr->PlotPtr, -1, NULL, " plotStrokeWidth(ctx,!AZ,\'!UL\');\n", grptr->Stroke ? "true" : "false", width); grptr->Stroke = false; grptr->StrokeWidth = width; } /*****************************************************************************/ /* Return a pointer to the string equivalent of the integer colour. */ char* GraphColorString (int Colour) { /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphColorString() %d\n", Colour); switch (Colour) { case COLOUR_BLACK : return (COLOUR_S_BLACK); case COLOUR_RED : return (COLOUR_S_RED); case COLOUR_GREEN : return (COLOUR_S_GREEN); case COLOUR_BLUE : return (COLOUR_S_BLUE); case COLOUR_YELLOW : return (COLOUR_S_YELLOW); case COLOUR_MAGENTA : return (COLOUR_S_MAGENTA); case COLOUR_CYAN : return (COLOUR_S_CYAN); case COLOUR_WHITE : return (COLOUR_S_WHITE); case COLOUR_GREY : return (COLOUR_S_GREY); case COLOUR_DRED : return (COLOUR_S_DRED); case COLOUR_DGREEN : return (COLOUR_S_DGREEN); case COLOUR_DBLUE : return (COLOUR_S_DBLUE); case COLOUR_DYELLOW : return (COLOUR_S_DYELLOW); case COLOUR_DMAGENTA : return (COLOUR_S_DMAGENTA); case COLOUR_DCYAN : return (COLOUR_S_DCYAN); case COLOUR_DWHITE : return (COLOUR_S_DWHITE); return ("[error]"); } } /*****************************************************************************/ /* Draws a straight line from (x1,y1) to (x2,y2) of the specified colour and width. */ void 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; } GraphSetStrokeWidth (grptr, width); GraphSetStrokeColour (grptr, colour); FaoToBuffer (grptr->PlotPtr, -1, NULL, " ctx.moveTo(!UL,!UL);ctx.lineTo(!UL,!UL);\n", x1, y1, x2, y2); grptr->Stroke = true; } /*****************************************************************************/ /* Draw a rectangle with opposite corners defined by (x1,y1) and (x2,y2) filled with the specified colour. */ void 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; } GraphSetFillColour (grptr, colour); GraphSetStrokeColour (grptr, colour); FaoToBuffer (grptr->PlotPtr, -1, NULL, " ctx.fillRect(!UL,!UL,!UL,!UL);\n", x1, y1, x2-x1+1, y2-y1+1); grptr->Stroke = true; } /*****************************************************************************/ /* Draw graduations along either the top or bottom X axis. */ void GraphGraduateXAxis ( GRAPH_STRUCT *grptr, int TopOrBottom, int Graduations, int length, int colour ) { int x, FromY, ToY; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphGraduateXAxis() %d %d %d\n", TopOrBottom, Graduations, colour); if (TopOrBottom == GRAPH_XAXIS_BOTTOM) { FromY = 0; ToY = length - 1; } else { FromY = grptr->Height - length; ToY = grptr->Height - 1; } GraphSetStrokeColour (grptr, colour); FaoToBuffer (grptr->PlotPtr, -1, NULL, " plotXaxis(ctx,!UL,!UL,!UL,!UL);\n", grptr->Width, Graduations, FromY, ToY); grptr->Stroke = true; } /*****************************************************************************/ /* Draw graduations along either the left or right Y axis. */ void GraphGraduateYAxis ( GRAPH_STRUCT *grptr, int LeftOrRight, int Graduations, int length, int colour ) { int y, FromX, ToX; /*********/ /* begin */ /*********/ if (GraphDebug) fprintf (stdout, "GraphGraduateYAxis() %d %d %d\n", LeftOrRight, Graduations, colour); if (LeftOrRight == GRAPH_YAXIS_LEFT) { FromX = 0; ToX = length - 1; } else { FromX = grptr->Width - length; ToX = grptr->Width - 1; } GraphSetStrokeColour (grptr, colour); FaoToBuffer (grptr->PlotPtr, -1, NULL, " plotYaxis(ctx,!UL,!UL,!UL,!UL);\n", grptr->Height, Graduations, FromX, ToX); grptr->Stroke = true; } /*****************************************************************************/ /* Place a border of the specified colour and width around the entire graphic. */ void 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); GraphSetStrokeWidth (grptr, width); GraphSetStrokeColour (grptr, colour); FaoToBuffer (grptr->PlotPtr, -1, NULL, " ctx.rect(0,0,!UL,!UL);\n", grptr->Width, grptr->Height); grptr->Stroke = true; } /*****************************************************************************/