adaptive-monitoring-framework

Details

diff --git a/app-example/pom.xml b/app-example/pom.xml
index ef57119..e08c131 100644
--- a/app-example/pom.xml
+++ b/app-example/pom.xml
@@ -219,7 +219,7 @@
         <dependency>
             <groupId>br.ufrgs.inf.prosoft</groupId>
             <artifactId>tigris</artifactId>
-            <version>0.10.0-SNAPSHOT</version>
+            <version>0.11.0-SNAPSHOT</version>
         </dependency>
     </dependencies>
 
diff --git a/app-example/src/main/resources/logback.xml b/app-example/src/main/resources/logback.xml
index 3f01712..b2ab98a 100644
--- a/app-example/src/main/resources/logback.xml
+++ b/app-example/src/main/resources/logback.xml
@@ -7,14 +7,15 @@
         <resetJUL>true</resetJUL>
     </contextListener>
 
-    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
-        <encoder>
-            <pattern>%-5level %logger{0} - %msg%n</pattern>
-        </encoder>
-    </appender>
+<!--    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">-->
+<!--        <encoder>-->
+<!--            <pattern>%-5level %logger{0} - %msg%n</pattern>-->
+<!--        </encoder>-->
+<!--    </appender>-->
 
     <logger name="org.hibernate" level="OFF"/>
     <logger name="org.springframework.samples.petclinic" level="DEBUG"/>
+    <logger name="br.ufrgs.inf.prosoft.tigris" level="DEBUG"/>
 
     <root level="info">
         <appender-ref ref="console"/>

tigris/pom.xml 6(+3 -3)

diff --git a/tigris/pom.xml b/tigris/pom.xml
index 23b475d..a972e98 100644
--- a/tigris/pom.xml
+++ b/tigris/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>br.ufrgs.inf.prosoft</groupId>
     <artifactId>tigris</artifactId>
-    <version>0.10.0-SNAPSHOT</version>
+    <version>0.11.0-SNAPSHOT</version>
 
     <properties>
         <aspectj.version>1.8.9</aspectj.version>
@@ -159,14 +159,14 @@
             <version>3.3.1</version>
         </dependency>
 
+        <!-- In order to use the library below, this should be run the first time locally: -->
+        <!-- mvn install:install-file -Dfile=lib\bullwinkle.jar -DgroupId=ca.uqac.lif -DartifactId=bullwinkle -Dversion=1.4.5 -Dpackaging=jar-->
         <dependency>
             <groupId>ca.uqac.lif</groupId>
             <artifactId>bullwinkle</artifactId>
             <version>1.4.5</version>
         </dependency>
 
