序
本文主要研究一下arthas的ArthasBootstrap
getInstance
com/taobao/arthas/core/server/ArthasBootstrap.java
/**
* 單例
*
* @param instrumentation JVM增強
* @return ArthasServer單例
* @throws Throwable
*/
public synchronized static ArthasBootstrap getInstance(Instrumentation instrumentation, Map<String, String> args) throws Throwable {
if (arthasBootstrap == null) {
arthasBootstrap = new ArthasBootstrap(instrumentation, args);
}
return arthasBootstrap;
}
ArthasBootstrap提供了getInstance的靜態方法使用者建立ArthasBootstrap
ArthasBootstrap
public class ArthasBootstrap {
private static final String ARTHAS_SPY_JAR = "arthas-spy.jar";
public static final String ARTHAS_HOME_PROPERTY = "arthas.home";
private static String ARTHAS_HOME = null;
public static final String CONFIG_NAME_PROPERTY = "arthas.config.name";
public static final String CONFIG_LOCATION_PROPERTY = "arthas.config.location";
public static final String CONFIG_OVERRIDE_ALL = "arthas.config.overrideAll";
private static ArthasBootstrap arthasBootstrap;
private ArthasEnvironment arthasEnvironment;
private Configure configure;
private AtomicBoolean isBindRef = new AtomicBoolean(false);
private Instrumentation instrumentation;
private InstrumentTransformer classLoaderInstrumentTransformer;
private Thread shutdown;
private ShellServer shellServer;
private ScheduledExecutorService executorService;
private SessionManager sessionManager;
private TunnelClient tunnelClient;
private File outputPath;
private static LoggerContext loggerContext;
private EventExecutorGroup workerGroup;
private Timer timer = new Timer("arthas-timer", true);
private TransformerManager transformerManager;
private ResultViewResolver resultViewResolver;
private HistoryManager historyManager;
private HttpApiHandler httpApiHandler;
private HttpSessionManager httpSessionManager;
private SecurityAuthenticator securityAuthenticator;
private ArthasBootstrap(Instrumentation instrumentation, Map<String, String> args) throws Throwable {
this.instrumentation = instrumentation;
initFastjson();
// 1. initSpy()
initSpy();
// 2. ArthasEnvironment
initArthasEnvironment(args);
String outputPathStr = configure.getOutputPath();
if (outputPathStr == null) {
outputPathStr = ArthasConstants.ARTHAS_OUTPUT;
}
outputPath = new File(outputPathStr);
outputPath.mkdirs();
// 3. init logger
loggerContext = LogUtil.initLogger(arthasEnvironment);
// 4. 增強ClassLoader
enhanceClassLoader();
// 5. init beans
initBeans();
// 6. start agent server
bind(configure);
executorService = Executors.newScheduledThreadPool(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
final Thread t = new Thread(r, "arthas-command-execute");
t.setDaemon(true);
return t;
}
});
shutdown = new Thread("as-shutdown-hooker") {
@Override
public void run() {
ArthasBootstrap.this.destroy();
}
};
transformerManager = new TransformerManager(instrumentation);
Runtime.getRuntime().addShutdownHook(shutdown);
}
//......
}
ArthasBootstrap的構造器執行initFastjson、initSpy、initArthasEnvironment、LogUtil.initLogger(arthasEnvironment)、enhanceClassLoader、initBeans、bind,最後註冊shutdownHook來執行destroy方法
initFastjson
private void initFastjson() {
// ignore getter error #1661
// #2081
JSON.config(JSONWriter.Feature.IgnoreErrorGetter, JSONWriter.Feature.WriteNonStringKeyAsString);
}
initFastjson這裡使用fastjson2的config方法來忽略getter的error,以及設定WriteNonStringKeyAsString
initSpy
private void initSpy() throws Throwable {
// TODO init SpyImpl ?
// 將Spy新增到BootstrapClassLoader
ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();
Class<?> spyClass = null;
if (parent != null) {
try {
spyClass =parent.loadClass("java.arthas.SpyAPI");
} catch (Throwable e) {
// ignore
}
}
if (spyClass == null) {
CodeSource codeSource = ArthasBootstrap.class.getProtectionDomain().getCodeSource();
if (codeSource != null) {
File arthasCoreJarFile = new File(codeSource.getLocation().toURI().getSchemeSpecificPart());
File spyJarFile = new File(arthasCoreJarFile.getParentFile(), ARTHAS_SPY_JAR);
instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(spyJarFile));
} else {
throw new IllegalStateException("can not find " + ARTHAS_SPY_JAR);
}
}
}
initSpy將Spy新增到BootstrapClassLoader
initArthasEnvironment
private void initArthasEnvironment(Map<String, String> argsMap) throws IOException {
if (arthasEnvironment == null) {
arthasEnvironment = new ArthasEnvironment();
}
/**
* <pre>
* 指令碼里傳過來的配置項,即命令列引數 > System Env > System Properties > arthas.properties
* arthas.properties 提供一個配置項,可以反轉優先順序。 arthas.config.overrideAll=true
* https://github.com/alibaba/arthas/issues/986
* </pre>
*/
Map<String, Object> copyMap;
if (argsMap != null) {
copyMap = new HashMap<String, Object>(argsMap);
// 新增 arthas.home
if (!copyMap.containsKey(ARTHAS_HOME_PROPERTY)) {
copyMap.put(ARTHAS_HOME_PROPERTY, arthasHome());
}
} else {
copyMap = new HashMap<String, Object>(1);
copyMap.put(ARTHAS_HOME_PROPERTY, arthasHome());
}
MapPropertySource mapPropertySource = new MapPropertySource("args", copyMap);
arthasEnvironment.addFirst(mapPropertySource);
tryToLoadArthasProperties();
configure = new Configure();
BinderUtils.inject(arthasEnvironment, configure);
}
initArthasEnvironment按命令列引數 > System Env > System Properties > arthas.properties這個順序來配置ArthasEnvironment
initLogger
public static LoggerContext initLogger(ArthasEnvironment env) {
String loggingConfig = env.resolvePlaceholders(LOGGING_CONFIG);
if (loggingConfig == null || loggingConfig.trim().isEmpty()) {
return null;
}
AnsiLog.debug("arthas logging file: " + loggingConfig);
File configFile = new File(loggingConfig);
if (!configFile.isFile()) {
AnsiLog.error("can not find arthas logging config: " + loggingConfig);
return null;
}
try {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.reset();
String fileName = env.getProperty(FILE_NAME_PROPERTY);
if (fileName != null) {
loggerContext.putProperty(ARTHAS_LOG_FILE, fileName);
}
String filePath = env.getProperty(FILE_PATH_PROPERTY);
if (filePath != null) {
loggerContext.putProperty(ARTHAS_LOG_PATH, filePath);
}
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(configFile.toURI().toURL()); // load logback xml file
// 查詢 arthas.log appender
Iterator<Appender<ILoggingEvent>> appenders = loggerContext.getLogger("root").iteratorForAppenders();
while (appenders.hasNext()) {
Appender<ILoggingEvent> appender = appenders.next();
if (appender instanceof RollingFileAppender) {
RollingFileAppender fileAppender = (RollingFileAppender) appender;
if ("ARTHAS".equalsIgnoreCase(fileAppender.getName())) {
logFile = new File(fileAppender.getFile()).getCanonicalPath();
}
}
}
return loggerContext;
} catch (Throwable e) {
AnsiLog.error("try to load arthas logging config file error: " + configFile, e);
}
return null;
}
initLogger從env獲取相應的日誌配置,然後設定到LoggerContext,然後設定到logback的JoranConfigurator
enhanceClassLoader
void enhanceClassLoader() throws IOException, UnmodifiableClassException {
if (configure.getEnhanceLoaders() == null) {
return;
}
Set<String> loaders = new HashSet<String>();
for (String s : configure.getEnhanceLoaders().split(",")) {
loaders.add(s.trim());
}
// 增強 ClassLoader#loadClsss ,解決一些ClassLoader載入不到 SpyAPI的問題
// https://github.com/alibaba/arthas/issues/1596
byte[] classBytes = IOUtils.getBytes(ArthasBootstrap.class.getClassLoader()
.getResourceAsStream(ClassLoader_Instrument.class.getName().replace('.', '/') + ".class"));
SimpleClassMatcher matcher = new SimpleClassMatcher(loaders);
InstrumentConfig instrumentConfig = new InstrumentConfig(AsmUtils.toClassNode(classBytes), matcher);
InstrumentParseResult instrumentParseResult = new InstrumentParseResult();
instrumentParseResult.addInstrumentConfig(instrumentConfig);
classLoaderInstrumentTransformer = new InstrumentTransformer(instrumentParseResult);
instrumentation.addTransformer(classLoaderInstrumentTransformer, true);
if (loaders.size() == 1 && loaders.contains(ClassLoader.class.getName())) {
// 如果只增強 java.lang.ClassLoader,可以減少查詢過程
instrumentation.retransformClasses(ClassLoader.class);
} else {
InstrumentationUtils.trigerRetransformClasses(instrumentation, loaders);
}
}
enhanceClassLoader會使用instrumentation來增強configure.getEnhanceLoaders()
initBeans
private void initBeans() {
this.resultViewResolver = new ResultViewResolver();
this.historyManager = new HistoryManagerImpl();
}
initBeans則建立ResultViewResolver、HistoryManagerImpl
bind
private void bind(Configure configure) throws Throwable {
long start = System.currentTimeMillis();
if (!isBindRef.compareAndSet(false, true)) {
throw new IllegalStateException("already bind");
}
// init random port
if (configure.getTelnetPort() != null && configure.getTelnetPort() == 0) {
int newTelnetPort = SocketUtils.findAvailableTcpPort();
configure.setTelnetPort(newTelnetPort);
logger().info("generate random telnet port: " + newTelnetPort);
}
if (configure.getHttpPort() != null && configure.getHttpPort() == 0) {
int newHttpPort = SocketUtils.findAvailableTcpPort();
configure.setHttpPort(newHttpPort);
logger().info("generate random http port: " + newHttpPort);
}
// try to find appName
if (configure.getAppName() == null) {
configure.setAppName(System.getProperty(ArthasConstants.PROJECT_NAME,
System.getProperty(ArthasConstants.SPRING_APPLICATION_NAME, null)));
}
try {
if (configure.getTunnelServer() != null) {
tunnelClient = new TunnelClient();
tunnelClient.setAppName(configure.getAppName());
tunnelClient.setId(configure.getAgentId());
tunnelClient.setTunnelServerUrl(configure.getTunnelServer());
tunnelClient.setVersion(ArthasBanner.version());
ChannelFuture channelFuture = tunnelClient.start();
channelFuture.await(10, TimeUnit.SECONDS);
}
} catch (Throwable t) {
logger().error("start tunnel client error", t);
}
try {
ShellServerOptions options = new ShellServerOptions()
.setInstrumentation(instrumentation)
.setPid(PidUtils.currentLongPid())
.setWelcomeMessage(ArthasBanner.welcome());
if (configure.getSessionTimeout() != null) {
options.setSessionTimeout(configure.getSessionTimeout() * 1000);
}
this.httpSessionManager = new HttpSessionManager();
if (IPUtils.isAllZeroIP(configure.getIp()) && StringUtils.isBlank(configure.getPassword())) {
// 當 listen 0.0.0.0 時,強制生成密碼,防止被遠端連線
String errorMsg = "Listening on 0.0.0.0 is very dangerous! External users can connect to your machine! "
+ "No password is currently configured. " + "Therefore, a default password is generated, "
+ "and clients need to use the password to connect!";
AnsiLog.error(errorMsg);
configure.setPassword(StringUtils.randomString(64));
AnsiLog.error("Generated arthas password: " + configure.getPassword());
logger().error(errorMsg);
logger().info("Generated arthas password: " + configure.getPassword());
}
this.securityAuthenticator = new SecurityAuthenticatorImpl(configure.getUsername(), configure.getPassword());
shellServer = new ShellServerImpl(options);
List<String> disabledCommands = new ArrayList<String>();
if (configure.getDisabledCommands() != null) {
String[] strings = StringUtils.tokenizeToStringArray(configure.getDisabledCommands(), ",");
if (strings != null) {
disabledCommands.addAll(Arrays.asList(strings));
}
}
BuiltinCommandPack builtinCommands = new BuiltinCommandPack(disabledCommands);
List<CommandResolver> resolvers = new ArrayList<CommandResolver>();
resolvers.add(builtinCommands);
//worker group
workerGroup = new NioEventLoopGroup(new DefaultThreadFactory("arthas-TermServer", true));
// TODO: discover user provided command resolver
if (configure.getTelnetPort() != null && configure.getTelnetPort() > 0) {
logger().info("try to bind telnet server, host: {}, port: {}.", configure.getIp(), configure.getTelnetPort());
shellServer.registerTermServer(new HttpTelnetTermServer(configure.getIp(), configure.getTelnetPort(),
options.getConnectionTimeout(), workerGroup, httpSessionManager));
} else {
logger().info("telnet port is {}, skip bind telnet server.", configure.getTelnetPort());
}
if (configure.getHttpPort() != null && configure.getHttpPort() > 0) {
logger().info("try to bind http server, host: {}, port: {}.", configure.getIp(), configure.getHttpPort());
shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),
options.getConnectionTimeout(), workerGroup, httpSessionManager));
} else {
// listen local address in VM communication
if (configure.getTunnelServer() != null) {
shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),
options.getConnectionTimeout(), workerGroup, httpSessionManager));
}
logger().info("http port is {}, skip bind http server.", configure.getHttpPort());
}
for (CommandResolver resolver : resolvers) {
shellServer.registerCommandResolver(resolver);
}
shellServer.listen(new BindHandler(isBindRef));
if (!isBind()) {
throw new IllegalStateException("Arthas failed to bind telnet or http port! Telnet port: "
+ String.valueOf(configure.getTelnetPort()) + ", http port: "
+ String.valueOf(configure.getHttpPort()));
}
//http api session manager
sessionManager = new SessionManagerImpl(options, shellServer.getCommandManager(), shellServer.getJobController());
//http api handler
httpApiHandler = new HttpApiHandler(historyManager, sessionManager);
logger().info("as-server listening on network={};telnet={};http={};timeout={};", configure.getIp(),
configure.getTelnetPort(), configure.getHttpPort(), options.getConnectionTimeout());
// 非同步回報啟動次數
if (configure.getStatUrl() != null) {
logger().info("arthas stat url: {}", configure.getStatUrl());
}
UserStatUtil.setStatUrl(configure.getStatUrl());
UserStatUtil.setAgentId(configure.getAgentId());
UserStatUtil.arthasStart();
try {
SpyAPI.init();
} catch (Throwable e) {
// ignore
}
logger().info("as-server started in {} ms", System.currentTimeMillis() - start);
} catch (Throwable e) {
logger().error("Error during start as-server", e);
destroy();
throw e;
}
}
bind方法主要是啟動AgentServer,若有設定tunnelServer則初始化tunnelClient,然後建立ShellServerImpl,對於有設定telnetPort的則註冊HttpTelnetTermServer,對於有設定httpPort的則註冊HttpTermServer,然後執行shellServer.listen,UserStatUtil.arthasStart(),最後執行SpyAPI.init()
destroy
public void destroy() {
if (shellServer != null) {
shellServer.close();
shellServer = null;
}
if (sessionManager != null) {
sessionManager.close();
sessionManager = null;
}
if (this.httpSessionManager != null) {
httpSessionManager.stop();
}
if (timer != null) {
timer.cancel();
}
if (this.tunnelClient != null) {
try {
tunnelClient.stop();
} catch (Throwable e) {
logger().error("stop tunnel client error", e);
}
}
if (executorService != null) {
executorService.shutdownNow();
}
if (transformerManager != null) {
transformerManager.destroy();
}
if (classLoaderInstrumentTransformer != null) {
instrumentation.removeTransformer(classLoaderInstrumentTransformer);
}
// clear the reference in Spy class.
cleanUpSpyReference();
shutdownWorkGroup();
UserStatUtil.destroy();
if (shutdown != null) {
try {
Runtime.getRuntime().removeShutdownHook(shutdown);
} catch (Throwable t) {
// ignore
}
}
logger().info("as-server destroy completed.");
if (loggerContext != null) {
loggerContext.stop();
}
}
destroy方法執行shellServer.close()、sessionManager.close()、httpSessionManager.stop()、timer.cancel()、tunnelClient.stop()、executorService.shutdownNow()、transformerManager.destroy()、instrumentation.removeTransformer(classLoaderInstrumentTransformer)、cleanUpSpyReference()、shutdownWorkGroup()、UserStatUtil.destroy()、Runtime.getRuntime().removeShutdownHook、loggerContext.stop()
小結
ArthasBootstrap的構造器執行initFastjson、initSpy、initArthasEnvironment、LogUtil.initLogger(arthasEnvironment)、enhanceClassLoader、initBeans、bind,最後註冊shutdownHook來執行destroy方法。