Repository: pinpoint-apm/pinpoint
Issue: thrift plugin InetSocketAddress#getHostName issue
Labels: bug
module:plugin

**What version of pinpoint are you using?**
v1.7.3

**Describe the bug**
when thrift plugin retrieve connection information, it use InetSocketAddress#getHostName method , and may block the bussiness thread. 

**Expected behavior**
In order to avoid the main process blocked, this API should not be used.


background: 
https://stackoverflow.com/questions/10420317/java-inetaddress-gethostname-taking-a-very-long-time-to-execute

From the InetAddress#getHostName() javadocs, that method will perform **a reverse hostname lookup**. So the performance of that method call depends on the performance of the network/technology stack between the JVM and the domain name server for the target host.

In brief, that method will make a system call to perform the reverse lookup (e.g. getaddrinfo(3)) and that call will be implemented by the operating system to perform the network actions required to gather the host information via the Name Server configured for your machine.


Issue comments:
@nickycheng Thanks for reporting, will fix in master.

Pull request review comments:
getHostString() in jdk6 is a package level accessor.
So all versions of jdk1.6 should be checked that method is included.

@koo-taejin Thanks for the reminder.
`getHostString()` is present in 1.6.0_0 and in 1.6.0_45, so it should be there for other versions of 1.6 as well.

Patch that resolved the issue:
diff --git a/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/plugin/util/SocketAddressUtils.java b/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/plugin/util/SocketAddressUtils.java
new file mode 100644
index 000000000000..77c75857598f
--- /dev/null
+++ b/bootstrap-core/src/main/java/com/navercorp/pinpoint/bootstrap/plugin/util/SocketAddressUtils.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2018 NAVER Corp.
+ *
+ * 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.navercorp.pinpoint.bootstrap.plugin.util;
+
+import com.navercorp.pinpoint.bootstrap.logging.PLogger;
+import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
+import com.navercorp.pinpoint.common.util.JvmUtils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+/**
+ * @author HyunGil Jeong
+ */
+public final class SocketAddressUtils {
+
+    private static final PLogger LOGGER = PLoggerFactory.getLogger(SocketAddressUtils.class);
+
+    // TODO JDK 7 InetSocketAddress.getHostString() - reflection not needed once we drop JDK 6 support.
+    private static final HostStringAccessor HOST_STRING_ACCESSOR = createHostStringAccessor();
+
+    private static HostStringAccessor createHostStringAccessor() {
+        try {
+            final Method m = InetSocketAddress.class.getDeclaredMethod("getHostString");
+            m.setAccessible(true);
+            return new ReflectiveHostStringAccessor(m);
+        } catch (NoSuchMethodException e) {
+            LOGGER.error("[{}] {} - getHostString() not present in class InetSocketAddress.",
+                    JvmUtils.getType(), JvmUtils.getVersion());
+            throw new IllegalStateException("Unexpected InetSocketAddress class", e);
+        }
+    }
+
+    private interface HostStringAccessor {
+        String getHostString(InetSocketAddress inetSocketAddress);
+    }
+
+    private static class ReflectiveHostStringAccessor implements HostStringAccessor {
+
+        private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
+
+        private final Method method;
+
+        private ReflectiveHostStringAccessor(Method method) {
+            if (method == null) {
+                throw new NullPointerException("method must not be null");
+            }
+            this.method = method;
+        }
+
+        @Override
+        public String getHostString(InetSocketAddress inetSocketAddress) {
+            try {
+                return (String) method.invoke(inetSocketAddress);
+            } catch (IllegalAccessException e) {
+                logger.error("[{}] {} - Cannot access method : {}",
+                        JvmUtils.getType(), JvmUtils.getVersion(), method.getName(), e);
+            } catch (InvocationTargetException e) {
+                logger.error("[{}] {} - Error invoking method : {}",
+                        JvmUtils.getType(), JvmUtils.getVersion(), method.getName(), e);
+            } catch (Exception e) {
+                logger.error("[{}] {} - Unexpected error retrieving hostString",
+                        JvmUtils.getType(), JvmUtils.getVersion(), e);
+            }
+            return null;
+        }
+    }
+
+    private SocketAddressUtils() {}
+
+    /**
+     * Returns the hostname of the given {@link InetSocketAddress}, or the String form of the IP address
+     * if it does not have one. Returns {@code null} if neither can be retrieved.
+     *
+     * <p> This <b>does not</b> trigger a reverse DNS lookup if was created using an address and the hostname
+     * is not yet available.
+     *
+     * @param inetSocketAddress the socket address in which to retrieve the hostname from
+     * @return  the hostname or the String representation of the address, or {@code null} if neither
+     *          can be found.
+     *
+     * @see java.net.InetSocketAddress#getHostString()
+     */
+    public static String getHostNameFirst(InetSocketAddress inetSocketAddress) {
+        if (inetSocketAddress == null) {
+            return null;
+        }
+        return HOST_STRING_ACCESSOR.getHostString(inetSocketAddress);
+    }
+
+    /**
+     * Returns the hostname of the given {@link InetSocketAddress}, or the String form of the IP address
+     * if it does not have one. Returns {@code defaultHostName} if neither can be retrieved.
+     *
+     * <p>This <b>does not</b> trigger a reverse DNS lookup if was created using an address and the hostname
+     * is not yet available.
+     *
+     * @param inetSocketAddress the socket address in which to retrieve the hostname from
+     * @param defaultHostName   the value to return if neither the hostname nor the string form of the
+     *                          address can be found
+     * @return  the hostname or the String representation of the address, or the {@code defaultHostName}
+     *          if neither can be found.
+     *
+     * @see java.net.InetSocketAddress#getHostString()
+     */
+    public static String getHostNameFirst(InetSocketAddress inetSocketAddress, String defaultHostName) {
+        String hostName = getHostNameFirst(inetSocketAddress);
+        if (hostName == null) {
+            return defaultHostName;
+        }
+        return hostName;
+    }
+
+    /**
+     * Returns the String form of the IP address of the given {@link InetSocketAddress}, or the hostname
+     * if it has not been resolved. Returns {@code null} if neither can be retrieved.
+     *
+     * <p>This <b>does not</b> trigger a DNS lookup if the address has not been resolved yet by simply
+     * returning the hostname.
+     *
+     * @param inetSocketAddress the socket address in which to retrieve the address from
+     * @return  the String representation of the address or the hostname, or {@code null} if neither
+     *          can be found.
+     *
+     * @see InetSocketAddress#isUnresolved()
+     */
+    public static String getAddressFirst(InetSocketAddress inetSocketAddress) {
+        if (inetSocketAddress == null) {
+            return null;
+        }
+        InetAddress inetAddress = inetSocketAddress.getAddress();
+        if (inetAddress != null) {
+            return inetAddress.getHostAddress();
+        }
+        // This won't trigger a DNS lookup as if it got to here, the hostName should not be null on the basis
+        // that InetSocketAddress does not allow both address and hostName fields to be null.
+        return inetSocketAddress.getHostName();
+    }
+
+    /**
+     * Returns the String form of the IP address of the given {@link InetSocketAddress}, or the hostname
+     * if it has not been resolved. Returns {@code defaultAddress} if neither can be retrieved.
+     *
+     * <p>This <b>does not</b> trigger a DNS lookup if the address has not been resolved yet by simply
+     * returning the hostname.
+     *
+     * @param inetSocketAddress the socket address in which to retrieve the address from
+     * @param defaultAddress    the value to return if neither the string form of the address nor the
+     *                          hostname can be found
+     * @return  the String representation of the address or the hostname, or {@code defaultAddress} if neither
+     *          can be found.
+     *
+     * @see InetSocketAddress#isUnresolved()
+     */
+    public static String getAddressFirst(InetSocketAddress inetSocketAddress, String defaultAddress) {
+        String address = getAddressFirst(inetSocketAddress);
+        if (address == null) {
+            return defaultAddress;
+        }
+        return address;
+    }
+}
diff --git a/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/ThriftUtils.java b/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/ThriftUtils.java
index f912d7918ea6..6276cf4e46cf 100644
--- a/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/ThriftUtils.java
+++ b/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/ThriftUtils.java
@@ -16,6 +16,7 @@
 
 package com.navercorp.pinpoint.plugin.thrift;
 
