feature[ats_ATS331083]: Add a GC listener to the test server

Change-Id: I7fd7e93c2bcb3c89b884f1b71e4382759c7a669a
diff --git a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/environment/interfaces/ITestLogger.java b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/environment/interfaces/ITestLogger.java
index 0aad8b0..0f09760 100644
--- a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/environment/interfaces/ITestLogger.java
+++ b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/environment/interfaces/ITestLogger.java
@@ -32,6 +32,8 @@
    public void log(TestRecord record);
 
    public void log(Level level, String message, Throwable th);
+   
+   public void log(ITestEnvironmentAccessor source, Level level, String message, Throwable th);
 
    public void methodCalled(ITestEnvironmentAccessor source);
 
diff --git a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/framework/testrun/BaseTestRunManager.java b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/framework/testrun/BaseTestRunManager.java
index c567629..17af221 100644
--- a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/framework/testrun/BaseTestRunManager.java
+++ b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/framework/testrun/BaseTestRunManager.java
@@ -21,6 +21,7 @@
 import org.eclipse.osee.ote.core.framework.MethodResultImpl;
 import org.eclipse.osee.ote.core.framework.ReturnCode;
 import org.eclipse.osee.ote.core.internal.Activator;
+import org.eclipse.osee.ote.core.log.GCHelper;
 
 public class BaseTestRunManager implements ITestRunManager {
 
@@ -61,6 +62,7 @@
          return result;
       }
       try {
+         GCHelper.enable(true);
          testRunThread = new TestRunThread(propertyStore, test, environment, listenerProvider, dataProvider);
          testRunThread.start();
          testRunThread.join();
@@ -71,6 +73,7 @@
          result = methodresult;
          OseeLog.log(Activator.class, Level.SEVERE, ex);
       } finally {
+         GCHelper.enable(false);
          aborted = false;
          testRunThread = null;
       }
diff --git a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/internal/Activator.java b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/internal/Activator.java
index b9b19b3..7e98b4e 100644
--- a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/internal/Activator.java
+++ b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/internal/Activator.java
@@ -22,6 +22,7 @@
 import org.eclipse.osee.ote.core.environment.TestEnvironmentInterface;
 import org.eclipse.osee.ote.core.environment.console.ConsoleCommandManager;
 import org.eclipse.osee.ote.core.environment.console.ICommandManager;
