Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

try to add a better localhost resolver. See issue #44 #1

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
93 changes: 93 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,93 @@
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;

// cached hostname result
private static boolean envResolved = false;
private static String hostname;

// update (refresh) time management
private static long lastUpdate = 0;
public static long getLastUpdateTime() { return lastUpdate; }
// this is mostly for testing, plus ability to force a refresh
public static void setLastUpdateTime(long time) {
lastUpdate = time;
}

static {
resolveByEnv();
}

/**
* 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 {
if(hostname == null || hostname.isEmpty()) {
hostname = java.net.InetAddress.getLocalHost().getHostName();
if (hostname == null) {
hostname = "localhost";
}
lastUpdate = System.currentTimeMillis();
}
} catch (UnknownHostException e) {
// fallthrough
}
}

/**
* try to resolve the hostname by env vars
*
*/
public static void resolveByEnv() {
String var;
if(System.getProperty("os.name").startsWith("Windows")) {
var = System.getenv(COMPUTERNAME);
if(var == null) {
var = "localhost";
}
}
else {
var = System.getenv(HOSTNAME);
}

hostname = var;
}
}
141 changes: 141 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,141 @@
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 {

private static Map<String, String> env = new HashMap<String, String>();

private static final String OS_NAME_PROP = "os.name";
private static final String EXPECTED_ENV_LOCALHOST = "env_localhost";

@BeforeClass
public static void oneTimeSetUp() {
// clear env vars
env.remove(LocalhostResolver.HOSTNAME);
env.remove(LocalhostResolver.COMPUTERNAME);
setEnv(env);

LocalhostResolver.refreshIntervalMillis = 1000;
}

@Before
public void setup() {
LocalhostResolver.setLastUpdateTime(0);
Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime());
}

@Test
public void testUpdateInterval() {
Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime());
String hostname = LocalhostResolver.getResolvedHostname();
long lastUpdateTime = LocalhostResolver.getLastUpdateTime();
Assert.assertNotNull(hostname);

// simulate time passing
LocalhostResolver.setLastUpdateTime(lastUpdateTime - (LocalhostResolver.refreshIntervalMillis + 500));

Assert.assertNotSame(lastUpdateTime, LocalhostResolver.getLastUpdateTime());
}

@Test
public void testCaching() {
Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime());
String hostname1 = LocalhostResolver.getResolvedHostname();
long updateTime1 = LocalhostResolver.getLastUpdateTime();
Assert.assertNotNull(hostname1);

String hostname2 = LocalhostResolver.getResolvedHostname();
long updateTime2 = LocalhostResolver.getLastUpdateTime();

Assert.assertEquals(hostname1, hostname2);
Assert.assertEquals(updateTime1, updateTime2);
}

@Test
public void testNoEnvVars() throws UnknownHostException {
String hostname = LocalhostResolver.getResolvedHostname();
long lastUpdateTime = LocalhostResolver.getLastUpdateTime();
Assert.assertNotNull(hostname);
Assert.assertNotSame(0, lastUpdateTime);

// ensure queried hostname without env vars
Assert.assertEquals(java.net.InetAddress.getLocalHost().getHostName(), hostname);
}

@Test
public void testEnvVarWindows() {
System.getProperties().put(OS_NAME_PROP, "Windows7");
env = new HashMap<String, String>(System.getenv());
env.put(LocalhostResolver.COMPUTERNAME, EXPECTED_ENV_LOCALHOST);
setEnv(env);
LocalhostResolver.resolveByEnv(); // force re-init

String hostname = LocalhostResolver.getResolvedHostname();
Assert.assertEquals(EXPECTED_ENV_LOCALHOST, hostname);
Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime());
}

@Test
public void testEnvVarNix() {
env = new HashMap<String, String>(System.getenv());
env.put(LocalhostResolver.HOSTNAME, EXPECTED_ENV_LOCALHOST);
setEnv(env);
LocalhostResolver.resolveByEnv(); // force re-init

System.getProperties().put(OS_NAME_PROP, "Linux");
String hostname = LocalhostResolver.getResolvedHostname();
Assert.assertEquals(EXPECTED_ENV_LOCALHOST, hostname);
Assert.assertEquals(0, LocalhostResolver.getLastUpdateTime());
}

/**
* 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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why??? Just add setEnv() in the production code and document it's a testing hook (which overrides getEnv()).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's talk about this, not sure why you prefer this approach.
I like to restrict hacks for testing to the test code and not actual code as much as possible.

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) {
throw new RuntimeException(e2);
}
} catch (Exception e1) {
throw new RuntimeException(e1);
}
}
}