+import com.navercorp.pinpoint.bootstrap.plugin.util.SocketAddressUtils;
 import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
 import org.apache.thrift.TBaseAsyncProcessor;
 import org.apache.thrift.TBaseProcessor;
@@ -85,16 +86,12 @@ public static String getAsyncMethodCallName(TAsyncMethodCall<?> asyncMethodCall)
      * @return the ip address retrieved from the given <tt>socketAddress</tt>,
      *      or {@literal ThriftConstants.UNKNOWN_ADDRESS} if it cannot be retrieved
      */
-    // TODO should probably be pulled up as a common API
     public static String getIp(SocketAddress socketAddress) {
-        if (socketAddress == null) {
-            return ThriftConstants.UNKNOWN_ADDRESS;
-        }
         if (socketAddress instanceof InetSocketAddress) {
-            InetSocketAddress addr = (InetSocketAddress)socketAddress;
-            return addr.getAddress().getHostAddress();
+            InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+            return SocketAddressUtils.getAddressFirst(inetSocketAddress, ThriftConstants.UNKNOWN_ADDRESS);
         }
-        return getSocketAddress(socketAddress);
+        return ThriftConstants.UNKNOWN_ADDRESS;
     }
     
     /**
@@ -104,16 +101,16 @@ public static String getIp(SocketAddress socketAddress) {
      * @return the ip/port retrieved from the given <tt>socketAddress</tt>,
      *      or {@literal ThriftConstants.UNKNOWN_ADDRESS} if it cannot be retrieved
      */
