diff --git a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/HealthConfig.java b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/HealthConfig.java index c4320da7b2..0da7660a12 100644 --- a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/HealthConfig.java +++ b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/HealthConfig.java @@ -21,6 +21,12 @@ import static io.micrometer.core.instrument.config.validate.PropertyValidator.getDuration; +/** + * {@link MeterRegistryConfig} for {@link HealthMeterRegistry}. + * + * @author Jon Schneider + * @since 1.6.0 + */ public interface HealthConfig extends MeterRegistryConfig { HealthConfig DEFAULT = key -> null; diff --git a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/QueryUtils.java b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/QueryUtils.java index 1fbf5c3b2a..9dc1fadfb1 100644 --- a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/QueryUtils.java +++ b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/QueryUtils.java @@ -17,12 +17,14 @@ import java.util.function.BinaryOperator; +/** + * Utilities for queries. + * + * @author Jon Schneider + */ class QueryUtils { - public static final BinaryOperator SUM_OR_NAN = (v1, v2) -> { + static final BinaryOperator SUM_OR_NAN = (v1, v2) -> { if (Double.isNaN(v1)) { - if (Double.isNaN(v2)) { - return Double.NaN; - } return v2; } else if (Double.isNaN(v2)) { return v1; @@ -30,11 +32,8 @@ class QueryUtils { return v1 + v2; }; - public static final BinaryOperator MAX_OR_NAN = (v1, v2) -> { + static final BinaryOperator MAX_OR_NAN = (v1, v2) -> { if (Double.isNaN(v1)) { - if (Double.isNaN(v2)) { - return Double.NaN; - } return v2; } else if (Double.isNaN(v2)) { return v1; diff --git a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/ServiceLevelObjective.java b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/ServiceLevelObjective.java index 44ba735d0b..0032f78d34 100644 --- a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/ServiceLevelObjective.java +++ b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/ServiceLevelObjective.java @@ -20,6 +20,7 @@ import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.distribution.HistogramSupport; import io.micrometer.core.instrument.distribution.ValueAtPercentile; import io.micrometer.core.instrument.search.Search; import io.micrometer.core.lang.Nullable; @@ -64,11 +65,14 @@ public abstract class ServiceLevelObjective { @Nullable private final String failedMessage; + private final Meter.Id id; + protected ServiceLevelObjective(String name, Tags tags, @Nullable String baseUnit, @Nullable String failedMessage) { this.name = name; this.tags = tags; this.baseUnit = baseUnit; this.failedMessage = failedMessage; + this.id = new Meter.Id(name, tags, baseUnit, failedMessage, Meter.Type.GAUGE); } public String getName() { @@ -85,7 +89,7 @@ public String getBaseUnit() { } public Meter.Id getId() { - return new Meter.Id(name, tags, baseUnit, failedMessage, Meter.Type.GAUGE); + return id; } @Nullable @@ -125,7 +129,7 @@ protected SingleIndicator(NumericQuery query, String testDescription, Predicate< @Override public boolean healthy(MeterRegistry registry) { - Double v = query.getValue(registry); + Double v = getValue(registry); return v.isNaN() || test.test(v); } @@ -181,8 +185,7 @@ public static class Builder { private final Collection requires; Builder(String name) { - this.name = name; - this.requires = new ArrayList<>(); + this(name, null, new ArrayList<>()); } Builder(String name, @Nullable String failedMessage, Collection requires) { @@ -215,7 +218,7 @@ public final Builder tags(String... tags) { } /** - * @param tags Tags to add to the eventual timer. + * @param tags Tags to add to the single indicator. * @return The builder with added tags. */ public final Builder tags(Iterable tags) { @@ -226,7 +229,7 @@ public final Builder tags(Iterable tags) { /** * @param key The tag key. * @param value The tag value. - * @return The timer builder with a single added tag. + * @return The single indicator builder with a single added tag. */ public final Builder tag(String key, String value) { this.tags = tags.and(key, value); @@ -278,15 +281,11 @@ public final NumericQuery total(Function search) { public final NumericQuery maxPercentile(Function search, double percentile) { return new Instant(name, tags, baseUnit, failedMessage, requires, search, s -> s.meters().stream() .map(m -> { - ValueAtPercentile[] valueAtPercentiles = new ValueAtPercentile[0]; - if (m instanceof DistributionSummary) { - valueAtPercentiles = ((DistributionSummary) m).takeSnapshot().percentileValues(); - } else if (m instanceof Timer) { - valueAtPercentiles = ((Timer) m).takeSnapshot().percentileValues(); - } else if (m instanceof LongTaskTimer) { - valueAtPercentiles = ((LongTaskTimer) m).takeSnapshot().percentileValues(); + if (!(m instanceof HistogramSupport)) { + return Double.NaN; } + ValueAtPercentile[] valueAtPercentiles = ((HistogramSupport) m).takeSnapshot().percentileValues(); return Arrays.stream(valueAtPercentiles) .filter(vap -> vap.percentile() == percentile) .map(ValueAtPercentile::value) @@ -653,7 +652,7 @@ public Builder tags(String... tags) { } /** - * @param tags Tags to add to the eventual timer. + * @param tags Tags to add to the multiple indicator. * @return The builder with added tags. */ public Builder tags(Iterable tags) { diff --git a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/JvmServiceLevelObjectives.java b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/JvmServiceLevelObjectives.java index 452589fb70..0d2b49bd98 100644 --- a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/JvmServiceLevelObjectives.java +++ b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/JvmServiceLevelObjectives.java @@ -23,6 +23,8 @@ import java.time.Duration; /** + * {@link ServiceLevelObjective ServiceLevelObjectives} for Java Virtual Machine. + * * @author Jon Schneider * @since 1.6.0 */ @@ -31,7 +33,7 @@ public class JvmServiceLevelObjectives { * A series of high-level heap monitors originally defined in * Team City's memory monitor. */ - public static final ServiceLevelObjective[] MEMORY = new ServiceLevelObjective[]{ + public static final ServiceLevelObjective[] MEMORY = new ServiceLevelObjective[] { ServiceLevelObjective .build("jvm.pool.memory") .failedMessage("Memory usage in a single memory pool exceeds 90% after garbage collection.") @@ -44,6 +46,7 @@ public class JvmServiceLevelObjectives { .build("jvm.gc.load") .failedMessage("Memory cleaning is taking more than 50% of CPU resources on average. " + "This usually means really serious problems with memory resulting in high performance degradation.") + .requires(new JvmHeapPressureMetrics()) .baseUnit("percent CPU time spent") .value(s -> s.name("jvm.gc.overhead")) .isLessThan(0.5), @@ -72,7 +75,7 @@ public class JvmServiceLevelObjectives { .and() }; - public static final ServiceLevelObjective[] ALLOCATIONS = new ServiceLevelObjective[]{ + public static final ServiceLevelObjective[] ALLOCATIONS = new ServiceLevelObjective[] { ServiceLevelObjective .build("jvm.allocations.g1.humongous") .failedMessage("A single object was allocated that exceeded 50% of the total size of the eden space.") diff --git a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/OperatingSystemServiceLevelObjectives.java b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/OperatingSystemServiceLevelObjectives.java index 663b9d72eb..8dd469887b 100644 --- a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/OperatingSystemServiceLevelObjectives.java +++ b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/objectives/OperatingSystemServiceLevelObjectives.java @@ -19,11 +19,13 @@ import io.micrometer.health.ServiceLevelObjective; /** + * {@link ServiceLevelObjective ServiceLevelObjectives} for Operating System. + * * @author Jon Schneider * @since 1.6.0 */ public class OperatingSystemServiceLevelObjectives { - public static final ServiceLevelObjective[] DISK = new ServiceLevelObjective[]{ + public static final ServiceLevelObjective[] DISK = new ServiceLevelObjective[] { ServiceLevelObjective.build("os.file.descriptors") .failedMessage("Too many file descriptors are open. When the max is reached, " + "further attempts to retrieve a file descriptor will block indefinitely.") diff --git a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/package-info.java b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/package-info.java index 7172066441..031a91c409 100644 --- a/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/package-info.java +++ b/implementations/micrometer-registry-health/src/main/java/io/micrometer/health/package-info.java @@ -13,6 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * SLO-based health meter registry. + */ @NonNullApi @NonNullFields package io.micrometer.health; diff --git a/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/HealthMeterRegistryTest.java b/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/HealthMeterRegistryTest.java index d07d9bb5cb..899cc2c7a4 100644 --- a/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/HealthMeterRegistryTest.java +++ b/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/HealthMeterRegistryTest.java @@ -29,6 +29,11 @@ import static io.micrometer.core.instrument.MockClock.clock; import static org.assertj.core.api.Assertions.assertThat; +/** + * Tests for {@link HealthMeterRegistry}. + * + * @author Jon Schneider + */ class HealthMeterRegistryTest { @Test void healthFromServiceLevelObjective() { @@ -123,8 +128,7 @@ void applyRequiredBinders() { .build(); assertThat(registry.getMeters().stream().map(m -> m.getId().getName())) - .containsOnly("jvm.memory.used") - .isNotEmpty(); + .containsOnly("jvm.memory.used"); } @Test @@ -143,7 +147,6 @@ public Meter.Id map(Meter.Id id) { assertThat(registry.getServiceLevelObjectives().stream().map(ServiceLevelObjective::getName)) .contains("jvm.collection.load") - .doesNotContain("jvm.pool.memory") - .isNotEmpty(); + .doesNotContain("jvm.pool.memory"); } } diff --git a/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/QueryTest.java b/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/QueryTest.java index 4519eba789..2cb539a296 100644 --- a/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/QueryTest.java +++ b/implementations/micrometer-registry-health/src/test/java/io/micrometer/health/QueryTest.java @@ -156,7 +156,7 @@ void isLessThan() { .count(s -> s.name("my.timer")) .isLessThan(4) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); assertThat( ServiceLevelObjective @@ -164,7 +164,7 @@ void isLessThan() { .total(s -> s.name("my.timer")) .isLessThan(Duration.ofSeconds(6)) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); } @Test @@ -175,14 +175,14 @@ void isLessThanOrEqualTo() { .count(s -> s.name("my.timer")) .isLessThanOrEqualTo(3) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); assertThat(ServiceLevelObjective .build("sum") .total(s -> s.name("my.timer")) .isLessThanOrEqualTo(Duration.ofSeconds(5)) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); } @Test @@ -193,7 +193,7 @@ void isGreaterThan() { .count(s -> s.name("my.timer")) .isGreaterThan(2) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); assertThat( ServiceLevelObjective @@ -201,7 +201,7 @@ void isGreaterThan() { .total(s -> s.name("my.timer")) .isGreaterThan(Duration.ofSeconds(4)) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); } @Test @@ -212,7 +212,7 @@ void isGreaterThanOrEqualTo() { .count(s -> s.name("my.timer")) .isGreaterThanOrEqualTo(3) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); assertThat( ServiceLevelObjective @@ -220,7 +220,7 @@ void isGreaterThanOrEqualTo() { .total(s -> s.name("my.timer")) .isGreaterThanOrEqualTo(Duration.ofSeconds(5)) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); } @Test @@ -231,7 +231,7 @@ void isEqualTo() { .count(s -> s.name("my.timer")) .isEqualTo(3) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); assertThat( ServiceLevelObjective @@ -239,6 +239,6 @@ void isEqualTo() { .total(s -> s.name("my.timer")) .isEqualTo(Duration.ofSeconds(5)) .healthy(registry) - ).isEqualTo(true); + ).isTrue(); } } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/search/Search.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/search/Search.java index 70f902d998..9d57e31da6 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/search/Search.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/search/Search.java @@ -222,64 +222,45 @@ public MeterFilterReply accept(Meter.Id id) { if (!nameMatches.test(id.getName())) { return MeterFilterReply.NEUTRAL; } - - boolean requiredKeysPresent = true; - if (!requiredTagKeys.isEmpty()) { - final List tagKeys = new ArrayList<>(); - id.getTags().forEach(t -> tagKeys.add(t.getKey())); - requiredKeysPresent = tagKeys.containsAll(requiredTagKeys); - } - - boolean tagPredicatesMatched = true; - if (!tagMatches.isEmpty()) { - final Set matchingTagKeys = new HashSet<>(); - id.getTags().forEach(t -> { - Collection> tagValueMatchers = tagMatches.get(t.getKey()); - if (tagValueMatchers != null) { - if (tagValueMatchers.stream().allMatch(matcher -> matcher.test(t.getValue()))) { - matchingTagKeys.add(t.getKey()); - } - } - }); - tagPredicatesMatched = tagMatches.keySet().size() == matchingTagKeys.size(); - } - - return requiredKeysPresent && tagPredicatesMatched && id.getTags().containsAll(tags) ? - MeterFilterReply.ACCEPT : MeterFilterReply.NEUTRAL; + return isTagsMatched(id) ? MeterFilterReply.ACCEPT : MeterFilterReply.NEUTRAL; } }; } - private Stream meterStream() { - Stream meterStream = registry.getMeters().stream().filter(m -> nameMatches.test(m.getId().getName())); + private boolean isTagsMatched(Meter.Id id) { + return isRequiredTagKeysPresent(id) && isTagPredicatesMatched(id) && id.getTags().containsAll(tags); + } - if (!tags.isEmpty() || !requiredTagKeys.isEmpty() || !tagMatches.isEmpty()) { - meterStream = meterStream.filter(m -> { - boolean requiredKeysPresent = true; - if (!requiredTagKeys.isEmpty()) { - final List tagKeys = new ArrayList<>(); - m.getId().getTags().forEach(t -> tagKeys.add(t.getKey())); - requiredKeysPresent = tagKeys.containsAll(requiredTagKeys); - } + private boolean isRequiredTagKeysPresent(Meter.Id id) { + if (!requiredTagKeys.isEmpty()) { + final Set tagKeys = new HashSet<>(); + id.getTags().forEach(t -> tagKeys.add(t.getKey())); + return tagKeys.containsAll(requiredTagKeys); + } + return true; + } - boolean tagPredicatesMatched = true; - if (!tagMatches.isEmpty()) { - final Set matchingTagKeys = new HashSet<>(); - m.getId().getTags().forEach(t -> { - Collection> tagValueMatchers = tagMatches.get(t.getKey()); - if (tagValueMatchers != null) { - if (tagValueMatchers.stream().allMatch(matcher -> matcher.test(t.getValue()))) { - matchingTagKeys.add(t.getKey()); - } - } - }); - tagPredicatesMatched = tagMatches.keySet().size() == matchingTagKeys.size(); + private boolean isTagPredicatesMatched(Meter.Id id) { + if (!tagMatches.isEmpty()) { + final Set matchingTagKeys = new HashSet<>(); + id.getTags().forEach(t -> { + Collection> tagValueMatchers = tagMatches.get(t.getKey()); + if (tagValueMatchers != null) { + if (tagValueMatchers.stream().allMatch(matcher -> matcher.test(t.getValue()))) { + matchingTagKeys.add(t.getKey()); + } } - - return requiredKeysPresent && tagPredicatesMatched && m.getId().getTags().containsAll(tags); }); + return tagMatches.keySet().size() == matchingTagKeys.size(); } + return true; + } + private Stream meterStream() { + Stream meterStream = registry.getMeters().stream().filter(m -> nameMatches.test(m.getId().getName())); + if (!tags.isEmpty() || !requiredTagKeys.isEmpty() || !tagMatches.isEmpty()) { + meterStream = meterStream.filter(m -> isTagsMatched(m.getId())); + } return meterStream; } diff --git a/samples/micrometer-samples-boot2/src/main/java/io/micrometer/boot2/samples/components/ServiceLevelObjectiveConfiguration.java b/samples/micrometer-samples-boot2/src/main/java/io/micrometer/boot2/samples/components/ServiceLevelObjectiveConfiguration.java index a2e7c5adc4..11c4d9a607 100644 --- a/samples/micrometer-samples-boot2/src/main/java/io/micrometer/boot2/samples/components/ServiceLevelObjectiveConfiguration.java +++ b/samples/micrometer-samples-boot2/src/main/java/io/micrometer/boot2/samples/components/ServiceLevelObjectiveConfiguration.java @@ -36,7 +36,7 @@ /** * Delete this when this feature is incorporated into Spring Boot. * - * @since 1.6.0 + * @author Jon Schneider */ @Configuration public class ServiceLevelObjectiveConfiguration {