From 1dba52f39f95fc0fb0a5e3d7b965f2394817af9f Mon Sep 17 00:00:00 2001
From: michaha2 <michaha2@cisco.com>
Date: Fri, 6 Jun 2014 00:58:40 -0700
Subject: [PATCH] added cartridge agent plugin support

---
 .../statistics/publisher/CartridgeStatistics.java  |  66 ++++++++++++
 .../publisher/HealthStatisticsNotifier.java        | 119 +++++++++++++++------
 .../publisher/HealthStatisticsReader.java          |  44 ++++----
 .../publisher/IHealthStatisticsReader.java         |  55 ++++++++++
 .../agent/statistics/publisher/PluginLoader.java   |  88 +++++++++++++++
 5 files changed, 313 insertions(+), 59 deletions(-)
 create mode 100644 components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/CartridgeStatistics.java
 create mode 100644 components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/IHealthStatisticsReader.java
 create mode 100644 components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/PluginLoader.java

diff --git a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/CartridgeStatistics.java b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/CartridgeStatistics.java
new file mode 100644
index 0000000..f92de2d
--- /dev/null
+++ b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/CartridgeStatistics.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.stratos.cartridge.agent.statistics.publisher;
+
+/**
+ * CartridgeStatistics is an instantaneous representaion of the cartridges current process and memory usage.
+ */
+public class CartridgeStatistics {
+
+    private double memoryUsage;
+    private double processorUsage;
+
+    /**
+     * Constructor
+     *
+     * @param memUsage the consumed memory, as a percentage of the available memory to the cartridge
+     * @param procUsage the processing used, as a percentage of the processing available to the cartridge
+     */
+    public CartridgeStatistics(double memUsage, double procUsage) {
+        memoryUsage = sanitiseUsage(memUsage);
+        processorUsage = sanitiseUsage(procUsage);
+    }
+
+    /**
+     * Called by contructor, utility to check input usage is a percentage.
+     * throws exception if the usage is not of the correct format
+     */
+    private double sanitiseUsage(double usage) throws IllegalArgumentException {
+        if ((usage < 0)) // || (usage > 100)) we currently get percentages over 100% for the procUsage is this fine?
+        {
+            throw new IllegalArgumentException("Usage statistic less than zero");
+        }
+        return usage;
+    }
+
+    /**
+     * Called to get memory usage
+     */
+    public double getMemoryUsage() {
+        return memoryUsage;
+    }
+
+    /**
+     * Called to get processor usage
+     */
+    public double getProcessorUsage() {
+        return processorUsage;
+    }
+}
diff --git a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsNotifier.java b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsNotifier.java
index 4bdad04..40826c5 100644
--- a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsNotifier.java
+++ b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsNotifier.java
@@ -19,6 +19,8 @@
 
 package org.apache.stratos.cartridge.agent.statistics.publisher;
 
+import org.apache.stratos.cartridge.agent.statistics.publisher.*;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.stratos.cartridge.agent.config.CartridgeAgentConfiguration;
@@ -26,6 +28,9 @@ import org.apache.stratos.cartridge.agent.event.publisher.CartridgeAgentEventPub
 import org.apache.stratos.cartridge.agent.util.CartridgeAgentConstants;
 import org.apache.stratos.cartridge.agent.util.CartridgeAgentUtils;
 
+import java.io.File;
+import java.util.List;
+
 /**
  * Health statistics notifier thread for publishing statistics periodically to CEP.
  */
