Adding ElasticsearchAppenderClass & longevitytests 59/38559/19
authorKumar Rishabh <shailrishabh@gmail.com>
Mon, 9 May 2016 07:07:25 +0000 (12:37 +0530)
committerLuis Gomez <ecelgp@gmail.com>
Wed, 8 Jun 2016 08:25:50 +0000 (08:25 +0000)
This adds ElasticsearchAppender Class which can query for harvested
MBeans in an Elasticsearch Node to which karaf-decanter dumps the data.
The current MBeans that can be currently queried for are:
    java.lang:type=GarbageCollector
    java.lang:type=Threading
    java.lang:type=ClassLoading
    java.lang:type=Memory
    java.lang:type=OperatingSystem

This also adds a method plot_points which draws graph using mathplotlib
for the above metrices
    Usage example is defined in the file.

This also adds long duration robot tests for the above class in the
folder longevity. A sample run would look something similar to

robot -v PORT:9200 -v IP:10.2.4.204 -v DURATION:20 -v STEP:1 010_Check_JVM_Resource.robot
where elasticsearch node is running on 10.2.4.204:9200 and each of the
tests is to be run for a duration of 20 seconds.
Also adds a test to plot_points where the graph is plotted using the
points from the starting of the elasticsearch node to the current time.

Change-Id: I4acb3cef32213e91cbaadb40c0c21deef8f4c060
Signed-off-by: Kumar Rishabh <shailrishabh@gmail.com>
.gitignore
csit/libraries/Appenders/ElasticsearchAppender.py [new file with mode: 0644]
csit/suites/test/Check_JVM_Resource.robot [new file with mode: 0644]
csit/variables/Variables.py

