Skip to content

Commit

Permalink
try to add a better localhost resolver. See issue riemann#44
Browse files Browse the repository at this point in the history
  • Loading branch information
lassic committed Jan 1, 2015
1 parent bf8dd67 commit 075c413
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 21 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.aphyr</groupId>
<artifactId>riemann-java-client</artifactId>
<version>0.2.12-SNAPSHOT</version>
<version>0.2.13-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>Riemann Java Client</name>
<description>Java client for http://aphyr.github.com/riemann/</description>
Expand Down
23 changes: 9 additions & 14 deletions riemann-java-client.iml
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,18 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/com" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/test-annotations" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target/antrun" />
<excludeFolder url="file://$MODULE_DIR$/target/classes" />
<excludeFolder url="file://$MODULE_DIR$/target/maven-archiver" />
<excludeFolder url="file://$MODULE_DIR$/target/surefire" />
<excludeFolder url="file://$MODULE_DIR$/target/surefire-reports" />
<excludeFolder url="file://$MODULE_DIR$/target/test-classes" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:2.3.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit-dep:4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.codehaus.jsr166-mirror:jsr166y:1.7.0" level="project" />
<orderEntry type="library" name="Maven: com.google.protobuf:protobuf-java:2.5.0" level="project" />
<orderEntry type="library" name="Maven: io.netty:netty:3.6.1.Final" level="project" />
<orderEntry type="library" name="Maven: com.yammer.metrics:metrics-core:2.1.2" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.6.4" level="project" />
<orderEntry type="library" name="Maven: com.codahale.metrics:metrics-core:3.0.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit-dep:4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-all:1.9.5" level="project" />
</component>
</module>

</module>
8 changes: 2 additions & 6 deletions src/main/java/com/aphyr/riemann/client/EventDSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
91 changes: 91 additions & 0 deletions src/main/java/com/aphyr/riemann/client/LocalhostResolver.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
146 changes: 146 additions & 0 deletions src/test/java/riemann/java/client/tests/LocalhostResolveTest.java
Original file line number Diff line number Diff line change
@@ -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<String, String> env = new HashMap<String, String>();

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<String, String>(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<String, String> newEnv) {
try {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.putAll(newEnv);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.putAll(newEnv);
}
catch (NoSuchFieldException e) {
try {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> 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<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newEnv);
}
}
} catch (Exception e2) {
e2.printStackTrace();
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
}

0 comments on commit 075c413

Please sign in to comment.