diff --git a/metrics/pom.xml b/metrics/pom.xml
index 574c1ac132..926b6a95c5 100644
--- a/metrics/pom.xml
+++ b/metrics/pom.xml
@@ -16,6 +16,8 @@
3.1.2
3.1.0
0.12.17
+ 1.0.0-rc.2
+ 2.0.0.M5
@@ -57,12 +59,64 @@
${netflix.servo.ver}
test
+
+
+ io.micrometer
+ micrometer-registry-atlas
+ ${micrometer.ver}
+
+
+
+ io.micrometer
+ micrometer-spring-legacy
+ ${micrometer.ver}
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.boot.ver}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.1
+
com.fasterxml.jackson.dataformat
jackson-dataformat-smile
- 2.8.9
- test
+ 2.9.1
+
+
+
+ com.netflix.spectator
+ spectator-api
+ 0.57.1
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ pom
+ import
+ ${spring.boot.ver}
+
+
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/libs-milestone
+
+ false
+
+
+
+
diff --git a/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java b/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java
new file mode 100644
index 0000000000..cf818f6600
--- /dev/null
+++ b/metrics/src/main/java/com/baeldung/metrics/micrometer/MicrometerApp.java
@@ -0,0 +1,20 @@
+package com.baeldung.metrics.micrometer;
+
+import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication
+public class MicrometerApp {
+
+ @Bean
+ JvmThreadMetrics threadMetrics() {
+ return new JvmThreadMetrics();
+ }
+
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(MicrometerApp.class, args);
+ }
+
+}
diff --git a/metrics/src/main/java/com/baeldung/metrics/micrometer/PeopleController.java b/metrics/src/main/java/com/baeldung/metrics/micrometer/PeopleController.java
new file mode 100644
index 0000000000..789e975a7f
--- /dev/null
+++ b/metrics/src/main/java/com/baeldung/metrics/micrometer/PeopleController.java
@@ -0,0 +1,54 @@
+package com.baeldung.metrics.micrometer;
+
+import io.micrometer.core.annotation.Timed;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author aiet
+ */
+@RestController
+@Timed("people")
+public class PeopleController {
+
+ private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+
+ @GetMapping("/people")
+ @Timed(value = "people.all", longTask = true)
+ public List listPeople() throws InterruptedException {
+ int seconds2Sleep = SECURE_RANDOM.nextInt(500);
+ System.out.println(seconds2Sleep);
+ TimeUnit.MILLISECONDS.sleep(seconds2Sleep);
+ return Arrays.asList("Jim", "Tom", "Tim");
+ }
+
+ @PostMapping("/people")
+ @Timed(value = "people.update", longTask = true)
+ public List putPeople() throws InterruptedException {
+ int seconds2Sleep = SECURE_RANDOM.nextInt(1000);
+ System.out.println(seconds2Sleep);
+ TimeUnit.MILLISECONDS.sleep(seconds2Sleep);
+ return Arrays.asList("Jim", "Tom", "Tim");
+ }
+
+ @GetMapping("/asset")
+ @Timed(value = "people.asset", longTask = true)
+ public void test() throws Exception {
+ throw new Exception("error!");
+ }
+
+ @GetMapping("/property")
+ @Timed(value = "people.property", longTask = true)
+ public void property(HttpServletResponse response) throws IOException {
+ response.sendRedirect("/asset");
+ }
+
+}
diff --git a/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasTest.java b/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasTest.java
new file mode 100644
index 0000000000..b2eb0ee7dc
--- /dev/null
+++ b/metrics/src/test/java/com/baeldung/metrics/micrometer/MicrometerAtlasTest.java
@@ -0,0 +1,308 @@
+package com.baeldung.metrics.micrometer;
+
+import com.netflix.spectator.atlas.AtlasConfig;
+import io.micrometer.atlas.AtlasMeterRegistry;
+import io.micrometer.core.instrument.*;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import io.micrometer.core.instrument.stats.hist.Histogram;
+import io.micrometer.core.instrument.stats.quantile.WindowSketchQuantiles;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.collection.IsMapContaining.hasEntry;
+import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author aiet
+ */
+public class MicrometerAtlasTest {
+
+ AtlasConfig atlasConfig;
+
+ @Before
+ public void init() {
+ atlasConfig = new AtlasConfig() {
+
+ @Override
+ public Duration step() {
+ return Duration.ofSeconds(1);
+ }
+
+ @Override
+ public String get(String k) {
+ return null;
+ }
+ };
+ }
+
+ @Test
+ public void givenCompositeRegistries_whenRecordMeter_thenAllRegistriesRecorded() {
+ CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
+
+ SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry();
+ AtlasMeterRegistry atlasMeterRegistry = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM);
+
+ compositeRegistry.add(oneSimpleMeter);
+ compositeRegistry.add(atlasMeterRegistry);
+
+ compositeRegistry.gauge("baeldung.heat", 90);
+
+ Optional oneGauge = oneSimpleMeter
+ .find("baeldung.heat")
+ .gauge();
+ assertTrue(oneGauge.isPresent());
+ Iterator measurements = oneGauge
+ .get()
+ .measure()
+ .iterator();
+
+ assertTrue(measurements.hasNext());
+ assertThat(measurements
+ .next()
+ .getValue(), equalTo(90.00));
+
+ Optional atlasGauge = atlasMeterRegistry
+ .find("baeldung.heat")
+ .gauge();
+ assertTrue(atlasGauge.isPresent());
+ Iterator anotherMeasurements = atlasGauge
+ .get()
+ .measure()
+ .iterator();
+
+ assertTrue(anotherMeasurements.hasNext());
+ assertThat(anotherMeasurements
+ .next()
+ .getValue(), equalTo(90.00));
+ }
+
+ @Test
+ public void givenGlobalRegistry_whenIncrementAnywhere_thenCounted() {
+ class CountedObject {
+ private CountedObject() {
+ Metrics
+ .counter("objects.instance")
+ .increment(1.0);
+ }
+ }
+ Metrics.addRegistry(new SimpleMeterRegistry());
+
+ Metrics
+ .counter("objects.instance")
+ .increment();
+ new CountedObject();
+
+ Optional counterOptional = Metrics.globalRegistry
+ .find("objects.instance")
+ .counter();
+
+ assertTrue(counterOptional.isPresent());
+ assertTrue(counterOptional
+ .get()
+ .count() == 2.0);
+ }
+
+ @Test
+ public void givenCounter_whenIncrement_thenValueChanged() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ Counter counter = Counter
+ .builder("objects.instance")
+ .description("indicates instance count of the object")
+ .tags("dev", "performance")
+ .register(registry);
+
+ counter.increment(2.0);
+ assertTrue(counter.count() == 2);
+
+ counter.increment(-1);
+ assertTrue(counter.count() == 2);
+ }
+
+ @Test
+ public void givenTimer_whenWrapTasks_thenTimeRecorded() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ Timer timer = registry.timer("app.event");
+ timer.record(() -> {
+ try {
+ TimeUnit.MILLISECONDS.sleep(1500);
+ } catch (InterruptedException ignored) {
+ }
+ });
+
+ timer.record(3000, TimeUnit.MILLISECONDS);
+
+ assertTrue(2 == timer.count());
+ assertTrue(4510 > timer.totalTime(TimeUnit.MILLISECONDS) && 4500 <= timer.totalTime(TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void givenLongTimer_whenRunTasks_thenTimerRecorded() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ LongTaskTimer longTaskTimer = LongTaskTimer
+ .builder("3rdPartyService")
+ .register(registry);
+
+ long currentTaskId = longTaskTimer.start();
+ try {
+ TimeUnit.SECONDS.sleep(2);
+ } catch (InterruptedException ignored) {
+ }
+ long timeElapsed = longTaskTimer.stop(currentTaskId);
+
+ assertTrue(timeElapsed / (int) 1e9 == 2);
+ }
+
+ @Test
+ public void givenGauge_whenMeterListSize_thenCurrentSizeMonitored() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ List list = new ArrayList<>(4);
+ Gauge gauge = Gauge
+ .builder("cache.size", list, List::size)
+ .register(registry);
+
+ assertTrue(gauge.value() == 0.0);
+
+ list.add("1");
+ assertTrue(gauge.value() == 1.0);
+ }
+
+ @Test
+ public void givenDistributionSummary_whenRecord_thenSummarized() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ DistributionSummary distributionSummary = DistributionSummary
+ .builder("request.size")
+ .baseUnit("bytes")
+ .register(registry);
+ distributionSummary.record(3);
+ distributionSummary.record(4);
+ distributionSummary.record(5);
+
+ assertTrue(3 == distributionSummary.count());
+ assertTrue(12 == distributionSummary.totalAmount());
+ }
+
+ @Test
+ public void givenTimer_whenEnrichWithQuantile_thenQuantilesComputed() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ Timer timer = Timer
+ .builder("test.timer")
+ .quantiles(WindowSketchQuantiles
+ .quantiles(0.3, 0.5, 0.95)
+ .create())
+ .register(registry);
+
+ timer.record(2, TimeUnit.SECONDS);
+ timer.record(2, TimeUnit.SECONDS);
+ timer.record(3, TimeUnit.SECONDS);
+ timer.record(4, TimeUnit.SECONDS);
+ timer.record(8, TimeUnit.SECONDS);
+ timer.record(13, TimeUnit.SECONDS);
+
+ List quantileGauges = registry
+ .getMeters()
+ .stream()
+ .filter(meter -> meter
+ .getType()
+ .name()
+ .equals("Gauge"))
+ .map(meter -> (Gauge) meter)
+ .collect(Collectors.toList());
+ assert (3 == quantileGauges.size());
+
+ Map quantileMap = quantileGauges
+ .stream()
+ .collect(Collectors.toMap(gauge -> {
+ Tag tag = gauge
+ .getId()
+ .getTags()
+ .iterator()
+ .next();
+ return tag.getKey() + "=" + tag.getValue();
+ }, gauge -> (int) (gauge.value() / 1e9)));
+
+ assertThat(quantileMap.keySet(), hasItems("quantile=0.3", "quantile=0.5", "quantile=0.95"));
+ assertThat(quantileMap.get("quantile=0.3"), is(2));
+ assertThat(quantileMap.get("quantile=0.5"), is(3));
+ assertThat(quantileMap.get("quantile=0.95"), is(8));
+ }
+
+ @Test
+ public void givenDistributionSummary_whenEnrichWithHistograms_thenDataAggregated() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ DistributionSummary hist = DistributionSummary
+ .builder("summary")
+ .histogram(Histogram.linear(0, 10, 5))
+ .register(registry);
+ hist.record(3);
+ hist.record(8);
+ hist.record(20);
+ hist.record(40);
+ hist.record(13);
+ hist.record(26);
+
+ Map histograms = registry
+ .getMeters()
+ .stream()
+ .filter(meter -> meter.getType() == Meter.Type.Counter)
+ .collect(Collectors.toMap(counter -> {
+ Tag tag = counter
+ .getId()
+ .getTags()
+ .iterator()
+ .next();
+ return tag.getKey() + "=" + tag.getValue();
+ }, counter -> (int) counter
+ .measure()
+ .iterator()
+ .next()
+ .getValue()));
+
+ assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=10.0", 2), hasEntry("bucket=20.0", 2), hasEntry("bucket=30.0", 1), hasEntry("bucket=40.0", 1), hasEntry("bucket=Infinity", 0)));
+ }
+
+ @Test
+ public void givenTimer_whenEnrichWithTimescaleHistogram_thenTimeScaleDataCollected() {
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+ Timer timer = Timer
+ .builder("timer")
+ .histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3))
+ .register(registry);
+
+ timer.record(1000, TimeUnit.MILLISECONDS);
+ timer.record(23, TimeUnit.MILLISECONDS);
+ timer.record(450, TimeUnit.MILLISECONDS);
+ timer.record(341, TimeUnit.MILLISECONDS);
+ timer.record(500, TimeUnit.MILLISECONDS);
+
+ Map histograms = registry
+ .getMeters()
+ .stream()
+ .filter(meter -> meter.getType() == Meter.Type.Counter)
+ .collect(Collectors.toMap(counter -> {
+ Tag tag = counter
+ .getId()
+ .getTags()
+ .iterator()
+ .next();
+ return tag.getKey() + "=" + tag.getValue();
+ }, counter -> (int) counter
+ .measure()
+ .iterator()
+ .next()
+ .getValue()));
+
+ assertThat(histograms, allOf(hasEntry("bucket=0.0", 0), hasEntry("bucket=2.0E8", 1), hasEntry("bucket=4.0E8", 1), hasEntry("bucket=Infinity", 3)));
+
+ }
+
+}