-    // TODO should probably be pulled up as a common API
     public static String getIpPort(SocketAddress socketAddress) {
-        if (socketAddress == null) {
-            return ThriftConstants.UNKNOWN_ADDRESS;
-        }
         if (socketAddress instanceof InetSocketAddress) {
-            InetSocketAddress addr = (InetSocketAddress)socketAddress;
-            return HostAndPort.toHostAndPortString(addr.getAddress().getHostAddress(), addr.getPort());
+            InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+            String address = SocketAddressUtils.getAddressFirst(inetSocketAddress);
+            if (address == null) {
+                return ThriftConstants.UNKNOWN_ADDRESS;
+            }
+            return HostAndPort.toHostAndPortString(address, inetSocketAddress.getPort());
         }
-        return getSocketAddress(socketAddress);
+        return ThriftConstants.UNKNOWN_ADDRESS;
     }
     
     /**
@@ -123,16 +120,12 @@ public static String getIpPort(SocketAddress socketAddress) {
      * @return the host retrieved from the given <tt>socketAddress</tt>,
      *      or {@literal ThriftConstants.UNKNOWN_ADDRESS} if it cannot be retrieved
      */
-    // TODO should probably be pulled up as a common API
     public static String getHost(SocketAddress socketAddress) {
-        if (socketAddress == null) {
-            return ThriftConstants.UNKNOWN_ADDRESS;
-        }
         if (socketAddress instanceof InetSocketAddress) {
-            InetSocketAddress addr = (InetSocketAddress)socketAddress;
-            return addr.getHostName();
+            InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+            return SocketAddressUtils.getHostNameFirst(inetSocketAddress, ThriftConstants.UNKNOWN_ADDRESS);
         }
-        return getSocketAddress(socketAddress);
+        return ThriftConstants.UNKNOWN_ADDRESS;
     }
     
     /**
@@ -142,32 +135,15 @@ public static String getHost(SocketAddress socketAddress) {
      * @return the host/port retrieved from the given <tt>socketAddress</tt>,
      *      or {@literal ThriftConstants.UNKNOWN_ADDRESS} if it cannot be retrieved
      */
-    // TODO should probably be pulled up as a common API
     public static String getHostPort(SocketAddress socketAddress) {
-        if (socketAddress == null) {
-            return ThriftConstants.UNKNOWN_ADDRESS;
-        }
         if (socketAddress instanceof InetSocketAddress) {
-            InetSocketAddress addr = (InetSocketAddress)socketAddress;
-            return HostAndPort.toHostAndPortString(addr.getHostName(), addr.getPort());
-        }
-        return getSocketAddress(socketAddress);
-    }
-    
-    private static String getSocketAddress(SocketAddress socketAddress) {
-        String address = socketAddress.toString();
-        int addressLength = address.length();
-
-        if (addressLength > 0) {
-            if (address.startsWith("/")) {
-                return address.substring(1);
-            } else {
-                final int delimiterIndex = address.indexOf('/');
-                if (delimiterIndex != -1 && delimiterIndex < addressLength) {
-                    return address.substring(address.indexOf('/') + 1);
-                }
+            InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+            String hostName = SocketAddressUtils.getHostNameFirst(inetSocketAddress);
+            if (hostName == null) {
+                return ThriftConstants.UNKNOWN_ADDRESS;
             }
+            return HostAndPort.toHostAndPortString(hostName, inetSocketAddress.getPort());
         }
-        return address;
+        return ThriftConstants.UNKNOWN_ADDRESS;
     }
 }
diff --git a/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/server/TBaseProcessorProcessInterceptor.java b/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/server/TBaseProcessorProcessInterceptor.java
index f09b89ac78ed..a29a9710d1ac 100644
--- a/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/server/TBaseProcessorProcessInterceptor.java
+++ b/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/server/TBaseProcessorProcessInterceptor.java
@@ -16,12 +16,9 @@
 
 package com.navercorp.pinpoint.plugin.thrift.interceptor.server;
 
-import java.net.Socket;
 
 import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScope;
 import org.apache.thrift.TBaseProcessor;
-import org.apache.thrift.protocol.TProtocol;
-import org.apache.thrift.transport.TTransport;
 
 import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor;
 import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
@@ -35,7 +32,6 @@
 import com.navercorp.pinpoint.plugin.thrift.ThriftClientCallContext;
 import com.navercorp.pinpoint.plugin.thrift.ThriftConstants;
 import com.navercorp.pinpoint.plugin.thrift.ThriftUtils;
