(*$L+,X+,S-*)
PROGRAM DEVMON (INPUT,OUTPUT,DEVICES);
{									}
{	This program will monitor the operation counts for a requested	}
{	group of I/O devices. For each device it reports:		}
{									}
{		1. The operation count per interval			}
{		2. The average operation count per second for the 	}
{		   current interval 					}
{		3. The average operation count per second since		}
{		   collection began 					}
{		4. The minimum operation count for any interval 	}
{		   since operations began				}
{		5. The maximum operation count for any interval 	}
{		   since operations began				}
{									}
{	The reported data is meant to be displayed on a VT100		}
{	type terminal. However, the output can be spooled to		}
{	a file by executing the program in either BATCH mode 		}
{	or spawning it and using the /OUTPUT=filename switch.		}
{									}
{	The inputs to the DEVMON program are:				}
{									}
{		1. The collection interval in VMS delta time		}
{		   format. This is an interactive prompt.		}
{		2. A list of the devices to collect data for.		}
{		   DEVMON expects this list to be in a file 		}
{		   with the logical name DEVICES with one device	}
{		   per line starting in column 1.			}
{									}
{	Revision History						}
{									}
{	  	12 OCT 1983 - Initial release				}
{									}
{		14 OCT 1983 - Allow input of both time and devices	}
{			      to monitor from either the disk file	}
{			      DEVICES or the terminal.			}
{									}
	CONST
		NULL = 0;
		WAIT_EFN = 1;
		DEVICE_EFN = 2;
		MAX_DEVICE_COUNT = 100;
	TYPE
		TIME_TYPE = PACKED ARRAY [1..16] OF CHAR;
		INT2 	  = PACKED ARRAY [1..2]  OF INTEGER;
		DEVICE_TYPE = PACKED ARRAY [1..5] OF CHAR;
			WORD = 0..65535;
		TIMETYPE = PACKED ARRAY [1..7] OF WORD;
		{ Record defintion for SYS$GETDVI item descritor	}
		RECD = RECORD
			HEADINFO : PACKED RECORD
				DVILEN, DVI_OPCNT : WORD
				END;{headinfo}
			PTR_OPCNT : ^INTEGER;
			PTR_OPCNT_LNGTH : ^INTEGER;
			ENDLIST : INTEGER;
		       END;{recd}			
	VAR

		DEVICES : TEXT;	
		DELAY : INT2;		
		FOREVER : BOOLEAN;
		DEVICE_LIST : ARRAY [1..MAX_DEVICE_COUNT] OF DEVICE_TYPE;
		START_OPCNT  : ARRAY [1..MAX_DEVICE_COUNT] OF INTEGER;
		CURRENT_OPCNT, LAST_OPCNT, OPERATION_COUNT : 
					ARRAY [1..MAX_DEVICE_COUNT] OF INTEGER;
		AVE_OPCNT_TOT, AVE_OPCNT_LST,
		MAX_OPCNT, MIN_OPCNT  : ARRAY [1..MAX_DEVICE_COUNT] OF REAL;
		SORT_LIST : ARRAY [1..MAX_DEVICE_COUNT] OF INTEGER;
		CURRENT_TIME, LAST_TIME, START_TIME	:	INTEGER;
		DELTA_TIME : TIME_TYPE;

	{ System services used by DEVMON }

	FUNCTION SYS$BINTIM(	%STDESCR TIMBUF : TIME_TYPE;
					VAR TIMADR : INT2)     : INTEGER; EXTERN;

	FUNCTION SYS$SETIMR(   	%IMMED EFN : INTEGER;
				VAR DAYTIM : INT2;
				%IMMED A,B : INTEGER   ) : INTEGER; EXTERN;

	FUNCTION SYS$WAITFR(	%IMMED EFN : INTEGER)  : INTEGER; EXTERN;

	FUNCTION SYS$GETDVI(	%IMMED EFN : INTEGER;
				%IMMED A : INTEGER;
				%STDESCR DEVICEBUF : DEVICE_TYPE;
				VAR ITMLIST :  RECD ;
				%IMMED B,C,D,E : INTEGER ) : INTEGER; EXTERN;

	FUNCTION SYS$NUMTIM( VAR TIMBUF : TIMETYPE ;
			     %IMMED A : INTEGER) : INTEGER ; EXTERN;


	FUNCTION LIB$SET_CURSOR( LINE, COLUMN : WORD ) : INTEGER ; EXTERN ;

	FUNCTION LIB$ERASE_PAGE( LINE, COLUMN : WORD ) : INTEGER ; EXTERN;

	FUNCTION LIB$SET_SCROLL(START_LINE,ENDLINE : WORD) : INTEGER ; EXTERN;

	{ CHECK checks the return codes of system service functions. 	}
	{ If the return code is odd then everything is OKAY, otherwise 	}
	{ the program halts. To use CHECK with a system service code 	}
	{ the statement as follows:					}
	{								}
	{	CHECK ( SYS$SYSTEM_SERVICE(PARAMETERS));		}
	{								}

	PROCEDURE CHECK (IT : INTEGER);

		BEGIN
		IF NOT ODD(IT) THEN
			BEGIN
			WRITELN('****** ERROR - TERMINATING PROGRAM *****',IT);
			HALT;
			END;{if}
		END;{check}

	{ UTILITIES							}


	{ Procedure GET_TIME returns the current time in seconds since  }
	{ the begining of the day					}    
	{								}

	PROCEDURE GET_TIME( VAR CURRENT_TIME : INTEGER );
	
		VAR
			TIMBUF : TIMETYPE;
			ISTAT : INTEGER;
	BEGIN
		ISTAT := SYS$NUMTIM(TIMBUF,NULL);
		CURRENT_TIME := TIMBUF[6] + 60 * TIMBUF[5] + 3600 * TIMBUF[4];
	END;{GET_TIME}

	{ Procedure GOTO_XY postions the cursor to a requested line and	}
	{ column.							}
	PROCEDURE GOTO_XY ( X,Y : WORD );
	BEGIN
		CHECK ( LIB$SET_CURSOR(X,Y));
		END;{GOTO_XY}
	
	{ Procedure CLEAR_SCREEN erases the page starting at the LINE 	}
	{ an CLOUMN requested as an input parameter.			}
	PROCEDURE CLEAR_SCREEN ( LINE, COLUMN : WORD );
	BEGIN
		CHECK ( LIB$ERASE_PAGE(LINE,COLUMN));
		END;{clear_screen}

	{ Procedure DISPLAY_TIME writes the current time on the 	}
	{ first line of the screen in inverse video.			}
	
	PROCEDURE DISPLAY_TIME;

		TYPE
			HHMMSSCC  = PACKED ARRAY [1..11] OF CHAR;
		VAR
			TODAYS_TIME : HHMMSSCC;
	
		FUNCTION LIB$PUT_SCREEN (%STDESCR TIME : HHMMSSCC;
					 LINE, COLUMN, FLAG  : WORD )
					: INTEGER; EXTERN;
	BEGIN
		TIME(TODAYS_TIME);
		CHECK ( LIB$PUT_SCREEN(TODAYS_TIME,1,28,2));
		WRITELN;
		END;{display_time}

	{ Procedure SET_SCROLL sets the scrolling window to the 	}
	{ BEGIN_LINE and END_LINE specified on input.			}

	PROCEDURE SET_SCROLL ( BEGIN_LINE, END_LINE : WORD );

	BEGIN
		CHECK ( LIB$SET_SCROLL( BEGIN_LINE, END_LINE ));
	END;

	{ Clear the screen and initialize necsssary counters and data 	}
	{ areas.							}

	PROCEDURE INITIALIZE;
	
		VAR
			INDEX : INTEGER;
	BEGIN
		FOREVER := TRUE;
		FOR INDEX := 1 TO MAX_DEVICE_COUNT DO
			BEGIN
			DEVICE_LIST[INDEX] := '     ';
			MIN_OPCNT[INDEX] := 999.9;
			MAX_OPCNT[INDEX] := 0.0;
			END;{for}
	END;{initialize}

	{ Set up the screen to display the reported data.		}
	PROCEDURE SET_UP_SCREEN;
		BEGIN
		CLEAR_SCREEN(1,1);
		GOTO_XY(3,1);
		WRITELN(' DEVICE',
			'     OPCNT',
			'   CURRENT',
			'   AVERAGE',
			'   MINIMUM',
			'   MAXIMUM ');
		SET_SCROLL(5,23);
		END;{set_up_screen

	{ Get the delta time input from the terminal and the devices to	}
	{ monitor from the file with a logical name of DEVICES.		}

	PROCEDURE GET_INPUTS;

		VAR
			INPUT_FROM,
			INDEX : INTEGER;
			DEVICE_INPUT : DEVICE_TYPE;
			
	BEGIN
		CLEAR_SCREEN(1,1);
		GOTO_XY(5,1);
		WRITELN('Where are the inputs coming from?');
		WRITELN; WRITELN('1 - DISK');
		WRITELN; WRITELN('2 - TERMINAL');
		INPUT_FROM := -1;
		WHILE ( INPUT_FROM <> 1 ) AND ( INPUT_FROM <> 2 ) DO
			BEGIN
			CLEAR_SCREEN(11,1);
			GOTO_XY(11,1);
			WRITE('---> ');
			READLN(INPUT_FROM);
			END;{while}
		CASE INPUT_FROM OF
		  1 :	BEGIN
			RESET(DEVICES);
			READLN(DEVICES,DELTA_TIME);
			CHECK ( SYS$BINTIM(DELTA_TIME,DELAY));
			INDEX := 1;
			READLN(DEVICES,DEVICE_LIST[INDEX]);
			WHILE NOT EOF(DEVICES) DO
				BEGIN
				INDEX := INDEX + 1;
				READLN(DEVICES,DEVICE_LIST[INDEX]);
				END;{while}
			CLOSE(DEVICES);
			END;{Case 1}
  		2 : 	BEGIN			
			CLEAR_SCREEN(1,1);
			GOTO_XY(6,1);
			WRITE('ENTER UPDATE RATE <DDDD HH:MM:SS.CC> ');
			READLN(DELTA_TIME);
			CHECK ( SYS$BINTIM(DELTA_TIME,DELAY));
			CLEAR_SCREEN(9,1);
			WRITELN('Enter devices one line at a time.');
			WRITELN('Enter END when Finished.');
			SET_SCROLL(12,23); 
			GOTO_XY(13,1);
			DEVICE_INPUT := '     ';
			INDEX := 1;
			WHILE DEVICE_INPUT <> 'END  ' DO
				BEGIN
				WRITE('Input next device -->');
				READLN(DEVICE_INPUT);
				IF DEVICE_INPUT <> 'END  ' THEN
					DEVICE_LIST[INDEX] := DEVICE_INPUT;
				INDEX := INDEX + 1;
				END;{while}
			END;{Case 2}
		END;{Case}
	END;{get_inputs}
									
	{ GET_DEVICES obtains the current OPCNT for the requested 	}
	{ device. The device name "DEVICE_NAME" is an imput parm. The 	}
	{ operation count is returned in "DEVICE_OPCNT".		}

	PROCEDURE GET_DEVICES( VAR DEVICE_OPCNT : INTEGER; 
			       VAR DEVICE_NAME : DEVICE_TYPE );
	
	VAR
		DVILIST	: RECD;

	BEGIN
	WITH DVILIST DO
		BEGIN
		HEADINFO.DVILEN := 4;
		HEADINFO.DVI_OPCNT := %X016;
		NEW (PTR_OPCNT);
		PTR_OPCNT^ := 0;
		NEW (PTR_OPCNT_LNGTH);
		PTR_OPCNT_LNGTH^ := 0;
		ENDLIST := 0;			
		END;{with}
	CHECK ( SYS$GETDVI(DEVICE_EFN,NULL,DEVICE_NAME,DVILIST,NULL,NULL,NULL,NULL));
	CHECK (SYS$WAITFR(DEVICE_EFN)); 
	WITH DVILIST DO
		BEGIN
		DEVICE_OPCNT := PTR_OPCNT^;
		DISPOSE (PTR_OPCNT);
		DISPOSE (PTR_OPCNT_LNGTH);
		END;{with}	
	END;{get_devices}

	{ Procedure to set a timer and wait till it expires to continue	}
	{ execution.							}

	PROCEDURE WAIT_AWHILE;

	BEGIN
		CHECK (SYS$WAITFR(WAIT_EFN));
		CHECK (SYS$SETIMR(WAIT_EFN,DELAY,NULL,NULL));
	END;{wait_awhile}

	{ procedure to get the current operation count for each device	}

	PROCEDURE GET_LATEST_DATA;
		VAR
			INDEX : INTEGER;
	BEGIN
		INDEX := 1;
		REPEAT
			GET_DEVICES(CURRENT_OPCNT[INDEX],DEVICE_LIST[INDEX]);
			INDEX := INDEX + 1;
		UNTIL DEVICE_LIST[INDEX] = '     ';
	END;{get_latest_data}

	{ Procedure to get the initial operation count for each device.	}

	PROCEDURE GET_START_DATA;

		VAR
			INDEX : INTEGER;
	BEGIN
		GET_TIME(START_TIME);
		LAST_TIME := START_TIME;
		INDEX := 1;
		REPEAT
			GET_DEVICES(START_OPCNT[INDEX],DEVICE_LIST[INDEX]);
			INDEX := INDEX + 1;
		UNTIL DEVICE_LIST[INDEX] = '     ';
		LAST_OPCNT := START_OPCNT;
		CHECK (SYS$SETIMR(WAIT_EFN,DELAY,NULL,NULL));
	END;{get_start_data}
	
	{ Procedure to calculate the reported data - current opcnt,	}
	{ ave opcnts, min opcnt, and max opcnt.				}

	PROCEDURE CALCULATE_DATA;

		VAR
			INDEX : INTEGER;
			TOTAL_TIME, THIS_TIME : REAL;
	BEGIN
		GET_TIME(CURRENT_TIME);
		TOTAL_TIME := CURRENT_TIME - START_TIME;
		THIS_TIME := CURRENT_TIME - LAST_TIME;
		INDEX := 1;
		REPEAT
			OPERATION_COUNT[INDEX] := 
				CURRENT_OPCNT[INDEX] - LAST_OPCNT[INDEX];
			AVE_OPCNT_TOT[INDEX] := 
				( CURRENT_OPCNT[INDEX] - START_OPCNT[INDEX] )
				 / TOTAL_TIME;
			AVE_OPCNT_LST[INDEX] := 
				OPERATION_COUNT[INDEX] / THIS_TIME;
			IF AVE_OPCNT_LST[INDEX] > MAX_OPCNT[INDEX] THEN
				MAX_OPCNT[INDEX] := AVE_OPCNT_LST[INDEX];
			IF AVE_OPCNT_LST[INDEX] < MIN_OPCNT[INDEX] THEN
				MIN_OPCNT[INDEX] := AVE_OPCNT_LST[INDEX];
			INDEX := INDEX + 1;
			UNTIL DEVICE_LIST[INDEX] = '     ';
		LAST_TIME := CURRENT_TIME;
		LAST_OPCNT := CURRENT_OPCNT;
	END;{calculate_data}

	PROCEDURE SORT_DATA;
	{ Procedure to sort the devices by descending average IO 	}
	{ rates.							}
	VAR	LOOP,OUTERLOOP,INDEX1,INDEX2	:	INTEGER;
		TEMP				:	INTEGER;
		BEGIN
		FOR LOOP := 1 TO MAX_DEVICE_COUNT DO
			SORT_LIST[LOOP] := LOOP;
		FOR OUTERLOOP := 1 TO MAX_DEVICE_COUNT -2 DO
			BEGIN
			FOR LOOP := 1 TO MAX_DEVICE_COUNT - 1 DO
				BEGIN
				INDEX1 := SORT_LIST[LOOP];
				INDEX2 := SORT_LIST[LOOP+1];
				IF AVE_OPCNT_TOT[INDEX1] < AVE_OPCNT_TOT[INDEX2] THEN
					BEGIN
					TEMP := SORT_LIST[LOOP];
					SORT_LIST[LOOP] := SORT_LIST[LOOP+1];
					SORT_LIST[LOOP+1] := TEMP;
					END;{IF}
				END;{FOR}
			END;{FOR}
	END;{SORT_DATA}

	{ Procedure to display the current interval report on the 	}
	{ terminal.							}

	PROCEDURE DISPLAY_DATA;
		VAR
			DISPLAY_COUNT,
			INDEX : INTEGER;
			SORT_INDEX : INTEGER;
	BEGIN
		DISPLAY_TIME;
		GOTO_XY(4,1);
		INDEX := 1;
		SORT_INDEX := SORT_LIST[INDEX];
		DISPLAY_COUNT := 0;
		REPEAT
(*			Display device if it has been active	*)
			IF AVE_OPCNT_TOT[SORT_INDEX] > 0.0 THEN
				BEGIN
				DISPLAY_COUNT := DISPLAY_COUNT + 1;
				WRITELN(DEVICE_LIST[SORT_INDEX],'  ',
					OPERATION_COUNT[SORT_INDEX]:10,
					AVE_OPCNT_LST[SORT_INDEX]:10:2,
					AVE_OPCNT_TOT[SORT_INDEX]:10:2,
					MIN_OPCNT[SORT_INDEX]:10:2,
					MAX_OPCNT[SORT_INDEX]:10:2);
				END;{IF}
			INDEX := INDEX + 1;
			SORT_INDEX := SORT_LIST[INDEX];
			UNTIL (DEVICE_LIST[SORT_INDEX] = '     ') OR
			      (DISPLAY_COUNT >= 15)	  	;
	END;{display_data}

{ Begin the main loop. - boolean operator FOREVER is set to TRUE to 	}
{ force the loop to execute until the program is terminated via 	}
{ external stimuli.							}

BEGIN
	INITIALIZE;	
	GET_INPUTS;
	SET_UP_SCREEN;
	GET_START_DATA;
	WHILE FOREVER DO
		BEGIN
		WAIT_AWHILE;
		GET_LATEST_DATA;
		CALCULATE_DATA;
		SORT_DATA;
		DISPLAY_DATA;
		END;{do}
END.	
