diff --git a/pom.xml b/pom.xml index 992f475..5b10f2d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.aphyr riemann-java-client - 0.2.12-SNAPSHOT + 0.2.13-SNAPSHOT bundle Riemann Java Client Java client for http://aphyr.github.com/riemann/ diff --git a/riemann-java-client.iml b/riemann-java-client.iml index af0253d..3d9ede7 100644 --- a/riemann-java-client.iml +++ b/riemann-java-client.iml @@ -6,23 +6,18 @@ - - - - - - - - - + - - - + + + + + + + - - + \ No newline at end of file diff --git a/src/main/java/com/aphyr/riemann/client/EventDSL.java b/src/main/java/com/aphyr/riemann/client/EventDSL.java index 0bbb7a7..5ee6619 100644 --- a/src/main/java/com/aphyr/riemann/client/EventDSL.java +++ b/src/main/java/com/aphyr/riemann/client/EventDSL.java @@ -17,12 +17,8 @@ public class EventDSL { public EventDSL(AbstractRiemannClient client) { this.client = client; this.builder = Event.newBuilder(); - try { - this.builder.setHost(java.net.InetAddress.getLocalHost().getHostName()); - } catch (java.net.UnknownHostException e) { - // If we can't get the local host, a null host is perfectly - // acceptable. Caller will know soon enough. :) - } + + this.builder.setHost(LocalhostResolver.getResolvedHostname()); } public EventDSL host(String host) { diff --git a/src/main/java/com/aphyr/riemann/client/LocalhostResolver.java b/src/main/java/com/aphyr/riemann/client/LocalhostResolver.java new file mode 100644 index 0000000..b416423 --- /dev/null +++ b/src/main/java/com/aphyr/riemann/client/LocalhostResolver.java @@ -0,0 +1,91 @@ +package com.aphyr.riemann.client; + +import java.net.UnknownHostException; + +/** + * A "Smarter" localhost resolver + * see issue: https://github.com/aphyr/riemann-java-client/issues/44 + * Trying to avoid a lot of calls to java.net.InetAddress.getLocalHost() + * which under AWS trigger DNS resolving and have relatively high latency *per event* + * usually, the hostname doesn't change so often to warrant a real query. + * + * A real call to java.net.InetAddress.getLocalHost().getHostName() + * is made only if: + * 1) the refresh interval has passed (=result is stale) + * AND + * 2) no env vars that identify the hostname are found + */ +public class LocalhostResolver { + + // default hostname env var names on Win/Nix + public static final String COMPUTERNAME = "COMPUTERNAME"; // Windows + public static final String HOSTNAME = "HOSTNAME"; // Nix + + // how often should we refresh the cached hostname + public static long RefreshIntervalMillis = 60 * 1000; + // enables setting a custom env var used for resolving + public static String CustomEnvVarName = null; + + // cached hostname result + private static String hostname; + + // update (refresh) time management + private static long lastUpdate = 0; + private static long lastNetUpdate = 0; + public static long getLastUpdateTime() { return lastUpdate; } + public static long getLastNetUpdateTime() { return lastNetUpdate; } + public static void resetUpdateTimes() { + lastUpdate = 0; + lastNetUpdate = 0; + } + + /** + * get resolved hostname. + * encapsulates all lookup and caching logic. + * + * @return the hostname + */ + public static String getResolvedHostname() { + long now = System.currentTimeMillis(); + if(now - RefreshIntervalMillis > lastUpdate) { + refreshResolve(); + } + + return hostname; + } + + /** + * forces a new resolve even if refresh interval has not passed yet + */ + public static void refreshResolve() { + try { + hostname = resolveByEnv(); + if(hostname == null || hostname.isEmpty()) { + hostname = java.net.InetAddress.getLocalHost().getHostName(); + lastNetUpdate = System.currentTimeMillis(); + } + } catch (UnknownHostException e) { + //e.printStackTrace(); + } + finally { + lastUpdate = System.currentTimeMillis(); + } + } + + /** + * try to resolve the hostname by env vars + * + * @return + */ + private static String resolveByEnv() { + if(CustomEnvVarName != null) { + return System.getenv(CustomEnvVarName); + } + + if(System.getProperty("os.name").startsWith("Windows")) { + return System.getenv(COMPUTERNAME); + } + + return System.getenv(HOSTNAME); + } +} diff --git a/src/test/java/riemann/java/client/tests/LocalhostResolveTest.java b/src/test/java/riemann/java/client/tests/LocalhostResolveTest.java new file mode 100644 index 0000000..5207564 --- /dev/null +++ b/src/test/java/riemann/java/client/tests/LocalhostResolveTest.java @@ -0,0 +1,146 @@ +package riemann.java.client.tests; + +import com.aphyr.riemann.client.LocalhostResolver; +import junit.framework.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class LocalhostResolveTest { + + protected Map env = new HashMap(); + + protected static final String OS_NAME_PROP = "os.name"; + protected static final String ExpectedWinHostname = "LocalHostResolverTestWin"; + protected static final String ExpectedNixHostname = "LocalHostResolverTestNix"; + + @BeforeClass + public static void oneTimeSetUp() { + LocalhostResolver.RefreshIntervalMillis = 1000; + } + + @Before + public void setup() { + env = new HashMap(System.getenv()); + env.put(LocalhostResolver.HOSTNAME, ExpectedNixHostname); + env.put(LocalhostResolver.COMPUTERNAME, ExpectedWinHostname); + setEnv(env); + + LocalhostResolver.CustomEnvVarName = null; + LocalhostResolver.resetUpdateTimes(); + Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime()); + Assert.assertEquals(0, LocalhostResolver.getLastNetUpdateTime()); + } + + @Test + public void testUpdateInterval() { + Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime()); + String hostname = LocalhostResolver.getResolvedHostname(); + long lastUpdateTime = LocalhostResolver.getLastUpdateTime(); + Assert.assertNotNull(hostname); + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + Assert.assertNotSame(lastUpdateTime, LocalhostResolver.getLastUpdateTime()); + } + + @Test + public void testNoEnvVars() throws UnknownHostException { + env.remove(LocalhostResolver.HOSTNAME); + env.remove(LocalhostResolver.COMPUTERNAME); + setEnv(env); + + String hostname = LocalhostResolver.getResolvedHostname(); + Assert.assertNotNull(hostname); + + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // ensure queried hostname without env vars + Assert.assertEquals(LocalhostResolver.getLastUpdateTime(), LocalhostResolver.getLastNetUpdateTime()); + Assert.assertEquals(java.net.InetAddress.getLocalHost().getHostName(), hostname); + } + + @Test + public void testEnvVarWindows() { + System.getProperties().put(OS_NAME_PROP, "Windows7"); + + String hostname = LocalhostResolver.getResolvedHostname(); + Assert.assertEquals(ExpectedWinHostname, hostname); + Assert.assertEquals(0, LocalhostResolver.getLastNetUpdateTime()); + } + + @Test + public void testEnvVarNix() { + System.getProperties().put(OS_NAME_PROP, "Linux"); + String hostname = LocalhostResolver.getResolvedHostname(); + Assert.assertEquals(ExpectedNixHostname, hostname); + Assert.assertEquals(0, LocalhostResolver.getLastNetUpdateTime()); + } + + @Test + public void testCustomEnvVar() { + final String customHostnameEnvVar = "AWS_HOST"; + final String customHostname = "EC2-LocalHostResolverTest"; + env.put(customHostnameEnvVar, customHostname); + setEnv(env); + + LocalhostResolver.CustomEnvVarName = customHostnameEnvVar; + String hostname = LocalhostResolver.getResolvedHostname(); + Assert.assertEquals(customHostname, hostname); + Assert.assertEquals(0, LocalhostResolver.getLastNetUpdateTime()); + } + + + /** + * evil hack for testing (only!) with env var in-memory modification + * see: http://stackoverflow.com/a/7201825/1469004 + * + * @param newEnv - to set in memory + */ + protected static void setEnv(Map newEnv) { + try { + Class processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); + Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); + theEnvironmentField.setAccessible(true); + Map env = (Map) theEnvironmentField.get(null); + env.putAll(newEnv); + Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); + theCaseInsensitiveEnvironmentField.setAccessible(true); + Map cienv = (Map) theCaseInsensitiveEnvironmentField.get(null); + cienv.putAll(newEnv); + } + catch (NoSuchFieldException e) { + try { + Class[] classes = Collections.class.getDeclaredClasses(); + Map env = System.getenv(); + for(Class cl : classes) { + if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { + Field field = cl.getDeclaredField("m"); + field.setAccessible(true); + Object obj = field.get(env); + Map map = (Map) obj; + map.clear(); + map.putAll(newEnv); + } + } + } catch (Exception e2) { + e2.printStackTrace(); + } + } catch (Exception e1) { + e1.printStackTrace(); + } + } +}