+import org.eclipse.osee.ote.core.log.GCHelper;
 import org.eclipse.osee.ote.message.internal.MessageIoManagementStarter;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -50,6 +51,7 @@
    @Override
    public void start(BundleContext context) throws Exception {
       activator = this;
+      GCHelper.installGCMonitoring();
       bundleContext = context;
       consoleCommandManager = new ConsoleCommandManager();
       if (OteProperties.isOteCmdConsoleEnabled()) {
diff --git a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/GCHelper.java b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/GCHelper.java
new file mode 100644
index 0000000..701ef4e
--- /dev/null
+++ b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/GCHelper.java
@@ -0,0 +1,221 @@
+package org.eclipse.osee.ote.core.log;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+import javax.management.Notification;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationListener;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import org.eclipse.osee.framework.logging.OseeLog;
+public class GCHelper {
+   
+   private static final String installGCMonitoring = System.getProperty("org.eclipse.osee.ote.gcmonitor", "true");
+   
+   
+   private static volatile boolean enableLog = false;
+   
+   public static void enable(boolean enable){
+      enableLog = enable;
+   }
+   
+   public static void installGCMonitoring(){
+      if(Boolean.parseBoolean(installGCMonitoring)){
+         List<GarbageCollectorMXBean> gcbeans = java.lang.management.ManagementFactory.getGarbageCollectorMXBeans();
+         for (GarbageCollectorMXBean gcbean : gcbeans) {
+            NotificationEmitter emitter = (NotificationEmitter) gcbean;
+            NotificationListener listener = new GCListener();
+            emitter.addNotificationListener(listener, null, null);
+         }
+      }
+    }
+   
+   private static class GCListener implements NotificationListener {
+
+      private GCNotification gcData;
+
+      public void printCompositeDataSupport(Object obj, int level){
+         if(obj instanceof CompositeDataSupport){
+            CompositeDataSupport dataSupport = (CompositeDataSupport)obj;
+            CompositeType type = dataSupport.getCompositeType();
+            for(String key : type.keySet()){
+               Object value = dataSupport.get(key);
+               if(value instanceof CompositeDataSupport || value instanceof TabularDataSupport){
+                  printCompositeDataSupport(value, level + 1);
+               } else {
+                  if(!populateCompositeData(key, type, value)){
+                     for(int i = 0; i < level; i++){
+                        System.out.print("   ");
+                     }
+                     System.out.printf("key[%s] desc[%s] value[%s] class[%s]\n", key , type.getDescription(key), value, value.getClass().getName());
+                  }
+               }
+            }
+         } else if (obj instanceof TabularDataSupport){
+            TabularDataSupport dataSupport = (TabularDataSupport)obj;
+            TabularType type = dataSupport.getTabularType();
+            for(Object value : dataSupport.values()){
+               if(value instanceof CompositeDataSupport || value instanceof TabularDataSupport){
+                  printCompositeDataSupport(value, level + 1);
+               } else {
+                  System.out.println("nope");
+               }
+            }
+         }
+         else {
+            if(!populateGenericValue(obj)){
+               for(int i = 0; i < level; i++){
+                  System.out.print("   ");
+               }
+               System.out.printf("valud[%s] class[%s]\n", obj, obj.getClass().getName());
+            }
+         }
+      }
+
+      private boolean populateCompositeData(String key, CompositeType type, Object value) {
+         if(key.equals("GcThreadCount")){
+            gcData.threadCount = (Integer)value;
+            return true;
+         } else if (key.equals("duration")){
+            gcData.duration = (Long)value;
+            return true;
+         } else if (key.equals("endTime")){
+            gcData.endtime = (Long)value;
+            return true;
+         } else if (key.equals("startTime")){
+            gcData.startTime = (Long)value;
+            return true;
+         } else if (key.equals("id")){
+            //ignore
+            return true;
+         } else if (key.equals("key")){
+            gcData.currentGcMemory = new GCMemory();
+            gcData.currentGcMemory.memType = GCMemory.getType(value);
+            if(gcData.currentGcMemory.memType == GCMemory.type.unknown){
+               System.out.printf("unknown mem type [%s]\n", value);
+            }
+            gcData.memory.add(gcData.currentGcMemory);
+            return true;
+         } else if (key.equals("committed")){
+            gcData.currentGcMemory.committed = (Long)value;
+            return true;
+         }else if (key.equals("init")){
+            gcData.currentGcMemory.init = (Long)value;
+            return true;
+         }else if (key.equals("max")){
+            gcData.currentGcMemory.max = (Long)value;
+            return true;
+         }else if (key.equals("used")){
+            gcData.currentGcMemory.used = (Long)value;
+            return true;
+         }
+         return false;
+      }
+
+      private boolean populateGenericValue(Object obj) {
+         if(obj.toString().equals("end of minor GC")){
+            gcData.isMinor = true;
+            return true;
+         } else if(obj.toString().equals("end of major GC")){
+            gcData.isMajor = true;
+            return true;
+         } else if (obj.toString().equals("System.gc()") || obj.toString().equals("Metadata GC Threshold") || obj.toString().equals("Allocation Failure") || 
+               obj.toString().equals("PS Scavenge") || obj.toString().equals("PS MarkSweep") || obj.toString().equals("ParNew") || obj.toString().equals("GCLocker Initiated GC")){
+            if(gcData.reason == null){
+               gcData.reason = obj.toString();
+            } else {
+               gcData.reason = gcData.reason +", " + obj.toString();
+            }
+            return true;
+         }
+         return false;
+      }
+
+      //implement the notifier callback handler
+      @Override
+      public void handleNotification(Notification notification, Object handback) {
+         if(enableLog){
+            gcData = new GCNotification();
+            Object userData = notification.getUserData();
+            if(userData instanceof CompositeDataSupport){
+               CompositeDataSupport dataSupport = (CompositeDataSupport)userData;
+               for(Object obj :dataSupport.values()){
+                  printCompositeDataSupport(obj, 0);
+               }
+
+            } else {
+               System.out.printf("%s - %s\n", userData.getClass().getName(), userData);
+            }
+            OseeLog.log(GCHelper.class, Level.INFO, gcData.toString());
+         }
+      }
+   }
+   
+   public static class GCNotification {
+      
+      public String reason;
+      public Long startTime;
+      public GCMemory currentGcMemory;
+
+      enum gcType { psScavenge, psMarkSweep; }
+      
+      private gcType type;
+      private boolean isMajor = false;
+      private boolean isMinor = false;
+      private int threadCount;
+      private long duration;
+      private long endtime;
+      private long id;
+      
+      private List<GCMemory> memory;
+      
+      public GCNotification(){
+         memory = new ArrayList<GCHelper.GCMemory>();
+         currentGcMemory = new GCMemory();
+      }
+      
+      public String toString(){
+         return String.format("GC %s - %s - elapsedTime[%d]", (isMajor ? "major" : "minor"), reason, duration);
+      }
+   }
+   
+   public static class GCMemory {
+      enum type { compressedClassSpace, psSurvivorSpace, parSurvivorSpace, psOldGen, parOldGen, psEdenSpace, parEdenSpace, Metaspace, CodeCache, unknown; }
+      
+      private type memType;
+      private long committed;
+      private long init;
+      private long max;
+      private long used;
+      
+      public static org.eclipse.osee.ote.core.log.GCHelper.GCMemory.type getType(Object value) {
+         if(value.equals("Compressed Class Space")){
+            return type.compressedClassSpace;
+         } else if (value.equals("PS Survivor Space")){
+            return type.psSurvivorSpace;
+         } else if (value.equals("PS Old Gen")){
+            return type.psOldGen;
+         } else if (value.equals("Metaspace")){
+            return type.Metaspace;
+         } else if (value.equals("PS Eden Space")){
+            return type.psEdenSpace;
+         } else if (value.equals("Code Cache")){
+            return type.CodeCache;
+         } else if (value.equals("Par Survivor Space")){
+            return type.parSurvivorSpace;
+         } else if (value.equals("CMS Old Gen")){
+            return type.parOldGen;
+         } else if (value.equals("Par Eden Space")){
+            return type.parEdenSpace;
+         } 
+         return type.unknown;
+      }
+   }
+   
+}
diff --git a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/TestLogger.java b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/TestLogger.java
index 6aa2131..2cf1f2d 100644
--- a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/TestLogger.java
+++ b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/TestLogger.java
@@ -13,6 +13,7 @@
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
+
 import org.eclipse.osee.framework.jdk.core.persistence.Xmlizable;
 import org.eclipse.osee.framework.jdk.core.persistence.XmlizableStream;
 import org.eclipse.osee.ote.core.MethodFormatter;
@@ -24,6 +25,7 @@
 import org.eclipse.osee.ote.core.environment.interfaces.ITestPoint;
 import org.eclipse.osee.ote.core.log.record.AttentionRecord;
 import org.eclipse.osee.ote.core.log.record.DebugRecord;
+import org.eclipse.osee.ote.core.log.record.InfoRecord;
 import org.eclipse.osee.ote.core.log.record.RequirementRecord;
 import org.eclipse.osee.ote.core.log.record.SevereRecord;
 import org.eclipse.osee.ote.core.log.record.SupportRecord;
@@ -297,4 +299,25 @@
       }
       log(record);
    }
