diff --git a/changelog/@unreleased/pr-309.v2.yml b/changelog/@unreleased/pr-309.v2.yml new file mode 100644 index 0000000..7dff707 --- /dev/null +++ b/changelog/@unreleased/pr-309.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Implement DnsCacheTtl accessors + links: + - https://github.com/palantir/jvm-diagnostics/pull/309 diff --git a/jvm-diagnostics/build.gradle b/jvm-diagnostics/build.gradle index 97005f0..289eca7 100644 --- a/jvm-diagnostics/build.gradle +++ b/jvm-diagnostics/build.gradle @@ -10,5 +10,8 @@ dependencies { } moduleJvmArgs { - exports 'java.management/sun.management', 'java.base/jdk.internal.platform' + exports 'java.management/sun.management', + 'java.base/jdk.internal.platform', + // Required for access to 'sun.net.InetAddressCachePolicy' for DNS cache TTL + 'java.base/sun.net' } diff --git a/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/DnsCacheTtlAccessor.java b/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/DnsCacheTtlAccessor.java new file mode 100644 index 0000000..281e811 --- /dev/null +++ b/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/DnsCacheTtlAccessor.java @@ -0,0 +1,33 @@ +/* + * (c) Copyright 2022 Palantir Technologies Inc. All rights reserved. + * + * Licensed 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 com.palantir.jvm.diagnostics; + +public interface DnsCacheTtlAccessor { + + /** Returns the number of seconds successful DNS results are cached before making another DNS request. */ + int getPositiveSeconds(); + + /** Returns the number of seconds unsuccessful DNS results are cached before making another DNS request. */ + int getNegativeSeconds(); + + /** + * The amount of time a previously successful DNS result will be used beyond {@link #getPositiveSeconds()} if + * subsequent DNS requests for the same hostname fail. + * Introduced in Java 21 by JDK-8306653. + */ + int getStaleSeconds(); +} diff --git a/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/JvmDiagnostics.java b/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/JvmDiagnostics.java index 7b4382a..f0bcf07 100644 --- a/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/JvmDiagnostics.java +++ b/jvm-diagnostics/src/main/java/com/palantir/jvm/diagnostics/JvmDiagnostics.java @@ -16,8 +16,10 @@ package com.palantir.jvm.diagnostics; +import java.lang.reflect.Method; import java.util.Optional; import java.util.OptionalLong; +import java.util.function.IntSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -122,6 +124,20 @@ public static Optional cpuShares() { } } + /** + * Returns a {@link HotspotDnsCacheTtlAccessor}. This functionality is not supported on all java runtimes, + * and an {@link Optional#empty()} is returned in cases TTL information is not accessible. + */ + public static Optional dnsCacheTtl() { + try { + HotspotDnsCacheTtlAccessor accessor = new HotspotDnsCacheTtlAccessor(); + return accessor.isEnabled() ? Optional.of(accessor) : Optional.empty(); + } catch (Throwable t) { + log.debug("Failed to create a HotspotDnsCacheTtlAccessor", t); + return Optional.empty(); + } + } + private JvmDiagnostics() {} private static final class HotspotSafepointTimeAccessor implements SafepointTimeAccessor { @@ -209,4 +225,54 @@ public OptionalLong getCpuShares() { return OptionalLong.of(result); } } + + private static final class HotspotDnsCacheTtlAccessor implements DnsCacheTtlAccessor { + + boolean isEnabled() { + try { + // Ensure invocations succeed. If sufficient exports aren't present, this will throw. + getPositiveSeconds(); + return true; + } catch (Throwable t) { + return false; + } + } + + private final IntSupplier staleAccessor = createStaleAccessor(); + + private static IntSupplier createStaleAccessor() { + if (Runtime.version().feature() >= 21) { + // Introduced in Java 21 by https://bugs.openjdk.org/browse/JDK-8306653 + try { + Method getStale = sun.net.InetAddressCachePolicy.class.getMethod("getStale"); + return () -> { + try { + return (Integer) getStale.invoke(null); + } catch (ReflectiveOperationException roe) { + log.debug("Failed to load stale InetAddressCachePolicy", roe); + return 0; + } + }; + } catch (ReflectiveOperationException roe) { + log.debug("Failed to load stale InetAddressCachePolicy", roe); + } + } + return () -> 0; + } + + @Override + public int getPositiveSeconds() { + return sun.net.InetAddressCachePolicy.get(); + } + + @Override + public int getNegativeSeconds() { + return sun.net.InetAddressCachePolicy.getNegative(); + } + + @Override + public int getStaleSeconds() { + return staleAccessor.getAsInt(); + } + } } diff --git a/jvm-diagnostics/src/test/java/com/palantir/jvm/diagnostics/JvmDiagnosticsTest.java b/jvm-diagnostics/src/test/java/com/palantir/jvm/diagnostics/JvmDiagnosticsTest.java index 4c2f145..02b1829 100644 --- a/jvm-diagnostics/src/test/java/com/palantir/jvm/diagnostics/JvmDiagnosticsTest.java +++ b/jvm-diagnostics/src/test/java/com/palantir/jvm/diagnostics/JvmDiagnosticsTest.java @@ -51,4 +51,11 @@ void testThreadUserTime() { .getUserTimeNanoseconds(Thread.currentThread().getId())) .isGreaterThan(0L); } + + @Test + void testDnsCacheTtl() { + assertThat(JvmDiagnostics.dnsCacheTtl().get().getPositiveSeconds()).isEqualTo(30); + assertThat(JvmDiagnostics.dnsCacheTtl().get().getNegativeSeconds()).isEqualTo(10); + assertThat(JvmDiagnostics.dnsCacheTtl().get().getStaleSeconds()).isEqualTo(0); + } }