//============================================================================= // // Task: WEBFSMEM.TSK // (Allocated Memory by NLM logging and reporting) // Author: Avanti Technology, Inc. // http://www.avanti-tech.com // Version: 1.02 - Updated Release (07 April 2008) - NW v6.5 SP7 // 1.01 - Updated Release (15 March 2005) - Del/Purge temp files // // // Description: // ============ // Collects information on all loaded NLMs then logs the information into // a quote enclosed, comma delimited ("data","data", ... ,"data") text file // over a user defined number of collection periods (cycles). // After collecting the information and updating the data file, it then parses // the current data producing an .HTML formatted report showing the data. // // The report can be used to easily identify NLMs that are 'leaking' memory. // // // Objective: // ========== // Monitor memory allocations by NLM to spot any 'leaking' memory. // // // Usage: // ====== // Script can be manually executed using the TaskMaster TMRUN command // at the TMConsole (Shell) Screen: // // Example: TMRUN [vol:path\]WEBFSMEM.TSK // // Note: [vol:path\] is not required if the task resides either // in SYS:SYSTEM or the TaskMaster NLM load directory. // // Script can be scheduled for automatic execution using Client interface or // using the TaskMaster TMSCHEDULE command at the TMConsole (Shell) Screen: // // Examples: TMSCHEDULE ADD WEBFSMEM.TSK 01 00:00 // (Executes the 1st of each month at 12:00am) // // TMSCHEDULE ADD WEBFSMEM.TSK YNNNNNN 02:00 // (Executes every Sunday [SMTWTFS] at 2:00am) // // TMSCHEDULE ADD WEBFSMEM.TSK YYYYYYY 20:00 // (Executes every day [SMTWTFS] at 8:00pm) // // Note: Scheduled tasks must reside either in SYS:SYSTEM or the // TaskMaster NLM load directory for security reasons. // // // Notes: // ====== // There are several lines which need to be modified for the specific // environment where used. These lines are preceded by comment lines // which start with [MODIFY]. Search the script for all lines with // [MODIFY] and change the command line which follows appropriately. // // This script was designed to be scheduled to run once per day. // However, it can easily be converted to different day/time/cycles. // // // Compatibility: // ============== // This task has been tested on the following platforms without demonstrating // any compatibility issues or any other reported/confirmed conflicts: // TaskMaster v4.13 (or later), TaskMaster Lite v4.13 (or later) // NetWare v5.x / v6.x / OES (NetWare kernel) // // Warning: // ======== // AS WITH ANY NEW SOFTWARE PROGRAM, BATCH SCRIPT, OR AUTOMATED PROCESSING // PROCEDURE, CAUTION SHOULD BE EXERCISED AND DUE DILIGENCE OBSERVED DURING // INITIAL IMPLEMENTATION. WHERE POSSIBLE, TESTING SHOULD BE PERFORMED ON // NON-PRODUCTION SYSTEMS PRIOR TO FULL IMPLEMENTATION. // // Comments: // ========= // This script is provided free of charge and without any warranty or // guarantee of fitness of purpose or performance. // // For additional TaskMaster script examples, visit the Sample Tasks page // on the Avanti Technology, Inc. WEB Site: http://www.avanti-tech.com // //============================================================================= // Check that the version of TaskMaster loaded is compatible (v4.13 or later) IF "%TM_VERSION%.%TM_SUBVERSION%%TM_REVISION%"<"4.13" ECHO. ECHO Error: Incompatible TaskMaster release (requires v4.13 or later)! ECHO. ABORT ENDIF // Check is this task is already running (only need one active copy to work) IF ACTIVE_TASK %TASK% THEN EXIT // Alias the variables to be used to simplify script legibility VARALIAS %VAR00% %INFO% VARALIAS %VAR01% %STATUS% VARALIAS %VAR02% %TEMP% VARALIAS %VAR03% %ENTRIES% VARALIAS %VAR04% %ENTRY% VARALIAS %VAR05% %MODULE% VARALIAS %VAR06% %TOTAL% VARALIAS %VAR07% %CODE% VARALIAS %VAR08% %DATA% VARALIAS %VAR09% %ALLOC% VARALIAS %VAR10% %RAM% VARALIAS %VAR11% %BEGPOS% VARALIAS %VAR12% %ENDPOS% // [MODIFY] Specify the number of data collections to track. // Note: If the task is run once a day then it is the number of days to track. // If the task is run once a week then it is the number of weeks to track. // Use two digit specifier (range 01-30 - larger may overflow buffer). DEFINE %ENTRIES% 30 // Check if module memory monitoring log file already exists. // If not, create a file with the header record to be used for monitoring IF NOT EXIST %TASK_PATH%\%TASK_FILE%.CSV // Create the base file OPEN WRITE #0 %TASK_PATH%\%TASK_FILE%.CSV // Write the header record for the .CSV file (used when importing data) // Note: Module Name & InUse have a leading space so they appear first // when sorted alphabetically. Code & Data are left justified. // WHILE/LOOP used to fill the header record with the specified // number of data collection (tracking) fields. // End Of Record (EOR) Carriage Return / Line Feed (CR/LF) is // excluded (-EOL) from each partial write then written at end. WRITE -EOL #0 " Module Name"," InUse ","Code ","Data " // Set counter to 0 for WHILE/LOOP that fills header record descriptions // Note: Use same number of digits in %ENTRIES% (i.e., 0 not same as 00) DEFINE %ENTRY% 00 // WHILE/LOOP to fill header record with specified tracking columns WHILE "%ENTRY%"<"%ENTRIES%" // WRITE entry header (all data items are fixed length for sort options) WRITE -EOL #0 ,"mm/dd hh:mm" // Increment entry count (can also use CALC %ENTRY% %ENTRY% + 01) DEFINE %ENTRY% %ENTRY%+=01 LOOP // WRITE End Of Record (EOR) Carriage Return / Line Feed (CR/LF) WRITE #0 // Finished creating the basic log file with header record CLOSE WRITE #0 ENDIF // Logging is dynamic meaning that it detects if NLMs are loaded/unloaded // First, the task builds a list of loaded NLMs then checks them against // the existing log and adds any not in the log after which it then updates // the NLMs in the log with the data appropriate to this cycle // Check if a list of modules has been specified or needs to be built // Note: To monitor only specific modules one of two options can be used: // 1). Create an ASCII text file of the module names (DOS 8.3 format) // with one module name per record (line) in DOS 8.3 format with // fixed length records of 12 bytes (pad shorter with spaces) // that are terminated with CR/LF (Carriage Return / Line Feed) // 2). Modify the LIST MODULES command which follows as appropriate: // LIST MODULES *.NLM >%TASK_PATH%\%TASK_FILE%.LST // LIST MODULES NSS*.* >%TASK_PATH%\%TASK_FILE%.LST // Note: If using multiple LIST MODULES command lines to search // for more than one module name pattern, be sure to use // >> (append) rather than > (create/overwrite) output // redirection for secondary LIST MODULES command lines. IF NOT EXIST %TASK_PATH%\%TASK_FILE%.LST // Output the list of loaded modules on the Server to a temporary file LIST MODULES >%TASK_PATH%\%TASK_FILE%.LST // Set to indicate dynamic list so output redirection file deleted/purged DEFINE %STATUS% %TASK_PATH%\%TASK_FILE%.LST ELSE // Clear to indicate specific NLM list file provided DEFINE %STATUS% ENDIF // Open current log and LIST MODULES redirected output to compare loaded NLMs // against logged NLMs, adding any loaded NLMs not currently being logged // Loaded NLMs from LIST MODULES output redirection file or // specific NLMS to track from User created NLM list file OPEN READ #0 %TASK_PATH%\%TASK_FILE%.LST IF ERRORLEVEL THEN ABORT // Loop to read LOADED MODULES / specified NLM list WHILE // Read first/next NLM name // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit READ #0 %MODULE% 1-12 PACK IF ERRORLEVEL THEN BREAK // Check for LIST MODULES output that is not a name (Loaded or has space) IF SCAN_STRING "Load" "%MODULE%" OR SCAN_STRING " " "%MODULE%" THEN CONTINUE // Check for NULL record (line) in LIST MODULES output or User list IF "%MODULE%"=="" THEN CONTINUE // Check if NLM loaded (indicates valid NLM name) IF NOT LOADED "%MODULE%" THEN CONTINUE // Format %MODULE% for comparison REFORMAT %MODULE% 12 LEFT // Open current log OPEN READ #1 %TASK_PATH%\%TASK_FILE%.CSV IF ERRORLEVEL THEN BREAK // Read/skip NLM name in first record (header record) READ #1 %TEMP% 2-13 // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit // Check for unexpected "corrupt" data IF ERRORLEVEL OR NOT "%TEMP%"==" Module Name" // Close the file CLOSE READ #1 // Terminate processing loop BREAK ENDIF // Loop to check if LOADED / specified NLM is already being tracked WHILE // Read the next logged NLM record (parsing only NLM name) // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit READ #1 %TEMP% 2-13 IF ERRORLEVEL THEN BREAK // Format for comparison REFORMAT %TEMP% 12 LEFT // Check for matching NLM names IF "%MODULE%"=="%TEMP%" THEN BREAK LOOP // Close the log file after checking if NLM already logged CLOSE READ #1 // Check if no match found and add to log IF NOT "%MODULE%"=="%TEMP%" // Open the log file (default = APPEND) to add the NLM name for tracking OPEN WRITE #0 %TASK_PATH%\%TASK_FILE%.CSV IF ERRORLEVEL THEN ABORT // Record the following items in the format specified (fixed length): // // Module Name = 12 + 3 (Offset: 01 - 15 / DOS 8.3 + "",) // Total RAM = 10 + 3 (Offset: 16 - 28 / 10 digits + "",) // Static Code = 10 + 3 (Offset: 29 - 41 / 10 digits + "",) // Static Data = 10 + 3 (Offset: 42 - 54 / 10 digits + "",) // Allocated RAM = 10 + 3 (Offset: 55 - 68 / 11 digits + "",) // * Allocated RAM repeats (%ENTRIES% - 1) data collection points // // Note: End Of Record (EOR) Carriage Return / Line Feed (CR/LF) is // excluded from each partial write then written alone at end. WRITE -EOL #0 "%MODULE%"," 0"," 0"," 0" // Set counter to 0 for WHILE/LOOP that fills tracking columns (data) // Note: Use same number of digits in %ENTRIES% (i.e., 0 not same as 00) DEFINE %ENTRY% 00 // WHILE/LOOP to fill record with specified tracking columns (data) WHILE "%ENTRY%"<"%ENTRIES%" // WRITE allocated memory tracking point (11 bytes each plus "",) WRITE -EOL #0 ," 0" // Increment entry count (can also use CALC %ENTRY% %ENTRY% + 01) DEFINE %ENTRY% %ENTRY%+=01 LOOP // WRITE End Of Record (EOR) Carriage Return / Line Feed (CR/LF) WRITE #0 // Close the log file after adding the new NLM record CLOSE WRITE #0 ENDIF LOOP // If not NULL (""), execute defined action (cleanup temp output redir file) IF NOT "%STATUS%"=="" DELETE %STATUS% PURGE %STATUS% ENDIF :UPDATE // Create a temporary output file for use in updating the new data OPEN WRITE #0 %TASK_PATH%\%TASK_FILE%.TMP TRUNCATE IF ERRORLEVEL THEN ABORT // Open the current data file to retrieve previously collected data OPEN READ #0 %TASK_PATH%\%TASK_FILE%.CSV IF ERRORLEVEL THEN ABORT // READ first record in the data file (header record) // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit READ #0 %INFO% IF ERRORLEVEL THEN ABORT // Parse NLM Name, Total RAM, Code & Data then append current date/time // Parse the remaining date points, excluding the last data point (shift right) // Note: Previously logged Allocated RAM (header=date) starts at 55 (at the "): PARSE %TEMP% %INFO% 1-54 DEFINE %TEMP% %TEMP%"%MONTH%/%DAY% %HOUR24%:%MINUTE%", CALC %ENDPOS% (55 + ((%ENTRIES% - 1) * 14)) - 2 PARSE %TEMP% %INFO% 55-%ENDPOS% APPEND WRITE #0 %TEMP% // WHILE/LOOP to collect new data, merge it with old data and write temp file WHILE // Read next record from the data file // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit READ #0 %INFO% IF ERRORLEVEL THEN BREAK // Parse the fixed length module name field from the record PARSE %MODULE% %INFO% 2-13 // WRITE module name part of the data record (12 + 3 bytes) to output file // Note: End Of Record (EOR) Carriage Return / Line Feed (CR/LF) is // excluded from each partial write then written alone at end. WRITE -EOL #0 "%MODULE%", // Set default values for memory fields DEFINE %ALLOC% 0 DEFINE %CODE% 0 DEFINE %DATA% 0 // If the NLM is loaded, use LIST MODULES to collect current data // Note: Output redirected to a temporary file for parsing IF LOADED %MODULE% // List loaded nlm information for %MODULE% redirected to temp file LIST MODULES %MODULE% /i >%TASK_PATH%\%TASK_FILE%.MOD ELSEIF EXIST %TASK_PATH%\%TASK_FILE%.MOD // NLM not loaded but temp file exist so delete to avoid invalid data DELETE %TASK_PATH%\%TASK_FILE%.MOD PURGE %TASK_PATH%\%TASK_FILE%.MOD ENDIF // Check if LIST MODULES output redirected file exists (i.e., NLM loaded) IF EXIST %TASK_PATH%\%TASK_FILE%.MOD // Open LIST MODULES output redirection to parse memory data OPEN READ #1 %TASK_PATH%\%TASK_FILE%.MOD // Check for error opening file IF NOT ERRORLEVEL // WHILE/LOOP to parse memory data WHILE // Read first/next line in output redirection file // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit READ #1 %TEMP% IF ERRORLEVEL THEN BREAK // Change any " to ' to avoid parsing conflicts for tests REPLACE %TEMP% ""'" // Check for Alloc data else skip record IF NOT SCAN_STRING " bytes" "%TEMP%" THEN CONTINUE // Reformat the data to strip leading spaces REFORMAT %TEMP% LEFT PACK // Confirm data and locate starting offset else next record IF SCAN_STRING "Alloc: " "%TEMP%" FINDSTR %BEGPOS% "Alloc: " "%TEMP%" ELSEIF SCAN_STRING "Code : " "%TEMP%" FINDSTR %BEGPOS% "Code : " "%TEMP%" ELSEIF SCAN_STRING "Data : " "%TEMP%" FINDSTR %BEGPOS% "Data : " "%TEMP%" ELSE CONTINUE ENDIF // Adjust beginning position to start of data field // Note: Can also use CALC %BEGPOS% %BEGPOS% + 8 DEFINE %BEGPOS% %BEGPOS%+=8 // Find end of data value FINDSTR %ENDPOS% " bytes" "%TEMP%" // Adjust ending position to end of data field // Note: Can also use CALC %ENDPOS% %ENDPOS% - 1 DEFINE %ENDPOS% %ENDPOS%-=1 // Parse the memory data appropriately IF SCAN_STRING "Alloc: " "%TEMP%" PARSE %ALLOC% %TEMP% %BEGPOS%-%ENDPOS% ELSEIF SCAN_STRING "Code : " "%TEMP%" PARSE %CODE% %TEMP% %BEGPOS%-%ENDPOS% ELSE PARSE %DATA% %TEMP% %BEGPOS%-%ENDPOS% ENDIF LOOP // Cleanup the temporary output redirection file (close/delete) CLOSE READ #1 DEL %TASK_PATH%\%TASK_FILE%.MOD ENDIF ENDIF // Calculate total memory (code + data + alloc) CALC %TOTAL% %CODE% + %DATA% + %ALLOC% // Format memory data fields for fixed length REFORMAT %ALLOC% 11 PACK RIGHT REFORMAT %CODE% 10 PACK RIGHT REFORMAT %DATA% 10 PACK RIGHT REFORMAT %TOTAL% 10 PACK RIGHT // Re-write static code / data memory and current allocated memory WRITE -EOL #0 "%TOTAL%","%CODE%","%DATA%","%ALLOC%", // The start of the data to be retained (first Allocated RAM entry) is 55 // to which must be added the number of data collection (tracking points) // less 1 (replaced) times 27 (size of data collection fields when quote // enclosed and comma delimited) then subtract 2 (sets position to closing // quotation mark " of the next to last record - last record dropped when // data is right shifted to insert new data collection to file) CALC %ENDPOS% (55 + ((%ENTRIES% - 1) * 14)) - 2 PARSE %INFO% %INFO% 55-%ENDPOS% // Write the remaining data WRITE #0 %INFO% LOOP // Close processing files CLOSE READ #0 CLOSE WRITE #0 // Check for successful processing (old .CSV and new .TMP are same size) // If so, DELETE .CSV and RENAME .TMP (to .CSV) DEFINE %INFO% %TASK_PATH%\%TASK_FILE%.CSV DEFINE %TEMP% %TASK_PATH%\%TASK_FILE%.TMP IF NOT "%FILE_SIZE_%INFO%%"=="%FILE_SIZE_%TEMP%%" THEN ABORT // Delete old .CSV & rename new .TMP to .CSV DELETE %TASK_PATH%\%TASK_FILE%.CSV PURGE %TASK_PATH%\%TASK_FILE%.CSV RENAME %TASK_PATH%\%TASK_FILE%.TMP %TASK_PATH%\%TASK_FILE%.CSV // Quote enclosed, comma delimited data file (.CSV) has been updated // Generate an .HTML formatted report using the collected data // First, sort the data into the order desired // Each of the following SORT command lines organizes the data differently // Select the appropriate SORT method based upon the report format desired // [MODIFY] Remove the comment characters (//) preceding the SORT desired // Generate a report sorted by the NLM name (ascending) SORT %TASK_PATH%\%TASK_FILE%.CSV %TASK_PATH%\%TASK_FILE%.SRT 2-13 // Generate a report sorted by the Total Memory In Use (descending) // SORT %TASK_PATH%\%TASK_FILE%.CSV %TASK_PATH%\%TASK_FILE%.SRT 17-26 /D // Generate a report sorted by the Allocated Memory In Use (descending) // SORT %TASK_PATH%\%TASK_FILE%.CSV %TASK_PATH%\%TASK_FILE%.SRT 56-65 /D // Check for successful SORT and skip report processing if not found IF NOT EXIST %TASK_PATH%\%TASK_FILE%.SRT THEN GOTO EOJ // Create a temporary output file for use in updating the new data OPEN WRITE #0 %TASK_PATH%\%TASK_FILE%.HTM TRUNCATE // Open the current data file to retrieve previously collected data OPEN READ #0 %TASK_PATH%\%TASK_FILE%.SRT // Read header record // Note: ERRORLEVEL indicates End Of File (EOF) or error so quit READ #0 %INFO% IF ERRORLEVEL THEN ABORT // Set starting offset for first (current) allocated memory data item // Set ending offset for first (current) allocated memory data item DEFINE %BEGPOS% 56 DEFINE %ENDPOS% 66 // Extract the current date/time for report header (save it as default start) PARSE %ALLOC% %INFO% %BEGPOS%-%ENDPOS% DEFINE %RAM% %ALLOC% // Set entry count index (current value is first entry) DEFINE %ENTRY% 01 // Process up to the maximum number of entries WHILE "%ENTRY%"<"%ENTRIES%" // Set starting offset for next allocated memory data item // Set ending offset for next allocated memory data item CALC %BEGPOS% %BEGPOS% + 14 CALC %ENDPOS% %ENDPOS% + 14 // Extract the next cycle date/time for report header PARSE %TEMP% %INFO% %BEGPOS%-%ENDPOS% // Check for unlogged date/time value IF SCAN_STRING "mm/dd" "%TEMP%" THEN BREAK // Save valid date/time for this cycle DEFINE %RAM% %TEMP% // Increment entry count (can also use CALC %ENTRY% %ENTRY% + 01) DEFINE %ENTRY% %ENTRY%+=01 LOOP // Start with basic HTML start page code then create table for data // [MODIFY] Customize the HTML code as desired to produce format/info desired WRITE #0 WRITE WRITE #0
WRITE #0| CALC %RAM% %ALLOC% - %RAM% DEFINE %TEMP% + ELSE // Allocated memory decrease so set cell color, calc inc & set sign WRITE #0 |
CALC %RAM% %RAM% - %ALLOC%
DEFINE %TEMP% -
ENDIF
// Reformat and sign changed value
REFORMAT %RAM% PACK
DEFINE %TEMP% %TEMP%%RAM%
// Write .HTML code for NLM name and changed value
WRITE #0 %MODULE%
WRITE #0 WRITE #0 %TEMP% ELSE // Write .HTML code for default cell attributes and NLM name WRITE #0 | WRITE #0 %MODULE% ENDIF // Write .HTML to close NLM name cell then build In Use, Code, & Data cells WRITE #0 | WRITE #0WRITE #0 | WRITE #0WRITE #0 %TOTAL% WRITE #0 | WRITE #0WRITE #0 %CODE% WRITE #0 | WRITE #0WRITE #0 %DATA% WRITE #0 | // Set starting offset for first (current) allocated memory data item // Set ending offset for first (current) allocated memory data item DEFINE %BEGPOS% 56 DEFINE %ENDPOS% 66 // Set counter to 0 for WHILE/LOOP that fills tracking columns (data) // Note: Use same number of digits in %ENTRIES% (i.e., 0 not same as 00) DEFINE %ENTRY% 00 // Processing loop to parse & write HTML code for each data point WHILE "%ENTRY%"<"%ENTRIES%" // Increment entry count (can also use CALC %ENTRY% %ENTRY% + 01) DEFINE %ENTRY% %ENTRY%+=01 // Extract/format the allocated memory data PARSE %ALLOC% %INFO% %BEGPOS%-%ENDPOS% REFORMAT %ALLOC% PACK // Set starting offset for next allocated memory data item // Set ending offset for next allocated memory data item CALC %BEGPOS% %BEGPOS% + 14 CALC %ENDPOS% %BEGPOS% + 10 // Extract/format the previous allocated memory data collection point PARSE %RAM% %INFO% %BEGPOS%-%ENDPOS% REFORMAT %RAM% PACK // Write the HTML code separating this data collection point WRITE #0WRITE #0 | // Check if last entry point else parse next allocated memory data IF "%ENTRY%"<"%ENTRIES%" AND NOT SCAN_STRING "/" "%ALLOC%" // Extract/format the allocated memory data PARSE %RAM% %INFO% %BEGPOS%-%ENDPOS% REFORMAT %RAM% PACK // Calculate change in allocated memory & set appropriate sign IF "%ALLOC%"=="0" OR "%RAM%"=="0" OR "%ALLOC%"=="%RAM%" DEFINE %TEMP% ELSEIF "%ALLOC%">"%RAM%" CALC %RAM% %ALLOC% - %RAM% DEFINE %TEMP% + ELSE CALC %RAM% %RAM% - %ALLOC% DEFINE %TEMP% - ENDIF // Check allocated memory change to set cell attributes IF "%TEMP%"=="+" WRITE #0ELSEIF "%TEMP%"=="-" WRITE #0 | ELSE WRITE #0 |
ENDIF
// Write allocated memory value
WRITE #0 %ALLOC%
// Check for, format & display change in allocated memory value
IF NOT "%TEMP%"==""
REFORMAT %RAM% PACK
WRITE #0 WRITE #0 %TEMP%%RAM% ENDIF // Close cell WRITE #0 |
ELSE
// Base (last retained) value so use basic HTML code to display
WRITE #0 WRITE #0 %ALLOC% WRITE #0 | ENDIF LOOP // Write the HTML code to finish this row of NLM data WRITE #0
|---|