You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2018/02/17 14:36:27 UTC

[1/2] incubator-unomi git commit: UNOMI-161 Add metrics to analyze internal performance

Repository: incubator-unomi
Updated Branches:
  refs/heads/master 189687478 -> a1574bf53


http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
----------------------------------------------------------------------
diff --git a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
index d5f85cf..153e19a 100644
--- a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
+++ b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/ElasticSearchPersistenceServiceImpl.java
@@ -25,6 +25,8 @@ import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.query.DateRange;
 import org.apache.unomi.api.query.IpRange;
 import org.apache.unomi.api.query.NumericRange;
+import org.apache.unomi.metrics.MetricAdapter;
+import org.apache.unomi.metrics.MetricsService;
 import org.apache.unomi.persistence.elasticsearch.conditions.*;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.persistence.spi.aggregate.*;
@@ -148,6 +150,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     private String aggregateQueryBucketSize = "5000";
 
+    private MetricsService metricsService;
     private Map<String, Map<String, Map<String, Object>>> knownMappings = new HashMap<>();
 
     public void setBundleContext(BundleContext bundleContext) {
@@ -243,10 +246,13 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
         this.aggregateQueryBucketSize = aggregateQueryBucketSize;
     }
 
+    public void setMetricsService(MetricsService metricsService) {
+        this.metricsService = metricsService;
+    }
     public void start() throws Exception {
 
         // on startup
-        new InClassLoaderExecute<Object>() {
+        new InClassLoaderExecute<Object>(null, null) {
             public Object execute(Object... args) throws Exception {
 
                 bulkProcessorConcurrentRequests = System.getProperty(BULK_PROCESSOR_CONCURRENT_REQUESTS, bulkProcessorConcurrentRequests);
@@ -452,7 +458,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     public void stop() {
 
-        new InClassLoaderExecute<Object>() {
+        new InClassLoaderExecute<Object>(null, null) {
             protected Object execute(Object... args) {
                 logger.info("Closing ElasticSearch persistence backend...");
                 if (bulkProcessor != null) {
@@ -553,7 +559,15 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public <T extends Item> PartialList<T> getAllItems(final Class<T> clazz, int offset, int size, String sortBy) {
-        return query(QueryBuilders.matchAllQuery(), sortBy, clazz, offset, size, null, null);
+        long startTime = System.currentTimeMillis();
+        try {
+            return query(QueryBuilders.matchAllQuery(), sortBy, clazz, offset, size, null, null);
+        } finally {
+            if (metricsService != null && metricsService.isActivated()) {
+                metricsService.updateTimer(this.getClass().getName() + ".getAllItems", startTime);
+            }
+        }
+
     }
 
     @Override
@@ -563,17 +577,23 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public <T extends Item> T load(final String itemId, final Date dateHint, final Class<T> clazz) {
-        return new InClassLoaderExecute<T>() {
+        return new InClassLoaderExecute<T>(metricsService, this.getClass().getName() + ".loadItem") {
             protected T execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
 
                     if (itemsMonthlyIndexed.contains(itemType) && dateHint == null) {
-                        PartialList<T> r = query(QueryBuilders.idsQuery(itemType).addIds(itemId), null, clazz, 0, 1, null, null);
-                        if (r.size() > 0) {
-                            return r.get(0);
-                        }
-                        return null;
+                        return new MetricAdapter<T>(metricsService, ".loadItemWithQuery") {
+                            @Override
+                            public T execute(Object... args) throws Exception {
+                                PartialList<T> r = query(QueryBuilders.idsQuery(itemType).addIds(itemId), null, clazz, 0, 1, null, null);
+                                if (r.size() > 0) {
+                                    putInCache(itemId, r.get(0));
+                                    return r.get(0);
+                                }
+                                return null;
+                            }
+                        }.execute();
                     } else {
                         String index = indexNames.containsKey(itemType) ? indexNames.get(itemType) :
                                 (itemsMonthlyIndexed.contains(itemType) ? getMonthlyIndexName(dateHint) : indexName);
@@ -609,7 +629,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean save(final Item item, final boolean useBatching) {
-        Boolean result =  new InClassLoaderExecute<Boolean>() {
+        Boolean result =  new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".saveItem") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String source = ESCustomObjectMapper.getObjectMapper().writeValueAsString(item);
@@ -650,7 +670,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean update(final String itemId, final Date dateHint, final Class clazz, final Map source) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".updateItem") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
@@ -681,7 +701,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean updateWithQueryAndScript(final Date dateHint, final Class<?> clazz, final String[] scripts, final Map<String, Object>[] scriptParams, final Condition[] conditions) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".updateWithQueryAndScript") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
@@ -736,7 +756,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean updateWithScript(final String itemId, final Date dateHint, final Class<?> clazz, final String script, final Map<String, Object> scriptParams) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".updateWithScript") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
@@ -769,10 +789,11 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public <T extends Item> boolean remove(final String itemId, final Class<T> clazz) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".removeItem") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
+                    deleteFromCache(itemId, clazz);
 
                     client.prepareDelete(getIndexNameForQuery(itemType), itemType, itemId)
                             .execute().actionGet();
@@ -790,7 +811,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public <T extends Item> boolean removeByQuery(final Condition query, final Class<T> clazz) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".removeByQuery") {
             protected Boolean execute(Object... args) throws Exception {
                 try {
                     String itemType = Item.getItemType(clazz);
@@ -845,7 +866,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
 
     public boolean indexTemplateExists(final String templateName) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".indexTemplateExists") {
             protected Boolean execute(Object... args) {
                 GetIndexTemplatesResponse getIndexTemplatesResponse = client.admin().indices().prepareGetTemplates(templateName).execute().actionGet();
                 return getIndexTemplatesResponse.getIndexTemplates().size() == 1;
@@ -859,7 +880,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public boolean removeIndexTemplate(final String templateName) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".removeIndexTemplate") {
             protected Boolean execute(Object... args) {
                 DeleteIndexTemplateResponse deleteIndexTemplateResponse = client.admin().indices().deleteTemplate(new DeleteIndexTemplateRequest(templateName)).actionGet();
                 return deleteIndexTemplateResponse.isAcknowledged();
@@ -873,7 +894,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public boolean createMonthlyIndexTemplate() {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".createMonthlyIndexTemplate") {
             protected Boolean execute(Object... args) {
                 PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest("context-monthly-indices")
                         .template(indexName + "-*")
@@ -912,7 +933,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public boolean createIndex(final String indexName) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".createItem") {
             protected Boolean execute(Object... args) {
                 IndicesExistsResponse indicesExistsResponse = client.admin().indices().prepareExists(indexName).execute().actionGet();
                 boolean indexExists = indicesExistsResponse.isExists();
@@ -937,7 +958,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public boolean removeIndex(final String indexName) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".removeIndex") {
             protected Boolean execute(Object... args) {
                 IndicesExistsResponse indicesExistsResponse = client.admin().indices().prepareExists(indexName).execute().actionGet();
                 boolean indexExists = indicesExistsResponse.isExists();
@@ -1010,7 +1031,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public Map<String, Map<String, Object>> getPropertiesMapping(final String itemType) {
-        return new InClassLoaderExecute<Map<String, Map<String, Object>>>() {
+        return new InClassLoaderExecute<Map<String, Map<String, Object>>>(metricsService, this.getClass().getName() + ".getPropertiesMapping") {
             @SuppressWarnings("unchecked")
             protected Map<String, Map<String, Object>> execute(Object... args) throws Exception {
                 GetMappingsResponse getMappingsResponse = client.admin().indices().prepareGetMappings().setTypes(itemType).execute().actionGet();
@@ -1085,7 +1106,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     public boolean saveQuery(final String queryName, final String query) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".saveQuery") {
             protected Boolean execute(Object... args) throws Exception {
                 //Index the query = register it in the percolator
                 try {
@@ -1118,7 +1139,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean removeQuery(final String queryName) {
-        Boolean result = new InClassLoaderExecute<Boolean>() {
+        Boolean result = new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".removeQuery") {
             protected Boolean execute(Object... args) throws Exception {
                 //Index the query = register it in the percolator
                 try {
@@ -1140,18 +1161,30 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public boolean testMatch(Condition query, Item item) {
+        long startTime = System.currentTimeMillis();
         try {
             return conditionEvaluatorDispatcher.eval(query, item);
         } catch (UnsupportedOperationException e) {
             logger.error("Eval not supported, continue with query", e);
+        } finally {
+            if (metricsService != null && metricsService.isActivated()) {
+                metricsService.updateTimer(this.getClass().getName() + ".testMatchLocally", startTime);
+            }
+        }
+        startTime = System.currentTimeMillis();
+        try {
+            final Class<? extends Item> clazz = item.getClass();
+            String itemType = Item.getItemType(clazz);
+
+            QueryBuilder builder = QueryBuilders.boolQuery()
+                    .must(QueryBuilders.idsQuery(itemType).addIds(item.getItemId()))
+                    .must(conditionESQueryBuilderDispatcher.buildFilter(query));
+            return queryCount(builder, itemType) > 0;
+        } finally {
+            if (metricsService != null && metricsService.isActivated()) {
+                metricsService.updateTimer(this.getClass().getName() + ".testMatchInElasticSearch", startTime);
+            }
         }
-        final Class<? extends Item> clazz = item.getClass();
-        String itemType = Item.getItemType(clazz);
-
-        QueryBuilder builder = QueryBuilders.boolQuery()
-                .must(QueryBuilders.idsQuery(itemType).addIds(item.getItemId()))
-                .must(conditionESQueryBuilderDispatcher.buildFilter(query));
-        return queryCount(builder, itemType) > 0;
     }
 
     @Override
@@ -1213,7 +1246,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     private long queryCount(final QueryBuilder filter, final String itemType) {
-        return new InClassLoaderExecute<Long>() {
+        return new InClassLoaderExecute<Long>(metricsService, this.getClass().getName() + ".queryCount") {
 
             @Override
             protected Long execute(Object... args) {
@@ -1229,7 +1262,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
     }
 
     private <T extends Item> PartialList<T> query(final QueryBuilder query, final String sortBy, final Class<T> clazz, final int offset, final int size, final String[] routing, final String scrollTimeValidity) {
-        return new InClassLoaderExecute<PartialList<T>>() {
+        return new InClassLoaderExecute<PartialList<T>>(metricsService, this.getClass().getName() + ".query") {
 
             @Override
             protected PartialList<T> execute(Object... args) throws Exception {
@@ -1353,7 +1386,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public <T extends Item> PartialList<T> continueScrollQuery(final Class<T> clazz, final String scrollIdentifier, final String scrollTimeValidity) {
-        return new InClassLoaderExecute<PartialList<T>>() {
+        return new InClassLoaderExecute<PartialList<T>>(metricsService, this.getClass().getName() + ".continueScrollQuery") {
 
             @Override
             protected PartialList<T> execute(Object... args) throws Exception {
@@ -1390,7 +1423,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public Map<String, Long> aggregateQuery(final Condition filter, final BaseAggregate aggregate, final String itemType) {
-        return new InClassLoaderExecute<Map<String, Long>>() {
+        return new InClassLoaderExecute<Map<String, Long>>(metricsService, this.getClass().getName() + ".aggregateQuery") {
 
             @Override
             protected Map<String, Long> execute(Object... args) {
@@ -1525,7 +1558,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public void refresh() {
-        new InClassLoaderExecute<Boolean>() {
+        new InClassLoaderExecute<Boolean>(metricsService, this.getClass().getName() + ".refresh") {
             protected Boolean execute(Object... args) {
                 if (bulkProcessor != null) {
                     bulkProcessor.flush();
@@ -1540,7 +1573,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public void purge(final Date date) {
-        new InClassLoaderExecute<Object>() {
+        new InClassLoaderExecute<Object>(metricsService, this.getClass().getName() + ".purgeWithDate") {
             @Override
             protected Object execute(Object... args) throws Exception {
                 IndicesStatsResponse statsResponse = client.admin().indices().prepareStats(indexName + "-*")
@@ -1582,7 +1615,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public void purge(final String scope) {
-        new InClassLoaderExecute<Void>() {
+        new InClassLoaderExecute<Void>(metricsService, this.getClass().getName() + ".purgeWithScope") {
             @Override
             protected Void execute(Object... args) {
                 QueryBuilder query = termQuery("scope", scope);
@@ -1627,7 +1660,7 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     @Override
     public Map<String, Double> getSingleValuesMetrics(final Condition condition, final String[] metrics, final String field, final String itemType) {
-        return new InClassLoaderExecute<Map<String, Double>>() {
+        return new InClassLoaderExecute<Map<String, Double>>(metricsService, this.getClass().getName() + ".getSingleValuesMetrics") {
 
             @Override
             protected Map<String, Double> execute(Object... args) {
@@ -1691,23 +1724,38 @@ public class ElasticSearchPersistenceServiceImpl implements PersistenceService,
 
     public abstract static class InClassLoaderExecute<T> {
 
+        private String timerName;
+        private MetricsService metricsService;
+
+        public InClassLoaderExecute(MetricsService metricsService, String timerName) {
+            this.timerName = timerName;
+            this.metricsService = metricsService;
+        }
+
         protected abstract T execute(Object... args) throws Exception;
 
         public T executeInClassLoader(Object... args) throws Exception {
+
+            long startTime = System.currentTimeMillis();
             ClassLoader tccl = Thread.currentThread().getContextClassLoader();
             try {
                 Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
                 return execute(args);
             } finally {
+                if (metricsService != null && metricsService.isActivated()) {
+                    metricsService.updateTimer(timerName, startTime);
+                }
                 Thread.currentThread().setContextClassLoader(tccl);
             }
         }
 
         public T catchingExecuteInClassLoader(boolean logError, Object... args) {
             try {
-                return executeInClassLoader(args);
+                return executeInClassLoader(timerName, args);
             } catch (Exception e) {
-                logger.error("Error while executing in class loader", e);
+                if (logError) {
+                    logger.error("Error while executing in class loader", e);
+                }
             }
             return null;
         }

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionEvaluatorDispatcher.java
----------------------------------------------------------------------
diff --git a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionEvaluatorDispatcher.java b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionEvaluatorDispatcher.java
index dff7ecb..6394300 100644
--- a/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionEvaluatorDispatcher.java
+++ b/persistence-elasticsearch/core/src/main/java/org/apache/unomi/persistence/elasticsearch/conditions/ConditionEvaluatorDispatcher.java
@@ -19,6 +19,8 @@ package org.apache.unomi.persistence.elasticsearch.conditions;
 
 import org.apache.unomi.api.Item;
 import org.apache.unomi.api.conditions.Condition;
+import org.apache.unomi.metrics.MetricAdapter;
+import org.apache.unomi.metrics.MetricsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -34,6 +36,12 @@ public class ConditionEvaluatorDispatcher {
 
     private Map<String, ConditionEvaluator> evaluators = new ConcurrentHashMap<>();
 
+    private MetricsService metricsService;
+
+    public void setMetricsService(MetricsService metricsService) {
+        this.metricsService = metricsService;
+    }
+
     public void addEvaluator(String name, ConditionEvaluator evaluator) {
         evaluators.put(name, evaluator);
     }
@@ -59,11 +67,21 @@ public class ConditionEvaluatorDispatcher {
 
         if (evaluators.containsKey(conditionEvaluatorKey)) {
             ConditionEvaluator evaluator = evaluators.get(conditionEvaluatorKey);
-            Condition contextualCondition = ConditionContextHelper.getContextualCondition(condition, context);
-            if (contextualCondition != null) {
-                return evaluator.eval(contextualCondition, item, context, this);
-            } else {
-                return true;
+            final ConditionEvaluatorDispatcher dispatcher = this;
+            try {
+                return new MetricAdapter<Boolean>(metricsService, this.getClass().getName() + ".conditions." + conditionEvaluatorKey) {
+                    @Override
+                    public Boolean execute(Object... args) throws Exception {
+                        Condition contextualCondition = ConditionContextHelper.getContextualCondition(condition, context);
+                        if (contextualCondition != null) {
+                            return evaluator.eval(contextualCondition, item, context, dispatcher);
+                        } else {
+                            return true;
+                        }
+                    }
+                }.runWithTimer();
+            } catch (Exception e) {
+                logger.error("Error executing condition evaluator with key=" + conditionEvaluatorKey, e);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index ad095c1..92871eb 100644
--- a/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/persistence-elasticsearch/core/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -53,6 +53,7 @@
         </cm:default-properties>
     </cm:property-placeholder>
 
+    <reference id="metricsService" interface="org.apache.unomi.metrics.MetricsService" />
     <service id="elasticSearchPersistenceService" ref="elasticSearchPersistenceServiceImpl">
         <interfaces>
             <value>org.apache.unomi.persistence.spi.PersistenceService</value>
@@ -65,6 +66,7 @@
 
     <bean id="conditionEvaluatorDispatcherImpl"
           class="org.apache.unomi.persistence.elasticsearch.conditions.ConditionEvaluatorDispatcher">
+        <property name="metricsService" ref="metricsService" />
     </bean>
 
     <bean id="elasticSearchPersistenceServiceImpl"
@@ -107,6 +109,7 @@
         <property name="maximalElasticSearchVersion" value="${es.maximalElasticSearchVersion}" />
 
         <property name="aggregateQueryBucketSize" value="${es.aggregateQueryBucketSize}" />
+        <property name="metricsService" ref="metricsService" />
     </bean>
 
     <!-- We use a listener here because using the list directly for listening to proxies coming from the same bundle didn't seem to work -->

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 0d8a61e..99ff1b9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -846,6 +846,8 @@
 
     <modules>
         <module>api</module>
+        <module>common</module>
+        <module>metrics</module>
         <module>persistence-spi</module>
         <module>lifecycle-watcher</module>
         <module>persistence-elasticsearch</module>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/services/pom.xml
----------------------------------------------------------------------
diff --git a/services/pom.xml b/services/pom.xml
index d62771c..a479b13 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -123,6 +123,13 @@
             <artifactId>org.apache.karaf.cellar.config</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-metrics</artifactId>
+            <version>1.3.0-incubating-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
----------------------------------------------------------------------
diff --git a/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java b/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
index 89f7d05..6306be6 100644
--- a/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
+++ b/services/src/main/java/org/apache/unomi/services/actions/ActionExecutorDispatcher.java
@@ -23,6 +23,8 @@ import org.apache.unomi.api.Event;
 import org.apache.unomi.api.actions.Action;
 import org.apache.unomi.api.actions.ActionExecutor;
 import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.metrics.MetricAdapter;
+import org.apache.unomi.metrics.MetricsService;
 import org.mvel2.MVEL;
 import org.mvel2.ParserConfiguration;
 import org.mvel2.ParserContext;
@@ -41,6 +43,11 @@ public class ActionExecutorDispatcher {
     private final Map<String, Serializable> mvelExpressions = new ConcurrentHashMap<>();
     private final Map<String, ValueExtractor> valueExtractors = new HashMap<>(11);
     private Map<String, ActionExecutor> executors = new ConcurrentHashMap<>();
+    private MetricsService metricsService;
+
+    public void setMetricsService(MetricsService metricsService) {
+        this.metricsService = metricsService;
+    }
 
     public ActionExecutorDispatcher() {
         valueExtractors.put("profileProperty", new ValueExtractor() {
@@ -177,7 +184,16 @@ public class ActionExecutorDispatcher {
 
         if (executors.containsKey(actionKey)) {
             ActionExecutor actionExecutor = executors.get(actionKey);
-            return actionExecutor.execute(getContextualAction(action, event), event);
+            try {
+                return new MetricAdapter<Integer>(metricsService, this.getClass().getName() + ".action." + actionKey) {
+                    @Override
+                    public Integer execute(Object... args) throws Exception {
+                        return actionExecutor.execute(getContextualAction(action, event), event);
+                    }
+                }.runWithTimer();
+            } catch (Exception e) {
+                logger.error("Error executing action with key=" + actionKey, e);
+            }
         }
         return EventService.NO_CHANGE;
     }

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 3d9b181..98a921a 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -50,6 +50,7 @@
     <reference id="karafCellarEventProducer" interface="org.apache.karaf.cellar.core.event.EventProducer" />
     <reference id="karafCellarGroupManager" interface="org.apache.karaf.cellar.core.GroupManager" />
     <reference id="osgiConfigurationAdmin" interface="org.osgi.service.cm.ConfigurationAdmin"/>
+    <reference id="metricsService" interface="org.apache.unomi.metrics.MetricsService" />
 
     <!-- Service definitions -->
 
@@ -100,6 +101,7 @@
 
     <bean id="actionExecutorDispatcherImpl"
           class="org.apache.unomi.services.actions.ActionExecutorDispatcher">
+        <property name="metricsService" ref="metricsService" />
     </bean>
 
     <bean id="rulesServiceImpl" class="org.apache.unomi.services.services.RulesServiceImpl"

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 38ec572..60018d9 100644
--- a/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -56,6 +56,8 @@
             <list>
                 <value>org.apache.unomi.lifecycle-watcher</value>
                 <value>org.apache.unomi.api</value>
+                <value>org.apache.unomi.common</value>
+                <value>org.apache.unomi.metrics</value>
                 <value>org.apache.unomi.persistence-spi</value>
                 <value>org.apache.unomi.persistence-elasticsearch-core</value>
                 <value>org.apache.unomi.services</value>


[2/2] incubator-unomi git commit: UNOMI-161 Add metrics to analyze internal performance

Posted by sh...@apache.org.
UNOMI-161 Add metrics to analyze internal performance


Project: http://git-wip-us.apache.org/repos/asf/incubator-unomi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-unomi/commit/a1574bf5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-unomi/tree/a1574bf5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-unomi/diff/a1574bf5

Branch: refs/heads/master
Commit: a1574bf53415d450c7b7f32096d873dcd3dc4b34
Parents: 1896874
Author: Serge Huber <sh...@apache.org>
Authored: Sat Feb 17 15:36:22 2018 +0100
Committer: Serge Huber <sh...@apache.org>
Committed: Sat Feb 17 15:36:22 2018 +0100

----------------------------------------------------------------------
 common/pom.xml                                  |  46 +++++
 .../java/org/apache/unomi/common/DataTable.java | 193 +++++++++++++++++++
 .../org/apache/unomi/common/DataTableTest.java  |  67 +++++++
 kar/pom.xml                                     |  10 +
 kar/src/main/feature/feature.xml                |   2 +
 metrics/README.md                               |  38 ++++
 metrics/pom.xml                                 |  88 +++++++++
 .../org/apache/unomi/metrics/CalleeCount.java   |  29 +++
 .../java/org/apache/unomi/metrics/Metric.java   |  33 ++++
 .../org/apache/unomi/metrics/MetricAdapter.java |  45 +++++
 .../apache/unomi/metrics/MetricsService.java    |  40 ++++
 .../unomi/metrics/commands/ActivateCommand.java |  33 ++++
 .../metrics/commands/CalleeStatusCommand.java   |  64 ++++++
 .../metrics/commands/DeactivateCommand.java     |  33 ++++
 .../unomi/metrics/commands/ListCommand.java     |  87 +++++++++
 .../metrics/commands/MetricsCommandSupport.java |  28 +++
 .../unomi/metrics/commands/ResetCommand.java    |  32 +++
 .../unomi/metrics/commands/ViewCommand.java     |  47 +++++
 .../unomi/metrics/internal/CalleeCountImpl.java |  66 +++++++
 .../unomi/metrics/internal/MetricImpl.java      |  66 +++++++
 .../metrics/internal/MetricsServiceImpl.java    | 118 ++++++++++++
 .../resources/OSGI-INF/blueprint/blueprint.xml  |  77 ++++++++
 .../metrics/internal/MetricsServiceTest.java    | 150 ++++++++++++++
 persistence-elasticsearch/core/pom.xml          |   7 +
 .../ElasticSearchPersistenceServiceImpl.java    | 128 ++++++++----
 .../ConditionEvaluatorDispatcher.java           |  28 ++-
 .../resources/OSGI-INF/blueprint/blueprint.xml  |   3 +
 pom.xml                                         |   2 +
 services/pom.xml                                |   7 +
 .../actions/ActionExecutorDispatcher.java       |  18 +-
 .../resources/OSGI-INF/blueprint/blueprint.xml  |   2 +
 .../resources/OSGI-INF/blueprint/blueprint.xml  |   2 +
 32 files changed, 1543 insertions(+), 46 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/common/pom.xml
----------------------------------------------------------------------
diff --git a/common/pom.xml b/common/pom.xml
new file mode 100644
index 0000000..1b25d6d
--- /dev/null
+++ b/common/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-root</artifactId>
+        <version>1.3.0-incubating-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>unomi-common</artifactId>
+    <name>Apache Unomi :: Common Classes</name>
+    <description>Common Classes used in the Apache Unomi Context server</description>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-csv</artifactId>
+            <version>1.5</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/common/src/main/java/org/apache/unomi/common/DataTable.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/unomi/common/DataTable.java b/common/src/main/java/org/apache/unomi/common/DataTable.java
new file mode 100644
index 0000000..06839fc
--- /dev/null
+++ b/common/src/main/java/org/apache/unomi/common/DataTable.java
@@ -0,0 +1,193 @@
+/*
+ * 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.unomi.common;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * An in memory table structure for storing data and performing operations such as sorting it, or generating JSON or
+ * CSV outputs.
+ */
+public class DataTable {
+
+    List<Row> rows = new ArrayList<>();
+    int maxColumns = 0;
+
+    public static final EmptyCell EMPTY_CELL = new EmptyCell();
+
+    public DataTable() {
+    }
+
+    public List<Row> getRows() {
+        return rows;
+    }
+
+    public void addRow(Comparable... rowData) {
+        if (rowData == null) {
+            return;
+        }
+        if (rowData.length > maxColumns) {
+            maxColumns = rowData.length;
+        }
+        Row row = new Row();
+        for (Comparable dataObject : rowData) {
+            row.addData(dataObject);
+        }
+        rows.add(row);
+    }
+
+    public int getMaxColumns() {
+        return maxColumns;
+    }
+
+    public static enum SortOrder {
+        ASCENDING,
+        DESCENDING;
+    }
+
+    public static class SortCriteria {
+        Integer columnIndex;
+        SortOrder sortOrder;
+
+        public SortCriteria(Integer columnIndex, SortOrder sortOrder) {
+            this.columnIndex = columnIndex;
+            this.sortOrder = sortOrder;
+        }
+    }
+
+    public class Row {
+        List<Comparable> rowData = new ArrayList<>();
+
+        public void addData(Comparable data) {
+            rowData.add(data);
+        }
+
+        public Comparable getData(int index) {
+            if (index >= maxColumns) {
+                throw new ArrayIndexOutOfBoundsException("Index on row data (" + index + ") is larger than max columns (" + maxColumns + ")");
+            }
+            if (index >= rowData.size()) {
+                return EMPTY_CELL;
+            }
+            return rowData.get(index);
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("[");
+            sb.append(rowData);
+            sb.append(']');
+            return sb.toString();
+        }
+    }
+
+    public void sort(SortCriteria... sortCriterias) {
+        rows.sort(new Comparator<Row>() {
+            @Override
+            public int compare(Row row1, Row row2) {
+                int i = 0;
+                while (i < sortCriterias.length) {
+                    Comparable row1Data = row1.getData(sortCriterias[i].columnIndex);
+                    Comparable row2Data = row2.getData(sortCriterias[i].columnIndex);
+                    if (row1Data == EMPTY_CELL && row2Data != EMPTY_CELL) {
+                        if (sortCriterias[i].sortOrder == SortOrder.ASCENDING) {
+                            return -1;
+                        } else {
+                            return 1;
+                        }
+                    }
+                    if (row2Data == EMPTY_CELL && row1Data != EMPTY_CELL) {
+                        if (sortCriterias[i].sortOrder == SortOrder.ASCENDING) {
+                            return 1;
+                        } else {
+                            return -1;
+                        }
+                    }
+                    int rowComparison = row1Data.compareTo(row2Data);
+                    if (rowComparison == 0) {
+                        // rows are equal on this criteria, let's go to the next criteria if it exists
+                        if (i < sortCriterias.length) {
+                            i++;
+                        } else {
+                            return 0;
+                        }
+                    } else {
+                        if (sortCriterias[i].sortOrder == SortOrder.ASCENDING) {
+                            return rowComparison;
+                        } else {
+                            return -rowComparison;
+                        }
+                    }
+                }
+                return 0;
+            }
+        });
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("[");
+        sb.append(rows);
+        sb.append(']');
+        return sb.toString();
+    }
+
+    public static class EmptyCell implements Comparable {
+        @Override
+        public int compareTo(Object o) {
+            if (o instanceof EmptyCell) {
+                return 0;
+            }
+            return -1;
+        }
+
+        @Override
+        public String toString() {
+            return "";
+        }
+    }
+
+    public String toCSV(String... headers) {
+        StringBuilder stringBuilder = new StringBuilder();
+        CSVFormat csvFormat = CSVFormat.DEFAULT;
+        if (headers != null && headers.length > 0) {
+            csvFormat = CSVFormat.DEFAULT.withHeader(headers);
+        }
+        try {
+            CSVPrinter printer = csvFormat.print(stringBuilder);
+            for (Row row : rows) {
+                List<String> values = new ArrayList<>();
+                for (int i = 0; i < maxColumns; i++) {
+                    values.add(row.getData(i).toString());
+                }
+                printer.printRecord(values.toArray(new String[values.size()]));
+            }
+            printer.close();
+        } catch (IOException e) {
+            e.printStackTrace(); // this will probably never happen as we are writing to a String.
+        }
+        return stringBuilder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/common/src/test/java/org/apache/unomi/common/DataTableTest.java
----------------------------------------------------------------------
diff --git a/common/src/test/java/org/apache/unomi/common/DataTableTest.java b/common/src/test/java/org/apache/unomi/common/DataTableTest.java
new file mode 100644
index 0000000..2ed4b9d
--- /dev/null
+++ b/common/src/test/java/org/apache/unomi/common/DataTableTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.unomi.common;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class DataTableTest {
+
+    @Test
+    public void testTableSorting() {
+        DataTable dataTable = new DataTable();
+
+        dataTable.addRow("Row1", 1, 2, 1);
+        dataTable.addRow("Row2", 3, 2, 2);
+        dataTable.addRow("Row3", 2, 1, 1);
+
+        dataTable.sort(new DataTable.SortCriteria(0, DataTable.SortOrder.ASCENDING));
+        assertEquals("Row 1 should be first", "Row1", dataTable.getRows().get(0).getData(0));
+        assertEquals("Row 2 should be second", "Row2", dataTable.getRows().get(1).getData(0));
+        assertEquals("Row 3 should be third", "Row3", dataTable.getRows().get(2).getData(0));
+
+        dataTable.sort(new DataTable.SortCriteria(1, DataTable.SortOrder.ASCENDING));
+        assertEquals("Row 1 should be first", "Row1", dataTable.getRows().get(0).getData(0));
+        assertEquals("Row 3 should be second", "Row3", dataTable.getRows().get(1).getData(0));
+        assertEquals("Row 2 should be third", "Row2", dataTable.getRows().get(2).getData(0));
+
+        dataTable.sort(new DataTable.SortCriteria(2, DataTable.SortOrder.ASCENDING),
+                new DataTable.SortCriteria(3, DataTable.SortOrder.DESCENDING));
+        assertEquals("Row 3 should be first", "Row3", dataTable.getRows().get(0).getData(0));
+        assertEquals("Row 2 should be second", "Row2", dataTable.getRows().get(1).getData(0));
+        assertEquals("Row 1 should be third", "Row1", dataTable.getRows().get(2).getData(0));
+
+        System.out.println("CSV version of data table:");
+        System.out.println(dataTable.toCSV("Row name", "\"Value\", 1", "Value 2", "Value 3"));
+    }
+
+    @Test
+    public void testNonSquareTable() {
+        DataTable dataTable = new DataTable();
+        dataTable.addRow("Row1", 1, 2, 1, "Value");
+        dataTable.addRow("Row2", 3, 2, 2);
+
+        Comparable cellData = dataTable.getRows().get(1).getData(4);
+        assertEquals("Excepted cell data is empty cell", DataTable.EMPTY_CELL, cellData);
+
+        dataTable.sort(new DataTable.SortCriteria(4, DataTable.SortOrder.ASCENDING));
+        assertEquals("Row 2 should be first", "Row2", dataTable.getRows().get(0).getData(0));
+
+        dataTable.sort(new DataTable.SortCriteria(4, DataTable.SortOrder.DESCENDING));
+        assertEquals("Row 1 should be first", "Row1", dataTable.getRows().get(0).getData(0));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/kar/pom.xml
----------------------------------------------------------------------
diff --git a/kar/pom.xml b/kar/pom.xml
index 49ad5f8..39d6316 100644
--- a/kar/pom.xml
+++ b/kar/pom.xml
@@ -43,6 +43,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-common</artifactId>
+            <version>1.3.0-incubating-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-metrics</artifactId>
+            <version>1.3.0-incubating-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
             <artifactId>unomi-services</artifactId>
             <version>1.3.0-incubating-SNAPSHOT</version>
         </dependency>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/kar/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/kar/src/main/feature/feature.xml b/kar/src/main/feature/feature.xml
index b4bdd90..9c49c1c 100644
--- a/kar/src/main/feature/feature.xml
+++ b/kar/src/main/feature/feature.xml
@@ -43,6 +43,8 @@
 
         <bundle start-level="70" start="false">mvn:org.apache.unomi/unomi-lifecycle-watcher/${project.version}</bundle>
         <bundle start-level="75" start="false">mvn:org.apache.unomi/unomi-api/${project.version}</bundle>
+        <bundle start-level="75" start="false">mvn:org.apache.unomi/unomi-common/${project.version}</bundle>
+        <bundle start-level="75" start="false">mvn:org.apache.unomi/unomi-metrics/${project.version}</bundle>
         <bundle start-level="75" start="false">mvn:org.apache.unomi/unomi-persistence-spi/${project.version}</bundle>
         <bundle start-level="76" start="false">mvn:org.apache.unomi/unomi-persistence-elasticsearch-core/${project.version}</bundle>
         <bundle start-level="77" start="false">mvn:org.apache.unomi/unomi-services/${project.version}</bundle>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/README.md
----------------------------------------------------------------------
diff --git a/metrics/README.md b/metrics/README.md
new file mode 100644
index 0000000..56e7d0e
--- /dev/null
+++ b/metrics/README.md
@@ -0,0 +1,38 @@
+<!--
+  ~ 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.
+  -->
+
+Metrics
+=======
+
+This project makes it easy to add metrics to your project, which will then give you the following features:
+
+- Count the number a of time a metric was executed
+- Get the accumulated time some measured code took
+- See the call stacks to a metric (deactivated by default in order to minimize performance impact)
+
+Adding metrics to your project : 
+
+In order to minimize the impact of metrics in your project, we recommend you use the following pattern to integrate 
+metrics:
+
+        long startTime = System.currentTimeMillis();
+        // code to be mesured should be here.
+        if (metricsService != null && metricsService.isActivated()) {
+            metricsService.updateTimer(this.getClass().getName() + YOUR_METRIC_NAME, startTime);
+        }
+        
+This will handle all the proper cases of metrics being deactivated as well as even the service not being available.        
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/pom.xml
----------------------------------------------------------------------
diff --git a/metrics/pom.xml b/metrics/pom.xml
new file mode 100644
index 0000000..bf4a708
--- /dev/null
+++ b/metrics/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.unomi</groupId>
+        <artifactId>unomi-root</artifactId>
+        <version>1.3.0-incubating-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>unomi-metrics</artifactId>
+    <name>Apache Unomi :: Metrics</name>
+    <description>Apache Unomi Context Server Metrics</description>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+            <version>3.0.8</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.table</artifactId>
+            <version>3.0.8</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-persistence-spi</artifactId>
+            <version>1.3.0-incubating-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-common</artifactId>
+            <version>1.3.0-incubating-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Unit tests -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.6.6</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/CalleeCount.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/CalleeCount.java b/metrics/src/main/java/org/apache/unomi/metrics/CalleeCount.java
new file mode 100644
index 0000000..3552b12
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/CalleeCount.java
@@ -0,0 +1,29 @@
+/*
+ * 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.unomi.metrics;
+
+import java.util.List;
+
+public interface CalleeCount {
+
+    String getHash();
+    List<String> getCallee();
+    long getCount();
+    long incCount();
+    long getTotalTime();
+    long addTime(long time);
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/Metric.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/Metric.java b/metrics/src/main/java/org/apache/unomi/metrics/Metric.java
new file mode 100644
index 0000000..556fb20
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/Metric.java
@@ -0,0 +1,33 @@
+/*
+ * 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.unomi.metrics;
+
+import java.util.Map;
+
+public interface Metric {
+
+    String getName();
+
+    long getTotalCount();
+    long incTotalCount();
+
+    long getTotalTime();
+    long addTotalTime(long time);
+
+    Map<String,CalleeCount> getCalleeCounts();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/MetricAdapter.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/MetricAdapter.java b/metrics/src/main/java/org/apache/unomi/metrics/MetricAdapter.java
new file mode 100644
index 0000000..64ffeee
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/MetricAdapter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.unomi.metrics;
+
+/**
+ * Utility method to run code inside a timer.
+ * @param <T> the type to be used as a result type for the method.
+ */
+public abstract class MetricAdapter<T> {
+
+    private MetricsService metricsService;
+    private String timerName;
+
+    public abstract T execute(Object... args) throws Exception;
+
+    public MetricAdapter(MetricsService metricsService, String timerName) {
+        this.metricsService = metricsService;
+        this.timerName = timerName;
+    }
+
+    public T runWithTimer(Object... args) throws Exception {
+        long startTime = System.currentTimeMillis();
+        try {
+            return execute(args);
+        } finally {
+            if (metricsService != null && metricsService.isActivated()) {
+                metricsService.updateTimer(timerName, startTime);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/MetricsService.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/MetricsService.java b/metrics/src/main/java/org/apache/unomi/metrics/MetricsService.java
new file mode 100644
index 0000000..5fd0d3c
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/MetricsService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.unomi.metrics;
+
+import java.util.Map;
+
+public interface MetricsService {
+
+    void setActivated(boolean activated);
+
+    boolean isActivated();
+
+    Map<String,Boolean> getCalleesStatus();
+
+    void setCalleeActivated(String timerName, boolean activated);
+
+    boolean isCalleeActivated(String timerName);
+
+    Map<String,Metric> getMetrics();
+
+    void resetMetrics();
+
+    void updateTimer(String timerName, long startTime);
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/ActivateCommand.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/ActivateCommand.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/ActivateCommand.java
new file mode 100644
index 0000000..ad17c24
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/ActivateCommand.java
@@ -0,0 +1,33 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.unomi.metrics.MetricsService;
+
+@Command(scope = "metrics", name = "activate", description = "This will activate the metrics system.")
+public class ActivateCommand extends MetricsCommandSupport {
+
+    @Override
+    protected Object doExecute() throws Exception {
+        metricsService.setActivated(true);
+        System.out.println("Metrics activated successfully.");
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/CalleeStatusCommand.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/CalleeStatusCommand.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/CalleeStatusCommand.java
new file mode 100644
index 0000000..5aec628
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/CalleeStatusCommand.java
@@ -0,0 +1,64 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.table.Row;
+import org.apache.karaf.shell.table.ShellTable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Command(scope = "metrics", name = "callee-status", description = "This command will list all the callee configurations, or change the callee status of a specific metric")
+public class CalleeStatusCommand extends MetricsCommandSupport {
+
+    @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
+    boolean noFormat;
+
+    @Argument(index = 0, name = "metricName", description = "The identifier for the metric", required = false, multiValued = false)
+    String metricName;
+
+    @Argument(index = 1, name = "status", description = "The new status for the metric's callee tracing", required = false, multiValued = false)
+    Boolean metricStatus;
+
+    @Override
+    protected Object doExecute() throws Exception {
+        if (metricName != null && metricStatus != null) {
+            metricsService.setCalleeActivated(metricName, metricStatus);
+            System.out.println("Metric callees " + metricName + " set to " + metricStatus);
+            return null;
+        }
+        Map<String,Boolean> calleesStatus = metricsService.getCalleesStatus();
+        ShellTable shellTable = new ShellTable();
+        shellTable.column("Metric");
+        shellTable.column("Activated");
+
+        for (Map.Entry<String,Boolean> calleeStatusEntry : calleesStatus.entrySet()) {
+            List<Object> rowData = new ArrayList<Object>();
+            rowData.add(calleeStatusEntry.getKey());
+            rowData.add(calleeStatusEntry.getValue() ? "x" : "");
+            Row row = shellTable.addRow();
+            row.addContent(rowData);
+        }
+
+        shellTable.print(System.out, !noFormat);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/DeactivateCommand.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/DeactivateCommand.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/DeactivateCommand.java
new file mode 100644
index 0000000..d16eac0
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/DeactivateCommand.java
@@ -0,0 +1,33 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.unomi.metrics.MetricsService;
+
+@Command(scope = "metrics", name = "deactivate", description = "This will de-activate the metrics system.")
+public class DeactivateCommand extends MetricsCommandSupport {
+
+    @Override
+    protected Object doExecute() throws Exception {
+        metricsService.setActivated(false);
+        System.out.println("Metrics de-activated successfully.");
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/ListCommand.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/ListCommand.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/ListCommand.java
new file mode 100644
index 0000000..861bb3c
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/ListCommand.java
@@ -0,0 +1,87 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.karaf.shell.table.Row;
+import org.apache.karaf.shell.table.ShellTable;
+import org.apache.unomi.common.DataTable;
+import org.apache.unomi.metrics.Metric;
+import org.apache.unomi.metrics.MetricsService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Command(scope = "metrics", name = "list", description = "This will list all the metrics")
+public class ListCommand extends MetricsCommandSupport {
+
+    @Option(name = "--no-format", description = "Disable table rendered output", required = false, multiValued = false)
+    boolean noFormat;
+
+    @Option(name = "--csv", description = "Output table in CSV format", required = false, multiValued = false)
+    boolean csv;
+
+    @Override
+    protected Object doExecute() throws Exception {
+
+        System.out.println("Metrics service status: " + (metricsService.isActivated() ? "active" : "inactive"));
+
+        Map<String,Metric> metrics = metricsService.getMetrics();
+
+        String[] headers = {
+                "Name",
+                "Callees",
+                "Count",
+                "Time [ms]"
+        };
+
+        DataTable dataTable = new DataTable();
+        for (Map.Entry<String,Metric> metricEntry : metrics.entrySet()) {
+            Metric metric = metricEntry.getValue();
+            dataTable.addRow(metric.getName(), metric.getCalleeCounts().size(), metric.getTotalCount(), metric.getTotalTime());
+        }
+        dataTable.sort(new DataTable.SortCriteria(3, DataTable.SortOrder.DESCENDING),
+                new DataTable.SortCriteria(2, DataTable.SortOrder.DESCENDING),
+                new DataTable.SortCriteria(0, DataTable.SortOrder.ASCENDING));
+
+        if (csv) {
+            System.out.println(dataTable.toCSV(headers));
+            return null;
+        }
+
+        ShellTable shellTable = new ShellTable();
+        shellTable.column("Name");
+        shellTable.column("Callees");
+        shellTable.column("Count");
+        shellTable.column("Time [ms]");
+
+        for (DataTable.Row dataTableRow :dataTable.getRows()) {
+            List<Object> rowData = new ArrayList<Object>();
+            rowData.add(dataTableRow.getData(0));
+            rowData.add(dataTableRow.getData(1));
+            rowData.add(dataTableRow.getData(2));
+            rowData.add(dataTableRow.getData(3));
+            Row row = shellTable.addRow();
+            row.addContent(rowData);
+        }
+        shellTable.print(System.out, !noFormat);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/MetricsCommandSupport.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/MetricsCommandSupport.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/MetricsCommandSupport.java
new file mode 100644
index 0000000..6706100
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/MetricsCommandSupport.java
@@ -0,0 +1,28 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.unomi.metrics.MetricsService;
+
+public abstract class MetricsCommandSupport extends OsgiCommandSupport {
+    MetricsService metricsService;
+
+    public void setMetricsService(MetricsService metricsService) {
+        this.metricsService = metricsService;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/ResetCommand.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/ResetCommand.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/ResetCommand.java
new file mode 100644
index 0000000..3d3f40c
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/ResetCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.console.OsgiCommandSupport;
+import org.apache.unomi.metrics.MetricsService;
+
+@Command(scope = "metrics", name = "reset", description = "This will reset all the metrics to zero.")
+public class ResetCommand extends MetricsCommandSupport {
+
+    @Override
+    protected Object doExecute() throws Exception {
+        metricsService.resetMetrics();
+        System.out.println("Metrics reset successfully.");
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/commands/ViewCommand.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/commands/ViewCommand.java b/metrics/src/main/java/org/apache/unomi/metrics/commands/ViewCommand.java
new file mode 100644
index 0000000..295f1dc
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/commands/ViewCommand.java
@@ -0,0 +1,47 @@
+/*
+ * 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.unomi.metrics.commands;
+
+import com.fasterxml.jackson.core.util.DefaultIndenter;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.unomi.metrics.Metric;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+
+@Command(scope = "metrics", name = "view", description = "This will display all the data for a single metric ")
+public class ViewCommand extends MetricsCommandSupport{
+
+    @Argument(index = 0, name = "metricName", description = "The identifier for the metric", required = true, multiValued = false)
+    String metricName;
+
+    @Override
+    protected Object doExecute() throws Exception {
+        Metric metric = metricsService.getMetrics().get(metricName);
+        if (metric == null) {
+            System.out.println("Couldn't find a metric with name=" + metricName);
+            return null;
+        }
+        // by default pretty printer will use spaces between array values, we change this to linefeeds to make
+        // the callee values easier to read.
+        DefaultPrettyPrinter defaultPrettyPrinter = new DefaultPrettyPrinter();
+        defaultPrettyPrinter = defaultPrettyPrinter.withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
+        String jsonMetric = CustomObjectMapper.getObjectMapper().writer(defaultPrettyPrinter).writeValueAsString(metric);
+        System.out.println(jsonMetric);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/internal/CalleeCountImpl.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/internal/CalleeCountImpl.java b/metrics/src/main/java/org/apache/unomi/metrics/internal/CalleeCountImpl.java
new file mode 100644
index 0000000..14f6094
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/internal/CalleeCountImpl.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.unomi.metrics.internal;
+
+import org.apache.unomi.metrics.CalleeCount;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class CalleeCountImpl implements CalleeCount {
+
+    private String hash;
+    private List<String> callee;
+    private AtomicLong count = new AtomicLong();
+    private AtomicLong totalTime = new AtomicLong();
+
+    public CalleeCountImpl(String hash, List<String> callee) {
+        this.hash = hash;
+        this.callee = callee;
+    }
+
+    @Override
+    public String getHash() {
+        return hash;
+    }
+
+    @Override
+    public List<String> getCallee() {
+        return callee;
+    }
+
+    @Override
+    public long getCount() {
+        return count.get();
+    }
+
+    @Override
+    public long incCount() {
+        return count.incrementAndGet();
+    }
+
+    @Override
+    public long getTotalTime() {
+        return totalTime.get();
+    }
+
+    @Override
+    public long addTime(long time) {
+        return totalTime.addAndGet(time);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricImpl.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricImpl.java b/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricImpl.java
new file mode 100644
index 0000000..76d6bf5
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricImpl.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.unomi.metrics.internal;
+
+import org.apache.unomi.metrics.CalleeCount;
+import org.apache.unomi.metrics.Metric;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class MetricImpl implements Metric {
+
+    private String name;
+    private long totalCount = 0L;
+    private long totalTime = 0L;
+    private Map<String,CalleeCount> calleeCounts = new ConcurrentHashMap<String, CalleeCount>();
+
+    public MetricImpl(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public long getTotalCount() {
+        return totalCount;
+    }
+
+    @Override
+    public long incTotalCount() {
+        return totalCount++;
+    }
+
+    @Override
+    public long getTotalTime() {
+        return totalTime;
+    }
+
+    @Override
+    public long addTotalTime(long time) {
+        return totalTime += time;
+    }
+
+    @Override
+    public Map<String, CalleeCount> getCalleeCounts() {
+        return calleeCounts;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricsServiceImpl.java
----------------------------------------------------------------------
diff --git a/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricsServiceImpl.java b/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricsServiceImpl.java
new file mode 100644
index 0000000..ea16ad3
--- /dev/null
+++ b/metrics/src/main/java/org/apache/unomi/metrics/internal/MetricsServiceImpl.java
@@ -0,0 +1,118 @@
+/*
+ * 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.unomi.metrics.internal;
+
+import org.apache.unomi.metrics.CalleeCount;
+import org.apache.unomi.metrics.Metric;
+import org.apache.unomi.metrics.MetricsService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MetricsServiceImpl implements MetricsService {
+
+    boolean activated = false;
+    Map<String,Metric> metrics = new ConcurrentHashMap<String,Metric>();
+    Map<String,Boolean> calleesStatus = new ConcurrentHashMap<>();
+
+    public void setActivated(boolean activated) {
+        this.activated = activated;
+        if (!activated) {
+            metrics.clear();
+        }
+    }
+
+    @Override
+    public boolean isActivated() {
+        return activated;
+    }
+
+    @Override
+    public Map<String, Metric> getMetrics() {
+        return metrics;
+    }
+
+    @Override
+    public void resetMetrics() {
+        metrics.clear();
+    }
+
+    public void updateTimer(String timerName, long startTime) {
+        if (!activated) {
+            return;
+        }
+        long totalTime = System.currentTimeMillis() - startTime;
+        Metric metric = metrics.get(timerName);
+        if (metric == null) {
+            metric = new MetricImpl(timerName);
+            metrics.put(timerName, metric);
+        }
+        metric.incTotalCount();
+        metric.addTotalTime(totalTime);
+        if (isCalleeActivated(timerName)) {
+            StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();
+            List<String> stackTraces = new ArrayList<String>();
+            if (stackTraceElements != null && stackTraceElements.length > 2) {
+                // we start at index 2 to remove the internal
+                for (int i = 2; i < stackTraceElements.length; i++) {
+                    stackTraces.add(String.valueOf(stackTraceElements[i]));
+                }
+                String stackTraceHash = Integer.toString(stackTraces.hashCode());
+                CalleeCount calleeCount = metric.getCalleeCounts().get(stackTraceHash);
+                if (calleeCount == null) {
+                    calleeCount = new CalleeCountImpl(stackTraceHash, stackTraces);
+                    calleeCount.incCount();
+                    calleeCount.addTime(totalTime);
+                    metric.getCalleeCounts().put(stackTraceHash, calleeCount);
+                } else {
+                    calleeCount.incCount();
+                    calleeCount.addTime(totalTime);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Map<String, Boolean> getCalleesStatus() {
+        return calleesStatus;
+    }
+
+    @Override
+    public void setCalleeActivated(String timerName, boolean activated) {
+        if (!activated) {
+            if (calleesStatus.containsKey(timerName)) {
+                calleesStatus.remove(timerName);
+            }
+        } else {
+            calleesStatus.put(timerName, true);
+        }
+    }
+
+    @Override
+    public boolean isCalleeActivated(String timerName) {
+        if (calleesStatus.containsKey(timerName)) {
+            return calleesStatus.get(timerName);
+        }
+        if (calleesStatus.containsKey("*")) {
+            return calleesStatus.get("*");
+        }
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --git a/metrics/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/metrics/src/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 0000000..7e3b58b
--- /dev/null
+++ b/metrics/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
+           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:shell="http://karaf.apache.org/xmlns/shell/v1.1.0"
+           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
+  http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0 http://aries.apache.org/schemas/blueprint-cm/blueprint-cm-1.1.0.xsd">
+
+    <cm:property-placeholder persistent-id="org.apache.unomi.metrics"
+                             update-strategy="reload" placeholder-prefix="${metrics.">
+        <cm:default-properties>
+            <cm:property name="activated" value="false"/>
+        </cm:default-properties>
+    </cm:property-placeholder>
+
+    <bean id="metricsServiceImpl" class="org.apache.unomi.metrics.internal.MetricsServiceImpl">
+        <property name="activated" value="${metrics.activated}"/>
+    </bean>
+
+    <service id="metricsService" ref="metricsServiceImpl">
+        <interfaces>
+            <value>org.apache.unomi.metrics.MetricsService</value>
+        </interfaces>
+    </service>
+
+    <shell:command-bundle>
+        <shell:command>
+            <shell:action class="org.apache.unomi.metrics.commands.ListCommand">
+                <shell:property name="metricsService" ref="metricsServiceImpl" />
+            </shell:action>
+        </shell:command>
+        <shell:command>
+            <shell:action class="org.apache.unomi.metrics.commands.ViewCommand">
+                <shell:property name="metricsService" ref="metricsServiceImpl" />
+            </shell:action>
+        </shell:command>
+        <shell:command>
+            <shell:action class="org.apache.unomi.metrics.commands.ResetCommand">
+                <shell:property name="metricsService" ref="metricsServiceImpl" />
+            </shell:action>
+        </shell:command>
+        <shell:command>
+            <shell:action class="org.apache.unomi.metrics.commands.ActivateCommand">
+                <shell:property name="metricsService" ref="metricsServiceImpl" />
+            </shell:action>
+        </shell:command>
+        <shell:command>
+            <shell:action class="org.apache.unomi.metrics.commands.DeactivateCommand">
+                <shell:property name="metricsService" ref="metricsServiceImpl" />
+            </shell:action>
+        </shell:command>
+        <shell:command>
+            <shell:action class="org.apache.unomi.metrics.commands.CalleeStatusCommand">
+                <shell:property name="metricsService" ref="metricsServiceImpl" />
+            </shell:action>
+        </shell:command>
+    </shell:command-bundle>
+
+
+</blueprint>

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/metrics/src/test/java/org/apache/unomi/metrics/internal/MetricsServiceTest.java
----------------------------------------------------------------------
diff --git a/metrics/src/test/java/org/apache/unomi/metrics/internal/MetricsServiceTest.java b/metrics/src/test/java/org/apache/unomi/metrics/internal/MetricsServiceTest.java
new file mode 100644
index 0000000..b293599
--- /dev/null
+++ b/metrics/src/test/java/org/apache/unomi/metrics/internal/MetricsServiceTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.unomi.metrics.internal;
+
+import org.apache.unomi.metrics.MetricsService;
+import org.junit.Test;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+public class MetricsServiceTest {
+
+    class Worker implements Callable<BigInteger> {
+
+        MetricsService metricsService;
+        String workerName;
+        int nbLoops = 1000;
+
+        public Worker(MetricsService metricsService, String workerName, int nbLoops) {
+            this.metricsService = metricsService;
+            this.workerName = workerName;
+            this.nbLoops = nbLoops;
+        }
+
+        @Override
+        public BigInteger call() {
+            long startTime = System.currentTimeMillis();
+            BigInteger n1=BigInteger.ZERO,n2=BigInteger.ONE,n3=BigInteger.ZERO;
+            for (int i = 0; i < nbLoops; i++) {
+                n3 = n1.add(n2);
+                n1 = n2;
+                n2 = n3;
+            }
+            if (metricsService != null) {
+                metricsService.updateTimer(workerName, startTime);
+            }
+            return n3;
+        }
+    }
+
+    @Test
+    public void testMetricsImpact() throws InterruptedException {
+        int workerCount = 100000;
+        System.out.println("Free memory=" + humanReadableByteCount(Runtime.getRuntime().freeMemory(), false));
+        System.out.println("Testing with metrics deactivated...");
+        ExecutorService executorService = Executors.newFixedThreadPool(1000);
+        List<Callable<BigInteger>> todo = new ArrayList<Callable<BigInteger>>(workerCount);
+        MetricsServiceImpl metricsService = new MetricsServiceImpl();
+        metricsService.setActivated(false);
+        Random random = new Random();
+        long withoutMetricsStartTime = System.currentTimeMillis();
+        for (int i = 0; i < workerCount; i++) {
+            todo.add(new Worker(null, "worker-" + i, 1000+ random.nextInt(1000)));
+        }
+        List<Future<BigInteger>> answers = executorService.invokeAll(todo);
+        long withoutMetricsTotalTime = System.currentTimeMillis() - withoutMetricsStartTime;
+        System.out.println("Total time without metrics=" + withoutMetricsTotalTime + "ms");
+        assertEquals("Metrics should be empty", 0, metricsService.getMetrics().size());
+        System.out.println("Free memory=" + humanReadableByteCount(Runtime.getRuntime().freeMemory(), false));
+
+        System.out.println("Testing with metrics activated (but no callees)...");
+        todo.clear();
+        metricsService.setActivated(true);
+        assertEquals("Callees should be completely empty", metricsService.getCalleesStatus().size(), 0);
+        long withMetricsStartTime = System.currentTimeMillis();
+        for (int i = 0; i < workerCount; i++) {
+            todo.add(new Worker(metricsService, "worker-" + i, 1000+ random.nextInt(1000)));
+        }
+        answers = executorService.invokeAll(todo);
+        long withMetricsTotalTime = System.currentTimeMillis() - withMetricsStartTime;
+        System.out.println("Total time with metrics (no callees) =" + withMetricsTotalTime + "ms");
+        assertEquals("Metrics count is not correct", workerCount, metricsService.getMetrics().size());
+        System.out.println("Free memory=" + humanReadableByteCount(Runtime.getRuntime().freeMemory(), false));
+
+        System.out.println("Testing with metrics activated (all callees activated)...");
+        todo.clear();
+        metricsService.setActivated(true);
+        metricsService.setCalleeActivated("*", true);
+        assertNotEquals("Callees should not be completely empty", metricsService.getCalleesStatus().size(), 0);
+        long withMetricsAndCalleesStartTime = System.currentTimeMillis();
+        for (int i = 0; i < workerCount; i++) {
+            todo.add(new Worker(metricsService, "worker-" + i, 1000+ random.nextInt(1000)));
+        }
+        answers = executorService.invokeAll(todo);
+        long withMetricsAndCalleesTotalTime = System.currentTimeMillis() - withMetricsAndCalleesStartTime;
+        System.out.println("Total time with metrics (with callees)=" + withMetricsAndCalleesTotalTime + "ms");
+        assertEquals("Metrics count is not correct", workerCount, metricsService.getMetrics().size());
+        System.out.println("Free memory=" + humanReadableByteCount(Runtime.getRuntime().freeMemory(), false));
+    }
+
+    @Test
+    public void testStackTraceGenerationSpeed() {
+        long startWithException = System.currentTimeMillis();
+        for (long i = 0; i < 100000; i++) {
+            String stackTrace = Arrays.toString(new Exception().getStackTrace());
+            int stackTraceHash = stackTrace.hashCode();
+        }
+        System.out.println("Total time using exception and getStackTrace = " + (System.currentTimeMillis()
+                - startWithException) + "ms.");
+
+        long startWithThrowable = System.currentTimeMillis();
+        for (long i = 0; i < 100000; i++) {
+            String stackTrace = Arrays.toString(new Throwable().getStackTrace());
+            int stackTraceHash = stackTrace.hashCode();
+        }
+        System.out.println("Total time using throwable and getStackTrace = " + (System.currentTimeMillis()
+                - startWithThrowable) + "ms.");
+
+        long startWithThread = System.currentTimeMillis();
+        for (long i = 0; i < 100000; i++) {
+            String stackTtrace = Arrays.toString(Thread.currentThread().getStackTrace());
+            int stackTraceHash = stackTtrace.hashCode();
+        }
+        System.out.println("Total time using current thread and getStackTrace = " + (System.currentTimeMillis()
+                - startWithThread) + "ms.");
+
+        System.out.println("Free memory=" + humanReadableByteCount(Runtime.getRuntime().freeMemory(), false));
+
+    }
+
+    public static String humanReadableByteCount(long bytes, boolean si) {
+        int unit = si ? 1000 : 1024;
+        if (bytes < unit) return bytes + " B";
+        int exp = (int) (Math.log(bytes) / Math.log(unit));
+        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
+        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-unomi/blob/a1574bf5/persistence-elasticsearch/core/pom.xml
----------------------------------------------------------------------
diff --git a/persistence-elasticsearch/core/pom.xml b/persistence-elasticsearch/core/pom.xml
index 198392f..143f7bf 100644
--- a/persistence-elasticsearch/core/pom.xml
+++ b/persistence-elasticsearch/core/pom.xml
@@ -133,6 +133,13 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.unomi</groupId>
+            <artifactId>unomi-metrics</artifactId>
+            <version>1.3.0-incubating-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>