@@ -33,12 +38,48 @@ public class HealthStatisticsNotifier implements Runnable {
     private static final Log log = LogFactory.getLog(HealthStatisticsNotifier.class);
 
     private final HealthStatisticsPublisher statsPublisher;
+    private IHealthStatisticsReader statsReader;
     private long statsPublisherInterval = 15000;
     private boolean terminated;
 
     public HealthStatisticsNotifier() {
         this.statsPublisher = new HealthStatisticsPublisher();
 
+        /* Find all jars in the current working directory */
+        String pluginFileName = System.getProperty("health.stats.reader.plugin");
+        if (pluginFileName != null)
+        {
+            File pluginFile = new File(pluginFileName);
+            if (    (pluginFile != null)
+                 && (pluginFile.exists())) {
+                List<Class> pluginClass = PluginLoader.loadPluginClassesFromJar(pluginFile, IHealthStatisticsReader.class);
+                if (!pluginClass.isEmpty())
+                {
+                    try
+                    {
+                        log.trace("Instantiating new instance of plugin type " + pluginClass);
+                        this.statsReader = (IHealthStatisticsReader)pluginClass.get(0).newInstance( );
+                    }
+                    catch(InstantiationException e)
+                    {
+                        log.error("Unable to instantiate plugin " + pluginClass, e);
+                    }
+                    catch(IllegalAccessException e)
+                    {
+                        log.error("Unable to instantiate plugin " + pluginClass, e);
+                    }
+                }
+            }
+            else
+            {
+                log.error("Plugin not found or malformed: " + pluginFileName + ((pluginFile == null)? " NULL": "Doesn't exist"));
+            }
+        }
+        if (this.statsReader == null)
+        {
+            this.statsReader = new HealthStatisticsReader();
+        }
+
         String interval = System.getProperty("stats.notifier.interval");
         if (interval != null) {
             statsPublisherInterval = Long.getLong(interval);
@@ -47,48 +88,56 @@ public class HealthStatisticsNotifier implements Runnable {
 
     @Override
     public void run() {
-        while (!terminated) {
-            try {
+        if (this.statsReader.init() == false)
+        {
+            log.error("Health statistics reader "+this.statsReader.getClass().getName()+" could not initialise");
+        }
+        else
+        {
+            while (!terminated) {
                 try {
-                    Thread.sleep(statsPublisherInterval);
-                } catch (InterruptedException ignore) {
-                }
+                    try {
+                        Thread.sleep(statsPublisherInterval);
+                    } catch (InterruptedException ignore) {
+                    }
 
-                if (statsPublisher.isEnabled()) {
+                    if (statsPublisher.isEnabled()) {
 
-                    double memoryConsumption = HealthStatisticsReader.getMemoryConsumption();
-                    if(log.isDebugEnabled()) {
-                        log.debug(String.format("Publishing memory consumption: %f", memoryConsumption));
-                    }
-                    statsPublisher.publish(
-                            CartridgeAgentConfiguration.getInstance().getClusterId(),
-                            CartridgeAgentConfiguration.getInstance().getNetworkPartitionId(),
-                            CartridgeAgentConfiguration.getInstance().getMemberId(),
-                            CartridgeAgentConfiguration.getInstance().getPartitionId(),
-                            CartridgeAgentConstants.MEMORY_CONSUMPTION,
-                            memoryConsumption
-                    );
+                        CartridgeStatistics stats = statsReader.getCartridgeStatistics();
+
+                        if(log.isDebugEnabled()) {
+                            log.debug(String.format("Publishing memory consumption: %f", stats.getMemoryUsage()));
+                        }
+                        statsPublisher.publish(
+                                CartridgeAgentConfiguration.getInstance().getClusterId(),
+                                CartridgeAgentConfiguration.getInstance().getNetworkPartitionId(),
+                                CartridgeAgentConfiguration.getInstance().getMemberId(),
+                                CartridgeAgentConfiguration.getInstance().getPartitionId(),
+                                CartridgeAgentConstants.MEMORY_CONSUMPTION,
+                                stats.getMemoryUsage()
+                        );
 
-                    double loadAverage = HealthStatisticsReader.getLoadAverage();
-                    if(log.isDebugEnabled()) {
-                        log.debug(String.format("Publishing load average: %f", loadAverage));
+                        if(log.isDebugEnabled()) {
+                            log.debug(String.format("Publishing load average: %f", stats.getProcessorUsage()));
+                        }
+                        statsPublisher.publish(
+                                CartridgeAgentConfiguration.getInstance().getClusterId(),
+                                CartridgeAgentConfiguration.getInstance().getNetworkPartitionId(),
+                                CartridgeAgentConfiguration.getInstance().getMemberId(),
+                                CartridgeAgentConfiguration.getInstance().getPartitionId(),
+                                CartridgeAgentConstants.LOAD_AVERAGE,
+                                stats.getProcessorUsage()
+                        );
+                    } else if (log.isWarnEnabled()) {
+                        log.warn("Statistics publisher is disabled");
+                    }
+                } catch (Exception e) {
+                    if (log.isErrorEnabled()) {
+                        log.error("Could not publish health statistics", e);
                     }
-                    statsPublisher.publish(
-                            CartridgeAgentConfiguration.getInstance().getClusterId(),
-                            CartridgeAgentConfiguration.getInstance().getNetworkPartitionId(),
-                            CartridgeAgentConfiguration.getInstance().getMemberId(),
-                            CartridgeAgentConfiguration.getInstance().getPartitionId(),
-                            CartridgeAgentConstants.LOAD_AVERAGE,
-                            loadAverage
-                    );
-                } else if (log.isWarnEnabled()) {
-                    log.warn("Statistics publisher is disabled");
-                }
-            } catch (Exception e) {
-                if (log.isErrorEnabled()) {
-                    log.error("Could not publish health statistics", e);
                 }
             }
+            this.statsReader.delete();
         }
     }
 
diff --git a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsReader.java b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsReader.java
index b986191..96e4661 100644
--- a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsReader.java
+++ b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/HealthStatisticsReader.java
@@ -26,47 +26,43 @@ import org.apache.stratos.cartridge.agent.config.CartridgeAgentConfiguration;
 import org.apache.stratos.cartridge.agent.util.CartridgeAgentUtils;
 
 import java.lang.management.ManagementFactory;
+import java.io.IOException;
 
 /**
  * Health statistics reader.
  */
-public class HealthStatisticsReader {
+public class HealthStatisticsReader implements IHealthStatisticsReader {
+
     private static final int MB = 1024 * 1024;
     private static final Log log = LogFactory.getLog(HealthStatisticsReader.class);
 
-    public static double getMemoryConsumption() {
+    public boolean init() {
+        return true;
+    }
+
+    public CartridgeStatistics getCartridgeStatistics() throws IOException {
     	OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
         double totalMemory = (double)(osBean.getTotalPhysicalMemorySize()/ MB);
         double usedMemory = (double)((totalMemory - (osBean.getFreePhysicalMemorySize() / MB) ));
-        
-        if(log.isDebugEnabled()) {
-        	log.debug("Calculating memory consumption: [totalMemory] "+totalMemory+" [usedMemory] "+usedMemory);
-        }
+        double loadAvg = (double)osBean.getSystemLoadAverage();
+        // assume system cores = available cores to JVM
+        int cores = osBean.getAvailableProcessors();
         double memoryConsumption = (usedMemory / totalMemory) * 100;
-        if(log.isDebugEnabled()) {
-        	log.debug("Calculating memory consumption: [percentage] "+memoryConsumption);
-        }
-        return memoryConsumption;
-    }
-
-    public static double getLoadAverage() {
-    	double loadAvg = (double)ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
-    	// assume system cores = available cores to JVM
-    	int cores = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();
-    	
-    	if(log.isDebugEnabled()) {
-        	log.debug("Calculating load average consumption: [loadAverage] "+loadAvg+" [cores] "+cores);
-        }
-    	
         double loadAvgPercentage = (loadAvg/cores) * 100;
+
         if(log.isDebugEnabled()) {
-        	log.debug("Calculating load average consumption: [percentage] "+loadAvgPercentage);
+            log.debug("Memory consumption: [totalMemory] "+totalMemory+"Mb [usedMemory] "+usedMemory+"Mb: "+memoryConsumption+"%");
+            log.debug("Processor consumption: [loadAverage] "+loadAvg+" [cores] "+cores+": "+loadAvgPercentage+"%");
         }
-		return loadAvgPercentage;
+    	
+        return (new CartridgeStatistics(memoryConsumption, loadAvgPercentage));
     }
 
     public static boolean allPortsActive() {
         return CartridgeAgentUtils.checkPortsActive(CartridgeAgentConfiguration.getInstance().getListenAddress(),
                                                     CartridgeAgentConfiguration.getInstance().getPorts());
     }
-}
\ No newline at end of file
+
+    public void delete() {
+    }
+}
diff --git a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/IHealthStatisticsReader.java b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/IHealthStatisticsReader.java
new file mode 100644
index 0000000..ddb4539
--- /dev/null
+++ b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/IHealthStatisticsReader.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.stratos.cartridge.agent.statistics.publisher;
+
+import java.io.IOException;
+
+/**
+ * Health statistics reader interface.
+ */
+public interface IHealthStatisticsReader {
+
+    /**
+     * Called exactly once, before any other method.
+     *
+     * Should be used, along with {@link #delete()}, to manage "unmanaged"
+     * resources, e.g. sockets. Standard objects will of course be managed by
+     * GC in the usual way and should be allocated in the ctor.
+     */
+    public boolean init();
+
+    /**
+     * Called repeatedly to obtain memory and processor use.
+     *
+     * Obtained from the cartridge agent hosti.
+     *
+     * Can throw IOException if the required metrics were not obtainable.
+     */
+    public CartridgeStatistics getCartridgeStatistics() throws IOException;
+
+    /**
+     * Called exactly once, after all other methods.
+     *
+     * Should be used, along with {@link #init()}, to manage "unmanaged"
+     * resources, e.g. sockets. Standard objects will of course be managed by
+     * GC in the usual way and will be collected when no longer referenced.
+     */
+    public void delete();
+}
diff --git a/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/PluginLoader.java b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/PluginLoader.java
new file mode 100644
index 0000000..afd6394
--- /dev/null
+++ b/components/org.apache.stratos.cartridge.agent/src/main/java/org/apache/stratos/cartridge/agent/statistics/publisher/PluginLoader.java
@@ -0,0 +1,88 @@
+package org.apache.stratos.cartridge.agent.statistics.publisher;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.Log;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.FileFilter;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.jar.*;
+import java.util.zip.*;
+
+
+public class PluginLoader
+{
+    private static final Log log = LogFactory.getLog(PluginLoader.class);
+
+    /* Built-in plugins are listed here. This is just easier than adding this
+     * jar to the list of jars to search, as it contains too much random stuff,
+     * including stuff with unsatisfied dependencies that we otherwise wouldn't
+     * load. There's a nice looking library called Reflections on Google Code
+     * that would probably be OK, but it's more logic.
+     */
+
+    public static List<Class> loadPluginClassesFromJar( File jarPath, Class pluginInterface )
+    {
+        List<Class> listeners = new LinkedList<Class>( );
+
+        try
+        {
+            URLClassLoader loader = new URLClassLoader( new URL[] { jarPath.toURI().toURL() } );
+            JarFile jar = new JarFile( jarPath );
+            Enumeration<? extends JarEntry> jarEnum = jar.entries();
+
+            log.trace( "Scanning jar file " + jarPath );
+
+            while( jarEnum.hasMoreElements() )
+            {
+                ZipEntry zipEntry = jarEnum.nextElement();
+                String fileName = zipEntry.getName();
+
+                if( fileName.endsWith( ".class" ) )
+                {
+                    log.trace( "Considering jar entry " + fileName );
+                    try
+                    {
+                        String className = fileName.replace( ".class", "" ).replace( "/", "." );
+                        Class cls = loader.loadClass( className );
+                        log.trace( "Loaded class " + className );
+
+                        if( hasInterface( cls, pluginInterface ) )
+                        {
+                            log.trace( "Class has " + pluginInterface.getName()  + " interface; adding " );
+                            listeners.add( cls );
+                        }
+                    }
+                    catch( ClassNotFoundException e )
+                    {
+                        log.error( "Unable to load class from " + fileName + " in " + jarPath );
+                    }
+                }
+            }
+
+        }
+        catch( IOException e )
+        {
+            log.error( "Unable to open JAR file " + jarPath, e );
+        }
+
+        return listeners;
+    }
+
+    private static boolean hasInterface( Class cls, Class iface )
+    {
+        for( Class in : cls.getInterfaces() )
+        {
+            if( in == iface )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
-- 
1.9.1

