/* Copyright 1999-2011 (c) My-Channels Copyright (c) 2012-2014 Software AG, Darmstadt, Germany and/or Software AG USA Inc., Reston, VA, USA, and/or its subsidiaries and/or its affiliates and/or their licensors. Use, reproduction, transfer, publication or disclosure is prohibited except as specifically provided for in your License Agreement with Software AG. */ using System; using System.Collections; using System.Threading; namespace com.pcbsys.nirvana.nAdminAPI.apps { using com.pcbsys.nirvana.client; using com.pcbsys.nirvana.nAdminAPI; /// /// * Displays live information on thread pools, event status, memory usage, connection status and also the log file. /// * The user can switch between the information being viewed by pressing a character key for example pressing 'm' will /// * switch the system to monitoring the memory usage. /// public class ntop : com.pcbsys.nirvana.nAdminAPI.nLogListener { private static ntop myself = null; private nRealmNode rNode = null; private char option = 'l'; private long totConsumed = -1; private long totPublished = 0; private int refreshRate = 2000; private string rname = "nsp://localhost:9000"; private long totMem = 0; private long freeMem = -1; // initialised to -1 for rate calculations. We do not want to calculate the change in free memory // when there is no previous calculation of free memory so we set the variable to an impossible value for free memory private int curCons = -1; private object change = new object(); private long prevTime; /// /// * This function creates a session based on RNAME and establishes the nRealmNode for that realm /// private void getRealmNode() { try { Console.Write("Establishing realm node..."); nSessionAttributes nsa = new nSessionAttributes(rname); nRealmNode rNode = new nRealmNode(nsa); if (!rNode.isAuthorised()) { Console.WriteLine("User not authorised on this node " + nsa); System.Environment.Exit(1); } Console.WriteLine("done\n"); this.rNode = rNode; } catch (Exception e) { Console.WriteLine(e.StackTrace); System.Environment.Exit(1); } } /// /// * The Vector of thread pool details for the realm /// private ArrayList getThreadPools() { return rNode.getThreadPoolDetails(); } /// /// * This method takes a vector of thread pool and prints out information from each in a table /// * /// * A vector of thread pools /// private void printThreadPools(ArrayList threadPools) { Console.WriteLine("\n\n\n\n\n"); //The names of the thread pools vary in size, to longest name is 23 characters. In case other names are //added at a later date, this length variable can be adjusted int nameLength = 25; Console.Write("Name"); //this function is used to print multiple spaces at a time for alignment purposes printSpaces(nameLength - 4); Console.WriteLine("Idle Allocated Queued Tasks"); printSpaces(nameLength); Console.WriteLine("Threads Threads Tasks Executed\n"); IEnumerator it = threadPools.GetEnumerator(); while (it.MoveNext()) { nThreadPool thread = (nThreadPool)it.Current; int idle = thread.getIdle(); string name = thread.Name; int queue = thread.getQueue(); int size = thread.getSize(); long total = thread.getTotal(); Console.Write(name + " "); printSpaces(nameLength - name.Length); Console.WriteLine(idle + "\t " + size + "\t " + queue + "\t " + total); } } /// /// * The class nTop implements nLogListener. By adding this class as a listener of the rNode, whenever a new /// * log entry is added to the rNode, the report method is called which prints out the log. /// private void getLog() { rNode.addLogListener(this); } /// /// * This method is called when a new log entry is added to the realm /// * The log entry /// public virtual void report(string rep) { Console.WriteLine(rep); } /// /// * Prints information on the events in the realm /// private void printEventsTable() { Console.WriteLine("\n\n\n\n\n"); long conPerSec = 0; long pubPerSec = 0; /// /// * if totConsumed is -1 then it means this is the first iteration of calculating the subscribers and consumers /// * therefore it is not possible to calculate the rate at which consumers and subscribers are increasing/decreasing /// if (totConsumed != -1) { long timeChange = nConstants.currentTimeMillis() - prevTime; conPerSec = (rNode.getTotalSubscribed() - totConsumed) / (timeChange / 1000); pubPerSec = (rNode.getTotalPublished() - totPublished) / (timeChange / 1000); } prevTime = nConstants.currentTimeMillis(); totConsumed = rNode.getTotalSubscribed(); totPublished = rNode.getTotalPublished(); Console.WriteLine("~~~~~~~~Event Status~~~~~~~\n"); Console.WriteLine("Consumed - " + totConsumed + "\n"); Console.WriteLine("Published - " + totPublished + "\n"); Console.WriteLine("Consumed/sec - " + conPerSec + "\n"); Console.WriteLine("Published/sec - " + pubPerSec + "\n"); Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); } /// /// * This method resets variables back to there initial values each time the option variable is changed. This is /// * necessary so that for example the consumed/sec rate is not calculated based on previous values. /// private void reset() { totConsumed = -1; totPublished = -1; freeMem = -1; curCons = -1; //try to remove the logListener. A log listener may not have been added however so must catch exception if it //has not. try { rNode.delLogListener(this); } catch (Exception e) { } } /// /// * This method prints the details of memory usage /// private void printMemoryUsage() { Console.WriteLine("\n\n\n\n\n"); long change = 0; if (freeMem != -1) { if ((change = rNode.getFreeMemory() - freeMem) < 0) { change = freeMem - rNode.getFreeMemory(); } } freeMem = rNode.getFreeMemory(); long usedMem = totMem - freeMem; long gcTime = rNode.getTotalGCTime(); long gcCount = rNode.getTotalGCCount(); /// /// * dividing by (1024*1024) gives the values in terms of mega bytes /// Console.WriteLine("~~Memory Usage (M)~~\n"); Console.WriteLine("Total - " + (totMem / (1024 * 1024))); Console.WriteLine("Free - " + (freeMem / (1024 * 1024))); Console.WriteLine("Used - " + (usedMem / (1024 * 1024))); Console.WriteLine("Change - " + (change / (1024 * 1024))); Console.WriteLine("GC Count - " + gcCount); Console.WriteLine("GC Time - " + gcTime); Console.WriteLine("~~~~~~~~~~~~~~~~~~~~\n"); } /// /// * Prints details of connections to the realm. /// private void printConnectionStatus() { Console.WriteLine("\n\n\n\n\n"); long totCons = rNode.getTotalConnections(); double rate = 0; if (curCons != -1) { long timeChange = nConstants.currentTimeMillis() - prevTime; //must cast to double otherwise the result of 3/2 = 1 rather than 1.5 rate = (double)(rNode.getCurrentConnections() - curCons) / (double)(timeChange / 1000); } curCons = rNode.getCurrentConnections(); prevTime = nConstants.currentTimeMillis(); Console.WriteLine("~~Connection Status~~\n"); Console.WriteLine("Total - " + totCons); Console.WriteLine("Current - " + curCons); Console.WriteLine("Rate - " + rate); //System.out.println("Allowed - In config"); Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~\n"); } /// /// * In order to align the tables being printed, it is often necessary to print out multiple spaces based on the length /// * of a name for example. /// * The number of spaces to be printed. /// private void printSpaces(int length) { for (int i = 0; i < length; i++) { Console.Write(" "); } } /// /// * This method gives details of how to use the program. It describes what information is displayed when certain keys /// * are pressed. /// private void helpMessage() { Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~Help~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); Console.WriteLine(" nTop [refreshRate] \n"); Console.WriteLine("In order to view different Information, press one of the"); Console.WriteLine("character keys specified below"); char plural = ' '; if (refreshRate != 1000) { plural = 's'; } Console.WriteLine("All information is updated every " + (refreshRate / 1000) + " second" + plural + ". Once you"); Console.WriteLine("have chosen an option and the information is displayed,"); Console.WriteLine("simply press another character key to change the"); Console.WriteLine("information shown.\n"); Console.WriteLine("t : realm thread pools"); Console.WriteLine("e : event status"); Console.WriteLine("m : memory usage"); Console.WriteLine("c : connection status"); Console.WriteLine("l : log file"); Console.WriteLine("h : show this help message"); Console.WriteLine("q : exit the system\n"); Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); } /// /// * Initially prints the help message to show the user the options available then reads the option from the input /// * and start a thread to display the required information. The method then monitors the keyboard for key presses /// * and updates the option variable depending on the input character. /// private void doit() { helpMessage(); String line = Console.ReadLine(); if (line != "q") { try { option = char.ToLower((char)line.ToCharArray()[0]); } catch (Exception read) { System.Environment.Exit(1); } } Thread screenWriter = new Thread(new ThreadStart(run)); screenWriter.Name = ("Screen writer"); screenWriter.IsBackground = true; screenWriter.Start(); while (true) { try { line = Console.ReadLine(); if (line.ToCharArray()[0] != 'q') { reset(); option = line.ToLower().ToCharArray()[0]; /// /// * The information will refresh every refreshRate milliseconds however, if the user requests to see new /// * information, then they should not have to wait refreshRate milliseconds for the new information to appear. /// * To solve this, rather than the thread sleeping, it waits for a maximum of refreshrate milliseconds and /// * then continues. If within this time the option is changed, we notify the thread here and it immediately /// * continues /// lock (change) { System.Threading.Monitor.Pulse(change); } } else { break; } } catch (Exception read) { Console.WriteLine(read.StackTrace); System.Environment.Exit(1); } } } /// /// * When a new thread is started, this method is called. The thread displays information depending on the character /// * in the option variable. /// public virtual void run() { while (true) { switch (option) { case 't': { ArrayList threadPools = getThreadPools(); printThreadPools(threadPools); break; } case 'e': { printEventsTable(); break; } //The logListener will print to the screen everytime a new log report is received. Therefore it is not //necessary to continue looping calling getLog() hence the option is set to X case 'l': { getLog(); option = 'X'; break; } case 'm': { printMemoryUsage(); break; } case 'c': { printConnectionStatus(); break; } case 'h': { helpMessage(); //The help message will not update so only needs to be printed once hence the option variable is changed to X. option = 'X'; break; } case 'q': { Console.WriteLine("System exiting"); System.Environment.Exit(0); break; } //This case can only be reached if the code sets option to X. If the user enters X into the terminal, //it will be converted to lower case. This means the X case can be used whenever //you want to print something only once rather than in the loop but you want the loop to continue. case 'X': { break; } default: { helpMessage(); option = 'X'; } break; } /// /// * the thread waits for a maximum of refreshRate milliseconds then continues (loops again and refreshes the /// * information). The thread may continue before this time is up if it received notification. /// lock (change) { try { Monitor.Wait(change, refreshRate); } catch (Exception ie) { Console.WriteLine(ie.StackTrace); } } } } /// /// * This program displays status information for the realm specified in the environment variable RNAME. The /// * information displayed on screen refreshes according to a program parameter (default:1000ms). Once the program is /// * running, the user can press keys to change the information being displayed. /// * An array of program parameters /// static void Main(string[] args) { myself = new ntop(); if (args == null || args.Length == 0) { usage(); System.Environment.Exit(1); } if ((args.Length != 0)) { if (args[0].Equals("-?")) { usage(); System.Environment.Exit(1); } else { try { myself.rname = args[0]; myself.refreshRate = Convert.ToInt32(args[1]); } catch (Exception e) //If the paramater entered is not a numerical value the usage will be printed out { usage(); System.Environment.Exit(1); } } } //this function establishes a realm node and assigns it to global variable called rNode myself.getRealmNode(); //update total memory as this value is constant myself.totMem = myself.rNode.getTotalMemory(); myself.doit(); try { myself.rNode.close(); } catch (Exception e) { } //Close any other sessions within this JVM so that we can exit nSessionFactory.shutdown(); } private static void usage() { Console.WriteLine("Usage...\n"); Console.WriteLine("nTop [refreshRate]"); Console.WriteLine(" - the rname of the server to connect to"); Console.WriteLine("\n[Optional Arguments] \n"); Console.WriteLine("[refreshRate] - the rate at which the information is reloaded on screen (milliseconds) \n"); } } }