forked from riemann/riemann-java-client
-
Notifications
You must be signed in to change notification settings - Fork 2
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
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/main/java/com/aphyr/riemann/client/LocalhostResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
141
src/test/java/riemann/java/client/tests/LocalhostResolveTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
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); | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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()).
There was a problem hiding this comment.
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.