diff --git a/bin/as.sh b/bin/as.sh index 7571559bb..2eb5b58f3 100755 --- a/bin/as.sh +++ b/bin/as.sh @@ -134,12 +134,15 @@ COMMAND= # batch file to execute BATCH_FILE= -# tunnel server url -TUNNEL_SERVER= - # channel server address CHANNEL_SERVER= +# channel client heartbeat interval seconds +HEARTBEAT_INTERVAL= + +# tunnel server url +TUNNEL_SERVER= + # agent id AGENT_ID= @@ -406,7 +409,7 @@ usage() Usage: $0 [-h] [--target-ip ] [--telnet-port ] [--http-port ] [--session-timeout ] [--arthas-home ] - [--channel-server ] + [--channel-server ] [--heartbeat-interval ] [--tunnel-server ] [--agent-id ] [--stat-url ] [--app-name ] [--username ] [--password ] @@ -427,8 +430,9 @@ Options and Arguments: --use-http Enforce use http to download, default use https --attach-only Attach target process only, do not connect --debug-attach Debug attach agent - --tunnel-server Remote tunnel server url --channel-server Remote channel server address + --heartbeat-interval The arthas agent (channel client) heartbeat interval seconds, default 5 + --tunnel-server Remote tunnel server url --agent-id Special agent id --app-name Special app name --username Special username @@ -450,6 +454,7 @@ EXAMPLES: ./as.sh --tunnel-server 'ws://192.168.10.11:7777/ws' --app-name demoapp ./as.sh --tunnel-server 'ws://192.168.10.11:7777/ws' --agent-id bvDOe8XbTM2pQWjF4cfw ./as.sh --channel-server '192.168.10.11:7700' + ./as.sh --channel-server '192.168.10.11:7700' --heartbeat-interval 10 ./as.sh --channel-server '192.168.10.11:7700' --agent-id bvDOe8XbTM2pQWjF4cfw ./as.sh --stat-url 'http://192.168.10.11:8080/api/stat' ./as.sh -c 'sysprop; thread' @@ -603,13 +608,18 @@ parse_arguments() shift # past argument shift # past value ;; - --tunnel-server) - TUNNEL_SERVER="$2" + --channel-server) + CHANNEL_SERVER="$2" shift # past argument shift # past value ;; - --channel-server) - CHANNEL_SERVER="$2" + --heartbeat-interval) + HEARTBEAT_INTERVAL="$2" + shift # past argument + shift # past value + ;; + --tunnel-server) + TUNNEL_SERVER="$2" shift # past argument shift # past value ;; @@ -819,14 +829,18 @@ attach_jvm() fi local tempArgs=() - if [ "${TUNNEL_SERVER}" ]; then - tempArgs+=("-tunnel-server") - tempArgs+=("${TUNNEL_SERVER}") - fi if [ "${CHANNEL_SERVER}" ]; then tempArgs+=("-channel-server") tempArgs+=("${CHANNEL_SERVER}") fi + if [ "${HEARTBEAT_INTERVAL}" ]; then + tempArgs+=("-heartbeat-interval") + tempArgs+=("${CHANNEL_SERVER}") + fi + if [ "${TUNNEL_SERVER}" ]; then + tempArgs+=("-tunnel-server") + tempArgs+=("${TUNNEL_SERVER}") + fi if [ "${AGENT_ID}" ]; then tempArgs+=("-agent-id") tempArgs+=("${AGENT_ID}") diff --git a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java index f271f176b..da2a00ad9 100644 --- a/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java +++ b/boot/src/main/java/com/taobao/arthas/boot/Bootstrap.java @@ -31,6 +31,7 @@ import com.taobao.middleware.cli.CommandLine; import com.taobao.middleware.cli.UsageMessageFormatter; import com.taobao.middleware.cli.annotations.Argument; import com.taobao.middleware.cli.annotations.CLIConfigurator; +import com.taobao.middleware.cli.annotations.DefaultValue; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; import com.taobao.middleware.cli.annotations.Option; @@ -51,6 +52,7 @@ import static com.taobao.arthas.boot.ProcessUtils.STATUS_EXEC_TIMEOUT; + " java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws' --app-name demoapp\n" + " java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws' --agent-id bvDOe8XbTM2pQWjF4cfw\n" + " java -jar arthas-boot.jar --channel-server '192.168.10.11:7700'\n" + + " java -jar arthas-boot.jar --channel-server '192.168.10.11:7700' --heartbeat-interval 10\n" + " java -jar arthas-boot.jar --channel-server '192.168.10.11:7700' --agent-id bvDOe8XbTM2pQWjF4cfw\n" + " java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'\n" + " java -jar arthas-boot.jar -c 'sysprop; thread' \n" @@ -118,8 +120,10 @@ public class Bootstrap { private String command; private String batchFile; - private String tunnelServer; private String channelServer; + private int heartbeatInterval; + + private String tunnelServer; private String agentId; private String appName; @@ -255,18 +259,25 @@ public class Bootstrap { this.verbose = verbose; } - @Option(longName = "tunnel-server") - @Description("The tunnel server url") - public void setTunnelServer(String tunnelServer) { - this.tunnelServer = tunnelServer; - } - @Option(longName = "channel-server") @Description("The channel server address") public void setChannelServer(String channelServer) { this.channelServer = channelServer; } + @Option(longName = "heartbeat-interval") + @DefaultValue("5") + @Description("The arthas agent (channel client) heartbeat interval seconds") + public void setHeartbeatInterval(int heartbeatInterval) { + this.heartbeatInterval = heartbeatInterval; + } + + @Option(longName = "tunnel-server") + @Description("The tunnel server url") + public void setTunnelServer(String tunnelServer) { + this.tunnelServer = tunnelServer; + } + @Option(longName = "agent-id") @Description("The agent id register to tunnel server") public void setAgentId(String agentId) { @@ -837,14 +848,18 @@ public class Bootstrap { return width; } - public String getTunnelServer() { - return tunnelServer; - } - public String getChannelServer() { return channelServer; } + public int getHeartbeatInterval() { + return heartbeatInterval; + } + + public String getTunnelServer() { + return tunnelServer; + } + public String getAgentId() { return agentId; } diff --git a/channel/channel-client/src/main/java/com/alibaba/arthas/channel/client/ChannelClient.java b/channel/channel-client/src/main/java/com/alibaba/arthas/channel/client/ChannelClient.java index 84f327f78..6982a98b9 100644 --- a/channel/channel-client/src/main/java/com/alibaba/arthas/channel/client/ChannelClient.java +++ b/channel/channel-client/src/main/java/com/alibaba/arthas/channel/client/ChannelClient.java @@ -46,12 +46,14 @@ public class ChannelClient { private ManagedChannel channel; private ScheduledFuture reconnectFuture; private String channelServerAddress; - private int reconnectDelay = 5000; + private int reconnectDelay = 5; + private int heartbeatInterval = 5; private EventLoopGroup group; //channel info private String channelVersion = "1.0.0"; private List channelFeatures = Arrays.asList("WebConsole", "ExecuteCommand"); + private long lastHeartbeatTime; public ChannelClient(String host, int port) { this.host = host; @@ -263,7 +265,7 @@ public class ChannelClient { public void onCompleted() { } }); - + lastHeartbeatTime = System.currentTimeMillis(); } //多线程发送数据貌似会出现错误,加上synchronized后没有出现错误 @@ -280,6 +282,7 @@ public class ChannelClient { } private void scheduleReconnectTask() { + logger.info("Agent reconnect delay seconds: "+reconnectDelay+", heartbeat interval seconds: "+heartbeatInterval); reconnectFuture = executorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { @@ -307,14 +310,17 @@ public class ChannelClient { } } else { // send heartbeat - try { - sendHeartbeat(arthasServiceStub); - } catch (Throwable e) { - logger.error("send heartbeat failure", e); + long delta = System.currentTimeMillis() - lastHeartbeatTime; + if (delta >= heartbeatInterval * 1000) { + try { + sendHeartbeat(arthasServiceStub); + } catch (Throwable e) { + logger.error("send heartbeat failure", e); + } } } } - }, reconnectDelay, reconnectDelay, TimeUnit.MILLISECONDS); + }, reconnectDelay, reconnectDelay, TimeUnit.SECONDS); } @@ -374,13 +380,25 @@ public class ChannelClient { } /** - * Set reconnect delay time (ms) + * Set reconnect delay seconds * @param reconnectDelay */ public void setReconnectDelay(int reconnectDelay) { this.reconnectDelay = reconnectDelay; } + public int getHeartbeatInterval() { + return heartbeatInterval; + } + + /** + * Set heartbeat interval seconds + * @param heartbeatInterval + */ + public void setHeartbeatInterval(int heartbeatInterval) { + this.heartbeatInterval = heartbeatInterval; + } + public ScheduledExecutorService getExecutorService() { return executorService; } diff --git a/core/src/main/java/com/taobao/arthas/core/Arthas.java b/core/src/main/java/com/taobao/arthas/core/Arthas.java index e9b686645..2f79f5fee 100644 --- a/core/src/main/java/com/taobao/arthas/core/Arthas.java +++ b/core/src/main/java/com/taobao/arthas/core/Arthas.java @@ -27,32 +27,31 @@ public class Arthas { } private Configure parse(String[] args) { - Option pid = new TypedOption().setType(Long.class).setShortName("pid").setRequired(true); - Option core = new TypedOption().setType(String.class).setShortName("core").setRequired(true); - Option agent = new TypedOption().setType(String.class).setShortName("agent").setRequired(true); - Option target = new TypedOption().setType(String.class).setShortName("target-ip"); - Option telnetPort = new TypedOption().setType(Integer.class) - .setShortName("telnet-port"); - Option httpPort = new TypedOption().setType(Integer.class) - .setShortName("http-port"); - Option sessionTimeout = new TypedOption().setType(Integer.class) - .setShortName("session-timeout"); + Option pid = createOption(Long.class, "pid", true); + Option core = createOption(String.class, "core", true); + Option agent = createOption(String.class, "agent", true); + Option target = createOption(String.class, "target-ip"); + Option telnetPort = createOption(Integer.class, "telnet-port"); + Option httpPort = createOption(Integer.class, "http-port"); + Option sessionTimeout = createOption(Integer.class, "session-timeout"); - Option username = new TypedOption().setType(String.class).setShortName("username"); - Option password = new TypedOption().setType(String.class).setShortName("password"); + Option username = createOption(String.class, "username"); + Option password = createOption(String.class, "password"); - Option tunnelServer = new TypedOption().setType(String.class).setShortName("tunnel-server"); - Option channelServer = new TypedOption().setType(String.class).setShortName("channel-server"); - Option agentId = new TypedOption().setType(String.class).setShortName("agent-id"); - Option appName = new TypedOption().setType(String.class).setShortName(ArthasConstants.APP_NAME); + Option channelServer = createOption(String.class, "channel-server"); + Option heartbeatInterval = createOption(Integer.class, "heartbeat-interval"); - Option statUrl = new TypedOption().setType(String.class).setShortName("stat-url"); + Option tunnelServer = createOption(String.class, "tunnel-server"); + Option agentId = createOption(String.class, "agent-id"); + Option appName = createOption(String.class, ArthasConstants.APP_NAME); + + Option statUrl = createOption(String.class, "stat-url"); CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target) .addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout) .addOption(username).addOption(password) - .addOption(tunnelServer).addOption(channelServer) - .addOption(agentId).addOption(appName).addOption(statUrl); + .addOption(channelServer).addOption(heartbeatInterval) + .addOption(tunnelServer).addOption(agentId).addOption(appName).addOption(statUrl); CommandLine commandLine = cli.parse(Arrays.asList(args)); Configure configure = new Configure(); @@ -77,14 +76,24 @@ public class Arthas { configure.setUsername((String) commandLine.getOptionValue("username")); configure.setPassword((String) commandLine.getOptionValue("password")); - configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server")); configure.setChannelServer((String) commandLine.getOptionValue("channel-server")); + configure.setHeartbeatInterval((Integer) commandLine.getOptionValue("heartbeat-interval")); + + configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server")); configure.setAgentId((String) commandLine.getOptionValue("agent-id")); configure.setStatUrl((String) commandLine.getOptionValue("stat-url")); configure.setAppName((String) commandLine.getOptionValue(ArthasConstants.APP_NAME)); return configure; } + private TypedOption createOption(Class type, String shortName, boolean required) { + return new TypedOption().setType(type).setShortName(shortName).setRequired(required); + } + + private TypedOption createOption(Class type, String shortName) { + return createOption(type, shortName, false); + } + private void attachAgent(Configure configure) throws Exception { VirtualMachineDescriptor virtualMachineDescriptor = null; for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) { diff --git a/core/src/main/java/com/taobao/arthas/core/config/Configure.java b/core/src/main/java/com/taobao/arthas/core/config/Configure.java index 411479af0..a76f40cff 100644 --- a/core/src/main/java/com/taobao/arthas/core/config/Configure.java +++ b/core/src/main/java/com/taobao/arthas/core/config/Configure.java @@ -28,7 +28,14 @@ public class Configure { private String tunnelServer; private String agentId; + /** + * Channel server address + */ private String channelServer; + /** + * Agent (Channel client) heartbeat interval seconds + */ + private int heartbeatInterval = 5; private String username; private String password; @@ -134,6 +141,14 @@ public class Configure { this.channelServer = channelServer; } + public int getHeartbeatInterval() { + return heartbeatInterval; + } + + public void setHeartbeatInterval(int heartbeatInterval) { + this.heartbeatInterval = heartbeatInterval; + } + public String getAgentId() { return agentId; } diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java index 0c7b1d72f..4db0337be 100644 --- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java @@ -457,6 +457,7 @@ public class ArthasBootstrap { if (configure.getChannelServer() != null) { try { channelClient = new ChannelClient(configure.getChannelServer()); + channelClient.setHeartbeatInterval(configure.getHeartbeatInterval()); channelClient.setAgentInfoService(new AgentInfoServiceImpl(channelClient, configure)); channelClient.setRequestListener(new ChannelRequestHandler(channelClient, sessionManager, historyManager)); channelClient.start();