diff --git a/metrics/pom.xml b/metrics/pom.xml
index 1053c66ff5..794c53b157 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -15,6 +15,7 @@
3.1.2
3.1.0
+ 0.12.16
@@ -43,6 +44,11 @@
javax.servlet-api
${dep.ver.servlet}
+
+ com.netflix.servo
+ servo-core
+ ${netflix.servo.ver}
+
diff --git a/metrics/src/test/java/com/baeldung/metrics/servo/MetricAnnotationTest.java b/metrics/src/test/java/com/baeldung/metrics/servo/MetricAnnotationTest.java
new file mode 100644
index 0000000000..3d6a73912f
--- /dev/null
+++ b/metrics/src/test/java/com/baeldung/metrics/servo/MetricAnnotationTest.java
@@ -0,0 +1,56 @@
+package com.baeldung.metrics.servo;
+
+import com.netflix.servo.Metric;
+import com.netflix.servo.annotations.DataSourceType;
+import com.netflix.servo.annotations.Monitor;
+import com.netflix.servo.annotations.MonitorTags;
+import com.netflix.servo.monitor.Monitors;
+import com.netflix.servo.tag.BasicTag;
+import com.netflix.servo.tag.BasicTagList;
+import com.netflix.servo.tag.TagList;
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author aiet
+ */
+public class MetricAnnotationTest extends MetricTestBase {
+
+ @Monitor(name = "integerCounter", type = DataSourceType.COUNTER, description = "Total number of update operations.")
+ private final AtomicInteger updateCount = new AtomicInteger(0);
+
+ @MonitorTags private TagList tags = new BasicTagList(newArrayList(new BasicTag("tag-key", "tag-value")));
+
+ @Test
+ public void givenAnnotatedMonitor_whenUpdated_thenDataCollected() throws Exception {
+ Monitors.registerObject("testObject", this);
+ assertTrue(Monitors.isObjectRegistered("testObject", this));
+
+ updateCount.incrementAndGet();
+ updateCount.incrementAndGet();
+ SECONDS.sleep(1);
+
+ List> metrics = observer.getObservations();
+ System.out.println(metrics);
+ assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
+
+ Iterator> metricIterator = metrics.iterator();
+ //skip first empty observation
+ metricIterator.next();
+ while (metricIterator.hasNext()) {
+ assertThat(metricIterator.next(), hasItem(hasProperty("config", hasProperty("name", is("integerCounter")))));
+ }
+
+ }
+}
diff --git a/metrics/src/test/java/com/baeldung/metrics/servo/MetricObserverTest.java b/metrics/src/test/java/com/baeldung/metrics/servo/MetricObserverTest.java
new file mode 100644
index 0000000000..14d3c2646f
--- /dev/null
+++ b/metrics/src/test/java/com/baeldung/metrics/servo/MetricObserverTest.java
@@ -0,0 +1,70 @@
+package com.baeldung.metrics.servo;
+
+import com.netflix.servo.DefaultMonitorRegistry;
+import com.netflix.servo.Metric;
+import com.netflix.servo.monitor.BasicGauge;
+import com.netflix.servo.monitor.DynamicCounter;
+import com.netflix.servo.monitor.Gauge;
+import com.netflix.servo.monitor.MonitorConfig;
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.List;
+
+import static com.netflix.servo.annotations.DataSourceType.GAUGE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author aiet
+ */
+public class MetricObserverTest extends MetricTestBase {
+
+ @Test
+ public void givenMetrics_whenRegister_thenMonitored() throws InterruptedException {
+ Gauge gauge = new BasicGauge<>(MonitorConfig
+ .builder("test")
+ .build(), () -> 2.32);
+ assertEquals(2.32, gauge.getValue(), 0.01);
+
+ DefaultMonitorRegistry
+ .getInstance()
+ .register(gauge);
+
+ for (int i = 0; i < 2; i++) {
+ SECONDS.sleep(1);
+ }
+
+ List> metrics = observer.getObservations();
+ assertThat(metrics, hasSize(greaterThanOrEqualTo(2)));
+
+ Iterator> metricIterator = metrics.iterator();
+ //skip first empty observation
+ metricIterator.next();
+ while (metricIterator.hasNext()) {
+ assertThat(metricIterator.next(), hasItem(allOf(hasProperty("config", hasProperty("tags", hasItem(GAUGE))), hasProperty("value", is(2.32)))));
+ }
+
+ }
+
+ @Test
+ public void givenMetrics_whenRegisterDynamically_thenMonitored() throws Exception {
+ for (int i = 0; i < 2; i++) {
+ DynamicCounter.increment("monitor-name", "tag-key", "tag-value");
+ SECONDS.sleep(1);
+ }
+
+ List> metrics = observer.getObservations();
+ assertThat(metrics, hasSize(greaterThanOrEqualTo(2)));
+
+ Iterator> metricIterator = metrics.iterator();
+ //skip first empty observation
+ metricIterator.next();
+ while (metricIterator.hasNext()) {
+ assertThat(metricIterator.next(), hasItem(hasProperty("value", greaterThanOrEqualTo(1.0))));
+ }
+ }
+
+}
diff --git a/metrics/src/test/java/com/baeldung/metrics/servo/MetricPollerTest.java b/metrics/src/test/java/com/baeldung/metrics/servo/MetricPollerTest.java
new file mode 100644
index 0000000000..4a9a77efde
--- /dev/null
+++ b/metrics/src/test/java/com/baeldung/metrics/servo/MetricPollerTest.java
@@ -0,0 +1,50 @@
+package com.baeldung.metrics.servo;
+
+import com.netflix.servo.Metric;
+import com.netflix.servo.publish.*;
+import org.junit.Test;
+
+import java.util.List;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.stream.Collectors.toList;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author aiet
+ */
+public class MetricPollerTest {
+
+ @Test
+ public void givenJvmPoller_whenMonitor_thenDataCollected() throws Exception {
+ MemoryMetricObserver observer = new MemoryMetricObserver();
+ PollRunnable pollRunnable = new PollRunnable(new JvmMetricPoller(), new BasicMetricFilter(true), observer);
+ PollScheduler
+ .getInstance()
+ .start();
+ PollScheduler
+ .getInstance()
+ .addPoller(pollRunnable, 1, SECONDS);
+
+ SECONDS.sleep(1);
+
+ PollScheduler
+ .getInstance()
+ .stop();
+ List> metrics = observer.getObservations();
+ assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
+
+ List args = metrics
+ .stream()
+ .filter(m -> !m.isEmpty())
+ .flatMap(ms -> ms
+ .stream()
+ .map(m -> m
+ .getConfig()
+ .getName()))
+ .collect(toList());
+ assertThat(args, hasItems("loadedClassCount", "initUsage", "maxUsage", "threadCount"));
+ }
+
+}
diff --git a/metrics/src/test/java/com/baeldung/metrics/servo/MetricTestBase.java b/metrics/src/test/java/com/baeldung/metrics/servo/MetricTestBase.java
new file mode 100644
index 0000000000..86a9d201e8
--- /dev/null
+++ b/metrics/src/test/java/com/baeldung/metrics/servo/MetricTestBase.java
@@ -0,0 +1,40 @@
+package com.baeldung.metrics.servo;
+
+import com.netflix.servo.publish.*;
+import org.junit.After;
+import org.junit.Before;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+/**
+ * @author aiet
+ */
+abstract class MetricTestBase {
+
+ MemoryMetricObserver observer;
+
+ @Before
+ public void prepareScheduler() {
+ System.setProperty("servo.pollers", "1000");
+ observer = new MemoryMetricObserver();
+ PollScheduler
+ .getInstance()
+ .start();
+ MetricFilter metricFilter = new BasicMetricFilter(true);
+ PollRunnable task = new PollRunnable(new MonitorRegistryMetricPoller(), metricFilter, observer);
+ PollScheduler
+ .getInstance()
+ .addPoller(task, 1, SECONDS);
+ }
+
+ @After
+ public void stopScheduler() {
+ if (PollScheduler
+ .getInstance()
+ .isStarted()) {
+ PollScheduler
+ .getInstance()
+ .stop();
+ }
+ }
+}
diff --git a/metrics/src/test/java/com/baeldung/metrics/servo/MetricTypeTest.java b/metrics/src/test/java/com/baeldung/metrics/servo/MetricTypeTest.java
new file mode 100644
index 0000000000..9f7b6bb6f3
--- /dev/null
+++ b/metrics/src/test/java/com/baeldung/metrics/servo/MetricTypeTest.java
@@ -0,0 +1,221 @@
+package com.baeldung.metrics.servo;
+
+import com.netflix.servo.monitor.*;
+import com.netflix.servo.stats.StatsConfig;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for simple App.
+ */
+public class MetricTypeTest {
+
+ @Test
+ public void givenDefaultCounter_whenManipulate_thenCountValid() {
+ Counter counter = Monitors.newCounter("test");
+ assertEquals("counter should start with 0", 0, counter
+ .getValue()
+ .intValue());
+ counter.increment();
+ assertEquals("counter should have increased by 1", 1, counter
+ .getValue()
+ .intValue());
+ counter.increment(-1);
+ assertEquals("counter should have decreased by 1", 0, counter
+ .getValue()
+ .intValue());
+ }
+
+ @Test
+ public void givenBasicCounter_whenManipulate_thenCountValid() {
+ Counter counter = new BasicCounter(MonitorConfig
+ .builder("test")
+ .build());
+ assertEquals("counter should start with 0", 0, counter
+ .getValue()
+ .intValue());
+ counter.increment();
+ assertEquals("counter should have increased by 1", 1, counter
+ .getValue()
+ .intValue());
+ counter.increment(-1);
+ assertEquals("counter should have decreased by 1", 0, counter
+ .getValue()
+ .intValue());
+ }
+
+ @Ignore
+ @Test
+ public void givenStepCounter_whenManipulate_thenRateValid() throws Exception {
+ System.setProperty("servo.pollers", "1000");
+ Counter counter = new StepCounter(MonitorConfig
+ .builder("test")
+ .build());
+ assertEquals("counter should start with rate 0.0", 0.0, counter.getValue());
+
+ counter.increment();
+ SECONDS.sleep(1);
+
+ assertEquals("counter rate should have increased to 1.0", 1.0, counter.getValue());
+ }
+
+ @Test
+ public void givenPeakRateCounter_whenManipulate_thenPeakRateReturn() throws Exception {
+ Counter counter = new PeakRateCounter(MonitorConfig
+ .builder("test")
+ .build());
+ assertEquals("counter should start with 0", 0, counter
+ .getValue()
+ .intValue());
+
+ counter.increment();
+ SECONDS.sleep(1);
+ counter.increment();
+ counter.increment();
+
+ assertEquals("peak rate should have be 2", 2, counter
+ .getValue()
+ .intValue());
+ }
+
+ @Test
+ public void givenTimer_whenExecuteTask_thenTimerUpdated() throws Exception {
+ BasicTimer timer = new BasicTimer(MonitorConfig
+ .builder("test")
+ .build(), SECONDS);
+
+ Stopwatch stopwatch = timer.start();
+ SECONDS.sleep(1);
+ timer.record(2, SECONDS);
+ stopwatch.stop();
+
+ assertEquals("timer should count 1 second", 1, timer
+ .getValue()
+ .intValue());
+ assertEquals("timer should count 3 seconds in total", 3.0, timer.getTotalTime(), 0.01);
+ assertEquals("timer should record 2 updates", 2, timer
+ .getCount()
+ .intValue());
+
+ assertEquals("timer should have max 2", 2, timer.getMax(), 0.01);
+ }
+
+ @Test
+ public void givenBucketTimer_whenRecord_thenStatsCalculated() throws Exception {
+ BucketTimer timer = new BucketTimer(MonitorConfig
+ .builder("test")
+ .build(), new BucketConfig.Builder()
+ .withBuckets(new long[] { 2L, 5L })
+ .withTimeUnit(SECONDS)
+ .build(), SECONDS);
+ timer.record(3);
+ timer.record(6);
+
+ assertEquals("timer should count 9 seconds in total", 9, timer
+ .getTotalTime()
+ .intValue());
+
+ final Map metricMap = new HashMap<>(3);
+ timer
+ .getMonitors()
+ .stream()
+ .filter(monitor -> monitor
+ .getConfig()
+ .getTags()
+ .containsKey("servo.bucket"))
+ .forEach(monitor -> metricMap.put(getMonitorTagValue(monitor, "servo.bucket"), (Long) monitor.getValue()));
+
+ assertThat(metricMap, allOf(hasEntry("bucket=2s", 0L), hasEntry("bucket=5s", 1L), hasEntry("bucket=overflow", 1L)));
+ }
+
+ private static String getMonitorTagValue(Monitor monitor, String tagKey) {
+ return monitor
+ .getConfig()
+ .getTags()
+ .getTag(tagKey)
+ .getValue();
+ }
+
+ @Test
+ public void givenStatsTimer_whenExecuteTask_thenStatsCalculated() throws Exception {
+ System.setProperty("netflix.servo", "1000");
+ StatsTimer timer = new StatsTimer(MonitorConfig
+ .builder("test")
+ .build(), new StatsConfig.Builder()
+ .withComputeFrequencyMillis(2000)
+ .withPercentiles(new double[] { 99.0, 95.0, 90.0 })
+ .withPublishMax(true)
+ .withPublishMin(true)
+ .withPublishCount(true)
+ .withPublishMean(true)
+ .withPublishStdDev(true)
+ .withPublishVariance(true)
+ .build(), SECONDS);
+
+ Stopwatch stopwatch = timer.start();
+ SECONDS.sleep(1);
+ timer.record(3, SECONDS);
+ stopwatch.stop();
+
+ stopwatch = timer.start();
+ timer.record(6, SECONDS);
+ SECONDS.sleep(2);
+ stopwatch.stop();
+
+ assertEquals("timer should count 12 seconds in total", 12, timer.getTotalTime());
+ assertEquals("timer should count 12 seconds in total", 12, timer.getTotalMeasurement());
+ assertEquals("timer should record 4 updates", 4, timer.getCount());
+ assertEquals("stats timer value time-cost/update should be 2", 3, timer
+ .getValue()
+ .intValue());
+
+ final Map metricMap = new HashMap<>(10);
+ timer
+ .getMonitors()
+ .forEach(monitor -> metricMap.put(getMonitorTagValue(monitor, "statistic"), (Number) monitor.getValue()));
+
+ assertThat(metricMap.keySet(), containsInAnyOrder("count", "totalTime", "max", "min", "variance", "stdDev", "avg", "percentile_99", "percentile_95", "percentile_90"));
+ }
+
+ @Test
+ public void givenGauge_whenCall_thenValueReturned() {
+ Gauge gauge = new BasicGauge<>(MonitorConfig
+ .builder("test")
+ .build(), () -> 2.32);
+ assertEquals(2.32, gauge.getValue(), 0.01);
+ }
+
+ @Test
+ public void givenMaxGauge_whenUpdateMultipleTimes_thenMaxReturned() {
+ MaxGauge gauge = new MaxGauge(MonitorConfig
+ .builder("test")
+ .build());
+ assertEquals(0, gauge
+ .getValue()
+ .intValue());
+
+ gauge.update(4);
+ assertEquals(4, gauge.getCurrentValue(0));
+
+ gauge.update(1);
+ assertEquals(4, gauge.getCurrentValue(0));
+ }
+
+ @Test
+ public void givenInformationalMonitor_whenRecord_thenInformationCollected() throws Exception {
+ BasicInformational informational = new BasicInformational(MonitorConfig
+ .builder("test")
+ .build());
+ informational.setValue("information collected");
+ assertEquals("information collected", informational.getValue());
+ }
+
+}