index eea525fe3fc2bfe5fcd3bfbb137f23aecff57233..795e713e6933a2397275e309a098d62df364835e 100644 (file)
@@ -17,6 +17,7 @@ opendaylight/northbound/integrationtest/logs/*
 *.iml
 *.iws
 .idea
+*.png
 *.pyc
 log.html
 output.xml
diff --git a/csit/libraries/Appenders/ElasticsearchAppender.py b/csit/libraries/Appenders/ElasticsearchAppender.py
new file mode 100644 (file)
index 0000000..fe1d8b5
--- /dev/null
@@ -0,0 +1,225 @@
+"""
+    Appenders Object Definition to be used with karaf-decanter
+    Used to collect resource usage metrics
+
+    Currently implements ElasticsearchAppender
+
+    Usage
+            declare foo = ElasticsearchAppender(hostname, port)
+            call
+                    foo.get_jvm_memory(), foo.get_jvm_classloading(),
+                    foo.get_jvm_threading(), foo.get_jvm_garbageCollector()
+                    foo.get_jvm_operatingsystem()
+            returns
+                    the latest resource usage statistics dictionary object
+                    (latest based on the @timestamp)
+            call
+                    foo.plot_points(title, filename, metric,
+                                    submetric, submetrickey)
+
+                    for example
+                    foo.plot_points('JVM Started Threads', 'threadcount.png',
+                                    'Threading', 'TotalStartedThreadCount')
+                    submetrickey is optional
+                    for more usage and examples see https://goo.gl/dT1RqT
+
+"""
+
+from datetime import datetime
+from operator import itemgetter
+from elasticsearch import Elasticsearch
+from elasticsearch_dsl import Search
+import re
+import matplotlib as mpl
+mpl.use('Agg')
+
+
+class MBeanNotFoundError(Exception):
+        def __init__(self, message, errors):
+            super(MBeanNotFoundError, self).__init__(message)
+
+
+class BaseAppender(object):
+    '''
+        Base Appender from which all appenders should inherit
+    '''
+
+    host = ''
+    port = ''
+
+    def __init__(self, host='localhost', port=9200):
+        self.host = host
+        self.port = port
+
+    def _get_index(self, need_all):
+        raise NotImplementedError
+
+    def _get_connection(self):
+        raise NotImplementedError
+
+
+class ElasticsearchAppender(BaseAppender):
+    '''
+        ElasticsearchAppender Class
+        Metrics supported : Memory, ClassLoading, Threading, GarbageCollector
+        Individual resource attributes as defined in attr dictionary object
+    '''
+
+    connection = ''
+    attr = {'Memory': ['HeapMemoryUsage', 'NonHeapMemoryUsage',
+                       '@timestamp'],
+            'ClassLoading': ['TotalLoadedClassCount', 'UnloadedClassCount',
+                             '@timestamp'],
+            'OperatingSystem': ['FreeSwapSpaceSize', 'TotalSwapSpaceSize',
+                                'FreePhysicalMemorySize',
+                                'TotalPhysicalMemorySize',
+                                'CommittedVirtualMemorySize', 'ProcessCpuLoad',
+                                'ProcessCpuTime', 'SystemCpuLoad',
+                                '@timestamp'],
+            'Threading': ['DaemonThreadCount', 'PeakThreadCount',
+                          'ThreadCount', 'TotalStartedThreadCount',
+                          '@timestamp'],
+            'GarbageCollector': ['LastGcInfo', 'CollectionCount',
+                                 '@timestamp', 'CollectionTime']}
+    label = {'Memory': 'Memory', 'ClassLoading': 'Class Loading',
+             'Threading': 'Threads', 'GarbageCollector': 'Garbage Collector'}
+
+    def __init__(self, host='localhost', port=9200):
+        host = self.cleanse_string(host)
+        port = self.cleanse_string(port)
+        super(ElasticsearchAppender, self).__init__(host, port)
+        self.connection = self._get_connection()
+
+    def get_jvm_memory(self):
+        return self._get_mbean_attr('Memory')
+
+    def get_jvm_classloading(self):
+        return self._get_mbean_attr('ClassLoading')
+
+    def get_jvm_threading(self):
+        return self._get_mbean_attr('Threading')
+
+    def get_jvm_garbagecollector(self):
+        return self._get_mbean_attr('GarbageCollector')
+
+    def get_jvm_operatingsystem(self):
+        return self._get_mbean_attr('OperatingSystem')
+
+    def cleanse_string(self, s):
+        return str(s).replace("'", "")
+
+    def plot_points(self, title, filename, metric, submetric,
+                    submetrickey=None):
+
+        from matplotlib import dates, pyplot as plt, ticker as tkr
+
+        metric = self.cleanse_string(metric)
+        submetric = self.cleanse_string(submetric)
+        if submetrickey is not None:
+            submetrickey = self.cleanse_string(submetrickey)
+
+        points = self._get_plot_points(metric, submetric, submetrickey)
+        points[0] = [p.replace(microsecond=0) for p in points[0]]
+        myFmt = dates.DateFormatter('%m-%d %H:%M:%S')
+        fig, ax = plt.subplots()
+
+        ax.plot_date(points[0], points[1], 'c-')
+        ax.grid(color='grey')
+        ax.patch.set_facecolor('black')
+        ax.xaxis.set_major_formatter(myFmt)
+
+        axes = plt.gca()
+        axes.get_yaxis().get_major_formatter().set_scientific(False)
+        axes.get_yaxis().get_major_formatter().set_useOffset(False)
+
+        ax.set_xlabel('Time')
+        xlabel = self._convert(submetric).title()
+        if submetrickey is not None:
+            xlabel = xlabel + ' : ' + str(submetrickey).title()
+        ax.set_ylabel(xlabel)
+
+        mx = max(points[1]) + max(points[1]) * 0.00001
+        mn = min(points[1]) - min(points[1]) * 0.00001
+        ax.set_ylim(mn, mx)
+
+        ax.set_title(title)
+        if isinstance(points[1][0], int):
+            axes.yaxis.set_major_formatter(tkr.FuncFormatter(lambda x, _:
+                                                             int(x)))
+        else:
+            axes.yaxis.set_major_formatter(tkr.FuncFormatter(lambda x, _:
+                                                             float(x)))
+        plt.gcf().autofmt_xdate()
+        plt.savefig(filename, bbox_inches='tight')
+
+    def _convert(self, name):
+        s1 = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', name)
+        return re.sub('([a-z0-9])([A-Z])', r'\1 \2', s1).lower()
+
+    def _get_y_val(self, response, metric, submetric=None):
+        if isinstance(response[metric], dict):
+            return response[metric][submetric]
+        else:
+            return response[metric]
+
+    def _get_plot_points(self, metric, submetric, submetrickey=None):
+        indices = self._get_index(need_all=True)
+        points = []
+        for index in indices:
+            responses = self._get_all_mbean_attr(metric, index)
+            for response in responses:
+                point = (self._get_datetime_object(response['@timestamp']),
+                         self._get_y_val(response, submetric, submetrickey))
+                points.append(point)
+        points.sort(key=itemgetter(0))
+        return zip(*points)
+
+    def _get_index(self, need_all=False):
+        indices = sorted([i for i in
+                         self.connection.indices.get_mapping().keys()
+                         if i.startswith('karaf')])
+        if need_all:
+            return indices
+        else:
+            return sorted(indices, reverse=True)[0]
+
+    def _get_connection(self):
+        con_obj = {'host': self.host, 'port': self.port}
+        es = Elasticsearch([con_obj])
+        return es
+
+    def _get_all_mbean_attr(self, mbean, index, dsl_class='match'):
+        s = Search(using=self.connection, index=index).\
+            filter(dsl_class, ObjectName=mbean).\
+            sort({"@timestamp": {"order": 'desc'}})
+        response = []
+        for hit in s.scan():
+            response.append(self._get_attr_obj([hit], mbean))
+        return response
+
+    def _get_mbean_attr(self, mbean, dsl_class='match'):
+        index = self._get_index()
+
+        try:
+            s = Search(using=self.connection, index=index).\
+                filter(dsl_class, ObjectName=mbean).\
+                sort({"@timestamp": {"order": 'desc'}})[0].execute()
+        except Exception:
+            raise MBeanNotFoundError('Could Not Fetch %s mbean' % mbean)
+
+        mem_attr = self._get_attr_obj(s, mbean)
+        return mem_attr
+
+    def _get_attr_obj(self, response, mbean):
+        mbean_attr = {}
+        for r in response:
+            for k in self.attr[mbean]:
+                is_to_dict = getattr(r[k], "to_dict", None)
+                if callable(is_to_dict):
+                    mbean_attr[k] = r[k].to_dict()
+                else:
+                    mbean_attr[k] = r[k]
+        return mbean_attr
+
+    def _get_datetime_object(self, timestamp):
+        return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S,%fZ')
diff --git a/csit/suites/test/Check_JVM_Resource.robot b/csit/suites/test/Check_JVM_Resource.robot
new file mode 100644 (file)
index 0000000..42d4718
--- /dev/null
@@ -0,0 +1,25 @@
+*** Settings ***
+Library           ${CURDIR}/../../libraries/Appenders/ElasticsearchAppender.py    ${ODL_SYSTEM_IP}    ${ELASTICPORT}
+Variables         ${CURDIR}/../../variables/Variables.py
+
+*** Test Cases ***
+Test JVM Keywords
+    [Documentation]    Call get_jvm methods for ${DURATION} s with ${STEP} s interval.
+    ${DURATION}=    Convert To Integer    ${DURATION}
+    : FOR    ${INDEX}    IN RANGE    0    ${DURATION+1}    ${STEP}
+    \    ${threading}=    Get Jvm Threading
+    \    Log    ${threading}
+    \    ${memory}=    Get Jvm Memory
+    \    Log    ${memory}
+    \    ${classload}=    Get Jvm Classloading
+    \    Log    ${classload}
+    \    ${operatingsystem}=    Get Jvm operatingsystem
+    \    Log    ${operatingsystem}
+    \    Sleep    ${STEP}
+
+Test Plot Points Call
+    [Documentation]    Draw Resource usage plot using plot_points method.
+    Plot Points    JVM ThreadCount    threadcount.png    'Threading'    'TotalStartedThreadCount'
+    Plot Points    JVM Heap Memory    heapmemory.png    'Memory'    'HeapMemoryUsage'    'used'
+    Plot Points    JVM LoadedClassCount    class_count.png    'ClassLoading'    'TotalLoadedClassCount'
+    Plot Points    JVM CPU Usage    cpu_usage.png    'OperatingSystem'    'ProcessCpuLoad'
index c29e0ac69388d63c5997c7c23049af981f317cbb..1fc59edae8ebe2d93a2844e1ef7cc5c6aa6d9979 100644 (file)
@@ -228,3 +228,6 @@ SET_DASHBOARDRECORD = '/restconf/operations/dashboardrule:set-dashboard'
 DELETE_DASHBOARDRECORD = '/restconf/operations/dashboardrule:delete-dashboard'
 SET_SUBSCRIBEUSER = '/restconf/operations/subscribe:subscribe-user'
 SUBSCRIPTION = '/restconf/config/subscribe:subscription/'
+
+# Elasticsearch Variables
+ELASTICPORT = 9200