+
+   @Override
+   public void log(ITestEnvironmentAccessor source, Level level, String message, Throwable th) {
+      if(level.intValue() >= Level.SEVERE.intValue()){
+         SevereRecord severe = new SevereRecord(source, message, true);
+         severe.setThrown(th);
+         this.log(severe);
+      } else if (level.intValue() >= Level.WARNING.intValue()){
+         WarningRecord warn = new WarningRecord(source, message, true);
+         warn.setThrown(th);
+         this.log(warn);
+      } else if (level.intValue() >= Level.INFO.intValue()){
+         InfoRecord info = new InfoRecord(source, message, true);
+         info.setThrown(th);
+         this.log(info);
+      } else {
+         this.log(level, message, th);
+      }
+   }
+
+ 
 }
\ No newline at end of file
diff --git a/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/record/InfoRecord.java b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/record/InfoRecord.java
new file mode 100644
index 0000000..7c6ed0d
--- /dev/null
+++ b/org.eclipse.osee.ote.core/src/org/eclipse/osee/ote/core/log/record/InfoRecord.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2007 Boeing.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Boeing - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osee.ote.core.log.record;
+
+import java.util.logging.Level;
+import org.eclipse.osee.ote.core.environment.interfaces.ITestEnvironmentAccessor;
+
+public class InfoRecord extends TestRecord {
+
+   private static final long serialVersionUID = -3124953320400273382L;
+
+   /**
+    * InfoRecord Constructor. Sets up a Warning log message.
+    * 
+    * @param source The object requesting the logging.
+    * @param msg The log message.
+    * @param timeStamp <b>True </b> if a timestamp should be recorded, <b>False </b> if not.
+    */
+   public InfoRecord(ITestEnvironmentAccessor source, String msg, boolean timeStamp) {
+      super(source, Level.INFO, msg, timeStamp);
+   }
+
+   /**
+    * InfoRecord Constructor. Sets up a Warning log message.
+    * 
+    * @param source The object requesting the logging.
+    * @param msg The log message.
+    */
+   public InfoRecord(ITestEnvironmentAccessor source, String msg) {
+      this(source, msg, true);
+   }
+}
\ No newline at end of file