-import com.navercorp.pinpoint.plugin.thrift.field.accessor.SocketFieldAccessor;
 
 /**
  * Entry/exit point for tracing synchronous processors for Thrift services.
@@ -133,30 +129,6 @@ private void processTraceObject(final Trace trace, Object target, Object[] args,
         SpanRecorder recorder = trace.getSpanRecorder();
         String methodUri = getMethodUri(target);
         recorder.recordRpcName(methodUri);
-        // retrieve connection information
-        String localIpPort = ThriftConstants.UNKNOWN_ADDRESS;
-        String remoteAddress = ThriftConstants.UNKNOWN_ADDRESS;
-        if (args.length == 2 && args[0] instanceof TProtocol) {
-            TProtocol inputProtocol = (TProtocol)args[0];
-            TTransport inputTransport = inputProtocol.getTransport();
-            if (inputTransport instanceof SocketFieldAccessor) {
-                Socket socket = ((SocketFieldAccessor)inputTransport)._$PINPOINT$_getSocket();
-                if (socket != null) {
-                    localIpPort = ThriftUtils.getHostPort(socket.getLocalSocketAddress());
-                    remoteAddress = ThriftUtils.getHost(socket.getRemoteSocketAddress());
-                }
-            } else {
-                if (isDebug) {
-                    logger.debug("Invalid target object. Need field accessor({}).", SocketFieldAccessor.class.getName());
-                }
-            }
-        }
-        if (localIpPort != ThriftConstants.UNKNOWN_ADDRESS) {
-            recorder.recordEndPoint(localIpPort);
-        }
-        if (remoteAddress != ThriftConstants.UNKNOWN_ADDRESS) {
-            recorder.recordRemoteAddress(remoteAddress);
-        }
     }
 
     private String getMethodUri(Object target) {
diff --git a/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/tprotocol/server/TProtocolReadMessageEndInterceptor.java b/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/tprotocol/server/TProtocolReadMessageEndInterceptor.java
index ebef5c275507..4e2d43fed7bd 100644
--- a/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/tprotocol/server/TProtocolReadMessageEndInterceptor.java
+++ b/plugins/thrift/src/main/java/com/navercorp/pinpoint/plugin/thrift/interceptor/tprotocol/server/TProtocolReadMessageEndInterceptor.java
@@ -250,10 +250,10 @@ private void recordConnection(SpanRecorder recorder, TTransport transport) {
         // retrieve connection information
         String localIpPort = ThriftConstants.UNKNOWN_ADDRESS;
         String remoteAddress = ThriftConstants.UNKNOWN_ADDRESS;
-        Socket socket = ((SocketFieldAccessor)transport)._$PINPOINT$_getSocket();
+        Socket socket = ((SocketFieldAccessor) transport)._$PINPOINT$_getSocket();
         if (socket != null) {
-            localIpPort = ThriftUtils.getHostPort(socket.getLocalSocketAddress());
-            remoteAddress = ThriftUtils.getHost(socket.getRemoteSocketAddress());
+            localIpPort = ThriftUtils.getIpPort(socket.getLocalSocketAddress());
+            remoteAddress = ThriftUtils.getIp(socket.getRemoteSocketAddress());
         }
         if (localIpPort != ThriftConstants.UNKNOWN_ADDRESS) {
             recorder.recordEndPoint(localIpPort);
diff --git a/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.java b/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.java
index 33e16c989fcc..d6185768d094 100644
--- a/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.java
+++ b/profiler/src/main/java/com/navercorp/pinpoint/profiler/DefaultAgent.java
@@ -23,6 +23,7 @@
 import com.navercorp.pinpoint.bootstrap.logging.PLogger;
 import com.navercorp.pinpoint.bootstrap.logging.PLoggerBinder;
 import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
+import com.navercorp.pinpoint.bootstrap.plugin.util.SocketAddressUtils;
 import com.navercorp.pinpoint.common.util.Assert;
 import com.navercorp.pinpoint.profiler.context.module.ApplicationContext;
 import com.navercorp.pinpoint.profiler.context.module.DefaultApplicationContext;
@@ -83,6 +84,8 @@ public DefaultAgent(AgentOption agentOption) {
 
         changeStatus(AgentStatus.INITIALIZING);
 
+        preloadOnStartup();
+
         this.profilerConfig = agentOption.getProfilerConfig();
 
         this.applicationContext = newApplicationContext(agentOption);
@@ -130,6 +133,11 @@ private void bindPLoggerFactory(PLoggerBinder binder) {
         PLoggerFactory.initialize(binder);
     }
 
+    private void preloadOnStartup() {
+        // Preload to fail fast on startup. This won't be necessary once JDK 6 support ends
+        // and reflective method handle is not needed.
+        SocketAddressUtils.getHostNameFirst(null);
+    }
 
     @Override
     public void start() {


Test patch:
diff --git a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/AsyncEchoTestClient.java b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/AsyncEchoTestClient.java
index 0a952a5be2f6..8a97ef52d9c6 100644
--- a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/AsyncEchoTestClient.java
+++ b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/AsyncEchoTestClient.java
@@ -24,6 +24,8 @@
 import java.nio.channels.SelectionKey;
 import java.util.concurrent.CountDownLatch;
 
+import com.navercorp.pinpoint.bootstrap.plugin.util.SocketAddressUtils;
+import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
 import org.apache.thrift.TBase;
 import org.apache.thrift.TException;
 import org.apache.thrift.TServiceClient;
@@ -79,7 +81,10 @@ public String echo(String message) throws TException {
 
     @Override
     public void verifyTraces(PluginTestVerifier verifier, String expectedMessage) throws Exception {
-        final InetSocketAddress actualServerAddress = this.environment.getServerAddress();
+        final InetSocketAddress socketAddress = this.environment.getServerAddress();
+        final String hostName = SocketAddressUtils.getHostNameFirst(socketAddress);
+        // refer to com.navercorp.pinpoint.plugin.thrift.ThriftUtils#getHostPort
+        final String remoteAddress = HostAndPort.toHostAndPortString(hostName, socketAddress.getPort());
         // ********** Asynchronous Traces
         // SpanEvent - Asynchronous Invocation
         ExpectedTrace asyncInvocationTrace = event("ASYNC", "Asynchronous Invocation");
@@ -101,13 +106,13 @@ public void verifyTraces(PluginTestVerifier verifier, String expectedMessage) th
         // ********** Root trace for Asynchronous traces
         // SpanEvent - TAsyncClientManager.call
         Method call = TAsyncClientManager.class.getDeclaredMethod("call", TAsyncMethodCall.class);
-        ExpectedAnnotation thriftUrl = Expectations.annotation("thrift.url", actualServerAddress.getHostName() + ":"
-                + actualServerAddress.getPort() + "/com/navercorp/pinpoint/plugin/thrift/dto/EchoService/echo_call");
+        ExpectedAnnotation thriftUrl = Expectations.annotation("thrift.url",
+                remoteAddress + "/com/navercorp/pinpoint/plugin/thrift/dto/EchoService/echo_call");
         ExpectedTrace callTrace = event("THRIFT_CLIENT", // ServiceType
                 call, // Method
                 null, // rpc
                 null, // endPoint
-                actualServerAddress.getHostName() + ":" + actualServerAddress.getPort(), // destinationId
+                remoteAddress, // destinationId
                 thriftUrl // Annotation("thrift.url")
         );
         verifier.verifyTrace(async(callTrace, asyncInvocationTrace, cleanUpAndFireCallbackTrace, receiveBaseTrace));
diff --git a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/SyncEchoTestClient.java b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/SyncEchoTestClient.java
index 0df82d87b6b4..0b4878c7612e 100644
--- a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/SyncEchoTestClient.java
+++ b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/client/SyncEchoTestClient.java
@@ -21,6 +21,7 @@
 import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
 
+import com.navercorp.pinpoint.bootstrap.plugin.util.SocketAddressUtils;
 import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
 import org.apache.thrift.TBase;
 import org.apache.thrift.TException;
@@ -60,14 +61,17 @@ public final String echo(String message) throws TException {
 
     @Override
     public void verifyTraces(PluginTestVerifier verifier, String expectedMessage) throws Exception {
-        final InetSocketAddress actualServerAddress = this.environment.getServerAddress();
+        final InetSocketAddress socketAddress = this.environment.getServerAddress();
+        final String hostName = SocketAddressUtils.getHostNameFirst(socketAddress);
+        // refer to com.navercorp.pinpoint.plugin.thrift.ThriftUtils#getHostPort
+        final String remoteAddress = HostAndPort.toHostAndPortString(hostName, socketAddress.getPort());
         // SpanEvent - TServiceClient.sendBase
         Method sendBase = TServiceClient.class.getDeclaredMethod("sendBase", String.class, TBase.class);
         // refer to com.navercorp.pinpoint.plugin.thrift.ThriftUtils#getClientServiceName
-        ExpectedAnnotation thriftUrl = Expectations.annotation("thrift.url", actualServerAddress.getHostName() + ":"
-                + actualServerAddress.getPort() + "/com/navercorp/pinpoint/plugin/thrift/dto/EchoService/echo");
-        ExpectedAnnotation thriftArgs = Expectations.annotation("thrift.args", "echo_args(message:" + expectedMessage
-                + ")");
+        ExpectedAnnotation thriftUrl = Expectations.annotation("thrift.url",
+                remoteAddress + "/com/navercorp/pinpoint/plugin/thrift/dto/EchoService/echo");
+        ExpectedAnnotation thriftArgs = Expectations.annotation("thrift.args",
+                "echo_args(message:" + expectedMessage + ")");
 
         // SpanEvent - TServiceClient.receiveBase
         Method receiveBase = TServiceClient.class.getDeclaredMethod("receiveBase", TBase.class, String.class);
@@ -78,7 +82,7 @@ public void verifyTraces(PluginTestVerifier verifier, String expectedMessage) th
                 sendBase, // Method
                 null, // rpc
                 null, // endPoint
-                HostAndPort.toHostAndPortString(actualServerAddress.getHostName(), actualServerAddress.getPort()), // destinationId
+                remoteAddress, // destinationId
                 thriftUrl, // Annotation("thrift.url")
                 thriftArgs), // Annotation("thrift.args")
                 event("THRIFT_CLIENT_INTERNAL", // ServiceType
diff --git a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/AsyncEchoTestServer.java b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/AsyncEchoTestServer.java
index ab901ae30f9c..187a493fcb77 100644
--- a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/AsyncEchoTestServer.java
+++ b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/AsyncEchoTestServer.java
@@ -22,6 +22,8 @@
 import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
 
+import com.navercorp.pinpoint.bootstrap.plugin.util.SocketAddressUtils;
+import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
 import org.apache.thrift.TBaseAsyncProcessor;
 import org.apache.thrift.TProcessor;
 import org.apache.thrift.server.AbstractNonblockingServer;
@@ -49,15 +51,16 @@ protected AsyncEchoTestServer(T server, TestEnvironment environment) throws TTra
 
     @Override
     public void verifyServerTraces(PluginTestVerifier verifier) throws Exception {
-        final InetSocketAddress actualServerAddress = super.environment.getServerAddress();
+        final InetSocketAddress socketAddress = super.environment.getServerAddress();
+        final String address = SocketAddressUtils.getAddressFirst(socketAddress);
         verifier.verifyTraceCount(2);
         Method process = TBaseAsyncProcessor.class.getDeclaredMethod("process", AsyncFrameBuffer.class);
         // RootSpan
         verifier.verifyTrace(root("THRIFT_SERVER", // ServiceType,
                 "Thrift Server Invocation", // Method
                 "com/navercorp/pinpoint/plugin/thrift/dto/EchoService/echo", // rpc
-                actualServerAddress.getHostName() + ":" + actualServerAddress.getPort(), // endPoint
-                actualServerAddress.getHostName() // remoteAddress
+                HostAndPort.toHostAndPortString(address, socketAddress.getPort()), // endPoint
+                address // remoteAddress
         ));
         // SpanEvent - TBaseAsyncProcessor.process
         verifier.verifyTrace(event("THRIFT_SERVER_INTERNAL", process));
diff --git a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/SyncEchoTestServer.java b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/SyncEchoTestServer.java
index fa8361b9dc0f..53e50c30f132 100644
--- a/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/SyncEchoTestServer.java
+++ b/agent/src/test/java/com/navercorp/pinpoint/plugin/thrift/common/server/SyncEchoTestServer.java
@@ -22,6 +22,8 @@
 import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
 
+import com.navercorp.pinpoint.bootstrap.plugin.util.SocketAddressUtils;
+import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
 import org.apache.thrift.TBaseProcessor;
 import org.apache.thrift.TProcessor;
 import org.apache.thrift.protocol.TProtocol;
@@ -52,7 +54,8 @@ protected SyncEchoTestServer(T server, TestEnvironment environment) throws TTran
 
     @Override
     public void verifyServerTraces(PluginTestVerifier verifier) throws Exception {
-        final InetSocketAddress actualServerAddress = super.environment.getServerAddress();
+        final InetSocketAddress socketAddress = super.environment.getServerAddress();
+        final String address = SocketAddressUtils.getAddressFirst(socketAddress);
         verifier.verifyTraceCount(2);
         Method process = TBaseProcessor.class.getDeclaredMethod("process", TProtocol.class, TProtocol.class);
         verifier.verifyDiscreteTrace(
@@ -60,8 +63,8 @@ public void verifyServerTraces(PluginTestVerifier verifier) throws Exception {
                 root("THRIFT_SERVER", // ServiceType,
                         "Thrift Server Invocation", // Method
                         "com/navercorp/pinpoint/plugin/thrift/dto/EchoService/echo", // rpc
-                        actualServerAddress.getHostName() + ":" + actualServerAddress.getPort(), // endPoint
-                        actualServerAddress.getHostName()), // remoteAddress
+                        HostAndPort.toHostAndPortString(address, socketAddress.getPort()), // endPoint
+                        address), // remoteAddress
                 // SpanEvent - TBaseProcessor.process
                 event("THRIFT_SERVER_INTERNAL", process));
         verifier.verifyTraceCount(0);
diff --git a/bootstrap-core/src/test/java/com/navercorp/pinpoint/bootstrap/plugin/util/SocketAddressUtilsTest.java b/bootstrap-core/src/test/java/com/navercorp/pinpoint/bootstrap/plugin/util/SocketAddressUtilsTest.java
new file mode 100644
index 000000000000..2ffa1c97abc1
--- /dev/null
+++ b/bootstrap-core/src/test/java/com/navercorp/pinpoint/bootstrap/plugin/util/SocketAddressUtilsTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2018 NAVER Corp.
+ *
+ * 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.navercorp.pinpoint.bootstrap.plugin.util;
+
+import com.navercorp.pinpoint.common.util.JvmUtils;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.AssumptionViolatedException;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Collections;
+
+/**
+ * @author HyunGil Jeong
+ */
+public class SocketAddressUtilsTest {
+
+    private static final String VALID_HOST = "naver.com";
+    private static final String INVALID_HOST = "pinpoint-none-existent-domain-hopefully";
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SocketAddressUtilsTest.class);
+
+    private static NameServiceReplacer NAME_SERVICE_REPLACER;
+
+    @BeforeClass
+    public static void setUpBeforeClass() {
+        NAME_SERVICE_REPLACER = createNameServiceReplacer();
+    }
+
+    @Test
+    public void fromValidHostName() {
+        String hostName = VALID_HOST;
+        InetSocketAddress socketAddress = new InetSocketAddress(hostName, 80);
+        assertResolved(socketAddress);
+
+        final String expectedHostName = hostName;
+        final String expectedAddress = socketAddress.getAddress().getHostAddress();
+
+        verify(socketAddress, expectedHostName, expectedAddress);
+    }
+
+    @Test
+    public void fromValidHostNameUnresolved() {
+        String hostName = VALID_HOST;
+        InetSocketAddress socketAddress = InetSocketAddress.createUnresolved(hostName, 80);
+        assertUnresolved(socketAddress);
+
+        final String expectedHostName = hostName;
+        final String expectedAddress = hostName;
+
+        verify(socketAddress, expectedHostName, expectedAddress);
+    }
+
+    @Test
+    public void fromInvalidHostName() {
+        String hostName = INVALID_HOST;
+        InetSocketAddress socketAddress = new InetSocketAddress(hostName, 80);
+        assertUnresolved(socketAddress);
+
+        final String expectedHostName = hostName;
+        final String expectedAddress = hostName;
+
+        verify(socketAddress, expectedHostName, expectedAddress);
+    }
+
+    @Test
+    public void fromAddress() {
+        String hostName = VALID_HOST;
+        InetAddress inetAddress = createAddressFromHostName(hostName);
+        Assume.assumeNotNull(inetAddress);
+
+        String address = inetAddress.getHostAddress();
+        InetSocketAddress socketAddress = new InetSocketAddress(address, 80);
+        assertResolved(socketAddress);
+
+        final String expectedHostName = address;
+        final String expectedAddress = address;
+
+        verify(socketAddress, expectedHostName, expectedAddress);
+    }
+
+    @Test
+    public void fromLookedUpAddress() {
+        String hostName = VALID_HOST;
+        InetAddress inetAddress = createAddressFromHostName(hostName);
+        Assume.assumeNotNull(inetAddress);
+
+        String address = inetAddress.getHostAddress();
+        InetSocketAddress socketAddress = new InetSocketAddress(address, 80);
+        assertResolved(socketAddress);
+
+        String expectedHostName = socketAddress.getHostName(); // lookup host name
+        String expectedAddress = address;
+
+        verify(socketAddress, expectedHostName, expectedAddress);
+    }
+
+    @Test
+    public void fromLocalAddress() {
+        InetAddress inetAddress = createLocalAddress();
+        Assume.assumeNotNull(inetAddress);
+
+        String address = inetAddress.getHostAddress();
+        InetSocketAddress socketAddress = new InetSocketAddress(address, 80);
+        assertResolved(socketAddress);
+
+        final String expectedHostName = address;
+        final String expectedAddress = address;
+
+        verify(socketAddress, expectedHostName, expectedAddress);
+    }
+
+    // Helpers
+
+    private static void assertResolved(InetSocketAddress socketAddress) {
+        Assert.assertFalse(socketAddress.isUnresolved());
+    }
+
+    private static void assertUnresolved(InetSocketAddress socketAddress) {
+        Assert.assertTrue(socketAddress.isUnresolved());
+    }
+
+    private static void verify(InetSocketAddress socketAddress, String expectedHostName, String expectedAddress) {
+        NAME_SERVICE_REPLACER.replace();
+        try {
+            String actualHostName = SocketAddressUtils.getHostNameFirst(socketAddress);
+            String actualAddress = SocketAddressUtils.getAddressFirst(socketAddress);
+            LOGGER.info("expectedHostName : {}, actualHostName : {}", expectedHostName, actualHostName);
+            LOGGER.info("expectedAddress : {}, actualAddress : {}", expectedAddress, actualAddress);
+            Assert.assertEquals(expectedHostName, actualHostName);
+            Assert.assertEquals(expectedAddress, actualAddress);
+        } finally {
+            NAME_SERVICE_REPLACER.rollback();
+        }
+    }
+
+    private static InetAddress createLocalAddress() {
+        try {
+            return InetAddress.getLocalHost();
+        } catch (UnknownHostException e) {
+            LOGGER.warn("Error creating local InetAddress", e);
+            return null;
+        }
+    }
+
+    private static InetAddress createAddressFromHostName(String hostName) {
+        try {
+            return InetAddress.getByName(hostName);
+        } catch (UnknownHostException e) {
+            LOGGER.warn("Error creating InetAddress from host : {}", hostName, e);
+            return null;
+        }
+    }
+
+    private interface NameServiceReplacer {
+        void replace();
+        void rollback();
+    }
+
+    private static NameServiceReplacer createNameServiceReplacer() {
+
+        Class<?> nameServiceClass = null;
+        try {
+            // pre jdk 9
+            nameServiceClass = Class.forName("sun.net.spi.nameservice.NameService");
+        } catch (ClassNotFoundException e) {
+            try {
+                // post jdk 9
+                nameServiceClass = Class.forName("java.net.InetAddress$NameService");
+            } catch (ClassNotFoundException e1) {
+                // ignore and skip test below
+            }
+        }
+
+        if (nameServiceClass == null) {
+            LOGGER.error("[{}] {} - NameService class not found, skipping test.",
+                    JvmUtils.getType(), JvmUtils.getVersion());
+            throw new AssumptionViolatedException("NameService class required for test not found.");
+        }
+
+        ClassLoader cl = InetAddress.class.getClassLoader();
+        final Object proxy = Proxy.newProxyInstance(cl, new Class<?>[]{nameServiceClass}, new InvocationHandler() {
+            @Override
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                String methodName = method.getName();
+                if (methodName.equals("lookupAllHostAddr")) {
+                    throw new UnsupportedOperationException("DNS lookup should not be made");
+                } else if (methodName.equals("getHostByAddr")) {
+                    throw new UnsupportedOperationException("Reverse DNS lookup should not be made");
+                } else if (methodName.equals("toString")) {
+                    return SocketAddressUtilsTest.class.getSimpleName() + " Name Service";
+                } else {
+                    LOGGER.error("[{}] {} - Unexpected method invocation : {}",
+                            JvmUtils.getType(), JvmUtils.getVersion(), methodName);
+                    throw new IllegalStateException("Unknown method : " + methodName + " invoked");
+                }
+            }
+        });
+
+        // jdk 7, 8
+        final String nameServicesFieldName = "nameServices";
+        final Object nameServicesFieldValue = getNameServiceFieldValue(nameServicesFieldName);
+        if (nameServicesFieldValue != null) {
+            return new NameServiceReplacer() {
+                @Override
+                public void replace() {
+                    setNameServiceFieldValue(nameServicesFieldName, Collections.singletonList(proxy));
+                }
+
+                @Override
+                public void rollback() {
+                    setNameServiceFieldValue(nameServicesFieldName, nameServicesFieldValue);
+                }
+            };
+        }
+
+        // jdk 6, jdk 9+
+        final String nameServiceFieldName = "nameService";
+        final Object nameServiceFieldValue = getNameServiceFieldValue(nameServiceFieldName);
+        if (nameServiceFieldValue != null) {
+            return new NameServiceReplacer() {
+                @Override
+                public void replace() {
+                    setNameServiceFieldValue(nameServiceFieldName, proxy);
+                }
+
+                @Override
+                public void rollback() {
+                    setNameServiceFieldValue(nameServiceFieldName, nameServiceFieldValue);
+                }
+            };
+        }
+
+        LOGGER.error("[{}] {} - Field for name service not found.", JvmUtils.getType(), JvmUtils.getVersion());
+        throw new AssumptionViolatedException("Cannot find field for name service.");
+    }
+
+    private static Object getNameServiceFieldValue(String fieldName) {
+        try {
+            Field nameServiceField = InetAddress.class.getDeclaredField(fieldName);
+            nameServiceField.setAccessible(true);
+            return nameServiceField.get(null);
+        } catch (NoSuchFieldException e) {
+            // This can happen depending on jvm
+            return null;
+        } catch (Exception e) {
+            LOGGER.error("[{}] {} - Unexpected error while getting field [{}], skipping test",
+                    JvmUtils.getType(), JvmUtils.getVersion(), fieldName, e);
+            throw new AssumptionViolatedException("Unexpected reflection exception", e);
+        }
+    }
+
+    private static void setNameServiceFieldValue(String fieldName, Object nameService) {
+        try {
+            Field nameServiceField = InetAddress.class.getDeclaredField(fieldName);
+            nameServiceField.setAccessible(true);
+            nameServiceField.set(null, nameService);
+        } catch (NoSuchFieldException e) {
+            throw new IllegalStateException("Expected field : [" + fieldName + "] not found");
+        } catch (Exception e) {
+            LOGGER.error("[{}] {} - Unexpected error while setting field [{}], skipping test",
+                    JvmUtils.getType(), JvmUtils.getVersion(), fieldName, e);
+            throw new AssumptionViolatedException("Unexpected reflection exception", e);
+        }
+    }
+}