/*
*
* Copyright (c) 1999 - 2011 my-Channels Ltd
* Copyright (c) 2012 - 2019 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 )
{
}
}
///
/// * 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 )
{
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 ) //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 )
{
}
//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");
}
}
}