-<!--        mvn install:install-file -Dfile=lib\bullwinkle.jar -DgroupId=ca.uqac.lif -DartifactId=bullwinkle -Dversion=1.4.5 -Dpackaging=jar-->
-
     </dependencies>
 
     <build>
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/configuration/annotation/TigrisConfiguration.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/configuration/annotation/TigrisConfiguration.java
index 31c7e28..c59e982 100644
--- a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/configuration/annotation/TigrisConfiguration.java
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/configuration/annotation/TigrisConfiguration.java
@@ -45,5 +45,5 @@ public @interface TigrisConfiguration {
      *
      * @return the int
      */
-    int cycleTimeInSeconds() default 3600; //3600 seconds = 1h
+    long cycleTimeInMilliseconds() default 3600000; //3600000 milliseconds = 1h
 }
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/metrics/StaticMetrics.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/metrics/StaticMetrics.java
index 1750b42..222afac 100644
--- a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/metrics/StaticMetrics.java
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/metrics/StaticMetrics.java
@@ -1,6 +1,9 @@
 package br.ufrgs.inf.prosoft.tigris.metrics;
 
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
@@ -12,6 +15,8 @@ import java.util.Map;
  */
 public class StaticMetrics {
 
+    static Logger logger = LoggerFactory.getLogger(StaticMetrics.class);
+
     private Map<String, StaticMetric> metrics;
 
     /**
@@ -53,7 +58,7 @@ public class StaticMetrics {
                     metrics.put(str[1], new StaticMetric(Long.valueOf(str[2]), Long.valueOf(str[3]), Long.valueOf(str[4])));
                 }
             } catch (IOException ex) {
-                throw new RuntimeException(ex);
+                logger.warn("Not able to open file {} with static metrics. Do not use these metrics for filtering.", filename);
             }
         }
     }
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/monitoring/aspects/TigrisCoordinator.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/monitoring/aspects/TigrisCoordinator.java
index 9716a08..e388f8f 100644
--- a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/monitoring/aspects/TigrisCoordinator.java
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/monitoring/aspects/TigrisCoordinator.java
@@ -25,7 +25,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -93,7 +92,8 @@ public class TigrisCoordinator implements Runnable {
         deniedPattern = Pattern.compile(componentScanConfig.denied());
         logger.info("@TigrisConfiguration will trace and cache methods into {} and deny {} package.", allowedPattern.pattern(), deniedPattern.pattern());
 
-        sampling = new Sampling(tigrisConfiguration.samplingPercentage(), tigrisConfiguration.cycleTimeInSeconds());
+        sampling = new Sampling(tigrisConfiguration.samplingPercentage(), tigrisConfiguration.cycleTimeInMilliseconds());
+        //TODO when to run it?
         samplingAdaptationExecutor.scheduleWithFixedDelay(
                 sampling, 120000, 450000, TimeUnit.MILLISECONDS);
 
@@ -124,13 +124,11 @@ public class TigrisCoordinator implements Runnable {
         }
 
         try {
-
             customMonitoringClass = ConfigurationUtils.getAvailableCustomMonitoringClass();
             if (customMonitoringClass != null) {
                 customMonitoring = (CustomMonitoring) customMonitoringClass.newInstance();
+                customMonitoring.initialize(repository);
             }
-
-            customMonitoring.initialize(repository);
         } catch (ConfigurationException e) {
             logger.info("AdaptiveCaching not found, disabling...");
         } catch (IllegalAccessException e) {
@@ -143,13 +141,14 @@ public class TigrisCoordinator implements Runnable {
     public boolean isAllowed(ProceedingJoinPoint joinPoint) {
         MethodSignature signature = (MethodSignature) joinPoint.getSignature();
         return
-                isEnabled()
-                        && !signature.getName().contains("br.ufrgs.inf.prosoft")
-                        && allowedPattern.matcher(signature.getMethod().getDeclaringClass().getPackage().getName()).matches()
-                        && !deniedPattern.matcher(signature.getMethod().getDeclaringClass().getPackage().getName()).matches();
+            isEnabled()
+            && !signature.getName().contains("br.ufrgs.inf.prosoft")
+            && allowedPattern.matcher(signature.getMethod().getDeclaringClass().getPackage().getName()).matches()
+            && !deniedPattern.matcher(signature.getMethod().getDeclaringClass().getPackage().getName()).matches();
     }
 
     public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
+        long monitoringStartTime = currentTimeMillis();
         String signature = joinPoint.getSignature().toString();
         boolean detailedTrace = (!coarseMonitoringEnabled ||
                 (coarseMonitoringEnabled && allowedFineGrained
@@ -195,17 +194,14 @@ public class TigrisCoordinator implements Runnable {
         }
 
         //trace only allowed by lightweight metrics
-        logger.debug("Is coarse monitoring enable? {}", coarseMonitoringEnabled);
-        logger.debug("Is {} in the allowed list? {}", signature, detailedTrace);
-        boolean shouldSample = sampling.simpleSamplingDecision();
-        logger.debug("Should sample it? {} ", shouldSample);
+//        boolean shouldSample = sampling.simpleSamplingDecision();
+        boolean shouldSample = sampling.samplingDecision(granularity);
         if (coarseMonitoringEnabled
                 && detailedTrace
                 && shouldSample) {
             logger.debug("New trace: " + signature);
 
-            //we do not cache null returns, but we trace them
-            //maybe the method can sometimes return null... so there is not verification here
+            //we do trace null returns - maybe the method can sometimes return null... so there is not verification here
             LogTrace logTrace = new LogTrace();
             logTrace.setStartTime(startTime);
             logTrace.setEndTime(endTime);
@@ -216,6 +212,7 @@ public class TigrisCoordinator implements Runnable {
             try {
                 repository.save(logTrace);
                 logger.debug("New trace entry: " + logTrace);
+                sampling.addSampledItem(granularity, currentTimeMillis() - monitoringStartTime);
             } catch (Exception e) {
                 logger.debug("Couldn't trace " + signature + " due to: " + e.getMessage());
             }
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Apdex.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Apdex.java
new file mode 100644
index 0000000..fc10a8f
--- /dev/null
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Apdex.java
@@ -0,0 +1,26 @@
+package br.ufrgs.inf.prosoft.tigris.sampling;
+
+public class Apdex {
+
+    private long satisfied;
+    private long tolerated;
+    private long n;
+
+    public Apdex(long satisfied, long tolerated, long n) {
+        this.satisfied = satisfied;
+        this.tolerated = tolerated;
+        this.n = n;
+    }
+
+    public long getSatisfied() {
+        return satisfied;
+    }
+
+    public long getTolerated() {
+        return tolerated;
+    }
+
+    public long getN() {
+        return n;
+    }
+}
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/PerformanceBaselineDataSet.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/PerformanceBaselineDataSet.java
index a5492e6..f0029df 100644
--- a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/PerformanceBaselineDataSet.java
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/PerformanceBaselineDataSet.java
@@ -1,14 +1,16 @@
 package br.ufrgs.inf.prosoft.tigris.sampling;
 
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
 import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
 
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 public class PerformanceBaselineDataSet {
 
-    private Map<Granularity, SummaryStatistics> granularityBaseline = new HashMap<>();
+    private Map<Granularity, SummaryStatistics> granularityBaseline = new ConcurrentHashMap<>();
+    private SummaryStatistics overallBaseline = new SummaryStatistics();
     private long n;
 
     public void addItem(Granularity item, long executionTime) {
@@ -19,6 +21,57 @@ public class PerformanceBaselineDataSet {
         statistics.addValue(executionTime);
         granularityBaseline.put(item, statistics);
         n++;
+
+        overallBaseline.addValue(executionTime);
+    }
+
+    public Apdex getApdexResultsPerEvent(Map<Granularity, DescriptiveStatistics> sampledDataSet){
+        long satisfied = 0, tolerated = 0, n = 0;
+        for (Granularity granularity : sampledDataSet.keySet()){
+            SummaryStatistics stats = granularityBaseline.get(granularity);
+            double meanPlusStd = stats.getMean() + stats.getStandardDeviation();
+            for (double value: sampledDataSet.get(granularity).getValues()) {
+                if (value < meanPlusStd){
+                    satisfied++;
+                    continue;
+                }
+                if (value > meanPlusStd &&
+                    value < stats.getMean() + (2 * stats.getStandardDeviation())) {
+                    tolerated++;
+                    continue;
+                }
+                n++;
+            }
+        }
+        return new Apdex(satisfied, tolerated, n);
+    }
+
+    public Apdex getApdexResults(Map<Granularity, DescriptiveStatistics> sampledDataSet){
+        long satisfied = 0, tolerated = 0, n = 0;
+        for (Granularity granularity : sampledDataSet.keySet()){
+            double meanPlusStd = getOverallAvg() + getOverallStd();
+            for (double value: sampledDataSet.get(granularity).getValues()) {
+                if (value < meanPlusStd){
+                    satisfied++;
+                    continue;
+                }
+                if (value > meanPlusStd &&
+                        value < getOverallAvg() + (2 * getOverallStd())) {
+                    tolerated++;
+                    continue;
+                }
+                n++;
+            }
+        }
+        return new Apdex(satisfied, tolerated, n);
+    }
+
+    public double getOverallAvg() {
+        return overallBaseline.getMean();
+    }
+
+    public double getOverallStd() {
+        return overallBaseline.getStandardDeviation();
     }
 
     public long getTotalItems(){
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Sampling.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Sampling.java
index 1c4795a..25ee90a 100644
--- a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Sampling.java
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/sampling/Sampling.java
@@ -3,11 +3,14 @@ package br.ufrgs.inf.prosoft.tigris.sampling;
 import org.apache.commons.collections4.queue.CircularFifoQueue;
 import org.apache.commons.math3.distribution.BinomialDistribution;
 import org.apache.commons.math3.ml.neuralnet.sofm.util.ExponentialDecayFunction;
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
 import org.apache.commons.math3.stat.inference.TestUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Map;
 import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * The type Sampling decision.
@@ -16,11 +19,13 @@ public class Sampling implements Runnable {
 
     private boolean samplingEnabled = true;
     private boolean performanceBaselineEnabled = false;
-    private double samplingRate = 0.5; // in percentage, 0 to 1
+    private double samplingRate; // in percentage, 0 to 1
     private FrequencyDataSet population = new FrequencyDataSet(), sample = new FrequencyDataSet();
     private PerformanceBaselineDataSet performanceBaselineDataSet = new PerformanceBaselineDataSet();
+    private Map<Granularity, DescriptiveStatistics> sampledDataSet = new ConcurrentHashMap<>();
     private Queue<PerformanceBaselineDataSet> lastFourPerformanceBaselineDataSets = new CircularFifoQueue<>(4);
     private ExponentialDecayFunction decayingPrecision;
+    private long startTime;
 
     Logger logger = LoggerFactory.getLogger(Sampling.class);
 
@@ -31,10 +36,10 @@ public class Sampling implements Runnable {
      */
     private double z = 1.96, p = 0.5, e = 0.05;
 
-    public Sampling(double initialSamplingRate, int cycleLengthInSeconds) {
+    public Sampling(double initialSamplingRate, long cycleLengthInMilliseconds) {
         samplingRate = initialSamplingRate;
-        //3600 seconds = 1h
-        decayingPrecision = new ExponentialDecayFunction(100, 0.01, cycleLengthInSeconds);
+        decayingPrecision = new ExponentialDecayFunction(1, 0.00001, cycleLengthInMilliseconds);
+        startMonitoringCycle();
     }
 
     public boolean simpleSamplingDecision(){
@@ -44,13 +49,13 @@ public class Sampling implements Runnable {
     public boolean samplingDecision(Granularity granularity) {
         population.addItem(granularity);
 
-        if(performanceBaselineEnabled) {
+        if (performanceBaselineEnabled) {
             return false;
         }
 
         boolean decision = samplingEnabled
                 && simpleSamplingDecision() // sampling rate evaluation
-                && population.getProportion(granularity) > sample.getProportion(granularity); // sample has not enough items of that granularity compared to the population
+                && population.getProportion(granularity) >= sample.getProportion(granularity); // sample has not enough items of that granularity compared to the population
 
         if (decision)
             sample.addItem(granularity);
@@ -63,74 +68,84 @@ public class Sampling implements Runnable {
     }
 
     public boolean isReady() {
+        double decayingConfidenceFactor = decayingConfidenceFactor(getMonitoringCycleTime());
         return
                 // margin of error is lower than threshold
-                getSampleSizeErrorMargin() < e
+                getSampleSizeErrorMargin(z * decayingConfidenceFactor) < e
                 // the sample has the min sample size based on the population
-                && sample.getTotalItems() > getMinimumSampleSize()
+                && sample.getTotalItems() > getMinimumSampleSize(z * decayingConfidenceFactor)
                 // proportion test
-                && isSameProportion()
+                && isSameProportion(decayingConfidenceFactor)
                 // t-test
-                && tTestEvaluation();
+                && tTestEvaluation(decayingConfidenceFactor);
     }
 
-    private double decayingConfidence(int timeInSeconds){
-        return decayingPrecision.value(timeInSeconds);
+    private double decayingConfidenceFactor(long timeInMilliseconds){
+        return decayingPrecision.value(timeInMilliseconds);
     }
 
-    private boolean tTestEvaluation() {
+    private boolean tTestEvaluation(double decayingConfidenceFactor) {
         //To test the (one-sample t-test - compare with the population mean)
         // hypothesis sample mean = mu at the 95% level
         return TestUtils.tTest(population.getAsDescriptiveStatistics().getMean(),
                 sample.getAsDescriptiveStatistics(),
-                0.05);
+                0.05 * decayingConfidenceFactor);
     }
 
     //sample proportion is the same as population
-    public boolean isSameProportion() {
-        return population.getGranularities().stream().allMatch(granularity -> population.getProportion(granularity) == sample.getProportion(granularity));
+    public boolean isSameProportion(double decayingConfidenceFactor) {
+        return population.getGranularities().stream().allMatch(
+                granularity -> {
+                    double popProportion = population.getProportion(granularity);
+                    double samProportion = sample.getProportion(granularity);
+                    double error = popProportion - (popProportion * decayingConfidenceFactor);
+
+                    return samProportion <= popProportion + error &&
+                            samProportion >= popProportion - error;
+                });
     }
 
     /**
      * @return the minimum sample size for the population
      */
     public long getMinimumSampleSize() {
-        long n_inf = (long) ((Math.pow(z, 2) * p * (1 - p)) / Math.pow(e, 2));
-        return n_inf / (1 + ((n_inf - 1) / population.getTotalItems()));
+        return getMinimumSampleSize(population.getTotalItems());
     }
 
     public long getMinimumSampleSize(long n) {
-        long n_inf = (long) ((Math.pow(z, 2) * p * (1 - p)) / Math.pow(e, 2));
-        return n_inf / (1 + ((n_inf - 1) / n));
-    }
-
-    public double getSampleSizeErrorMargin() {
-        double e_n_inf = Math.sqrt((Math.pow(z, 2) * p * (1 - p)) / sample.getTotalItems());
-        return e_n_inf * Math.sqrt((population.getTotalItems() - sample.getTotalItems()) / (population.getTotalItems() - 1));
+        return getMinimumSampleSize(n, z);
     }
 
-    public void disable() {
-        samplingEnabled = false;
+    public long getMinimumSampleSize(double precision) {
+        return getMinimumSampleSize(population.getTotalItems(), precision);
     }
 
-    public void enable() {
-        samplingEnabled = true;
+    public long getMinimumSampleSize(long n, double precision) {
+        long n_inf = (long) ((Math.pow(precision, 2) * p * (1 - p)) / Math.pow(e, 2));
+        return n_inf / (1 + ((n_inf - 1) / n));
     }
 
-    public boolean isSamplingEnabled() {
-        return samplingEnabled;
+    public double getSampleSizeErrorMargin() {
+        return getSampleSizeErrorMargin(z);
     }
 
-    public double getSamplingRate() {
-        return samplingRate;
+    public double getSampleSizeErrorMargin(double precision) {
+        double e_n_inf = Math.sqrt((Math.pow(precision, 2) * p * (1 - p)) / sample.getTotalItems());
+        return e_n_inf * Math.sqrt((population.getTotalItems() - sample.getTotalItems()) / (population.getTotalItems() - 1));
     }
 
     public void startMonitoringCycle() {
+        startTime = System.currentTimeMillis();
+    }
 
+    public long getMonitoringCycleTime(){
+        return (System.currentTimeMillis() - startTime);
     }
 
     public void endMonitoringCycle() {
-
+        this.sampledDataSet = new ConcurrentHashMap<>();
+        releaseForAnalysis();
+        startMonitoringCycle();
     }
 
     public boolean shouldCollectPerformanceBaseline() {
@@ -138,21 +153,34 @@ public class Sampling implements Runnable {
     }
 
     public void adaptSamplingRate() {
-        //TODO
+        Apdex apdex = this.performanceBaselineDataSet.getApdexResults(this.sampledDataSet);
+        double impact = 1 - ((apdex.getSatisfied() + 0.5 * apdex.getTolerated()) / apdex.getN());
+        if (impact == 0) {
+            logger.info("No monitoring impact detected, increasing the sampling rate...");
+            //if no impact, increase by 1%
+            samplingRate += 0.01;
+        }
+        if (impact > 0.1) {
+            logger.info("Monitoring impact detected, decreasing the sampling rate by {}...", impact);
+            //reduce by the amount of overhead
+            samplingRate = samplingRate - impact;
+        }
+
+        //otherwise stays the same - not necessary here
+        //if (impact > 0 && impact <= 0.1) {        }
     }
 
     @Override
     public void run() {
-        logger.info("Running sampling adaptation.");
+        //TODO this is supposed to run from time to time based on triggers? Or every new trace collected?
 
         if (isReady()) {
             logger.info("Sample is ready, releasing for analysis and resetting...");
-            //TODO
-            releaseForAnalysis();
-            reset();
+            endMonitoringCycle();
             return;
         }
         if (shouldCollectPerformanceBaseline()) {
+            logger.info("Enabling performance baseline...");
             enablePerformanceBaseline();
             return;
         }
@@ -166,22 +194,41 @@ public class Sampling implements Runnable {
     public void releaseForAnalysis() {
     }
 
-    public void reset() {
-    }
-
     public boolean isPerformanceBaselineEnabled() {
         return performanceBaselineEnabled;
     }
 
+    private Long minimumSampleSize;
     public void addPerformanceBaselineItem(Granularity granularity, long executionTime) {
+        if (minimumSampleSize == null) {
+            minimumSampleSize = getMinimumSampleSize(this.population.getTotalItems());
+        }
+
+        logger.info("Collecting performance baseline for the next {} traces...",
+                minimumSampleSize - this.performanceBaselineDataSet.getTotalItems());
         this.performanceBaselineDataSet.addItem(granularity, executionTime);
 
-        if(this.performanceBaselineDataSet.getTotalItems() >=
-                getMinimumSampleSize(this.performanceBaselineDataSet.getTotalItems())) {
-            enable();
+        if(this.performanceBaselineDataSet.getTotalItems() > minimumSampleSize) {
+            //got enough traces for PB
+            logger.info("Finished to collect the performance baseline, enabling sampling again...");
+            samplingEnabled = true;
+            minimumSampleSize = null;
             this.performanceBaselineEnabled = false;
             lastFourPerformanceBaselineDataSets.add(this.performanceBaselineDataSet);
             this.performanceBaselineDataSet = new PerformanceBaselineDataSet();
         }
     }
+
+    public void addSampledItem(Granularity granularity, long executionTime) {
+        DescriptiveStatistics statistics = new DescriptiveStatistics();
+        if (sampledDataSet.containsKey(granularity)){
+            statistics = sampledDataSet.get(granularity);
+        }
+        statistics.addValue(executionTime);
+        sampledDataSet.put(granularity, statistics);
+
+
+        //TODO run it every new trace collected?
+        run();
+    }
 }
diff --git a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/utils/ConfigurationUtils.java b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/utils/ConfigurationUtils.java
index 2322644..0834b9f 100644
--- a/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/utils/ConfigurationUtils.java
+++ b/tigris/src/main/java/br/ufrgs/inf/prosoft/tigris/utils/ConfigurationUtils.java
@@ -33,6 +33,8 @@ public final class ConfigurationUtils {
                 reflections.getSubTypesOf(CustomMonitoring.class);
         if (configurations.size() > 1)
             throw new ConfigurationException("Multiple implementations of CustomMonitoring.class.");
+        if (configurations.size() == 0)
+            return null;
         return configurations.iterator().next();
     }
 
diff --git a/tigris/src/test/java/br/ufrgs/inf/prosoft/tigris/SamplingTest.java b/tigris/src/test/java/br/ufrgs/inf/prosoft/tigris/SamplingTest.java
new file mode 100644
index 0000000..26ee2ce
--- /dev/null
+++ b/tigris/src/test/java/br/ufrgs/inf/prosoft/tigris/SamplingTest.java
@@ -0,0 +1,16 @@
+package br.ufrgs.inf.prosoft.tigris;
+
+import br.ufrgs.inf.prosoft.tigris.sampling.Granularity;
+import br.ufrgs.inf.prosoft.tigris.sampling.GranularityType;
+import br.ufrgs.inf.prosoft.tigris.sampling.Sampling;
+import org.junit.Test;
+
+public class SamplingTest {
+
+    @Test
+    public void samplingDecision(){
+        Sampling sampling = new Sampling(0.5, 100);
+        sampling.samplingDecision(new Granularity(GranularityType.METHOD, "function"));
+        sampling.samplingDecision(new Granularity(GranularityType.METHOD, "function"));
+    }
+}